diff options
Diffstat (limited to 'static')
58 files changed, 0 insertions, 2507 deletions
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 Binary files differdeleted file mode 100644 index 9530d84..0000000 --- a/static/fonts/noto-sans/default.ttf +++ /dev/null diff --git a/static/fonts/noto-sans/italic.ttf b/static/fonts/noto-sans/italic.ttf Binary files differdeleted file mode 100644 index 6245ba0..0000000 --- a/static/fonts/noto-sans/italic.ttf +++ /dev/null diff --git a/static/fonts/pacifico/regular.ttf b/static/fonts/pacifico/regular.ttf Binary files differdeleted file mode 100644 index e7def95..0000000 --- a/static/fonts/pacifico/regular.ttf +++ /dev/null diff --git a/static/icon.png b/static/icon.png Binary files differdeleted file mode 100644 index 7ff5837..0000000 --- a/static/icon.png +++ /dev/null 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 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - width="512" - height="512" - viewBox="0 0 512 512" - version="1.1" - id="svg1" - inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)" - sodipodi:docname="icon.svg" - inkscape:export-filename="icon.png" - inkscape:export-xdpi="96" - inkscape:export-ydpi="96" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns="http://www.w3.org/2000/svg" - xmlns:svg="http://www.w3.org/2000/svg"> - <sodipodi:namedview - id="namedview1" - pagecolor="#ffffff" - bordercolor="#000000" - borderopacity="0.25" - inkscape:showpageshadow="2" - inkscape:pageopacity="0.0" - inkscape:pagecheckerboard="true" - inkscape:deskcolor="#d1d1d1" - inkscape:document-units="px" - inkscape:zoom="0.60644339" - inkscape:cx="112.12918" - inkscape:cy="349.57921" - inkscape:window-width="1914" - inkscape:window-height="1054" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="1" - inkscape:current-layer="layer1" /> - <defs - id="defs1" /> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1"> - <circle - style="fill:#dfdfdf;fill-opacity:1;stroke:none;stroke-width:15.5767;stroke-dasharray:none;stroke-opacity:1" - id="path2-1" - cx="256" - cy="256" - r="448.711" /> - <circle - style="fill:#726eff;fill-opacity:1;stroke-width:1.32394" - id="path1" - cx="23.562067" - cy="215.85896" - r="250.06198" /> - <circle - style="fill:#212b38;fill-opacity:1;stroke:none;stroke-width:6.64688;stroke-dasharray:none;stroke-opacity:1" - id="path2" - cx="474.28168" - cy="398.27499" - r="191.47348" /> - <text - xml:space="preserve" - style="font-size:24px;font-family:'Sans Serif';-inkscape-font-specification:'Sans Serif';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;white-space:pre;inline-size:34.4024;display:inline;fill:#212b38;fill-opacity:1;stroke:#dfdfdf;stroke-width:0.523775;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" - x="254.17958" - y="345.31635" - id="text1" - transform="matrix(9.4165391,0,0,9.677421,-2291.1707,-2987.8748)"><tspan - x="254.17958" - y="345.31635" - id="tspan3"><tspan - style="font-family:Pacifico;-inkscape-font-specification:Pacifico" - id="tspan2">M</tspan></tspan></text> - </g> -</svg> diff --git a/static/icons/192.png b/static/icons/192.png Binary files differdeleted file mode 100644 index 38ee689..0000000 --- a/static/icons/192.png +++ /dev/null diff --git a/static/icons/512.png b/static/icons/512.png Binary files differdeleted file mode 100644 index 7ff5837..0000000 --- a/static/icons/512.png +++ /dev/null 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 `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus" viewBox="0 0 16 16"> - <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4"/> -</svg>`; 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 `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-calendar-week-fill" viewBox="0 0 16 16"> - <path d="M4 .5a.5.5 0 0 0-1 0V1H2a2 2 0 0 0-2 2v1h16V3a2 2 0 0 0-2-2h-1V.5a.5.5 0 0 0-1 0V1H4zM16 14V5H0v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2M9.5 7h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5m3 0h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5M2 10.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5zm3.5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5"/> -</svg>`; 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 `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check-lg" viewBox="0 0 16 16"> - <path d="M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425z"/> -</svg>` 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 `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x" viewBox="0 0 16 16"> - <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708"/> -</svg>`; 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 `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pen-fill" viewBox="0 0 16 16"> - <path d="m13.498.795.149-.149a1.207 1.207 0 1 1 1.707 1.708l-.149.148a1.5 1.5 0 0 1-.059 2.059L4.854 14.854a.5.5 0 0 1-.233.131l-4 1a.5.5 0 0 1-.606-.606l1-4a.5.5 0 0 1 .131-.232l9.642-9.642a.5.5 0 0 0-.642.056L6.854 4.854a.5.5 0 1 1-.708-.708L9.44.854A1.5 1.5 0 0 1 11.5.796a1.5 1.5 0 0 1 1.998-.001"/> -</svg>`; 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 `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-house-fill" viewBox="0 0 16 16"> - <path d="M8.707 1.5a1 1 0 0 0-1.414 0L.646 8.146a.5.5 0 0 0 .708.708L8 2.207l6.646 6.647a.5.5 0 0 0 .708-.708L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293z"/> - <path d="m8 3.293 6 6V13.5a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 2 13.5V9.293z"/> -</svg>`; 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 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - width="512" - height="512" - viewBox="0 0 512 512" - version="1.1" - id="svg1" - inkscape:export-filename="192.png" - inkscape:export-xdpi="36" - inkscape:export-ydpi="36" - inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)" - sodipodi:docname="icon.svg" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns="http://www.w3.org/2000/svg" - xmlns:svg="http://www.w3.org/2000/svg"> - <sodipodi:namedview - id="namedview1" - pagecolor="#ffffff" - bordercolor="#000000" - borderopacity="0.25" - inkscape:showpageshadow="2" - inkscape:pageopacity="0.0" - inkscape:pagecheckerboard="0" - inkscape:deskcolor="#d1d1d1" - inkscape:document-units="px" - inkscape:zoom="1.1960048" - inkscape:cx="267.55745" - inkscape:cy="277.59085" - inkscape:window-width="1914" - inkscape:window-height="1054" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="1" - inkscape:current-layer="layer1" /> - <defs - id="defs1" /> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1"> - <circle - style="fill:#000000;stroke-width:3.04152;stroke-linecap:round;stroke-linejoin:round" - id="path1" - cx="256" - cy="256" - r="227.42381" /> - </g> -</svg> 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 `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-right-short" viewBox="0 0 16 16"> - <path fill-rule="evenodd" d="M4 8a.5.5 0 0 1 .5-.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5A.5.5 0 0 1 4 8"/> -</svg>`; 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 `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16"> - <path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/> -</svg>`; 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 `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-gear-fill" viewBox="0 0 16 16"> - <path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/> -</svg>`; 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 `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-shuffle" viewBox="0 0 16 16"> - <path fill-rule="evenodd" d="M0 3.5A.5.5 0 0 1 .5 3H1c2.202 0 3.827 1.24 4.874 2.418.49.552.865 1.102 1.126 1.532.26-.43.636-.98 1.126-1.532C9.173 4.24 10.798 3 13 3v1c-1.798 0-3.173 1.01-4.126 2.082A9.6 9.6 0 0 0 7.556 8a9.6 9.6 0 0 0 1.317 1.918C9.828 10.99 11.204 12 13 12v1c-2.202 0-3.827-1.24-4.874-2.418A10.6 10.6 0 0 1 7 9.05c-.26.43-.636.98-1.126 1.532C4.827 11.76 3.202 13 1 13H.5a.5.5 0 0 1 0-1H1c1.798 0 3.173-1.01 4.126-2.082A9.6 9.6 0 0 0 6.444 8a9.6 9.6 0 0 0-1.317-1.918C4.172 5.01 2.796 4 1 4H.5a.5.5 0 0 1-.5-.5"/> - <path d="M13 5.466V1.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384l-2.36 1.966a.25.25 0 0 1-.41-.192m0 9v-3.932a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384l-2.36 1.966a.25.25 0 0 1-.41-.192"/> -</svg>`; 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 @@ -<!DOCTYPE html> -<html> - <head> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Memora</title> - <script type="module" src="index.js"></script> - <link rel="icon" href="icon.png" type="image/png"> - <link rel="manifest" href="manifest.json" /> - </head> - <body> - </body> -</html> 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 -Subproject 9d74e1679915cc0c5f56413879276fc43b09389 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 = ''; - } -} |