diff options
| author | Nathan Reiner <nathan@nathanreiner.xyz> | 2026-05-20 10:16:50 +0200 |
|---|---|---|
| committer | Nathan Reiner <nathan@nathanreiner.xyz> | 2026-05-20 10:16:50 +0200 |
| commit | 44445734ba4a0f05564f808c2fd34a7f23c6fafb (patch) | |
| tree | 3bd22add718620cbf931286f55137bcc666100be /static/widgets | |
| parent | 95ae9f15e1db335a7f8358d811ea37e2c89dd10f (diff) | |
Let's do a rewrite!
Created project structure and changed `build.zig` and added licence.
Diffstat (limited to 'static/widgets')
| -rw-r--r-- | static/widgets/editable/index.css | 36 | ||||
| -rw-r--r-- | static/widgets/editable/index.js | 77 | ||||
| -rw-r--r-- | static/widgets/image/index.css | 134 | ||||
| -rw-r--r-- | static/widgets/image/index.js | 108 | ||||
| -rw-r--r-- | static/widgets/index.js | 6 | ||||
| -rw-r--r-- | static/widgets/month-select/index.css | 134 | ||||
| -rw-r--r-- | static/widgets/month-select/index.js | 87 | ||||
| -rw-r--r-- | static/widgets/password-dialog/index.css | 51 | ||||
| -rw-r--r-- | static/widgets/password-dialog/index.js | 60 | ||||
| -rw-r--r-- | static/widgets/search/index.css | 50 | ||||
| -rw-r--r-- | static/widgets/search/index.js | 68 | ||||
| -rw-r--r-- | static/widgets/upload-bar/index.css | 41 | ||||
| -rw-r--r-- | static/widgets/upload-bar/index.js | 44 |
13 files changed, 0 insertions, 896 deletions
diff --git a/static/widgets/editable/index.css b/static/widgets/editable/index.css deleted file mode 100644 index b474370..0000000 --- a/static/widgets/editable/index.css +++ /dev/null @@ -1,36 +0,0 @@ - -#container { - width: 100%; - background: var(--card-background); - box-shadow: var(--shadow); - border-radius: var(--border-radius); - display: grid; - grid-template-columns: min-content auto min-content; -} - -#container label { - user-select: none; - font-weight: bold; - background: var(--primary); - color: var(--fg-primary); - align-content: center; - padding: 10px; - border-top-left-radius: var(--border-radius); - border-bottom-left-radius: var(--border-radius); -} - -#container input { - background: var(--card-background); - user-select: none; - font-size: 1em; -} - -#container input:read-only { - caret-color: transparent; -} - -#edit { - width: 35px; - padding: 10px; - cursor: pointer; -} diff --git a/static/widgets/editable/index.js b/static/widgets/editable/index.js deleted file mode 100644 index b7e24de..0000000 --- a/static/widgets/editable/index.js +++ /dev/null @@ -1,77 +0,0 @@ -import icons from 'icons'; -import * as sfw from 'sfw'; -const { Div, Label, Input } = sfw.element.native; - -import stylesheet from './index.css'; - -const css = await sfw.css.sheet(stylesheet); - -export default class Editable extends sfw.element.Container { - #label - #input - #edit - - constructor() { - super({ css }); - - this.onupdate = () => {}; - - this.body.append( - Div.new({ - id: 'container', - children: [ - this.#label = Label.new({ htmlFor: 'input' }), - this.#input = Input.new({ - readOnly: true, - onkeydown: (e) => { - if (e.key === 'Enter') { - this.#input.readOnly = true; - this.#update() - this.onupdate(); - } - }, - }), - this.#edit = Div.new({ - id: 'edit', - children: [ icons.edit ], - onclick: () => { - this.#input.readOnly = !this.#input.readOnly; - this.#update(); - - if (this.#input.readOnly) { - this.onupdate(); - } - } - }), - ], - }) - ); - } - - #update() { - this.#edit.innerHTML = ''; - this.#edit.append( - this.#input.readOnly ? icons.edit : icons.check - ); - - if (!this.#input.readOnly) { - this.#input.select(); - } - } - - set title(value) { - this.#label.innerText = value; - } - - set value(value) { - this.#input.value = value; - } - - get value() { - return this.#input.value; - } - - set type(type) { - this.#input.type = type; - } -} diff --git a/static/widgets/image/index.css b/static/widgets/image/index.css deleted file mode 100644 index a4105e9..0000000 --- a/static/widgets/image/index.css +++ /dev/null @@ -1,134 +0,0 @@ -#container { - position: relative; - height: 100%; - min-height: 100px; - border-radius: var(--border-radius); - overflow: hidden; - box-shadow: #223223aa 1px 1px 4px; - background: var(--card-background); - margin: auto; -} - -#container img { - border-radius: var(--border-radius); - max-width: 700px; - width: calc(100vw - 20px); - visibility: hidden; - margin-bottom: -5px; - height: calc((100vw - 20px) * 0.8); -} - -#container.loaded img { - visibility: visible; - width: 100%; - height: unset; -} - -@keyframes loader { - 0% { - width: 60px; - height: 60px; - } - - 25% { - border: 5px solid var(--page-background); - } - - 50% { - width: 80px; - height: 80px; - } - - 75% { - border: 10px solid var(--page-background); - } - - 100% { - width: 60px; - height: 60px; - } -} - -#loading { - position: absolute; - border: 10px solid var(--page-background); - border-radius: 100%; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - animation: 0.5s infinite loader ease; -} - -.loaded #loading { - display: none; -} - -#menu { - position: absolute; - top: 0px; - left: 0px; - height: 100%; - width: 0px; - background: #efefef99; - backdrop-filter: blur(5px); - border-radius: var(--border-radius); - transition: width 0.2s ease, padding 0.2s ease; - overflow: hidden; - align-content: center; - overflow: hidden; - padding: 0px; - display: grid; - grid-template-columns: 1fr; - gap: 10px; -} - -#menu.open { - width: 100%; - padding: 10px; -} - -#delete { - background: #ee5151; - padding: 10px; - color: #fff; - font-weight: bold; - border-radius: var(--border-radius); - cursor: pointer; - user-select: none; -} - -#download { - background: var(--primary); - padding: 10px; - border-radius: var(--border-radius); - color: #fff; - font-weight: bold; - user-select: none; - cursor: pointer; -} - -#label { - display: grid; - grid-template-columns: 55px auto; -} - -#label #title { - padding: 10px; - background: var(--primary); - border-top-left-radius: var(--border-radius); - border-bottom-left-radius: var(--border-radius); - color: #fff; - font-weight: bold; - user-select: none; -} - -.element { - background: #efefef99; - padding: 10px; - font-weight: bold; - overflow-x: auto; - user-select: none; - white-space: nowrap; - border-top-right-radius: var(--border-radius); - border-bottom-right-radius: var(--border-radius); -} diff --git a/static/widgets/image/index.js b/static/widgets/image/index.js deleted file mode 100644 index b063864..0000000 --- a/static/widgets/image/index.js +++ /dev/null @@ -1,108 +0,0 @@ -import * as api from 'api'; -import * as sfw from 'sfw'; -import Month from '../../month.js'; -const { Div, Img, A: Link } = sfw.element.native; - -import stylesheet from './index.css'; - -const css = await sfw.css.sheet(stylesheet); - -export default class Image extends sfw.element.Container { - #container - #image - #menu - #id - #month - #date - #id_element - #disabled - - constructor() { - super ({ css }) - - this.#disabled = false; - - this.body.append( - this.#container = Div.new({ - id: 'container', - children: [ - this.#image = Img.new({ - onload: () => this.#container.classList.add('loaded'), - }), - Div.new({ id: 'loading' }), - this.#menu = Div.new({ - id: 'menu', - children: [ - Div.new({ - id: 'label', - children: [ - Div.new({ id: 'title', innerText: 'Id' }), - this.#id_element = Div.new({ className: 'element id' }), - ], - }), - Div.new({ - id: 'label', - children: [ - Div.new({ id: 'title', innerText: 'Date' }), - this.#date = Div.new({ className: 'element' }), - ], - }), - Div.new({ - id: 'download', - innerText: 'Download', - onclick: () => { - const a = Link.new({ - href: `/api/image/load/${this.#id}`, - download: `${this.#id}.jpeg` - }) - a.click() - } - }), - Div.new({ - id: 'delete', - innerText: 'Delete', - onclick: () => { - api.images.remove(this.#id); - this.parentNode.removeChild(this); - } - }), - ], - ondblclick: (e) => { - if (e.target != this.#menu) { - e.stopPropagation(); - } - } - }) - ], - ondblclick: (e) => { - if (!this.#disabled) { - this.#menu.classList.toggle('open'); - e.preventDefault(); - } - } - }) - ); - } - - set metadata(image) { - const date = new Date(image.timestamp * 1000); - this.#id = image.id; - this.#month = Month.from_unix(image.timestamp); - this.#container.classList.remove('loaded'); - this.#date.innerText = date.toLocaleString(); - this.#id_element.innerText = image.id; - } - - load() { - this.#image.src = `/api/image/load/${this.#id}`; - } - - get month() { - return this.#month; - } - - set disabled(value) { - this.#disabled = value; - this.#image.setAttribute('draggable', !value); - } -} diff --git a/static/widgets/index.js b/static/widgets/index.js deleted file mode 100644 index 6220318..0000000 --- a/static/widgets/index.js +++ /dev/null @@ -1,6 +0,0 @@ -export { default as Editable } from './editable/index.js'; -export { default as Image } from './image/index.js'; -export { default as MonthSelect } from './month-select/index.js'; -export { default as PasswordDialog } from './password-dialog/index.js'; -export { default as Search } from './search/index.js'; -export { default as UploadBar } from './upload-bar/index.js'; diff --git a/static/widgets/month-select/index.css b/static/widgets/month-select/index.css deleted file mode 100644 index 25ed5d2..0000000 --- a/static/widgets/month-select/index.css +++ /dev/null @@ -1,134 +0,0 @@ -:host { - position: fixed; - top: 0; - left: 0; -} - -#container { - z-index: 1000; - position: fixed; - bottom: -100%; - left: 5px; - right: 5px; - background: #fff9; - backdrop-filter: blur(10px); - border-radius: var(--border-radius); - transition: bottom 0.2s ease; - height: calc(100% - 10px); - box-shadow: var(--shadow); - padding: 10px; -} - -#container.visible { - bottom: 5px; -} - -#close-button { - position: absolute; - top: 10px; - right: 10px; - width: 25px; - height: 25px; - border-radius: 100%; - background: var(--page-background); - cursor: pointer; -} - -#title { - font-size: 1.2em; - user-select: none; - margin-bottom: 10px; -} - -#month-container { - height: calc(100% - 30px); - overflow-y: auto; - overscroll-behavior: contain; -} - -.month-item { - padding: 10px; - padding-left: 20px; - margin: 5px; - border-radius: var(--border-radius); - user-select: none; - cursor: pointer; - position: relative; -} - -.month-item.none { - font-style: italic; -} - -.month-item:before { - content: ''; - display: block; - position: absolute; - background: var(--primary); - top: 0px; - left: 0px; - width: 4px; - height: 47px; -} - -.month-item:after { - content: ''; - display: block; - position: absolute; - background: var(--primary); - top: 50%; - transform: translate(0, -50%); - left: 0px; - width: 12px; - height: 4px; -} - -.month-item:first-child:before { - top: 50%; - height: 27px; -} - -.month-item.none:before { - top: 0; - height: 20px; -} - -.month-item:hover { - background: var(--primary); - color: var(--fg-primary); -} - -.year-item { - padding: 10px 20px; - font-size: 1.1em; - margin: 5px; - position: relative; - font-weight: bold; - background: #fffa; - border-radius: var(--border-radius); - box-shadow: #22322355 1px 1px 2px; -} - -.year-item:before { - content: ''; - display: block; - position: absolute; - background: var(--primary); - top: 0px; - left: 0px; - width: 4px; - height: 50px; -} - -.year-item:after { - content: ''; - display: block; - position: absolute; - background: var(--primary); - top: 50%; - transform: translate(0, -50%); - left: -4px; - width: 12px; - height: 12px; - border-radius: 100%; -} diff --git a/static/widgets/month-select/index.js b/static/widgets/month-select/index.js deleted file mode 100644 index 7e4d583..0000000 --- a/static/widgets/month-select/index.js +++ /dev/null @@ -1,87 +0,0 @@ -import icons from 'icons'; -import * as sfw from 'sfw'; -const { Div } = sfw.element.native; - -import stylesheet from './index.css'; - -const css = await sfw.css.sheet(stylesheet); - -export default class MonthSelect extends sfw.element.Container { - #container - #month_container - - constructor() { - super({ css }); - - this.onscroll = (e) => e.stopPropagate(); - this.onmonth = () => {}; - - this.body.append( - this.#container = Div.new({ - id: 'container', - children: [ - Div.new({ id: 'title', innerText: 'Select Month' }), - Div.new({ - id: 'close-button', - children: [ icons.close ], - onclick: () => this.hide(), - }), - this.#month_container = Div.new({ - id: 'month-container', - }), - ], - }), - ); - } - - show() { - this.#container.classList.add('visible'); - } - - hide() { - this.#container.classList.remove('visible'); - } - - set months(months) { - months.sort((a, b) => a.is_before(b)); - - this.#month_container.innerHTML = ''; - - let last = null; - - for (const month of months) { - if (last != null && !last.is_same_year(month)) { - this.#month_container.append( - Div.new({ - className: 'year-item', - innerText: last.year, - }) - ); - } - - this.#month_container.append( - Div.new({ - className: 'month-item', - innerText: month.name, - onclick: () => { - this.hide(); - this.onmonth(month); - } - }) - ); - - last = month; - } - - this.#month_container.append( - Div.new({ - className: 'month-item none', - innerText: 'No Date', - onclick: () => { - this.hide(); - this.onmonth(null); - } - }) - ); - } -} diff --git a/static/widgets/password-dialog/index.css b/static/widgets/password-dialog/index.css deleted file mode 100644 index ad8c1c2..0000000 --- a/static/widgets/password-dialog/index.css +++ /dev/null @@ -1,51 +0,0 @@ -#container { - position: absolute; - max-width: 500px; - width: 70%; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - display: grid; - gap: 10px; - background: #efefef99; - backdrop-filter: blur(10px); - padding: 10px; - border-radius: var(--border-radius); - box-shadow: var(--shadow); -} - -#title { - font-size: 1.1em; -} - -#close { - width: 25px; - height: 25px; - position: absolute; - top: 5px; - right: 5px; - cursor: pointer; - background: var(--page-background); - border-radius: 100%; - padding: 2px; -} - -#button { - background: var(--primary); - padding: 10px; - color: var(--fg-primary); - font-weight: bold; - border-radius: var(--border-radius); - box-shadow: var(--box-shadow); - text-align: center; -} - -#error { - font-style: italic; - font-weight: bold; - color: #ee5151; -} - -#error.hidden { - display: none; -} diff --git a/static/widgets/password-dialog/index.js b/static/widgets/password-dialog/index.js deleted file mode 100644 index 76fbec6..0000000 --- a/static/widgets/password-dialog/index.js +++ /dev/null @@ -1,60 +0,0 @@ -import * as api from 'api'; -import icons from 'icons'; -import * as sfw from 'sfw'; -const { Div, Input } = sfw.element.native; - -import stylesheet from './index.css'; - -const css = await sfw.css.sheet(stylesheet); - -export default class PasswordDialog extends sfw.element.Container { - #current - #new - #confirm - #error - - constructor() { - super({ css }); - - this.callclose = () => this.close(); - - document.body.addEventListener('click', this.callclose); - this.onclick = (e) => e.stopPropagation(); - - this.body.append(Div.new({ - id: 'container', - children: [ - Div.new({ id: 'title', innerText: 'Change Password' }), - Div.new({ id: 'close', children: [ icons.close ], onclick: () => this.close() }), - this.#current = Input.new({ placeholder: 'Current Password', type: 'password' }), - this.#new = Input.new({ placeholder: 'New Password', type: 'password' }), - this.#confirm = Input.new({ placeholder: 'Confirm Password', type: 'password' }), - this.#error = Div.new({ id: 'error', className: 'hidden' }), - Div.new({ - id: 'button', - innerText: 'Update', - onclick: async () => { - if (this.#new.value !== this.#confirm.value) { - this.#error.innerText = 'Passwords do not match'; - this.#error.className = ''; - return; - } - - if (!await api.profile.update_password(this.#current.value, this.#new.value)) { - this.#error.innerText = 'invalid password'; - this.#error.className = ''; - return; - } - - this.close(); - }, - }), - ], - })); - } - - close() { - this.parentNode.removeChild(this); - document.body.removeEventListener('click', this.callclose); - } -} diff --git a/static/widgets/search/index.css b/static/widgets/search/index.css deleted file mode 100644 index 14e650d..0000000 --- a/static/widgets/search/index.css +++ /dev/null @@ -1,50 +0,0 @@ -:host { - position: fixed; - top: 0; - left: 0; -} - -#container { - position: fixed; - top: -200px; - left: 5px; - right: 5px; - height: 100px; - background: #fff9; - backdrop-filter: blur(10px); - border-radius: var(--border-radius); - transition: top 0.2s ease; - padding: 10px; - display: grid; - gap: 10px; - box-shadow: var(--shadow); -} - -#container.visible { - top: 5px; -} - -#title { - font-size: 1.2em; - align-content: center; - padding-left: 5px; - user-select: none; -} - -#search-box { - width: 100%; - display: grid; - grid-template-columns: auto 50px; -} - -input { - border-top-right-radius: 0px !important; - border-bottom-right-radius: 0px !important; -} - -button { - width: 50px; - border-top-left-radius: 0px !important; - border-bottom-left-radius: 0px !important; - height: 42px; -} diff --git a/static/widgets/search/index.js b/static/widgets/search/index.js deleted file mode 100644 index b014cdd..0000000 --- a/static/widgets/search/index.js +++ /dev/null @@ -1,68 +0,0 @@ -import icons from 'icons'; -import * as sfw from 'sfw'; -const { Div, Input, Button } = sfw.element.native; - -import stylesheet from './index.css'; - -const css = await sfw.css.sheet(stylesheet); - -export default class Search extends sfw.element.Container { - #container - #search - - constructor() { - super({ css }); - - this.onsubmit = () => {} - this.onhide = () => {} - - this.onclick = (e) => e.stopPropagation(); - this.hide = () => { - this.#container.classList.remove('visible'); - document.removeEventListener('click', this.hide); - this.onhide(); - }; - - this.body.append( - this.#container = Div.new({ - id: 'container', - children: [ - Div.new({ innerText: 'Search', id: 'title' }), - Div.new({ - id: 'search-box', - children: [ - this.#search = Input.new({ - type: 'search', - onsearch: () => this.submit(), - onkeydown: (event) => { - if (event.key === 'Enter') { - this.submit(); - } - } - }), - Button.new({ - children: [ icons.search ], - onclick: () => this.submit(), - }), - ] - }) - ] - }) - ); - } - - submit() { - this.onsubmit(this.#search.value); - this.#search.blur(); - this.hide(); - } - - toggle() { - this.#container.classList.toggle('visible'); - - if (this.#container.classList.contains('visible')) { - this.#search.focus() - document.addEventListener('click', this.hide) - } - } -} diff --git a/static/widgets/upload-bar/index.css b/static/widgets/upload-bar/index.css deleted file mode 100644 index 8ec1697..0000000 --- a/static/widgets/upload-bar/index.css +++ /dev/null @@ -1,41 +0,0 @@ - -#container { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: calc(100% - 10px); - height: 100px; - background: #efefef99; - backdrop-filter: blur(10px); - border-radius: var(--border-radius); - max-width: 500px; - padding: 10px 20px; - display: grid; -} - -#files { - background: #efefef; - padding: 10px; - border-radius: 20px; - margin: auto; - width: min-content; - white-space: nowrap; -} - -#inner-progress { - width: 100%; - height: 5px; - border-radius: 5px; - background: #efefef; - overflow: hidden; - margin: auto; -} - - -#progress { - background: var(--primary); - height: 100%; - border-radius: 5px; - transition: width 0.2s ease; -} diff --git a/static/widgets/upload-bar/index.js b/static/widgets/upload-bar/index.js deleted file mode 100644 index f6d37b5..0000000 --- a/static/widgets/upload-bar/index.js +++ /dev/null @@ -1,44 +0,0 @@ -import * as sfw from 'sfw'; -const { Div } = sfw.element.native; - -import stylesheet from './index.css'; - -const css = await sfw.css.sheet(stylesheet); - -export default class UploadBar extends sfw.element.Container { - #files_progress - #inner_progress - #container - - constructor() { - super({ css }); - - this.body.append( - this.#container = Div.new({ - id: 'container', - style: { display: 'none' }, - children: [ - this.#files_progress = Div.new({ id: 'files', innerText: 'Uploading...' }), - Div.new({ - id: 'inner-progress', - children: [ - this.#inner_progress = Div.new({ id: 'progress' }), - ] - }) - ] - }) - ); - } - - set progress(progress) { - this.#inner_progress.style.width = `${progress * 100}%` - } - - hide() { - this.#container.style.display = 'none'; - } - - show() { - this.#container.style.display = ''; - } -} |