From 44445734ba4a0f05564f808c2fd34a7f23c6fafb Mon Sep 17 00:00:00 2001 From: Nathan Reiner Date: Wed, 20 May 2026 10:16:50 +0200 Subject: Let's do a rewrite! Created project structure and changed `build.zig` and added licence. --- static/api/auth.js | 12 --- static/api/images.js | 83 ----------------- static/api/index.js | 4 - static/api/profile.js | 15 --- static/api/rest.js | 22 ----- static/api/session.js | 23 ----- static/fonts/noto-sans/default.ttf | Bin 2044548 -> 0 bytes static/fonts/noto-sans/italic.ttf | Bin 2300468 -> 0 bytes static/fonts/pacifico/regular.ttf | Bin 315408 -> 0 bytes static/icon.png | Bin 27614 -> 0 bytes static/icon.svg | 75 --------------- static/icons/192.png | Bin 9550 -> 0 bytes static/icons/512.png | Bin 27614 -> 0 bytes static/icons/add.js | 3 - static/icons/calendar.js | 3 - static/icons/check.js | 3 - static/icons/close.js | 3 - static/icons/edit.js | 3 - static/icons/home.js | 4 - static/icons/icon.svg | 51 ----------- static/icons/index.js | 39 -------- static/icons/next.js | 3 - static/icons/search.js | 3 - static/icons/settings.js | 3 - static/icons/shuffle.js | 4 - static/index.css | 70 -------------- static/index.html | 12 --- static/index.js | 137 --------------------------- static/manifest.json | 17 ---- static/month.js | 95 ------------------- static/pages/image-viewer/index.css | 9 -- static/pages/image-viewer/index.js | 34 ------- static/pages/index.js | 5 - static/pages/login/index.css | 153 ------------------------------- static/pages/login/index.js | 118 ------------------------ static/pages/main/index.css | 70 -------------- static/pages/main/index.js | 98 -------------------- static/pages/settings/index.css | 104 --------------------- static/pages/settings/index.js | 112 ---------------------- static/pages/shuffle/index.css | 39 -------- static/pages/shuffle/index.js | 110 ---------------------- static/service-worker/index.js | 21 ----- static/service-worker/worker.js | 41 --------- static/sfw | 1 - static/tsconfig.json | 10 -- static/widgets/editable/index.css | 36 -------- static/widgets/editable/index.js | 77 ---------------- static/widgets/image/index.css | 134 --------------------------- static/widgets/image/index.js | 108 ---------------------- static/widgets/index.js | 6 -- static/widgets/month-select/index.css | 134 --------------------------- static/widgets/month-select/index.js | 87 ------------------ static/widgets/password-dialog/index.css | 51 ----------- static/widgets/password-dialog/index.js | 60 ------------ static/widgets/search/index.css | 50 ---------- static/widgets/search/index.js | 68 -------------- static/widgets/upload-bar/index.css | 41 --------- static/widgets/upload-bar/index.js | 44 --------- 58 files changed, 2508 deletions(-) delete mode 100644 static/api/auth.js delete mode 100644 static/api/images.js delete mode 100644 static/api/index.js delete mode 100644 static/api/profile.js delete mode 100644 static/api/rest.js delete mode 100644 static/api/session.js delete mode 100644 static/fonts/noto-sans/default.ttf delete mode 100644 static/fonts/noto-sans/italic.ttf delete mode 100644 static/fonts/pacifico/regular.ttf delete mode 100644 static/icon.png delete mode 100644 static/icon.svg delete mode 100644 static/icons/192.png delete mode 100644 static/icons/512.png delete mode 100644 static/icons/add.js delete mode 100644 static/icons/calendar.js delete mode 100644 static/icons/check.js delete mode 100644 static/icons/close.js delete mode 100644 static/icons/edit.js delete mode 100644 static/icons/home.js delete mode 100644 static/icons/icon.svg delete mode 100644 static/icons/index.js delete mode 100644 static/icons/next.js delete mode 100644 static/icons/search.js delete mode 100644 static/icons/settings.js delete mode 100644 static/icons/shuffle.js delete mode 100644 static/index.css delete mode 100644 static/index.html delete mode 100644 static/index.js delete mode 100644 static/manifest.json delete mode 100644 static/month.js delete mode 100644 static/pages/image-viewer/index.css delete mode 100644 static/pages/image-viewer/index.js delete mode 100644 static/pages/index.js delete mode 100644 static/pages/login/index.css delete mode 100644 static/pages/login/index.js delete mode 100644 static/pages/main/index.css delete mode 100644 static/pages/main/index.js delete mode 100644 static/pages/settings/index.css delete mode 100644 static/pages/settings/index.js delete mode 100644 static/pages/shuffle/index.css delete mode 100644 static/pages/shuffle/index.js delete mode 100644 static/service-worker/index.js delete mode 100644 static/service-worker/worker.js delete mode 160000 static/sfw delete mode 100644 static/tsconfig.json delete mode 100644 static/widgets/editable/index.css delete mode 100644 static/widgets/editable/index.js delete mode 100644 static/widgets/image/index.css delete mode 100644 static/widgets/image/index.js delete mode 100644 static/widgets/index.js delete mode 100644 static/widgets/month-select/index.css delete mode 100644 static/widgets/month-select/index.js delete mode 100644 static/widgets/password-dialog/index.css delete mode 100644 static/widgets/password-dialog/index.js delete mode 100644 static/widgets/search/index.css delete mode 100644 static/widgets/search/index.js delete mode 100644 static/widgets/upload-bar/index.css delete mode 100644 static/widgets/upload-bar/index.js (limited to 'static') diff --git a/static/api/auth.js b/static/api/auth.js deleted file mode 100644 index e663d88..0000000 --- a/static/api/auth.js +++ /dev/null @@ -1,12 +0,0 @@ -import * as rest from './rest.js'; - - -export async function login(user, password) { - const response = await rest.post('/api/auth/login', { user, password }); - return response?.success ?? false; -} - -export async function is_first_login(user) { - const response = await rest.post('/api/auth/first-login', { user }); - return response?.is_first ?? false; -} diff --git a/static/api/images.js b/static/api/images.js deleted file mode 100644 index 8e9cff2..0000000 --- a/static/api/images.js +++ /dev/null @@ -1,83 +0,0 @@ -import * as sfw from 'sfw'; -const { Input } = sfw.element.native; -import * as rest from './rest.js'; - -class FileUploader { - constructor(url, files) { - this.onprogress = () => {} - this.ondone = () => {} - this.url = url; - this.sessions = []; - this.files = files; - } - - send() { - let count = 0; - this.sessions = this.sessions.concat(this.files.map( - (file) => new Promise((resolve) => { - const xhr = new XMLHttpRequest(); - xhr.addEventListener("loadend", (event) => { - count += 1; - - this.onprogress(count, this.files.length) - - resolve(xhr.readyState === 4 && xhr.status === 200); - - if (count == this.files.length) { - this.ondone(); - } - }); - - xhr.open("POST", this.url, true); - xhr.setRequestHeader("Content-Type", "application/octet-stream"); - xhr.send(file.slice()); - }) - )); - } -} - -export async function upload_to_timeline() { - const input = Input.new({ - type: 'file', - multiple: true, - accept: 'image/jpeg', - }) - input.click(); - - - return new Promise((resolve) => { - input.onchange = () => { - resolve(new FileUploader('/api/image/upload', [...input.files])); - } - }) -} - -export async function upload_to_profile(id) { - const input = Input.new({ - type: 'file', - multiple: false, - accept: 'image/jpeg', - }) - input.click(); - - return new Promise((resolve) => { - input.onchange = () => { - resolve(new FileUploader(`/api/profile/image/upload/${id}`, [...input.files])); - } - }) -} - -export function list() { - return rest.get('/api/image/list') - .then(r => { - r.images.forEach(i => { - i.date = new Date(i.timestamp * 1000) - }); - return r.images; - }); -} - - -export function remove(id) { - return rest.post('/api/image/remove', { id: id }); -} diff --git a/static/api/index.js b/static/api/index.js deleted file mode 100644 index cddd7bd..0000000 --- a/static/api/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export * as images from './images.js'; -export * as auth from './auth.js'; -export * as session from './session.js'; -export * as profile from './profile.js'; diff --git a/static/api/profile.js b/static/api/profile.js deleted file mode 100644 index d6acd16..0000000 --- a/static/api/profile.js +++ /dev/null @@ -1,15 +0,0 @@ -import * as rest from './rest.js'; - -export async function set(name, birthday) { - await rest.post('/api/profile/set', { - full_name: name, - birthday: birthday, - }); -} - -export function update_password(old, next) { - return rest.post('/api/profile/update-password', { - current_password: old, - new_password: next, - }).then(r => r.success); -} diff --git a/static/api/rest.js b/static/api/rest.js deleted file mode 100644 index 6b14034..0000000 --- a/static/api/rest.js +++ /dev/null @@ -1,22 +0,0 @@ - -async function handle_fetch(promise) { - const result = await promise; - const json = await result.json(); - - if (!result.ok) { - throw json; - } - - return json; -} - -export function get(url) { - return handle_fetch(fetch(url)); -} - -export function post(url, body) { - return handle_fetch(fetch(url, { - method: 'POST', - body: JSON.stringify(body), - })); -} diff --git a/static/api/session.js b/static/api/session.js deleted file mode 100644 index 6e14d55..0000000 --- a/static/api/session.js +++ /dev/null @@ -1,23 +0,0 @@ -import * as rest from './rest.js'; - -export function current() { - return rest.get('/api/session/current'); -} - -export async function is_valid() { - const result = await rest.get('/api/session/is-valid'); - return result.is_valid; -} - -export async function drop() { - const result = await rest.get('/api/session/drop'); -} - -export async function is_online() { - try { - const result = await rest.get('/api/session/is-online'); - return result.is_online; - } catch { - return false; - } -} diff --git a/static/fonts/noto-sans/default.ttf b/static/fonts/noto-sans/default.ttf deleted file mode 100644 index 9530d84..0000000 Binary files a/static/fonts/noto-sans/default.ttf and /dev/null differ diff --git a/static/fonts/noto-sans/italic.ttf b/static/fonts/noto-sans/italic.ttf deleted file mode 100644 index 6245ba0..0000000 Binary files a/static/fonts/noto-sans/italic.ttf and /dev/null differ diff --git a/static/fonts/pacifico/regular.ttf b/static/fonts/pacifico/regular.ttf deleted file mode 100644 index e7def95..0000000 Binary files a/static/fonts/pacifico/regular.ttf and /dev/null differ diff --git a/static/icon.png b/static/icon.png deleted file mode 100644 index 7ff5837..0000000 Binary files a/static/icon.png and /dev/null differ diff --git a/static/icon.svg b/static/icon.svg deleted file mode 100644 index 83fc1bd..0000000 --- a/static/icon.svg +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - M - - diff --git a/static/icons/192.png b/static/icons/192.png deleted file mode 100644 index 38ee689..0000000 Binary files a/static/icons/192.png and /dev/null differ diff --git a/static/icons/512.png b/static/icons/512.png deleted file mode 100644 index 7ff5837..0000000 Binary files a/static/icons/512.png and /dev/null differ diff --git a/static/icons/add.js b/static/icons/add.js deleted file mode 100644 index 12650a8..0000000 --- a/static/icons/add.js +++ /dev/null @@ -1,3 +0,0 @@ -export default ` - -`; diff --git a/static/icons/calendar.js b/static/icons/calendar.js deleted file mode 100644 index 9d9b2ec..0000000 --- a/static/icons/calendar.js +++ /dev/null @@ -1,3 +0,0 @@ -export default ` - -`; diff --git a/static/icons/check.js b/static/icons/check.js deleted file mode 100644 index dfc2763..0000000 --- a/static/icons/check.js +++ /dev/null @@ -1,3 +0,0 @@ -export default ` - -` diff --git a/static/icons/close.js b/static/icons/close.js deleted file mode 100644 index 8c96dbd..0000000 --- a/static/icons/close.js +++ /dev/null @@ -1,3 +0,0 @@ -export default ` - -`; diff --git a/static/icons/edit.js b/static/icons/edit.js deleted file mode 100644 index e13bb8a..0000000 --- a/static/icons/edit.js +++ /dev/null @@ -1,3 +0,0 @@ -export default ` - -`; diff --git a/static/icons/home.js b/static/icons/home.js deleted file mode 100644 index a6b4886..0000000 --- a/static/icons/home.js +++ /dev/null @@ -1,4 +0,0 @@ -export default ` - - -`; diff --git a/static/icons/icon.svg b/static/icons/icon.svg deleted file mode 100644 index 87b8771..0000000 --- a/static/icons/icon.svg +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - diff --git a/static/icons/index.js b/static/icons/index.js deleted file mode 100644 index 5402be0..0000000 --- a/static/icons/index.js +++ /dev/null @@ -1,39 +0,0 @@ -import * as sfw from 'sfw'; -const { I } = sfw.element.native; - -import Settings from './settings'; -import Shuffle from './shuffle'; -import Calendar from './calendar'; -import Search from './search'; -import Close from './close'; -import Home from './home'; -import Add from './add'; -import Edit from './edit'; -import Check from './check'; -import Next from './next'; - -const icons = { - 'settings': Settings, - 'shuffle': Shuffle, - 'calendar': Calendar, - 'search': Search, - 'close': Close, - 'home': Home, - 'add': Add, - 'edit': Edit, - 'check': Check, - 'next': Next, -}; - -const target = { icons: icons }; - -const handler = { - get(target, prop, receiver) { - return I.new({ - innerHTML: target.icons[prop] ?? '?', - className: 'icon', - }); - } -}; - -export default new Proxy(target, handler); diff --git a/static/icons/next.js b/static/icons/next.js deleted file mode 100644 index 9728ee7..0000000 --- a/static/icons/next.js +++ /dev/null @@ -1,3 +0,0 @@ -export default ` - -`; diff --git a/static/icons/search.js b/static/icons/search.js deleted file mode 100644 index a35ff2a..0000000 --- a/static/icons/search.js +++ /dev/null @@ -1,3 +0,0 @@ -export default ` - -`; diff --git a/static/icons/settings.js b/static/icons/settings.js deleted file mode 100644 index 258c8f8..0000000 --- a/static/icons/settings.js +++ /dev/null @@ -1,3 +0,0 @@ -export default ` - -`; diff --git a/static/icons/shuffle.js b/static/icons/shuffle.js deleted file mode 100644 index d538fd8..0000000 --- a/static/icons/shuffle.js +++ /dev/null @@ -1,4 +0,0 @@ -export default ` - - -`; diff --git a/static/index.css b/static/index.css deleted file mode 100644 index d5d74cd..0000000 --- a/static/index.css +++ /dev/null @@ -1,70 +0,0 @@ -:host, :root { - --primary: #726eff; - --fg-primary: #fff; - --fg: #212b38; - --fg-disabled: #37465b; - --bg-label: #dedee8; - --border-radius: 4px; - --card-background: #fff; - --page-background: #dfdfdf; - --shadow: #223223aa 1px 1px 4px; -} - -html, body { - margin: 0; - padding: 0; - width: 100%; - height: 100%; - overflow: hidden; -} - -body { - font-family: "Noto Sans", sans-serif; - color: var(--fg); -} - -button { - font-family: "Noto Sans", sans-serif; - background: var(--primary); - color: var(--fg-primary); - padding: 10px; - border: none; - border-radius: var(--border-radius); - font-weight: bold; - cursor: pointer; -} - -input { - font-family: "Noto Sans", sans-serif; - color: var(--fg); - padding: 10px; - border: none; - outline: none; - background: var(--bg-label); - border-radius: var(--border-radius); -} - -* { - box-sizing: border-box; -} - -.icon svg { - width: 100%; - height: 100%; -} - -@font-face { - font-family: Pacifico; - src: url(/fonts/pacifico/regular.ttf); -} - -@font-face { - font-family: "Noto Sans"; - src: url(/fonts/noto-sans/default.ttf); -} - -@font-face { - font-family: "Noto Sans"; - font-style: italic - src: url(/fonts/noto-sans/italic.ttf); -} diff --git a/static/index.html b/static/index.html deleted file mode 100644 index 8fa5079..0000000 --- a/static/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - Memora - - - - - - - diff --git a/static/index.js b/static/index.js deleted file mode 100644 index ddbead7..0000000 --- a/static/index.js +++ /dev/null @@ -1,137 +0,0 @@ -import * as sfw from 'sfw'; -import * as api from 'api'; -import Month from './month.js'; - -import LoginView from './pages/login/index.js'; -import MainView from './pages/main/index.js'; -import ImageViewer from './pages/image-viewer/index.js'; -import Search from './widgets/search/index.js'; -import MonthSelect from './widgets/month-select/index.js'; -import SettingsView from './pages/settings/index.js'; -import ShuffleView from './pages/shuffle/index.js'; -import UploadBar from './widgets/upload-bar/index.js'; - -import stylesheet from './index.css'; -sfw.theme.add_css(await sfw.css.sheet(stylesheet)); - -const image_viewer = ImageViewer.new(); - -const reload = () => { - api.images.list().then(images => { - const last = Month.from_unix(images[0].timestamp); - let first = Month.from_unix(images.findLast(i => i.timestamp).timestamp); - month_select.months = first.to(last); - - image_viewer.images = images; - shuffle.images = images; - }); -} - -const login = LoginView.new({ - onlogin: async (user, password) => { - if (await api.auth.login(user, password)) { - reload(); - login.hide(); - } else { - login.comment = 'Incorrect username or password.'; - } - }, - - onpassword: async (user) => { - if (await api.auth.is_first_login(user)) { - login.comment = 'Please enter a new password.'; - } else { - login.comment = ''; - } - }, -}); - -const search = Search.new({ - onsubmit: (content) => console.log(content), - onhide: () => main.show(), -}); - -const month_select = MonthSelect.new({ - onmonth: (month) => image_viewer.jump_to(month), -}); -const upload_bar = UploadBar.new(); - -const settings = SettingsView.new({ - onlogout: async () => { - await api.session.drop(); - login.show(); - main.active_view = image_viewer; - settings.profile = null; - }, -}); - -const shuffle = ShuffleView.new({ }); - -const main = MainView.new({ - active_view: image_viewer, - active_kind: MainView.Kind.upload, - - onsearch: () => { - main.active_kind = MainView.Kind.upload; - main.active_view = image_viewer; - search.toggle(); - main.hide(); - }, - onmonth: () => { - main.active_kind = MainView.Kind.upload; - main.active_view = image_viewer; - month_select.show(); - }, - onupload: async () => { - upload_bar.progress = 0; - upload_bar.show(); - - const uploader = await api.images.upload_to_timeline(); - - uploader.onprogress = (count, total) => { - upload_bar.progress = count / total; - } - - uploader.ondone = () => { - upload_bar.hide(); - reload(); - } - - uploader.send(); - }, - onshuffle: () => { - shuffle.open(); - main.active_kind = MainView.Kind.home; - main.active_view = shuffle; - }, - onsettings: async () => { - if (!settings.profile) { - settings.profile = await api.session.current(); - } - - main.active_kind = MainView.Kind.home; - main.active_view = settings; - }, - onhome: () => { - main.active_kind = MainView.Kind.upload; - main.active_view = image_viewer; - }, -}); - -document.body.append( - login, - main, - search, - month_select, - upload_bar, -); - -if (!await api.session.is_online()) { - login.hide(); - reload(); -} if (await api.session.is_valid()) { - login.hide(); - reload(); -} else { - login.focus(); -} diff --git a/static/manifest.json b/static/manifest.json deleted file mode 100644 index e91b5dc..0000000 --- a/static/manifest.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "Memora", - "icons": [ - { - "src": "icons/512.png", - "type": "image/png", - "sizes": "512x512" - }, - { - "src": "icons/192.png", - "type": "image/png", - "sizes": "192x192" - } - ], - "start_url": ".", - "display": "standalone" -} diff --git a/static/month.js b/static/month.js deleted file mode 100644 index 05037d4..0000000 --- a/static/month.js +++ /dev/null @@ -1,95 +0,0 @@ - -export default class Month { - constructor(month, year) { - this.month = month; - this.year = year; - } - - next(n) { - n = n ?? 1; - - const month = this.month + n - 1; - this.month = (month % 12) + 1; - this.year += Math.floor(month / 12); - - return this; - } - - previous(n) { - n = n ?? 1; - - let month = this.month - n - 1; - - if (month < 0) { - month += 12; - } - - this.month = (month % 12) + 1; - this.year += Math.floor(month / 12); - - return this; - } - - to(other) { - const inner = []; - const current = this.copy(); - - while (current.is_same(other) || current.is_before(other)) { - inner.push(current.copy()); - current.next(); - } - - return inner; - } - - copy() { - return new Month(this.month, this.year); - } - - is_before(other) { - return this.year < other.year || (this.year == other.year && this.month < other.month); - } - - is_after(other) { - return this.year > other.year || (this.year == other.year && this.month > other.month); - } - - is_same(other) { - return this.year == other?.year && this.month == other?.month; - } - - is_same_year(other) { - return this.year == other.year; - } - - get name() { - const date = new Date(); - date.setMonth(this.month - 1); - return date.toLocaleDateString('default', { month: 'long' }) - } - - static from_unix(date) { - if (date == null) return null; - - return Month.from_date(new Date(date * 1000)); - } - - static from_date(date) { - return new Month(date.getMonth() + 1, date.getFullYear()); - } - - static from_string(string) { - const [ year, month ] = string.split('-'); - return new Month(parseInt(month), parseInt(year)) - } - - static literal(strings) { - return Month.from_string(strings[0]); - } - - static get current() { - return Month.from_date(new Date()); - } -} - -export const literal = Month.literal; diff --git a/static/pages/image-viewer/index.css b/static/pages/image-viewer/index.css deleted file mode 100644 index 4567050..0000000 --- a/static/pages/image-viewer/index.css +++ /dev/null @@ -1,9 +0,0 @@ - -#container { - width: 100%; - height: 100vh; - overflow-y: auto; - display: grid; - gap: 10px; - padding: 10px; -} diff --git a/static/pages/image-viewer/index.js b/static/pages/image-viewer/index.js deleted file mode 100644 index 34b5f69..0000000 --- a/static/pages/image-viewer/index.js +++ /dev/null @@ -1,34 +0,0 @@ -import * as sfw from 'sfw'; -const { Img, Div } = sfw.element.native; - -import Month from '../../month.js'; - -import stylesheet from './index.css'; - -const css = await sfw.css.sheet(stylesheet); - -export default class ImageViewer extends sfw.element.Container { - #images - #image_elements - - constructor() { - super({ css }); - - this.#images = []; - - this.body.append( - Div.new({ - id: 'container', - onscroll: () => console.log('scroll'), - }) - ); - } - - set images(images) { - this.#images = images; - } - - jump_to(month) { - // TODO - } -} diff --git a/static/pages/index.js b/static/pages/index.js deleted file mode 100644 index 56b0848..0000000 --- a/static/pages/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export ImageViewer from './image-viewer/index.js'; -export LoginView from './login/index.js'; -export MainView from './main/index.js'; -export SettingsView from './settings/index.js'; -export ShuffleView from './shuffle/index.js'; diff --git a/static/pages/login/index.css b/static/pages/login/index.css deleted file mode 100644 index d9d0f41..0000000 --- a/static/pages/login/index.css +++ /dev/null @@ -1,153 +0,0 @@ -#container { - background: var(--page-background); - overflow: hidden; - width: 100%; - height: 100%; - z-index: 1000; - position: absolute; - top: 0; - bottom: 0; - transition: top 0.2s ease; -} - -#container.hidden { - top: -100%; -} - -#box { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - max-width: 300px; - width: 100%; - padding: 0px; - box-shadow: #223223aa 1px 1px 4px; - border-radius: var(--border-radius); - z-index: 5000; - display: grid; - grid-template-columns: auto 30px; - background: #ffffff99; - backdrop-filter: blur(10px); -} - -#box input { - border-top-right-radius: 0px; - border-bottom-right-radius: 0px; - background: #0000; -} - -#box #submit { - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - background: var(--page-background); - color: var(--fg); - padding: 5px; - background: #0000; -} - -#username-label { - position: absolute; - top: calc(50% - 40px); - left: 50%; - transform: translate(-50%, -50%); - z-index: 10000; - user-select: none; - color: var(--fg-primary); - font-size: 0.8em; - cursor: pointer; -} -#comment { - position: absolute; - top: calc(50% + 40px); - left: 50%; - transform: translate(-50%, -50%); - z-index: 10000; - user-select: none; - color: var(--primary); - font-size: 0.8em; - cursor: pointer; -} - -#title { - font-family: 'Pacifico'; - position: absolute; - width: 100%; - height: 100%; - text-align: center; - top: 0px; - padding-top: 50px; - font-size: 2.5em; - z-index: 1000; - text-shadow: -1px -1px 0 var(--page-background), - 1px 1px 0 var(--page-background), - 1px -1px 0 var(--page-background), - -1px 1px 0 var(--page-background); - overflow: hidden; -} - -@keyframes left-bubble { - from { - width: 0px; - height: 0px; - } - to { - width: 100vw; - height: 100vw; - } -} - -#title:before { - content: ''; - display: block; - position: absolute; - top: -30px; - left: 0px; - background: var(--primary); - transform: translate(-50%, -50%); - border-radius: 100%; - z-index: -1; - animation: left-bubble 1s normal forwards ease; - width: 100vw; - height: 100vw; -} - -@keyframes right-bubble { - from { - width: 0px; - height: 0px; - } - to { - width: 200vw; - height: 200vw; - } -} - -#title:after { - content: ''; - display: block; - position: absolute; - bottom: 55%; - right: 0px; - width: 200vw; - height: 200vw; - transform: translate(50%, 50%); - background: var(--fg); - border-radius: 100%; - z-index: -2; - animation: right-bubble 1s normal forwards ease; -} -#subtitle { - position: absolute; - bottom: 50px; - left: 50%; - transform: translate(-50%, 0); - white-space: nowrap; - font-family: 'Pacifico'; - color: #69717d; - z-index: 1000; - text-shadow: -1px -1px 0 var(--page-background), - 1px 1px 0 var(--page-background), - 1px -1px 0 var(--page-background), - -1px 1px 0 var(--page-background); -} diff --git a/static/pages/login/index.js b/static/pages/login/index.js deleted file mode 100644 index 0390cc0..0000000 --- a/static/pages/login/index.js +++ /dev/null @@ -1,118 +0,0 @@ -import icons from 'icons'; -import * as sfw from 'sfw'; -const { Div, Label, H1: Title, Input, Button, Form } = sfw.element.native; - -import stylesheet from './index.css'; - -const css = await sfw.css.sheet(stylesheet); - -export default class LoginView extends sfw.element.Container { - #container - #user - #username - #password - #comment - - constructor() { - super({ css }); - - this.onlogin = () => {}; - this.onpassword = () => {}; - - this.body.append( - this.#container = Div.new({ - id: 'container', - children: [ - Div.new({ id: 'title', innerText: 'Memora' }), - this.#user = Div.new({ - id: 'username-label', - onclick: () => this.#reset(), - }), - Form.new({ - id: 'box', - onsubmit: (e) => e.preventDefault(), - children: [ - this.#username = Input.new({ - id: 'username', - placeholder: 'Username', - autocomplete: 'username', - }), - this.#password = Input.new({ - id: 'password', - placeholder: 'Password', - autocomplete: 'current-password', - type: 'password', - style: { display: 'none' }, - onkeydown: (e) => { - if (e.key === 'Enter') { - return this.#login(); - } - }, - }), - Button.new({ - id: 'submit', - children: [ icons.next ], - onclick: () => this.#next(), - }), - ] - }), - this.#comment = Div.new({ - id: 'comment', - onclick: () => this.#reset(), - }), - Div.new({ - id: 'subtitle', - innerText: 'Where nostalgia is home.', - }), - ] - }) - ); - } - - #next() { - if (this.#username.style.display != 'none') { - this.#go_to_password(); - } else { - this.#login(); - } - } - - #go_to_password() { - if (this.#username.value === '') return; - - this.#user.innerText = this.#username.value; - this.#username.style.display = 'none'; - this.#password.style.display = ''; - this.#password.focus(); - } - - async #login() { - await this.onlogin(this.#username.value, this.#password.value); - this.#reset(); - } - - #reset() { - this.#user.innerText = ''; - this.#username.value = ''; - this.#password.value = ''; - this.#username.style.display = ''; - this.#password.style.display = 'none'; - } - - set comment(value) { - this.#comment.innerText = value; - } - - show() { - this.#container.classList.remove('hidden'); - this.focus(); - } - - hide() { - this.#container.classList.add('hidden'); - } - - focus() { - this.#username.focus(); - } -} diff --git a/static/pages/main/index.css b/static/pages/main/index.css deleted file mode 100644 index 21d5022..0000000 --- a/static/pages/main/index.css +++ /dev/null @@ -1,70 +0,0 @@ - -:host { - display: grid; - height: 100%; - width: 100%; - background: var(--page-background); -} - -#bar { - position: fixed; - bottom: 5px; - left: 5px; - right: 5px; - background: #fff9; - box-shadow: #223223aa 1px 1px 4px; - display: grid; - grid-template-columns: 1fr 1fr 1fr 1fr 1fr ; - height: 50px; - backdrop-filter: blur(10px); - border-radius: var(--border-radius); - transition: bottom 0.2s ease; - max-width: 600px; - margin: 0 auto; -} - -#bar.hidden { - bottom: -200px; -} - -.menu-button { - color: var(--fg-disabled); - width: 18px; - height: 18px; - border-radius: 100%; - margin: auto; - cursor: pointer; - user-select: none; - align-content: center; - text-align: center; -} - -.menu-button.add { - width: 60px; - height: 60px; - margin-top: -20px; - background: var(--primary); - color: var(--card-background); - font-weight: bold; - font-size: 2em; -} - -.menu-button.add .icon { - width: 30px; - height: 30px; - display: grid; - margin: auto; -} - -.menu-button.add .icon .bi-house-fill { - width: 20px; - height: 20px; - margin: auto; -} - -.menu-button .icon { - width: 100%; - height: 100%; - display: grid; - margin: auto; -} diff --git a/static/pages/main/index.js b/static/pages/main/index.js deleted file mode 100644 index b0a13f6..0000000 --- a/static/pages/main/index.js +++ /dev/null @@ -1,98 +0,0 @@ -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 MainView extends sfw.element.Container { - #active_kind - #active_button - #active_view - #bar - - static Kind = Object.freeze({ - 'upload': 0, - 'home': 1, - }); - - constructor() { - super({ css }); - - this.onsearch = () => {} - this.onmonth = () => {} - this.onupload = () => {} - this.onshuffle = () => {} - this.onhome = () => {} - - this.body.append( - this.#active_view = Div.new({ id: 'main' }), - - this.#bar = Div.new({ - id: 'bar', - children: [ - Div.new({ - className: 'menu-button', - children: [ icons.search ], - onclick: (e) => { - e.stopPropagation(); - this.onsearch(); - } - }), - - Div.new({ - className: 'menu-button', - children: [ icons.calendar ], - onclick: () => this.onmonth(), - }), - - this.#active_button = Div.new({ - className: 'menu-button add', - children: [ icons.add ], - onclick: () => this.onupload() - }), - - Div.new({ - className: 'menu-button', - children: [ icons.shuffle ], - onclick: () => this.onshuffle(), - }), - - Div.new({ - className: 'menu-button', - children: [ icons.settings ], - onclick: () => this.onsettings(), - }), - ], - }), - ); - } - - hide() { - this.#bar.classList.add('hidden'); - } - - show() { - this.#bar.classList.remove('hidden'); - } - - set active_view(element) { - this.#active_view.innerHTML = ''; - this.#active_view.append(element); - } - - set active_kind(kind) { - if (kind == MainView.Kind.home) { - this.#active_button.innerHTML = ''; - this.#active_button.append(icons.home); - this.#active_button.onclick = () => this.onhome(); - } else if (kind == MainView.Kind.upload) { - this.#active_button.innerHTML = ''; - this.#active_button.append(icons.add); - this.#active_button.onclick = () => this.onupload(); - } else { - console.error(`invalid kind ${kind}`); - } - } -} diff --git a/static/pages/settings/index.css b/static/pages/settings/index.css deleted file mode 100644 index dcd952f..0000000 --- a/static/pages/settings/index.css +++ /dev/null @@ -1,104 +0,0 @@ - -#container { - width: 100%; - height: 100%; - background: var(--page-background); - padding: 40px; - display: flex; - flex-flow: column; - gap: 20px; - max-width: 700px; - margin: auto; -} - -#profile-image { - width: 200px; - height: 200px; - position: relative; - margin: auto; - margin-top: 50px; - margin-bottom: 30px; -} - -#image-container { - background: #fff; - position: relative; - border-radius: 100%; - width: 100%; - height: 100%; - top: 0px; - left: 0px; - overflow: hidden; -} - -#image-container img { - visibility: hidden; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - min-width: 100%; - min-height: 100%; - width: 100%; - height: 100%; - object-fit: cover; - user-select: none; -} - -#image-container img[src] { - visibility: visible; -} - -#profile-image #edit-container { - overflow: unset; -} - -#profile-image #edit { - position: absolute; - top: 15px; - right: 15px; - background: var(--card-background); - width: 30px; - height: 30px; - padding: 6px; - border-radius: 100%; - box-shadow: var(--shadow); - cursor: pointer; -} - -#info-box { - width: 100%; - text-align: center; - color: var(--fg-disabled); - font-weight: lighter; - user-select: none; -} - -#info-box #name { - font-family: 'Pacifico'; -} - -#change-password { - background: var(--primary); - padding: 10px; - border-radius: var(--border-radius); - color: var(--fg-primary); - box-shadow: var(--shadow); - font-weight: bold; - text-align: center; - cursor: pointer; - user-select: none; -} - -#logout { - padding: 10px; - width: 100%; - text-align: center; - background: #ee5151; - box-shadow: var(--shadow); - color: #fff; - font-weight: bold; - border-radius: var(--border-radius); - cursor: pointer; - user-select: none; -} diff --git a/static/pages/settings/index.js b/static/pages/settings/index.js deleted file mode 100644 index 5cc40be..0000000 --- a/static/pages/settings/index.js +++ /dev/null @@ -1,112 +0,0 @@ -import icons from 'icons'; -import * as api from 'api'; -import * as sfw from 'sfw'; -const { Div, Img } = sfw.element.native; - -import Editable from '../../widgets/editable/index.js'; -import PasswordDialog from '../../widgets/password-dialog/index.js'; - -import stylesheet from './index.css'; - -const css = await sfw.css.sheet(stylesheet); - -export default class SettingsView extends sfw.element.Container { - #profile_image - #name - #birthday - - #profile - #change_password - - constructor() { - super({ css }); - - this.onlogout = () => {}; - - this.body.append( - Div.new({ - id: 'container', - children: [ - Div.new({ - id: 'profile-image', - children: [ - Div.new({ - id: 'image-container', - children: [ - this.#profile_image = Img.new(), - ] - }), - Div.new({ - id: 'edit', - children: [ icons.edit ], - onclick: async () => { - const uploader = await api.images.upload_to_profile(this.#profile.name); - uploader.ondone = async () => { - const blob = await fetch( - `/api/profile/image/load/${this.#profile.name}`, - {cache: 'reload', mode: 'no-cors'} - ).then(r => r.blob()); - - this.#profile_image.src = URL.createObjectURL(blob); - }; - uploader.send() - } - }), - ] - }), - this.#name = Editable.new({ - title: 'Name', - value: '', - onupdate: () => this.#update(), - }), - this.#birthday = Editable.new({ - title: 'Birthday', - type: 'date', - value: '', - onupdate: () => this.#update(), - }), - Div.new({ - id: 'change-password', - innerText: 'Change Password', - onclick: (e) => { - e.stopPropagation(); - this.body.append(PasswordDialog.new()); - } - }), - Div.new({ - id: 'logout', - innerText: 'Logout', - onclick: () => this.onlogout(), - }), - Div.new({ - id: 'info-box', - children: [ - Div.new({ id: 'name', innerText: 'Memora' }), - Div.new({ id: 'version', innerText: '0.0.1-unstable' }), - ] - }), - ] - }) - ) - } - - #update() { - api.profile.set(this.#name.value, this.#birthday.value); - } - - set profile(profile) { - this.#profile = profile; - - if (this.#profile) { - this.#profile_image.src = `/api/profile/image/load/${profile.name}`; - this.#profile_image.onerror = () => this.#profile_image.removeAttribute('src'); - - this.#name.value = profile.full_name; - this.#birthday.value = profile.birthday; - } - } - - get profile() { - return this.#profile; - } -} diff --git a/static/pages/shuffle/index.css b/static/pages/shuffle/index.css deleted file mode 100644 index 30d30ac..0000000 --- a/static/pages/shuffle/index.css +++ /dev/null @@ -1,39 +0,0 @@ -:host { - display: block; - width: 100vw; - height: 100vh; - top: 0px; - left: 0px; - overflow: hidden; - position: relative; -} - -sfw-image { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, calc(-50% - 50px)); - width: 100%; - max-width: calc(100vw - 20px); - max-height: calc(100vh - 200px); - user-select: none; - transition: transform 0.05s ease; -} - -#front { - z-index: 1000; -} - -#back { - z-index: 500; - filter: blur(50px); -} - -#date { - margin: 10px; - border-radius: var(--border-radius); - padding: 10px; - background: #efefef99; - backdrop-filter: blur(10px); - box-shadow: #223223aa 1px 1px 4px; -} diff --git a/static/pages/shuffle/index.js b/static/pages/shuffle/index.js deleted file mode 100644 index 620195c..0000000 --- a/static/pages/shuffle/index.js +++ /dev/null @@ -1,110 +0,0 @@ -import * as sfw from 'sfw'; -const { Div, Canvas } = sfw.element.native; - -import { Image } from 'widgets'; - -import stylesheet from './index.css'; - -const css = await sfw.css.sheet(stylesheet); - -export default class ShuffleView extends sfw.element.Container { - #front_image - #back_image - #front_image_index - #back_image_index - #images - #date - #drag_start_position - #drag_strength - - constructor() { - super({ css }); - - this.#front_image_index = null; - this.#back_image_index = null; - - this.body.append( - this.#date = Div.new({ id: 'date' }), - this.#front_image = Image.new({ - id: 'front', - disabled: true, - ontouchstart: (e) => { - if (e.touches.length != 1) return; - - this.#drag_start_position = [e.touches[0].pageX, e.touches[0].pageY]; - }, - ontouchmove: (e) => { - if (e.touches.length != 1) return; - - const position = [e.touches[0].pageX, e.touches[0].pageY]; - - const delta = [ - position[0] - this.#drag_start_position[0], - position[1] - this.#drag_start_position[1] - ]; - - const strength = Math.min(1, Math.max((delta[0]**2 + delta[1]**2) / (40 ** 2), 0) / 100); - this.#drag_strength = strength; - - const angle_x = position[0] - window.screen.width / 2; - const angle_y = position[1] - window.screen.height * 1.2; - let angle = (Math.atan((angle_y) / (angle_x)) * 180 / Math.PI + 90); - - if (angle_x < 0) { - angle -= 180; - } - - this.#front_image.style.transform = ` - translate(-50%, calc(-50% - 50px)) - translate(${delta[0]}px, ${delta[1]}px) - rotate(${angle}deg) - `; - this.#back_image.style.filter = `blur(${(1 - strength) * 50}px)`; - }, - ontouchend: (e) => { - if (this.#drag_strength > 0.3) { - this.next(); - } - - this.#front_image.style.transform = ''; - this.#back_image.style.filter = ''; - }, - }), - this.#back_image = Image.new({ id: 'back', disabled: true }), - ) - - this.onclick = () => this.next(); - } - - set images(images) { - this.#images = images; - } - - open() { - this.next(); - this.next(); - } - - next() { - this.#front_image_index = this.#back_image_index; - this.#back_image_index = Math.floor(Math.random() * this.#images.length); - - if (this.#front_image_index !== null) { - this.#front_image.metadata = this.#images[this.#front_image_index]; - this.#front_image.load(); - - const date = this.#images[this.#front_image_index].date - - this.#date.innerText = date.toLocaleString('default', { - month: 'long', - year: 'numeric', - day: 'numeric', - hour: 'numeric', - minute: 'numeric' - }); ; - } - - this.#back_image.metadata = this.#images[this.#back_image_index]; - this.#back_image.load(); - } -} diff --git a/static/service-worker/index.js b/static/service-worker/index.js deleted file mode 100644 index cff4b95..0000000 --- a/static/service-worker/index.js +++ /dev/null @@ -1,21 +0,0 @@ -export const register = async () => { - if (!'serviceWorker' in navigator) { - console.warn('service worker not supported by browser'); - return; - } - - try { - const registration = await navigator.serviceWorker.register("/service-worker/worker.js", { - scope: "/", - }); - if (registration.installing) { - console.log("Service worker installing"); - } else if (registration.waiting) { - console.log("Service worker installed"); - } else if (registration.active) { - console.log("Service worker active"); - } - } catch (error) { - console.error(`Registration failed with ${error}`); - } -} diff --git a/static/service-worker/worker.js b/static/service-worker/worker.js deleted file mode 100644 index 666c7d3..0000000 --- a/static/service-worker/worker.js +++ /dev/null @@ -1,41 +0,0 @@ -const cachable_api = [ - '/api/image/load/', -]; - -const is_cachable = (url) => { - for (const api of cachable_api) { - console.log(api, url); - if (url.includes(api)) { - return true; - } - } - - return false; -} - -const put_in_cache = async (request, response) => { - const cache = await caches.open("v1"); - await cache.put(request, response); -}; - -const cache_first = async (request, event) => { - if (request.method !== 'GET') { - return await fetch(request); - } - - if (!is_cachable(request.url)) { - return await fetch(request); - } - - const cache_response = await caches.match(request); - if (cache_response) { - return cache_response; - } - const network_response = await fetch(request); - event.waitUntil(put_in_cache(request, network_response.clone())); - return network_response; -}; - -self.addEventListener("fetch", (event) => { - event.respondWith(cache_first(event.request, event)); -}); diff --git a/static/sfw b/static/sfw deleted file mode 160000 index 9d74e16..0000000 --- a/static/sfw +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9d74e1679915cc0c5f56413879276fc43b093892 diff --git a/static/tsconfig.json b/static/tsconfig.json deleted file mode 100644 index 97d1355..0000000 --- a/static/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "paths": { - "sfw": ["./sfw"], - "api": ["./api"], - "icons": ["./icons"], - "widgets": ["./widgets"] - }, - } -} 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 = ''; - } -} -- cgit v1.2.3-70-g09d2