aboutsummaryrefslogtreecommitdiffstats
path: root/src/ui
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2021-02-05 18:32:12 +0000
committers-ol <s-ol@users.noreply.github.com>2021-02-05 18:42:02 +0000
commit464174b7e5e4e5c389c896173e8c7ab21badda8a (patch)
tree0f88ffc3db86026f86cb2941e4a05bd8a2753903 /src/ui
parentlinks leave shadow (diff)
downloadfedidag-464174b7e5e4e5c389c896173e8c7ab21badda8a.tar.gz
fedidag-464174b7e5e4e5c389c896173e8c7ab21badda8a.zip
clean up UI code
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/Links.js50
-rw-r--r--src/ui/Note.js155
-rw-r--r--src/ui/css.js13
-rw-r--r--src/ui/index.js10
-rw-r--r--src/ui/theme.js26
5 files changed, 254 insertions, 0 deletions
diff --git a/src/ui/Links.js b/src/ui/Links.js
new file mode 100644
index 0000000..dfd981f
--- /dev/null
+++ b/src/ui/Links.js
@@ -0,0 +1,50 @@
+import { render, h, Fragment } from 'preact';
+import { useRef, useLayoutEffect } from 'preact/hooks';
+
+export const Links = ({ links, ...props }) => {
+ const canvas = useRef(null);
+
+ useLayoutEffect(() => {
+ const ctx = canvas.current.getContext('2d');
+ ctx.lineWidth = 4;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ ctx.strokeStyle = '#696969';
+ ctx.fillStyle = '#696969';
+ ctx.shadowBlur = 2;
+ ctx.shadowOffsetX = 2;
+ ctx.shadowOffsetY = 4;
+
+ ctx.clearRect(0, 0, canvas.current.width, canvas.current.height);
+
+ for (const points of links) {
+ ctx.shadowColor = 'rgba(0,0,0, 0.7)';
+ ctx.beginPath();
+ ctx.moveTo(...points[1]);
+ for (let i = 4; i < points.length; i += 3) {
+ const [ax, ay] = points[i - 2];
+ const [bx, by] = points[i - 1];
+ const [x, y] = points[i];
+ ctx.bezierCurveTo(ax, ay, bx, by, x, y);
+ }
+ ctx.stroke();
+
+ const [lx, ly] = points[points.length - 1];
+ const [tx, ty] = points[0];
+ const [dx, dy] = [tx - lx, ty - ly];
+ const [hx, hy] = [dy*0.7, dx*-0.7];
+
+ ctx.shadowColor = 'transparent';
+ ctx.beginPath();
+ ctx.moveTo(lx + dx*0.8, ly + dy*0.8);
+ ctx.lineTo(lx - dx*0.5 + hx, ly - dy*0.5 + hy);
+ ctx.lineTo(lx - dx*0.5 - hx, ly - dy*0.5 - hy);
+ ctx.fill();
+ }
+ });
+
+ return (
+ <canvas {...props} ref={canvas} />
+ );
+};
+
diff --git a/src/ui/Note.js b/src/ui/Note.js
new file mode 100644
index 0000000..b7387a8
--- /dev/null
+++ b/src/ui/Note.js
@@ -0,0 +1,155 @@
+import { render, h, Fragment } from 'preact';
+import { useRef, useLayoutEffect } from 'preact/hooks';
+import ColorHash from 'color-hash';
+import { formatRelative } from 'date-fns';
+import css from './css';
+
+const now = new Date();
+const Time = ({ time }) => {
+ const dt = new Date(time);
+ return formatRelative(dt, now);
+};
+
+css`
+.flex-space {
+ flex: 1;
+ margin: 0;
+}
+`;
+const Space = () => <div class="flex-space" />;
+
+css`
+section > header {
+ display: flex;
+ flex-direction: row;
+
+ height: 1.75rem;
+
+ color: var(--theme-header-fg);
+ background: var(--theme-header-bg);
+
+ cursor: pointer;
+ transition: opacity 0.3s;
+}
+
+section > header:hover {
+ opacity: 0.5;
+}
+
+section > header.small {
+ height: 0.5em;
+}
+
+section > header > * {
+ margin: 0 0.5em;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ line-height: 1.75rem;
+}
+
+section > header > img {
+ height: 1.5rem;
+ width: 1.5rem;
+ flex: 0 0 1.5rem;
+ margin: 0.125rem;
+ border-radius: 0.2rem;
+}
+
+section > header > .user {
+ flex: 0 0.5 auto;
+}
+
+section > header > .time {
+ flex: 0 1 auto;
+ font-size: 0.8em;
+ color: #363636;
+}
+`;
+const Header = ({ user, published, onClick }) => {
+ const username = user.name || user.preferredUsername;
+
+ return (
+ <header onClick={onClick}>
+ <img src={new URL(user.icon.url, user.id)} />
+ <span className="user">{username}</span>
+ <Space />
+ <span className="time"><Time time={published} /></span>
+ </header>
+ );
+};
+
+css`
+section {
+ position: absolute;
+ overflow: hidden;
+
+ width: 22em;
+ color: var(--theme-note-fg);
+ background: var(--theme-note-bg);
+ box-shadow: rgba(0,0,0, 0.7) 2px 4px 5px;
+
+ border-radius: 0.4em;
+}
+
+section.type-Tombstone {
+ color: var(--theme-note-bg);
+ background: var(--theme-note-fg);
+}
+
+section > main {
+ padding: 1em;
+ font-family: serif;
+}
+
+section.tombstone > main {
+ font-family: inherit;
+}
+
+section.hidden {
+ display: none;
+}
+
+section.collapsed {
+ width: auto;
+}
+section.collapsed > main {
+ display: none;
+}
+`;
+
+const colors = new ColorHash({ lightness: 0.8 });
+export const Note = ({
+ id, type, attributedTo, published, content,
+ smallHeader = false, position,
+ onHide, onSize,
+}) => {
+ const backgroundColor = attributedTo.color || colors.hex(attributedTo.id);
+
+ const root = useRef(null);
+ useLayoutEffect(() => {
+ onSize(id, root.current.offsetWidth, root.current.offsetHeight);
+ });
+
+ return (
+ <section
+ style={{
+ '--theme-header-bg': backgroundColor,
+ 'left': position && `${position[0] - root.current.offsetWidth / 2}px`,
+ 'top': position && `${position[1] - root.current.offsetHeight / 2}px`,
+ 'visibility': position ? 'visible' : 'hidden',
+ }}
+ className={`type-${type}`}
+ data-id={id}
+ ref={root}
+ >
+ {smallHeader
+ ? <header className="small" onClick={onHide} />
+ : <Header onClick={onHide} user={attributedTo} published={published} />
+ }
+ <main>
+ {content}
+ </main>
+ </section>
+ );
+};
diff --git a/src/ui/css.js b/src/ui/css.js
new file mode 100644
index 0000000..e793c4f
--- /dev/null
+++ b/src/ui/css.js
@@ -0,0 +1,13 @@
+export default ([ code] ) => {
+ const style = document.createElement('style');
+ document.head.appendChild(style);
+ style.appendChild(document.createTextNode(''));
+
+ let i = 0;
+ for (const v of code.split('}')) {
+ if (v.indexOf('{') < 0) continue;
+ style.sheet.insertRule(v + '}', i++);
+ }
+
+ return style;
+};
diff --git a/src/ui/index.js b/src/ui/index.js
new file mode 100644
index 0000000..3224b71
--- /dev/null
+++ b/src/ui/index.js
@@ -0,0 +1,10 @@
+import './theme';
+import { Links } from './Links';
+import { Note } from './Note';
+import css from './css';
+
+export {
+ Links,
+ Note,
+ css,
+};
diff --git a/src/ui/theme.js b/src/ui/theme.js
new file mode 100644
index 0000000..372cd46
--- /dev/null
+++ b/src/ui/theme.js
@@ -0,0 +1,26 @@
+import css from './css';
+
+css`
+body {
+ --theme-backdrop: #1e1e1e;
+ --theme-fg: #eeeeee;
+ --theme-note-bg: #eeeeee;
+ --theme-note-fg: #363636;
+ --theme-header-fg: #121212;
+}
+
+body.darktheme {
+ --theme-backdrop: #1e1e1e;
+ --theme-note-bg: #363636;
+ --theme-note-fg: #eeeeee;
+ --theme-header-fg: #121212;
+}
+
+body {
+ color: var(--theme-fg);
+ background: var(--theme-backdrop);
+ font-family: sans-serif;
+ font-size: 11px;
+}
+`;
+