From 6a03b702418faed7ecdc122d46aaeef09c4f2910 Mon Sep 17 00:00:00 2001 From: gigamaster <1905497+gigamaster@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:36:00 +0100 Subject: [PATCH] Web Apps Launcher --- app/asset/app.js | 2 +- app/asset/launcher.json | 132 ++++++ app/asset/lib/component.min.js | 2 + app/asset/modal-launcher.html | 53 +++ app/tools/tasks/index.html | 53 +++ app/tools/tasks/scripts/AppCollapsible.js | 34 ++ app/tools/tasks/scripts/AppDatePicker.js | 163 +++++++ app/tools/tasks/scripts/AppDraggable.js | 353 +++++++++++++++ app/tools/tasks/scripts/AppFlip.js | 196 ++++++++ app/tools/tasks/scripts/AppIcon.js | 26 ++ app/tools/tasks/scripts/AppSortable.js | 145 ++++++ app/tools/tasks/scripts/TodoApp.js | 192 ++++++++ app/tools/tasks/scripts/TodoController.js | 93 ++++ app/tools/tasks/scripts/TodoCustomList.js | 161 +++++++ app/tools/tasks/scripts/TodoDay.js | 52 +++ app/tools/tasks/scripts/TodoFrameCustom.js | 133 ++++++ app/tools/tasks/scripts/TodoFrameDays.js | 121 +++++ app/tools/tasks/scripts/TodoItem.js | 160 +++++++ app/tools/tasks/scripts/TodoItemInput.js | 67 +++ app/tools/tasks/scripts/TodoList.js | 68 +++ app/tools/tasks/scripts/TodoLogic.js | 357 +++++++++++++++ app/tools/tasks/scripts/util.js | 99 +++++ app/tools/tasks/scripts/uuid.js | 8 + app/tools/tasks/styles/app-tasks.css | 212 +++++++++ app/tools/tasks/styles/base.css | 491 +++++++++++++++++++++ app/web-app/graphite.webp | Bin 0 -> 29336 bytes app/web-app/livecodes.webp | Bin 0 -> 33144 bytes app/web-app/livecodes/readme.md | 0 app/web-app/tldraw.webp | Bin 0 -> 31246 bytes src/template/foot.html | 4 +- 30 files changed, 3374 insertions(+), 3 deletions(-) create mode 100644 app/asset/launcher.json create mode 100644 app/asset/lib/component.min.js create mode 100644 app/asset/modal-launcher.html create mode 100644 app/tools/tasks/index.html create mode 100644 app/tools/tasks/scripts/AppCollapsible.js create mode 100644 app/tools/tasks/scripts/AppDatePicker.js create mode 100644 app/tools/tasks/scripts/AppDraggable.js create mode 100644 app/tools/tasks/scripts/AppFlip.js create mode 100644 app/tools/tasks/scripts/AppIcon.js create mode 100644 app/tools/tasks/scripts/AppSortable.js create mode 100644 app/tools/tasks/scripts/TodoApp.js create mode 100644 app/tools/tasks/scripts/TodoController.js create mode 100644 app/tools/tasks/scripts/TodoCustomList.js create mode 100644 app/tools/tasks/scripts/TodoDay.js create mode 100644 app/tools/tasks/scripts/TodoFrameCustom.js create mode 100644 app/tools/tasks/scripts/TodoFrameDays.js create mode 100644 app/tools/tasks/scripts/TodoItem.js create mode 100644 app/tools/tasks/scripts/TodoItemInput.js create mode 100644 app/tools/tasks/scripts/TodoList.js create mode 100644 app/tools/tasks/scripts/TodoLogic.js create mode 100644 app/tools/tasks/scripts/util.js create mode 100644 app/tools/tasks/scripts/uuid.js create mode 100644 app/tools/tasks/styles/app-tasks.css create mode 100644 app/tools/tasks/styles/base.css create mode 100644 app/web-app/graphite.webp create mode 100644 app/web-app/livecodes.webp create mode 100644 app/web-app/livecodes/readme.md create mode 100644 app/web-app/tldraw.webp diff --git a/app/asset/app.js b/app/asset/app.js index 69034ca9..26305876 100644 --- a/app/asset/app.js +++ b/app/asset/app.js @@ -92,7 +92,7 @@ document.addEventListener('alpine:init', () => { currentIndex: -1, init() { // Initialize data - fetch('./search.json') + fetch('https://gigamaster.github.io/codemo/asset/search.json') .then(res => res.json()) .then(data => this.data = data.data); }, diff --git a/app/asset/launcher.json b/app/asset/launcher.json new file mode 100644 index 00000000..cf1c6ac8 --- /dev/null +++ b/app/asset/launcher.json @@ -0,0 +1,132 @@ +[ + { + "id": 1, + "url": "https://gigamaster.github.io/codemo/web-app/dpaint/", + "name": "DPaint", + "author": "Author", + "desc": "Web app graphic image editor modeled after the legendary Deluxe Paint", + "cat": "Graphics", + "icon": "icon-apps", + "image": "https://gigamaster.github.io/codemo/web-app/dpaint.webp" + }, + { + "id": 2, + "url": "https://gigamaster.github.io/codemo/web-app/drawio/", + "name": "Draw.io", + "author": "Author", + "desc": "Web app to make flowcharts, process and network diagrams, org charts, UML, ER.", + "cat": "Diagram", + "icon": "icon-apps", + "image": "https://gigamaster.github.io/codemo/web-app/drawio.webp" + }, + { + "id": 3, + "url": "https://gigamaster.github.io/codemo/web-app/encrypt/", + "name": "Encrypt", + "author": "Author", + "desc": "Static HTML Encryption for public host", + "cat": "Security Privacy", + "icon": "icon-apps", + "image": "https://gigamaster.github.io/codemo/web-app/encrypt.webp" + }, + { + "id": 4, + "url": "https://gigamaster.github.io/codemo/web-app/erd-editor/", + "name": "ERD-editor", + "author": "Author", + "desc": "Entity-Relationship Diagram Editor", + "cat": "Database", + "icon": "icon-apps", + "image": "https://gigamaster.github.io/codemo/web-app/erp-editor.webp" + }, + { + "id": 5, + "url": "https://gigamaster.github.io/codemo/web-app/fuxa-hmi-scada/", + "name": "Fuxa", + "author": "Author", + "desc": "Web app SCADA/HMI/Dashboard to create modern process visualizations with individual designs", + "cat": "Dashboard", + "icon": "icon-apps", + "image": "https://gigamaster.github.io/codemo/web-app/fuxa.webp" + }, + { + "id": 6, + "url": "https://gigamaster.github.io/codemo/web-app/grapesjs/", + "name": "Grapesjs", + "author": "Author", + "desc": "Web app drag and drop website builder", + "cat": "Web Design", + "icon": "icon-apps", + "image": "https://gigamaster.github.io/codemo/web-app/grapesjs.webp" + }, + { + "id": 7, + "url": "https://gigamaster.github.io/codemo/web-app/graphite/", + "name": "Graphite", + "author": "Author", + "desc": "Web app to create, compose and edit vector images and graphics", + "cat": "Graphics", + "icon": "icon-apps", + "image": "https://gigamaster.github.io/codemo/web-app/graphite.webp" + }, + { + "id": 8, + "url": "https://gigamaster.github.io/codemo/web-app/livecodes/", + "name": "Livecodes", + "author": "Author", + "desc": "Web app code editor with support for 90+ languages & frameworks, embedded playgrounds and AI code assistant", + "cat": "Code Editor", + "icon": "icon-apps", + "image": "https://gigamaster.github.io/codemo/web-app/livecodes.webp" + }, + { + "id": 9, + "url": "https://gigamaster.github.io/codemo/web-app/mermaid-editor/", + "name": "Mermaid editor", + "author": "Author", + "desc": "Web app editor to create and modify complex charts and diagrams", + "cat": "Diagram", + "icon": "icon-apps", + "image": "https://gigamaster.github.io/codemo/web-app/mermaid.webp" + }, + { + "id": 10, + "url": "https://gigamaster.github.io/codemo/web-app/studio/", + "name": "Studio", + "author": "Author", + "desc": "Web app to create designs and templates for social media", + "cat": "Graphics", + "icon": "icon-apps", + "image": "https://gigamaster.github.io/codemo/web-app/studio.webp" + }, + { + "id": 11, + "url": "https://gigamaster.github.io/codemo/web-app/tldraw/", + "name": "Tldraw", + "author": "Author", + "desc": "Web app to draw and write on the canvas, add images and video", + "cat": "Drawing 2D", + "icon": "icon-apps", + "image": "https://gigamaster.github.io/codemo/web-app/tldraw.webp" + }, + { + "id": 12, + "url": "https://gigamaster.github.io/codemo/web-app/voxel-builder/", + "name": "Voxel-builder", + "author": "Author", + "desc": "Web app to create 3D voxel art, modeling art game graphics", + "cat": "3D graphics", + "icon": "icon-apps", + "image": "https://gigamaster.github.io/codemo/web-app/voxel-builder.webp" + }, + { + "id": 13, + "url": "https://gigamaster.github.io/codemo/web-app/vvvebjs/", + "name": "Vvvebjs", + "author": "Author", + "desc": "Drag and drop web page builder", + "cat": "Web Design", + "icon": "icon-apps", + "image": "https://gigamaster.github.io/codemo/web-app/vvveb.webp" + } +] diff --git a/app/asset/lib/component.min.js b/app/asset/lib/component.min.js new file mode 100644 index 00000000..e914a43f --- /dev/null +++ b/app/asset/lib/component.min.js @@ -0,0 +1,2 @@ +/* alpinejs-component@1.2.7 */ +(()=>{function p(e,n){let s=(n.includes("global")?[...document.styleSheets]:[...document.styleSheets].filter(({title:t})=>n.includes(t))).flatMap(({cssRules:t})=>[...t]),i=new CSSStyleSheet;for(let t of s)t instanceof CSSStyleRule&&t.selectorText===":root"||i.insertRule(t.cssText);e.adoptedStyleSheets=[i]}async function u(e,n,o){function l(i){let t=document.getElementById(i),a=new DOMParser().parseFromString(t.innerHTML,"text/html").body.firstChild;return Promise.resolve(a)}let s=await l(n);o.appendChild(s),e.initTree(o)}async function h(e,n,o){let s=await(await fetch(n)).text(),i=new DOMParser().parseFromString(s,"text/html").body.firstChild;o.appendChild(i),e.initTree(o)}function d(e){class n extends HTMLElement{connectedCallback(){if(this._hasInit)return;let s=this.attachShadow({mode:"open"}),i=this.hasAttribute(":template"),t=this.hasAttribute(":url");(i||t)&&e.initTree(this);let{template:a={value:""},url:f={value:""},styles:S={value:""}}=this.attributes,m=a.value,c=f.value,r=S.value.split(",");m.length&&u(e,m,s),c.length&&h(e,c,s),r.length&&p(s,r),this._hasInit=!0}}let{name:o}=window?.xComponent||{name:"x-component"};window.customElements.get(o)||(customElements.define(o,n),new n)}document.addEventListener("alpine:init",()=>window.Alpine.plugin(d));})(); diff --git a/app/asset/modal-launcher.html b/app/asset/modal-launcher.html new file mode 100644 index 00000000..54da8017 --- /dev/null +++ b/app/asset/modal-launcher.html @@ -0,0 +1,53 @@ + +
+
+ + +
+
\ No newline at end of file diff --git a/app/tools/tasks/index.html b/app/tools/tasks/index.html new file mode 100644 index 00000000..b3293b8a --- /dev/null +++ b/app/tools/tasks/index.html @@ -0,0 +1,53 @@ + + + + + + + + + Codemo Task Manager + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + diff --git a/app/tools/tasks/scripts/AppCollapsible.js b/app/tools/tasks/scripts/AppCollapsible.js new file mode 100644 index 00000000..6001435f --- /dev/null +++ b/app/tools/tasks/scripts/AppCollapsible.js @@ -0,0 +1,34 @@ +/** + * @param {HTMLElement} el + */ +export function AppCollapsible(el) { + let show = true; + + setTimeout(() => el.classList.add('-animated'), 200); + + el.addEventListener('collapse', (e) => { + if (typeof e.detail === 'boolean') { + show = e.detail; + } + + update(); + }); + + el.querySelector('.bar > .toggle').addEventListener('click', () => { + show = !show; + update(); + }); + + update(); + + function update() { + el.querySelector('.bar > .toggle > .app-icon').classList.toggle( + '-r180', + show, + ); + + el.querySelectorAll('.body').forEach((el) => { + el.style.height = show ? `${el.children[0].offsetHeight}px` : '0'; + }); + } +} diff --git a/app/tools/tasks/scripts/AppDatePicker.js b/app/tools/tasks/scripts/AppDatePicker.js new file mode 100644 index 00000000..bc54c24e --- /dev/null +++ b/app/tools/tasks/scripts/AppDatePicker.js @@ -0,0 +1,163 @@ +import { AppIcon } from './AppIcon.js'; +import { formatMonth } from './util.js'; + +const datesCell = /* html */ ` + +`; + +const datesRow = /* html */ ` + + ${datesCell} + ${datesCell} + ${datesCell} + ${datesCell} + ${datesCell} + ${datesCell} + ${datesCell} + +`; + +/** + * @param {HTMLElement} el + */ +export function AppDatePicker(el) { + const now = new Date(); + let at = { + year: now.getFullYear(), + month: now.getMonth() + 1, + }; + let show = false; + + el.innerHTML = /* html */ ` +

+ + + +

+ + + + + + + + + + + + + + ${datesRow} + ${datesRow} + ${datesRow} + ${datesRow} + ${datesRow} + + + `; + + el.querySelectorAll('.app-icon').forEach(AppIcon); + + el.addEventListener('toggleDatePicker', (e) => { + show = e.detail ?? !show; + update(); + }); + + el.querySelector('.previousmonth').addEventListener('click', previousMonth); + el.querySelector('.nextmonth').addEventListener('click', nextMonth); + + el.addEventListener('click', (e) => { + if (!e.target.matches('.pick')) return; + + show = false; + update(); + + el.dispatchEvent( + new CustomEvent('pickDate', { + detail: new Date( + e.target.dataset.year, + e.target.dataset.month - 1, + e.target.dataset.day, + ), + bubbles: true, + }), + ); + }); + + function previousMonth() { + if (at.month > 1) { + at = { + year: at.year, + month: at.month - 1, + }; + } else { + at = { + year: at.year - 1, + month: 12, + }; + } + + update(); + } + + function nextMonth() { + if (at.month < 12) { + at = { + year: at.year, + month: at.month + 1, + }; + } else { + at = { + year: at.year + 1, + month: 1, + }; + } + + update(); + } + + function update() { + el.classList.toggle('-show', show); + + const now = new Date(); + const first = new Date(at.year, at.month - 1, 1); + + el.querySelector('.month').innerHTML = `${formatMonth( + first, + )} ${first.getFullYear()}`; + + let current = new Date(first); + current.setDate(1 - current.getDay()); + + const datesBody = el.querySelector('.dates > tbody'); + + for (let i = 0; i < 35; ++i) { + const row = Math.floor(i / 7); + const column = i % 7; + + const cell = datesBody.children[row].children[column]; + const button = cell.children[0]; + + button.innerHTML = current.getDate(); + + button.dataset.year = current.getFullYear(); + button.dataset.month = current.getMonth() + 1; + button.dataset.day = current.getDate(); + + button.classList.toggle( + '-highlight', + current.getFullYear() === now.getFullYear() && + current.getMonth() === now.getMonth() && + current.getDate() === now.getDate(), + ); + + current.setDate(current.getDate() + 1); + } + } + + update(); +} diff --git a/app/tools/tasks/scripts/AppDraggable.js b/app/tools/tasks/scripts/AppDraggable.js new file mode 100644 index 00000000..7dc6159d --- /dev/null +++ b/app/tools/tasks/scripts/AppDraggable.js @@ -0,0 +1,353 @@ +/** + * @param {HTMLElement} el + * @param {{ + * dropSelector: string; + * dragThreshold?: number; + * dropRange?: number; + * }} options + */ +export function AppDraggable(el, options) { + const dragThreshold = options.dragThreshold ?? 5; + const dropRange = options.dropRange ?? 50; + const dropRangeSquared = dropRange * dropRange; + + let originX, originY; + let clientX, clientY; + let startTime; + let dragging = false; + let clicked = false; + let data; + let image; + let imageSource; + let imageX, imageY; + let currentTarget; + + el.addEventListener('touchstart', start); + el.addEventListener('mousedown', start); + + // Maybe prevent click + el.addEventListener( + 'click', + (e) => { + if (dragging || clicked) { + e.preventDefault(); + e.stopImmediatePropagation(); + } + }, + true, + ); + + function start(e) { + if (el.classList.contains('_nodrag')) return; + if (e.type === 'mousedown' && e.button !== 0) return; + if (e.touches && e.touches.length > 1) return; + + e.preventDefault(); + + const p = getPositionHost(e); + clientX = originX = p.clientX ?? p.pageX; + clientY = originY = p.clientY ?? p.pageY; + startTime = Date.now(); + + startListening(); + } + + function move(e, ) { + e.preventDefault(); + const p = getPositionHost(e); + clientX = p.clientX ?? p.pageX; + clientY = p.clientY ?? p.pageY; + + if (dragging) { + dispatchDrag(); + dispatchTarget(); + return; + } + + const deltaX = clientX - originX; + const deltaY = clientY - originY; + + if (Math.abs(deltaX) < dragThreshold && Math.abs(deltaY) < dragThreshold) { + return; + } + + // prevent unintentional dragging on touch devices + if (e.touches && Date.now() - startTime < 50) { + stopListening(); + return; + } + + dragging = true; + data = {}; + + dispatchStart(); + dispatchDrag(); + dispatchTarget(); + dispatchOverContinuously(); + } + + function end(e) { + e.preventDefault(); + + if (!dragging) { + e.target.click(); + clicked = true; + } + + stopListening(); + + requestAnimationFrame(() => { + clicked = false; + + if (dragging) { + dispatchTarget(); + dispatchEnd(); + } + }); + } + + function startListening() { + el.addEventListener('touchmove', move); + el.addEventListener('touchend', end); + window.addEventListener('mousemove', move); + window.addEventListener('mouseup', end); + } + + function stopListening() { + el.removeEventListener('touchmove', move); + el.removeEventListener('touchend', end); + window.removeEventListener('mousemove', move); + window.removeEventListener('mouseup', end); + } + + // + + function dispatchStart() { + setImage(el); + + el.dispatchEvent( + new CustomEvent('draggableStart', { + detail: buildDetail(), + bubbles: true, + }), + ); + } + + function dispatchDrag() { + image.dispatchEvent( + new CustomEvent('draggableDrag', { + detail: buildDetail(), + bubbles: true, + }), + ); + } + + function dispatchTarget() { + if (!dragging) return; + + const nextTarget = getTarget(); + + if (nextTarget === currentTarget) return; + + if (currentTarget) { + currentTarget.addEventListener('draggableLeave', removeDropClassOnce); + currentTarget.dispatchEvent( + new CustomEvent('draggableLeave', { + detail: buildDetail(), + bubbles: true, + }), + ); + } + + if (nextTarget) { + nextTarget.addEventListener('draggableEnter', addDropClassOnce); + nextTarget.dispatchEvent( + new CustomEvent('draggableEnter', { + detail: buildDetail(), + bubbles: true, + }), + ); + } + + currentTarget = nextTarget; + } + + function dispatchOverContinuously() { + if (!dragging) return; + + dispatchOver(); + setTimeout(dispatchOver, 50); + } + + function dispatchOver() { + if (currentTarget) { + currentTarget.dispatchEvent( + new CustomEvent('draggableOver', { + detail: buildDetail(), + bubbles: true, + }), + ); + } + + setTimeout(dispatchOver, 50); + } + + function dispatchEnd() { + if (currentTarget) { + currentTarget.addEventListener('draggableDrop', cleanUpOnce); + currentTarget.dispatchEvent( + new CustomEvent('draggableDrop', { + detail: buildDetail(), + bubbles: true, + }), + ); + } else { + image.dispatchEvent( + new CustomEvent('draggableCancel', { + detail: buildDetail(), + bubbles: true, + }), + ); + } + } + + // + + function buildDetail() { + const detail = { + el, + data, + image, + imageSource, + originX, + originY, + clientX, + clientY, + imageX, + imageY, + setImage: (source) => { + setImage(source); + detail.image = image; + }, + }; + + return detail; + } + + function setImage(source) { + if (imageSource === source) return; + imageSource = source; + + removeImage(); + + image = imageSource.cloneNode(true); + image.style.position = 'fixed'; + image.style.left = '0'; + image.style.top = '0'; + image.style.width = `${imageSource.offsetWidth}px`; + image.style.height = `${imageSource.offsetHeight}px`; + image.style.margin = '0'; + image.style.zIndex = 9999; + image.classList.add('-dragging'); + + const rect = source.getBoundingClientRect(); + imageX = originX - rect.left; + imageY = originY - rect.top; + + image.addEventListener('draggableDrag', (e) => { + const x = e.detail.clientX - e.detail.imageX; + const y = e.detail.clientY - e.detail.imageY; + image.style.transition = 'none'; + image.style.transform = `translate(${x}px, ${y}px)`; + }); + + image.addEventListener('draggableCancel', cleanUp); + + document.body.appendChild(image); + } + + function addDropClassOnce(e) { + e.target.removeEventListener(e.type, addDropClassOnce); + e.target.classList.add('-drop'); + } + + function removeDropClassOnce(e) { + e.target.removeEventListener(e.type, addDropClassOnce); + e.target.classList.remove('-drop'); + } + + function cleanUpOnce(e) { + e.target.removeEventListener(e.type, cleanUpOnce); + cleanUp(); + } + + function cleanUp() { + currentTarget?.classList.remove('-drop'); + + removeImage(); + + data = null; + image = null; + imageSource = null; + currentTarget = null; + } + + function removeImage() { + image?.parentNode?.removeChild(image); + } + + function getTarget() { + const candidates = []; + + document.querySelectorAll(options.dropSelector).forEach((el) => { + const rect = el.getBoundingClientRect(); + const distanceSquared = pointDistanceToRectSquared( + clientX, + clientY, + rect, + ); + + if (distanceSquared > dropRangeSquared) return; + + candidates.push({ + el, + distance2: distanceSquared, + }); + }); + + candidates.sort((a, b) => { + if (a.distance2 === 0 && b.distance2 === 0) { + // in this case, the client position is inside both rectangles + // if A contains B, B is the correct target and vice versa + // TODO sort by z-index somehow? + return a.el.contains(b.el) ? -1 : b.el.contains(a.el) ? 1 : 0; + } + + // sort by distance, ascending + return a.distance2 - b.distance2; + }); + + return candidates.length > 0 ? candidates[0].el : null; + } + + function pointDistanceToRectSquared(x, y, rect) { + const dx = + x < rect.left ? x - rect.left : x > rect.right ? x - rect.right : 0; + const dy = + y < rect.top ? y - rect.top : y > rect.bottom ? y - rect.bottom : 0; + + return dx * dx + dy * dy; + } + + function getPositionHost(e) { + if (e.targetTouches && e.targetTouches.length > 0) { + return e.targetTouches[0]; + } + + if (e.changedTouches && e.changedTouches.length > 0) { + return e.changedTouches[0]; + } + + return e; + } +} diff --git a/app/tools/tasks/scripts/AppFlip.js b/app/tools/tasks/scripts/AppFlip.js new file mode 100644 index 00000000..4ebf49be --- /dev/null +++ b/app/tools/tasks/scripts/AppFlip.js @@ -0,0 +1,196 @@ +/** + * @param {HTMLElement} el + * @param {{ + * initialDelay?: number; + * removeTimeout: number; + * selector: string; + * }} options + */ +export function AppFlip(el, options) { + let enabled = options.initialDelay === 0; + let first; + let level = 0; + + // Enable animations only after an initial delay. + setTimeout(() => { + enabled = true; + }, options.initialDelay ?? 100); + + // Take a snapshot before any HTML changes. + // Do this only for the first beforeFlip event in the current cycle. + el.addEventListener('beforeFlip', () => { + if (!enabled) return; + if (++level > 1) return; + + first = snapshot(); + }); + + // Take a snapshot after HTML changes, calculate and play animations. + // Do this only for the last flip event in the current cycle. + el.addEventListener('flip', () => { + if (!enabled) return; + if (--level > 0) return; + + const last = snapshot(); + const toRemove = invertForRemoval(first, last); + const toAnimate = invertForAnimation(first, last); + + requestAnimationFrame(() => { + requestAnimationFrame(() => { + remove(toRemove); + animate(toAnimate); + + first = null; + }); + }); + }); + + // Build a snapshot of the current HTML's client rectangles, + // including original transforms and hierarchy. + function snapshot() { + const map = new Map(); + + el.querySelectorAll(options.selector).forEach((el) => { + const key = el.dataset.key ?? el; + + // Parse original transform, + // i.e. strip inverse transform using "scale(1)" marker. + const transform = el.style.transform + ? el.style.transform.replace(/^.*scale\(1\)/, '') + : ''; + + map.set(key, { + key, + el, + rect: el.getBoundingClientRect(), + ancestor: null, + transform, + }); + }); + + resolveAncestors(map); + + return map; + } + + function resolveAncestors(map) { + map.forEach((entry) => { + let current = entry.el.parentNode; + + while (current && current !== el) { + const ancestor = map.get(current.dataset.key ?? current); + + if (ancestor) { + entry.ancestor = ancestor; + return; + } + + current = current.parentNode; + } + }); + } + + // Reinsert removed elements at their original position. + function invertForRemoval(first, last) { + const toRemove = []; + + first.forEach((entry) => { + if (entry.el.classList.contains('_noflip')) return; + if (!needsRemoval(entry)) return; + + entry.el.style.position = 'fixed'; + entry.el.style.left = `${entry.rect.left}px`; + entry.el.style.top = `${entry.rect.top}px`; + entry.el.style.width = `${entry.rect.right - entry.rect.left}px`; + entry.el.style.transition = 'none'; + entry.el.style.transform = ''; + + el.appendChild(entry.el); + toRemove.push(entry); + }); + + return toRemove; + + function needsRemoval(entry) { + if (entry.ancestor && needsRemoval(entry.ancestor)) { + return false; + } + + return !last.has(entry.key); + } + } + + // Set position of moved elements to their original position, + // or set opacity to zero for new elements to appear nicely. + function invertForAnimation(first, last) { + const toAnimate = []; + + last.forEach((entry) => { + if (entry.el.classList.contains('_noflip')) return; + + calculate(entry); + + if (entry.appear) { + entry.el.style.transition = 'none'; + entry.el.style.opacity = '0'; + toAnimate.push(entry); + } else if (entry.deltaX !== 0 || entry.deltaY !== 0) { + // Set inverted transform with "scale(1)" marker, see above. + entry.el.style.transition = 'none'; + entry.el.style.transform = `translate(${entry.deltaX}px, ${entry.deltaY}px) scale(1) ${entry.transform}`; + toAnimate.push(entry); + } + }); + + return toAnimate; + + // Calculate inverse transform relative to any animated ancestors. + function calculate(entry) { + if (entry.calculated) return; + entry.calculated = true; + + const b = first.get(entry.key); + + if (b) { + entry.deltaX = b.rect.left - entry.rect.left; + entry.deltaY = b.rect.top - entry.rect.top; + + if (entry.ancestor) { + calculate(entry.ancestor); + + entry.deltaX -= entry.ancestor.deltaX; + entry.deltaY -= entry.ancestor.deltaY; + } + } else { + entry.appear = true; + entry.deltaX = 0; + entry.deltaY = 0; + } + } + } + + // Play remove animations and remove elements after timeout. + function remove(entries) { + entries.forEach((entry) => { + entry.el.style.transition = ''; + entry.el.style.opacity = '0'; + }); + + setTimeout(() => { + entries.forEach((entry) => { + if (entry.el.parentNode) { + entry.el.parentNode.removeChild(entry.el); + } + }); + }, options.removeTimeout); + } + + // Play move/appear animations. + function animate(entries) { + entries.forEach((entry) => { + entry.el.style.transition = ''; + entry.el.style.transform = entry.transform; + entry.el.style.opacity = ''; + }); + } +} diff --git a/app/tools/tasks/scripts/AppIcon.js b/app/tools/tasks/scripts/AppIcon.js new file mode 100644 index 00000000..523843b5 --- /dev/null +++ b/app/tools/tasks/scripts/AppIcon.js @@ -0,0 +1,26 @@ +// export const BASE_URL = + export const BASE_URL ='https://api.iconify.design'; + const iconset = 'tabler'; + const iconH = '16'; + const cache = {}; +/** + * iconify options + * https://iconify.design/docs/usage/svg/no-code/ + */ +/** + * @param {HTMLElement} el + */ +export function AppIcon(el) { + if (el.children.length > 0) return; + + const id = el.dataset.id; + let promise = cache[id]; + + if (!promise) { + promise = cache[id] = fetch(`${BASE_URL}/${iconset}/${id}.svg?height=${iconH}`).then((r) => r.text()); + } + + promise.then((svg) => { + el.innerHTML = el.classList.contains('-double') ? svg + svg : svg; + }); +} diff --git a/app/tools/tasks/scripts/AppSortable.js b/app/tools/tasks/scripts/AppSortable.js new file mode 100644 index 00000000..cc3a60e7 --- /dev/null +++ b/app/tools/tasks/scripts/AppSortable.js @@ -0,0 +1,145 @@ +/** + * @param {HTMLElement} el + * @param {{ + * direction?: 'horizontal' | 'vertical'; + * }} options + */ +export function AppSortable(el, options) { + let placeholder; + let placeholderSource; + let currentIndex = -1; + + const isBefore = options.direction === 'horizontal' ? isLeft : isAbove; + + el.addEventListener('draggableStart', (e) => + e.detail.image.addEventListener('draggableCancel', cleanUp), + ); + + el.addEventListener('draggableOver', (e) => + maybeDispatchUpdate(calculateIndex(e.detail.image), e), + ); + + el.addEventListener('draggableLeave', (e) => maybeDispatchUpdate(-1, e)); + + el.addEventListener('draggableDrop', (e) => + el.dispatchEvent( + new CustomEvent('sortableDrop', { + detail: buildDetail(e), + bubbles: true, + }), + ), + ); + + el.addEventListener('sortableUpdate', (e) => { + if (!placeholder) { + e.detail.setPlaceholder(e.detail.originalEvent.detail.imageSource); + } + + if (e.detail.index >= 0) { + insertPlaceholder(e.detail.index); + } else { + removePlaceholder(); + } + + removeByKey(e.detail.data.key); + }); + + el.addEventListener('sortableDrop', cleanUp); + + function maybeDispatchUpdate(index, originalEvent) { + if (index !== currentIndex) { + currentIndex = index; + + el.dispatchEvent( + new CustomEvent('sortableUpdate', { + detail: buildDetail(originalEvent), + bubbles: true, + }), + ); + } + } + + function cleanUp() { + removePlaceholder(); + placeholder = null; + placeholderSource = null; + currentIndex = -1; + } + + function buildDetail(e) { + const detail = { + data: e.detail.data, + index: currentIndex, + placeholder, + setPlaceholder: (source) => { + setPlaceholder(source); + detail.placeholder = placeholder; + }, + originalEvent: e, + }; + + return detail; + } + + function setPlaceholder(source) { + if (placeholderSource === source) return; + placeholderSource = source; + + removePlaceholder(); + + placeholder = placeholderSource.cloneNode(true); + placeholder.classList.add('-placeholder'); + placeholder.removeAttribute('data-key'); + } + + function insertPlaceholder(index) { + if (placeholder && el.children[index] !== placeholder) { + if (placeholder.parentNode === el) el.removeChild(placeholder); + el.insertBefore(placeholder, el.children[index]); + } + } + + function removePlaceholder() { + placeholder?.parentNode?.removeChild(placeholder); + } + + function removeByKey(key) { + for (let i = 0, l = el.children.length; i < l; ++i) { + const child = el.children[i]; + + if (child && child.dataset.key === key) { + el.removeChild(child); + } + } + } + + function calculateIndex(image) { + if (el.children.length === 0) return 0; + + const rect = image.getBoundingClientRect(); + let p = 0; + + for (let i = 0, l = el.children.length; i < l; ++i) { + const child = el.children[i]; + + if (isBefore(rect, child.getBoundingClientRect())) return i - p; + if (child === placeholder) p = 1; + } + + return el.children.length - p; + } + + function isAbove(rectA, rectB) { + return ( + rectA.top + (rectA.bottom - rectA.top) / 2 <= + rectB.top + (rectB.bottom - rectB.top) / 2 + ); + } + + function isLeft(rectA, rectB) { + return ( + rectA.left + (rectA.right - rectA.left) / 2 <= + rectB.left + (rectB.right - rectB.left) / 2 + ); + } +} diff --git a/app/tools/tasks/scripts/TodoApp.js b/app/tools/tasks/scripts/TodoApp.js new file mode 100644 index 00000000..b0697af3 --- /dev/null +++ b/app/tools/tasks/scripts/TodoApp.js @@ -0,0 +1,192 @@ +import { AppCollapsible } from './AppCollapsible.js'; +import { AppFlip } from './AppFlip.js'; +import { AppIcon } from './AppIcon.js'; +import { AppDatePicker } from './AppDatePicker.js'; +import { TodoController } from './TodoController.js'; +import { TodoFrameCustom } from './TodoFrameCustom.js'; +import { TodoFrameDays } from './TodoFrameDays.js'; +import { TodoLogic } from './TodoLogic.js'; + +/** + * @param {HTMLElement} el + */ +export function TodoApp(el) { + let todoData = TodoLogic.initTodoData(); + + el.innerHTML = /* html */ ` +
+

+ Codemo Task Manager +

+ +

+ + + + +

+
+
+
+
+

+ +

+
+
+
+
+ + `; + + AppFlip(el, { + selector: '.todo-item, .todo-item-input, .todo-day, .todo-custom-list', + removeTimeout: 200, + }); + + TodoController(el); + + el.querySelectorAll('.app-collapsible').forEach(AppCollapsible); + el.querySelectorAll('.app-icon').forEach(AppIcon); + + el.querySelector('.home').addEventListener('click', () => + el.dispatchEvent(new CustomEvent('seekToToday', { bubbles: true })), + ); + + el.querySelectorAll('.app-date-picker').forEach(AppDatePicker); + + el.querySelector('.pickdate').addEventListener('click', () => + el + .querySelector('.datepicker') + .dispatchEvent(new CustomEvent('toggleDatePicker')), + ); + + el.querySelector('.datepicker').addEventListener('pickDate', (e) => + el.dispatchEvent( + new CustomEvent('seekToDate', { detail: e.detail, bubbles: true }), + ), + ); + + TodoFrameDays(el.querySelector('.todo-frame.-days')); + TodoFrameCustom(el.querySelector('.todo-frame.-custom')); + + el.querySelector('[name=importFile]').addEventListener('change', (e) => { + const f = e.target.files[0]; + if (!f) return; + + const reader = new FileReader(); + + reader.addEventListener('load', (e) => { + try { + const todoData = JSON.parse(e.target.result); + + el.dispatchEvent( + new CustomEvent('importTodoData', { + detail: todoData, + bubbles: true, + }), + ); + } catch (err) { + alert(`Could not import data (${err.message})`); + } + }); + + reader.readAsText(f); + }); + + el.querySelector('.app-header > .actions > .export').addEventListener( + 'click', + () => { + el.dispatchEvent(new CustomEvent('exportTodoData', { bubbles: true })); + }, + ); + + // Each of these events make changes to the HTML to be animated using FLIP. + // Listening to them using "capture" dispatches "beforeFlip" before any changes. + el.addEventListener('todoData', beforeFlip, true); + el.addEventListener('sortableUpdate', beforeFlip, true); + el.addEventListener('draggableCancel', beforeFlip, true); + el.addEventListener('draggableDrop', beforeFlip, true); + + // Some necessary work to orchestrate drag & drop with FLIP animations + el.addEventListener('draggableStart', (e) => { + e.detail.image.classList.add('_noflip'); + el.appendChild(e.detail.image); + }); + + el.addEventListener('draggableCancel', (e) => { + e.detail.image.classList.remove('_noflip'); + update(); + }); + + el.addEventListener('draggableDrop', (e) => { + e.detail.image.classList.remove('_noflip'); + }); + + el.addEventListener('sortableUpdate', (e) => { + e.detail.placeholder.classList.add('_noflip'); + }); + + // Dispatch "focusOther" on .use-focus-other inputs if they are not active. + // Ensures only one edit input is active. + el.addEventListener('focusin', (e) => { + if (!e.target.classList.contains('use-focus-other')) return; + + document.querySelectorAll('.use-focus-other').forEach((el) => { + if (el === e.target) return; + el.dispatchEvent(new CustomEvent('focusOther')); + }); + }); + + // Listen to the TodoController's data. + // This is the main update. + // Everything else is related to drag & drop or FLIP animations. + el.addEventListener('todoData', (e) => { + todoData = e.detail; + update(); + }); + + // Dispatch "flip" after HTML changes from the following events. + // This plays the FLIP animations. + el.addEventListener('todoData', flip); + el.addEventListener('sortableUpdate', flip); + el.addEventListener('draggableCancel', flip); + el.addEventListener('draggableDrop', flip); + + el.dispatchEvent(new CustomEvent('loadTodoData')); + + function update() { + el.querySelectorAll('.todo-frame').forEach((el) => + el.dispatchEvent(new CustomEvent('todoData', { detail: todoData })), + ); + + el.querySelectorAll('.app-collapsible').forEach((el) => + el.dispatchEvent(new CustomEvent('collapse')), + ); + } + + function beforeFlip(e) { + if (e.type === 'todoData' && e.target !== el) return; + + el.dispatchEvent(new CustomEvent('beforeFlip')); + } + + function flip() { + el.dispatchEvent(new CustomEvent('flip')); + } +} diff --git a/app/tools/tasks/scripts/TodoController.js b/app/tools/tasks/scripts/TodoController.js new file mode 100644 index 00000000..61f55026 --- /dev/null +++ b/app/tools/tasks/scripts/TodoController.js @@ -0,0 +1,93 @@ +import { TodoLogic } from './TodoLogic.js'; +import { toDataURL } from './util.js'; + +/** + * @param {HTMLElement} el + */ +export function TodoController(el) { + let todoData = TodoLogic.initTodoData(); + let saveTimeout; + + el.addEventListener('loadTodoData', load); + el.addEventListener('importTodoData', (e) => importTodoData(e.detail)); + el.addEventListener('exportTodoData', exportTodoData); + + for (const action of [ + 'addTodoItem', + 'checkTodoItem', + 'editTodoItem', + 'moveTodoItem', + 'deleteTodoItem', + 'addCustomTodoList', + 'editCustomTodoList', + 'moveCustomTodoList', + 'deleteCustomTodoList', + 'seekDays', + 'seekToToday', + 'seekToDate', + 'seekCustomTodoLists', + ]) { + el.addEventListener(action, (e) => { + todoData = TodoLogic[action](todoData, e.detail); + update(); + }); + } + + function update() { + save(); + + el.dispatchEvent( + new CustomEvent('todoData', { + detail: todoData, + bubbles: false, + }), + ); + } + + function load() { + try { + if (localStorage?.todo) { + todoData = TodoLogic.movePastTodoItems({ + ...todoData, + ...JSON.parse(localStorage.todo), + }); + } + } catch (err) { + // eslint-disable-next-line no-console + console.warn(err); + } + + update(); + } + + function save() { + clearTimeout(saveTimeout); + + saveTimeout = setTimeout(() => { + try { + localStorage.todo = JSON.stringify(todoData); + } catch (err) { + // eslint-disable-next-line no-console + console.warn(err); + } + }, 100); + } + + function importTodoData(input) { + // TODO validate? + todoData = input; + + update(); + } + + async function exportTodoData() { + const json = JSON.stringify(todoData, null, 2); + const href = await toDataURL(json); + const link = document.createElement('a'); + link.setAttribute('download', 'todo.json'); + link.setAttribute('href', href); + document.querySelector('body').appendChild(link); + link.click(); + link.remove(); + } +} diff --git a/app/tools/tasks/scripts/TodoCustomList.js b/app/tools/tasks/scripts/TodoCustomList.js new file mode 100644 index 00000000..a46e8ddb --- /dev/null +++ b/app/tools/tasks/scripts/TodoCustomList.js @@ -0,0 +1,161 @@ +import { AppDraggable } from './AppDraggable.js'; +import { AppIcon } from './AppIcon.js'; +import { TodoList } from './TodoList.js'; + +/** + * @param {HTMLElement} el + */ +export function TodoCustomList(el) { + let list; + let editing = false; + let startEditing = false; + let saveOnBlur = true; + + el.innerHTML = /* html */ ` +
+

+

+ + +

+
+
+ `; + + const titleEl = el.querySelector('.title'); + const inputEl = el.querySelector('.input'); + const deleteEl = el.querySelector('.delete'); + + AppDraggable(titleEl, { + dropSelector: '.todo-frame.-custom .container', + }); + el.querySelectorAll('.app-icon').forEach(AppIcon); + TodoList(el.querySelector('.todo-list')); + + titleEl.addEventListener('click', () => { + startEditing = true; + editing = true; + update(); + }); + + deleteEl.addEventListener( + 'touchstart', + () => { + saveOnBlur = false; + }, + { passive: true }, + ); + + deleteEl.addEventListener('mousedown', () => { + saveOnBlur = false; + }); + + inputEl.addEventListener('blur', () => { + if (saveOnBlur) save(); + saveOnBlur = true; + }); + + inputEl.addEventListener('focusOther', () => { + if (editing) save(); + }); + + inputEl.addEventListener('keyup', (e) => { + switch (e.keyCode) { + case 13: // enter + save(); + break; + case 27: // escape + cancelEdit(); + break; + } + }); + + deleteEl.addEventListener('click', () => { + if (list.items.length > 0) { + if ( + !confirm( + 'Deleting this list will delete its items as well. Are you sure?', + ) + ) { + return; + } + } + + el.dispatchEvent( + new CustomEvent('deleteCustomTodoList', { + detail: list, + bubbles: true, + }), + ); + }); + + el.addEventListener('draggableStart', (e) => { + if (e.target !== titleEl) return; + + e.detail.data.list = list; + e.detail.data.key = list.id; + + // Update image (default would only be title element). + e.detail.setImage(el); + + // Override for horizontal dragging only. + e.detail.image.addEventListener('draggableDrag', (e) => { + const x = e.detail.clientX - e.detail.imageX; + const y = e.detail.originY - e.detail.imageY; + e.detail.image.style.transform = `translate(${x}px, ${y}px)`; + }); + }); + + el.addEventListener('addTodoItem', (e) => { + e.detail.listId = list.id; + }); + + el.addEventListener('moveTodoItem', (e) => { + e.detail.listId = list.id; + e.detail.index = e.detail.index ?? 0; + }); + + el.addEventListener('customTodoList', (e) => { + list = e.detail; + update(); + }); + + function save() { + el.dispatchEvent( + new CustomEvent('editCustomTodoList', { + detail: { ...list, title: inputEl.value.trim() }, + bubbles: true, + }), + ); + editing = false; + update(); + } + + function cancelEdit() { + saveOnBlur = false; + editing = false; + update(); + } + + function update() { + titleEl.innerText = list.title || '...'; + + el.querySelector('.todo-list').dispatchEvent( + new CustomEvent('todoItems', { detail: list.items }), + ); + + el.querySelector('.todo-list > .todo-item-input').dataset.key = + `todo-item-input${list.id}`; + + el.classList.toggle('-editing', editing); + + if (editing && startEditing) { + inputEl.value = list.title; + inputEl.focus(); + inputEl.select(); + startEditing = false; + } + } +} diff --git a/app/tools/tasks/scripts/TodoDay.js b/app/tools/tasks/scripts/TodoDay.js new file mode 100644 index 00000000..1f3c16de --- /dev/null +++ b/app/tools/tasks/scripts/TodoDay.js @@ -0,0 +1,52 @@ +import { TodoList } from './TodoList.js'; +import { formatDate, formatDayOfWeek } from './util.js'; + +/** + * @param {HTMLElement} el + */ +export function TodoDay(el) { + const dateId = el.dataset.key; + let items = []; + + el.innerHTML = /* html */ ` +
+

+

+
+
+ `; + + TodoList(el.querySelector('.todo-list')); + + el.addEventListener('addTodoItem', (e) => { + e.detail.listId = dateId; + }); + + el.addEventListener('moveTodoItem', (e) => { + e.detail.listId = dateId; + e.detail.index = e.detail.index ?? 0; + }); + + el.addEventListener('todoDay', (e) => { + items = e.detail.items; + update(); + }); + + function update() { + const date = new Date(`${dateId}T00:00:00`); + const today = new Date(); + today.setHours(0, 0, 0, 0); + const tomorrow = new Date(today); + tomorrow.setDate(tomorrow.getDate() + 1); + + el.classList.toggle('-past', date < today); + el.classList.toggle('-today', date >= today && date < tomorrow); + el.classList.toggle('-future', date >= tomorrow); + + el.querySelector('.header > .dayofweek').innerText = formatDayOfWeek(date); + el.querySelector('.header > .date').innerText = formatDate(date); + el.querySelector('.todo-list').dispatchEvent( + new CustomEvent('todoItems', { detail: items }), + ); + } +} diff --git a/app/tools/tasks/scripts/TodoFrameCustom.js b/app/tools/tasks/scripts/TodoFrameCustom.js new file mode 100644 index 00000000..76165d57 --- /dev/null +++ b/app/tools/tasks/scripts/TodoFrameCustom.js @@ -0,0 +1,133 @@ +import { AppIcon } from './AppIcon.js'; +import { AppSortable } from './AppSortable.js'; +import { TodoCustomList } from './TodoCustomList.js'; +import { TodoLogic } from './TodoLogic.js'; + +/** + * @param {HTMLElement} el + */ +export function TodoFrameCustom(el) { + let todoData = TodoLogic.initTodoData(); + + el.innerHTML = /* html */ ` +
+

+
+
+
+

+

+
+ `; + + AppSortable(el.querySelector('.container'), { direction: 'horizontal' }); + + setTimeout(() => el.classList.add('-animated'), 200); + + el.querySelectorAll('.app-icon').forEach(AppIcon); + + el.querySelector('.back').addEventListener('click', () => + el.dispatchEvent( + new CustomEvent('seekCustomTodoLists', { detail: -1, bubbles: true }), + ), + ); + + el.querySelector('.forward').addEventListener('click', () => + el.dispatchEvent( + new CustomEvent('seekCustomTodoLists', { detail: 1, bubbles: true }), + ), + ); + + el.querySelector('.add').addEventListener('click', () => { + el.dispatchEvent(new CustomEvent('addCustomTodoList', { bubbles: true })); + // TODO seek if not at end + }); + + el.addEventListener('sortableDrop', (e) => { + if (!e.detail.data.list) return; + + el.dispatchEvent( + new CustomEvent('moveCustomTodoList', { + detail: { + ...e.detail.data.list, + index: e.detail.index, + }, + bubbles: true, + }), + ); + }); + + el.addEventListener('draggableOver', (e) => { + if (e.detail.data.list) updatePositions(); + }); + + el.addEventListener('todoData', (e) => { + todoData = e.detail; + update(); + }); + + function update() { + const customLists = TodoLogic.getCustomTodoLists(todoData); + + const container = el.querySelector('.container'); + const obsolete = new Set(container.children); + const childrenByKey = new Map(); + + obsolete.forEach((child) => childrenByKey.set(child.dataset.key, child)); + + const children = customLists.map((list) => { + let child = childrenByKey.get(list.id); + + if (child) { + obsolete.delete(child); + } else { + child = document.createElement('div'); + child.className = 'card todo-custom-list'; + child.dataset.key = list.id; + TodoCustomList(child); + } + + child.dispatchEvent(new CustomEvent('customTodoList', { detail: list })); + + return child; + }); + + obsolete.forEach((child) => container.removeChild(child)); + + children.forEach((child, index) => { + if (child !== container.children[index]) { + container.insertBefore(child, container.children[index]); + } + }); + + updatePositions(); + updateHeight(); + } + + function updatePositions() { + el.querySelectorAll('.container > *').forEach((child, index) => { + child.style.transform = `translateX(${ + (index - todoData.customAt) * 100 + }%)`; + }); + } + + function updateHeight() { + let height = 280; + const container = el.querySelector('.container'); + + for (let i = 0, l = container.children.length; i < l; ++i) { + container.children[i].style.height = `auto`; + height = Math.max(container.children[i].offsetHeight, height); + } + + el.style.height = `${height + 80}px`; + + for (let i = 0, l = container.children.length; i < l; ++i) { + container.children[i].style.height = `${height + 30}px`; + } + + // Update collapsible on changing heights + el.dispatchEvent(new CustomEvent('collapse', { bubbles: true })); + } +} diff --git a/app/tools/tasks/scripts/TodoFrameDays.js b/app/tools/tasks/scripts/TodoFrameDays.js new file mode 100644 index 00000000..0b611b17 --- /dev/null +++ b/app/tools/tasks/scripts/TodoFrameDays.js @@ -0,0 +1,121 @@ +import { AppIcon } from './AppIcon.js'; +import { TodoDay } from './TodoDay.js'; +import { TodoLogic } from './TodoLogic.js'; + +/** + * @param {HTMLElement} el + */ +export function TodoFrameDays(el) { + const RANGE = 14; + let todoData = TodoLogic.initTodoData(); + + el.innerHTML = /* html */ ` + +
+ + `; + + setTimeout(() => el.classList.add('-animated'), 200); + + el.querySelectorAll('.app-icon').forEach(AppIcon); + + el.querySelector('.backward').addEventListener('click', () => + el.dispatchEvent( + new CustomEvent('seekDays', { detail: -1, bubbles: true }), + ), + ); + + el.querySelector('.forward').addEventListener('click', () => + el.dispatchEvent(new CustomEvent('seekDays', { detail: 1, bubbles: true })), + ); + + el.querySelector('.fastbackward').addEventListener('click', () => + el.dispatchEvent( + new CustomEvent('seekDays', { detail: -5, bubbles: true }), + ), + ); + + el.querySelector('.fastforward').addEventListener('click', () => + el.dispatchEvent(new CustomEvent('seekDays', { detail: 5, bubbles: true })), + ); + + + + el.addEventListener('todoData', (e) => { + todoData = e.detail; + update(); + }); + + function update() { + const listsByDay = TodoLogic.getTodoListsByDay(todoData, RANGE); + + const container = el.querySelector('.container'); + const obsolete = new Set(container.children); + const childrenByKey = new Map(); + + obsolete.forEach((child) => childrenByKey.set(child.dataset.key, child)); + + const children = listsByDay.map((day) => { + let child = childrenByKey.get(day.id); + + if (child) { + obsolete.delete(child); + } else { + child = document.createElement('div'); + child.className = 'card todo-day'; + child.dataset.key = day.id; + TodoDay(child); + } + + child.dispatchEvent(new CustomEvent('todoDay', { detail: day })); + child.style.transform = `translateX(${day.position * 100}%)`; + + return child; + }); + + obsolete.forEach((child) => container.removeChild(child)); + + children.forEach((child, index) => { + if (child !== container.children[index]) { + container.insertBefore(child, container.children[index]); + } + }); + + updateHeight(); + } + + function updateHeight() { + let height = 280; + const container = el.querySelector('.container'); + + for (let i = 0, l = container.children.length; i < l; ++i) { + height = Math.max(container.children[i].offsetHeight, height); + } + + el.style.height = `${height + 50}px`; + } +} diff --git a/app/tools/tasks/scripts/TodoItem.js b/app/tools/tasks/scripts/TodoItem.js new file mode 100644 index 00000000..6efddf8d --- /dev/null +++ b/app/tools/tasks/scripts/TodoItem.js @@ -0,0 +1,160 @@ +import { AppDraggable } from './AppDraggable.js'; +import { AppIcon } from './AppIcon.js'; + +/** + * @param {HTMLElement} el + */ +export function TodoItem(el) { + let item; + let editing = false; + let startEditing = false; + let saveOnBlur = true; + + el.innerHTML = /* html */ ` +
+ +
+

+

+ + +

+ `; + + const checkboxEl = el.querySelector('.checkbox'); + const labelEl = el.querySelector('.label'); + const inputEl = el.querySelector('.input'); + const saveEl = el.querySelector('.save'); + + AppDraggable(el, { + dropSelector: '.todo-list > .items', + }); + + el.querySelectorAll('.app-icon').forEach(AppIcon); + + checkboxEl.addEventListener( + 'touchstart', + () => { + saveOnBlur = false; + }, + { passive: true }, + ); + + checkboxEl.addEventListener('mousedown', () => { + saveOnBlur = false; + }); + + checkboxEl.addEventListener('click', () => { + if (editing) save(); + + el.dispatchEvent( + new CustomEvent('checkTodoItem', { + detail: { + ...item, + done: !item.done, + }, + bubbles: true, + }), + ); + }); + + labelEl.addEventListener('click', () => { + startEditing = true; + editing = true; + update(); + }); + + inputEl.addEventListener('keyup', (e) => { + switch (e.keyCode) { + case 13: // Enter + save(); + break; + case 27: // Escape + cancelEdit(); + break; + } + }); + + inputEl.addEventListener('blur', () => { + if (saveOnBlur) save(); + saveOnBlur = true; + }); + + inputEl.addEventListener('focusOther', () => { + if (editing) save(); + }); + + saveEl.addEventListener('mousedown', () => { + saveOnBlur = false; + }); + + saveEl.addEventListener('click', save); + + el.addEventListener('draggableStart', (e) => { + e.detail.data.item = item; + e.detail.data.key = item.id; + }); + + el.addEventListener('todoItem', (e) => { + item = e.detail; + update(); + }); + + function save() { + const label = inputEl.value.trim(); + + if (label === '') { + // Deferred deletion prevents a bug at reconciliation in TodoList: + // Failed to execute 'removeChild' on 'Node': The node to be removed is + // no longer a child of this node. Perhaps it was moved in a 'blur' + // event handler? + requestAnimationFrame(() => { + el.dispatchEvent( + new CustomEvent('deleteTodoItem', { + detail: item, + bubbles: true, + }), + ); + }); + + return; + } + + el.dispatchEvent( + new CustomEvent('editTodoItem', { + detail: { + ...item, + label, + }, + bubbles: true, + }), + ); + + editing = false; + update(); + } + + function cancelEdit() { + saveOnBlur = false; + editing = false; + update(); + } + + function update() { + el.classList.toggle('-done', item.done); + checkboxEl.querySelector('input').checked = item.done; + labelEl.innerText = item.label; + + el.classList.toggle('-editing', editing); + el.classList.toggle('_nodrag', editing); + + if (editing && startEditing) { + inputEl.value = item.label; + inputEl.focus(); + inputEl.select(); + startEditing = false; + } + } +} diff --git a/app/tools/tasks/scripts/TodoItemInput.js b/app/tools/tasks/scripts/TodoItemInput.js new file mode 100644 index 00000000..efbaa18b --- /dev/null +++ b/app/tools/tasks/scripts/TodoItemInput.js @@ -0,0 +1,67 @@ +import { AppIcon } from './AppIcon.js'; + +/** + * @param {HTMLElement} el + */ +export function TodoItemInput(el) { + let saveOnBlur = true; + + el.innerHTML = /* html */ ` + + + `; + + const inputEl = el.querySelector('.input'); + const saveEl = el.querySelector('.save'); + + el.querySelectorAll('.app-icon').forEach(AppIcon); + + inputEl.addEventListener('keyup', (e) => { + switch (e.keyCode) { + case 13: // Enter + save(); + break; + case 27: // Escape + clear(); + break; + } + }); + + inputEl.addEventListener('blur', () => { + if (saveOnBlur) save(); + saveOnBlur = true; + }); + + inputEl.addEventListener('focusOther', save); + + saveEl.addEventListener('mousedown', () => { + saveOnBlur = false; + }); + + saveEl.addEventListener('click', () => { + save(); + inputEl.focus(); + }); + + function save() { + const label = inputEl.value.trim(); + + if (label === '') return; + + inputEl.value = ''; + + el.dispatchEvent( + new CustomEvent('addTodoItem', { + detail: { label }, + bubbles: true, + }), + ); + } + + function clear() { + inputEl.value = ''; + inputEl.blur(); + } +} diff --git a/app/tools/tasks/scripts/TodoList.js b/app/tools/tasks/scripts/TodoList.js new file mode 100644 index 00000000..0de5ed0d --- /dev/null +++ b/app/tools/tasks/scripts/TodoList.js @@ -0,0 +1,68 @@ +import { AppSortable } from './AppSortable.js'; +import { TodoItem } from './TodoItem.js'; +import { TodoItemInput } from './TodoItemInput.js'; + +/** + * @param {HTMLElement} el + */ +export function TodoList(el) { + let items = []; + + el.innerHTML = /* html */ ` +
+
+ `; + + AppSortable(el.querySelector('.items'), {}); + TodoItemInput(el.querySelector('.todo-item-input')); + + el.addEventListener('sortableDrop', (e) => + el.dispatchEvent( + new CustomEvent('moveTodoItem', { + detail: { + ...e.detail.data.item, + index: e.detail.index, + }, + bubbles: true, + }), + ), + ); + + el.addEventListener('todoItems', (e) => { + items = e.detail; + update(); + }); + + function update() { + const container = el.querySelector('.items'); + const obsolete = new Set(container.children); + const childrenByKey = new Map(); + + obsolete.forEach((child) => childrenByKey.set(child.dataset.key, child)); + + const children = items.map((item) => { + let child = childrenByKey.get(item.id); + + if (child) { + obsolete.delete(child); + } else { + child = document.createElement('div'); + child.classList.add('todo-item'); + child.dataset.key = item.id; + TodoItem(child); + } + + child.dispatchEvent(new CustomEvent('todoItem', { detail: item })); + + return child; + }); + + obsolete.forEach((child) => container.removeChild(child)); + + children.forEach((child, index) => { + if (child !== container.children[index]) { + container.insertBefore(child, container.children[index]); + } + }); + } +} diff --git a/app/tools/tasks/scripts/TodoLogic.js b/app/tools/tasks/scripts/TodoLogic.js new file mode 100644 index 00000000..7bcb9d3a --- /dev/null +++ b/app/tools/tasks/scripts/TodoLogic.js @@ -0,0 +1,357 @@ +import { formatDateId } from './util.js'; +import { uuid } from './uuid.js'; + +/** + * @typedef {{ + * id: string; + * listId: string; + * index: number; + * label: string; + * done: boolean; + * fixed: boolean; + * }} TodoDataItem + */ + +/** + * @typedef {{ + * id: string; + * index: number; + * title: string; + * }} TodoDataCustomList + */ + +/** + * @typedef {{ + * items: TodoDataItem[]; + * customLists: TodoDataCustomList[]; + * at: string; + * customAt: number; + * }} TodoData + */ + +export class TodoLogic { + /** + * @param {Date} now + * @returns {TodoData} + */ + static initTodoData(now = new Date()) { + return { + items: [], + customLists: [], + at: formatDateId(now), + customAt: 0, + }; + } + + static getTodoListsByDay(data, range) { + const listsByDay = []; + + for (let i = 0; i < 2 * range; ++i) { + const t = new Date(`${data.at}T00:00:00`); + t.setDate(t.getDate() - range + i); + const id = formatDateId(t); + + listsByDay.push({ + id, + items: TodoLogic.getTodoItemsForList(data, id), + position: -range + i, + }); + } + + return listsByDay; + } + + static getTodoItemsForList(data, listId) { + return data.items + .filter((item) => item.listId === listId) + .sort((a, b) => a.index - b.index); + } + + /** + * @param {TodoData} data + * @param {{listId: string, label: string}} input + * @returns {TodoData} + */ + static addTodoItem(data, input, now = new Date()) { + let index = 0; + + for (const item of data.items) { + if (item.listId === input.listId) { + index = Math.max(index, item.index + 1); + } + } + + return { + ...data, + items: [ + ...data.items, + { + ...input, + id: uuid(), + index, + done: false, + fixed: this.isListInThePast(input.listId, now), + }, + ], + }; + } + + /** + * @param {TodoData} data + * @param {{id: string, done: boolean}} input + * @returns {TodoData} + */ + static checkTodoItem(data, input) { + return { + ...data, + items: data.items.map((item) => + item.id === input.id ? { ...item, done: input.done } : item, + ), + }; + } + + /** + * @param {TodoData} data + * @param {{id: string, label: string}} input + * @returns {TodoData} + */ + static editTodoItem(data, input) { + return { + ...data, + items: data.items.map((item) => + item.id === input.id ? { ...item, label: input.label } : item, + ), + }; + } + + /** + * @param {TodoData} data + * @param {{id: string, listId: string, index: number}} input + * @returns {TodoData} + */ + static moveTodoItem(data, input, now = new Date()) { + const itemToMove = data.items.find((item) => item.id === input.id); + + if (!itemToMove) return data; + + // Reinsert item at target list and index + let list = data.items.filter( + (item) => item.listId === input.listId && item.id !== input.id, + ); + list.splice(input.index, 0, { + ...itemToMove, + listId: input.listId, + fixed: this.isListInThePast(input.listId, now), + }); + list = TodoLogic.setIndexes(list); + + // Reinsert updated list + let items = data.items.filter( + (item) => item.listId !== input.listId && item.id !== input.id, + ); + items = [...items, ...list]; + + return { + ...data, + items, + }; + } + + /** + * @param {TodoData} data + * @param {{id: string}} input + * @returns {TodoData} + */ + static deleteTodoItem(data, input) { + return { + ...data, + items: data.items.filter((item) => item.id !== input.id), + }; + } + + /** + * @param {TodoData} data + * @param {Date} now + * @returns {TodoData} + */ + static movePastTodoItems(data, now = new Date()) { + const todayListId = formatDateId(now); + + let targetIndex = 0; + + for (const item of data.items) { + if (item.listId === todayListId && item.index > targetIndex) { + targetIndex = item.index; + } + } + + return { + ...data, + items: data.items.map((item) => { + if ( + !item.done && + !item.fixed && + this.isListInThePast(item.listId, now) + ) { + return { ...item, listId: todayListId, index: targetIndex++ }; + } + + return item; + }), + }; + } + + static isListInThePast(listId, now = new Date()) { + const todayListId = formatDateId(now); + + return listId.match(/\d\d\d\d-\d\d-\d\d/) && listId < todayListId; + } + + // + + static getCustomTodoLists(data) { + return data.customLists + .map((list) => ({ + id: list.id, + index: list.index, + title: list.title, + items: TodoLogic.getTodoItemsForList(data, list.id), + })) + .sort((a, b) => a.index - b.index); + } + + /** + * @param {TodoData} data + * @returns {TodoData} + */ + static addCustomTodoList(data) { + let index = 0; + + for (const customList of data.customLists) { + index = Math.max(index, customList.index + 1); + } + + return { + ...data, + customLists: [ + ...data.customLists, + { + id: uuid(), + index, + title: '', + }, + ], + }; + } + + /** + * @param {TodoData} data + * @param {{id: string, title: string}} input + * @returns {TodoData} + */ + static editCustomTodoList(data, input) { + return { + ...data, + customLists: data.customLists.map((customList) => + customList.id === input.id + ? { ...customList, title: input.title } + : customList, + ), + }; + } + + /** + * @param {TodoData} data + * @param {{id: string, index: number}} input + * @returns {TodoData} + */ + static moveCustomTodoList(data, input) { + const customListToMove = data.customLists.find( + (customList) => customList.id === input.id, + ); + + let customLists = data.customLists + .filter((customList) => customList.id !== input.id) + .sort((a, b) => a.index - b.index); + customLists.splice(input.index, 0, customListToMove); + customLists = TodoLogic.setIndexes(customLists); + + return { + ...data, + customLists, + }; + } + + /** + * @param {TodoData} data + * @param {{id: string}} input + * @returns {TodoData} + */ + static deleteCustomTodoList(data, input) { + return { + ...data, + customLists: data.customLists.filter( + (customList) => customList.id !== input.id, + ), + }; + } + + // + + /** + * @param {TodoData} data + * @param {number} delta + * @returns {TodoData} + */ + static seekDays(data, delta) { + const t = new Date(`${data.at}T00:00:00`); + t.setDate(t.getDate() + delta); + + return { ...data, at: formatDateId(t) }; + } + + /** + * @param {TodoData} data + * @returns {TodoData} + */ + static seekToToday(data) { + return { ...data, at: formatDateId(new Date()) }; + } + + /** + * @param {TodoData} data + * @param {Date} date + * @returns {TodoData} + */ + static seekToDate(data, date) { + return { ...data, at: formatDateId(date) }; + } + + /** + * @param {TodoData} data + * @param {number} delta + * @returns {TodoData} + */ + static seekCustomTodoLists(data, delta) { + return { + ...data, + customAt: Math.max( + 0, + Math.min(data.customLists.length - 1, data.customAt + delta), + ), + }; + } + + // + + /** + * @template {{index?: number}} T + * @param {T[]} array + * @returns {T[]} + */ + static setIndexes(array) { + return array.map((item, index) => + item.index === index ? item : { ...item, index }, + ); + } +} diff --git a/app/tools/tasks/scripts/util.js b/app/tools/tasks/scripts/util.js new file mode 100644 index 00000000..2bda634e --- /dev/null +++ b/app/tools/tasks/scripts/util.js @@ -0,0 +1,99 @@ +/** + * @param {Date} date + * @returns {string} + */ +export function formatDateId(date) { + const y = date.getFullYear(); + const m = date.getMonth() + 1; + const d = date.getDate(); + const ys = y.toString().padStart(4, '0'); + const ms = m.toString().padStart(2, '0'); + const ds = d.toString().padStart(2, '0'); + + return `${ys}-${ms}-${ds}`; +} + +/** + * @param {Date} date + * @returns {string} + */ +export function formatDate(date) { + const m = formatMonth(date); + const d = formatDayOfMonth(date); + const y = date.getFullYear().toString().padStart(4, '0'); + + return `${m} ${d} ${y}`; +} + +/** + * @param {Date} date + * @returns {string} + */ +export function formatDayOfMonth(date) { + const d = date.getDate(); + const t = d % 10; + + return d === 11 || d === 12 || d === 13 + ? `${d}th` + : t === 1 + ? `${d}st` + : t === 2 + ? `${d}nd` + : t === 3 + ? `${d}rd` + : `${d}th`; +} + +export const DAY_NAMES = [ + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', +]; + +/** + * @param {Date} date + * @returns {string} + */ +export function formatDayOfWeek(date) { + return DAY_NAMES[date.getDay()]; +} + +export const MONTH_NAMES = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', +]; + +/** + * @param {Date} date + * @returns {string} + */ +export function formatMonth(date) { + return MONTH_NAMES[date.getMonth()]; +} + +/** + * https://developer.mozilla.org/en-US/docs/Glossary/Base64 + * @param {BlobPart} input + */ +export async function toDataURL(input, type) { + return await new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.addEventListener('load', () => resolve(reader.result)); + reader.addEventListener('error', () => reject(reader.error)); + reader.readAsDataURL(new File([input], '', { type })); + }); +} diff --git a/app/tools/tasks/scripts/uuid.js b/app/tools/tasks/scripts/uuid.js new file mode 100644 index 00000000..facb3697 --- /dev/null +++ b/app/tools/tasks/scripts/uuid.js @@ -0,0 +1,8 @@ +export function uuid() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0, + v = c === 'x' ? r : (r & 0x3) | 0x8; + + return v.toString(16); + }); +} diff --git a/app/tools/tasks/styles/app-tasks.css b/app/tools/tasks/styles/app-tasks.css new file mode 100644 index 00000000..9dd9361f --- /dev/null +++ b/app/tools/tasks/styles/app-tasks.css @@ -0,0 +1,212 @@ +/* app header */ + +.app-header { + background: var(--header-bg); + padding: 10px 20px; + position: relative; + user-select: none; + } + + .app-header > .title { + color: var(--header-text); + font-size: 1.25rem; + font-weight: 400; + line-height: 1.25em; + margin: 0; + } + + .app-header > .actions { + position: absolute; + right: 1.5rem; + top: .5rem; + margin: 0; + } + +/* app collapsible */ +.app-collapsible > .bar { + height: 32px; + line-height: 29px; + margin: 0; + padding: 0 0.75em; + background: var(--bar-active-bg); + } + + .app-collapsible.-animated > .body { + transition: height 0.2s ease-out; + overflow: hidden; + } +/* app date-picket */ +.app-date-picker { + position: absolute; + top: 3rem; + right: 1.5rem; + display:none; + width: 260px; + background: #263239; /* var(--main-bg); */ + border-radius: 4px; + box-shadow: rgb(0 0 0 / 10%) 0 4px 12px; + padding: 8px; + transform: translate(0, -110%); + transition: all 0.2s ease-in-out; + text-align: center; + user-select: none; + z-index: 99; + } + + .app-date-picker.-show { + display:block; + transform: translate(0, 0); + } + + .app-date-picker > .header { + display: flex; + font-size: 1em; + margin: 0 0 1em; + line-height: 1.5em; + } + + .app-date-picker > .header > .month { + flex-grow: 1; + font-weight: bold; + text-transform: uppercase; + } + + .app-date-picker > .dates { + width: 100%; + } + + /* stylelint-disable-next-line rscss/class-format */ + .app-date-picker > .dates > thead > tr > th { + font-weight: normal; + text-transform: uppercase; + padding-bottom: 0.4em; + } + + /* stylelint-disable-next-line rscss/class-format */ + .app-date-picker > .dates > tbody > tr > td { + padding: 0; + } + + /* stylelint-disable-next-line rscss/class-format */ + .app-date-picker > .dates > tbody > tr > td > button { + width: 100%; + height: 1.9em; + } + + @media (width >= 320px) { + .app-date-picker { + width: 300px; + } + } + +/* app buttons */ +.app-button, +label.app-button { + background: transparent; + border: 0; + border-radius: 4px; + color: var(--button-text); + cursor: pointer; + display: inline-block; + font-size: 1em; + line-height: 1em; + margin: 0; + padding: 0.25em; + outline: 0; + transition: all 0.1s ease-out; + vertical-align: middle; +} + +.app-button:hover { + color: var(--button-active-text); +} + +.app-button:active { + transform: translate(0, 1px); + color: var(--button-active-text); + background: var(--button-active-bg); +} + +.app-button:focus { + color: var(--button-active-text); +} + +.app-button.rounded-md { + width: 1.5em; + height: 1.5em; + border-radius: .5rem; +} + +.app-button.-xl { + font-size: 1.5em; +} + +.app-button.-highlight { + color: var(--button-highlight-text); + font-weight: bold; +} + +.app-button.-highlight:hover { + color: var(--button-active-text); +} + +@media (width >= 600px) { + .app-button.-xl { + font-size: 2em; + } +} + +/* app icons */ +.app-icon { + display: inline-block; + vertical-align: baseline; + height: 1em; + } + + .app-icon > svg { + display: inline-block; + width: 1em; + height: 1em; + vertical-align: middle; + fill: currentcolor; + transition: transform 0.1s ease-out; + position: relative; + top: -0.1em; + } + + .icon-codemo { + background-color:currentColor; + display:inline-block; + width:1em; + height:1em; + mask-image:var(--svg); + mask-repeat:no-repeat; + mask-size:100% 100%; + vertical-align:-.175em; + --svg:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='currentColor' d='M12 0h-.079c-1.66 0-3.239.349-4.667.978l.074-.029A12.269 12.269 0 0 0 3.52 3.523A12.219 12.219 0 0 0 .978 7.251l-.031.079A11.398 11.398 0 0 0 0 11.919v.086v-.004v.079c0 1.66.349 3.239.978 4.667l-.029-.074a12.276 12.276 0 0 0 2.572 3.807a12.224 12.224 0 0 0 3.729 2.542l.079.031c1.354.6 2.933.949 4.593.949h.083h-.004h.079c1.66 0 3.239-.349 4.667-.978l-.074.029a12.276 12.276 0 0 0 3.809-2.573a12.219 12.219 0 0 0 2.542-3.728l.031-.079c.6-1.354.949-2.932.949-4.593v-.158c0-1.66-.349-3.239-.978-4.667l.029.074a12.286 12.286 0 0 0-2.573-3.806A12.219 12.219 0 0 0 16.754.981L16.675.95C15.321.35 13.741 0 12.08 0h-.083zm.64 22.79v-2.087l5.193-2.633a.421.421 0 0 0 .154-.129l.001-.001a.335.335 0 0 0 .059-.191v-.011v.001v-1.186c.225-.082.412-.226.543-.412l.002-.004c.133-.179.214-.404.214-.648v-.118a1.146 1.146 0 0 0-.366-.746l-.001-.001a1.09 1.09 0 0 0-.75-.297h-.022h.001h-.02c-.308 0-.587.127-.786.332c-.205.2-.332.478-.332.787v.021v-.001v.059c.002.03.011.057.024.081v-.001c.013.222.096.423.227.583l-.001-.002c.133.163.304.289.501.364l.008.003v.949l-4.649 2.372v-1.942l2.039-.949a.533.533 0 0 0 .166-.13l.001-.001a.3.3 0 0 0 .071-.194v-3.233l3.793-2.134a.318.318 0 0 0 .142-.141l.001-.002a.407.407 0 0 0 .047-.189v-.901c.223-.079.409-.218.543-.397l.002-.003c.133-.177.214-.401.214-.644v-.04c0-.308-.127-.587-.332-.786a1.066 1.066 0 0 0-.775-.332H18.4a1.082 1.082 0 0 0-.71.366l-.001.001a1.113 1.113 0 0 0-.285.746v.025v-.001v.02c0 .243.08.466.216.646l-.002-.003c.137.182.322.321.538.397l.008.003v.688l-3.818 2.134a.51.51 0 0 0-.129.129l-.001.002a.338.338 0 0 0-.055.184v.017v-.001v3.2l-1.52.711v-6.972l3.2-1.566a.379.379 0 0 0 .153-.141l.001-.002a.347.347 0 0 0 .059-.189V7.04c.223-.079.409-.218.543-.397l.002-.003c.133-.177.214-.401.214-.644v-.021v.001v-.061a.078.078 0 0 0-.024-.057a1.127 1.127 0 0 0-.366-.719l-.001-.001a1.06 1.06 0 0 0-.738-.297h-.128a1.108 1.108 0 0 0-.719.378l-.001.001a1.103 1.103 0 0 0-.297.754v.123c.032.227.12.428.251.596l-.002-.003c.127.167.301.291.502.354l.007.002v.972l-2.656 1.304V5.288c.225-.082.412-.226.543-.412l.002-.004c.133-.179.214-.404.214-.648V4.2c0-.308-.127-.587-.332-.786a1.095 1.095 0 0 0-.787-.332h-.021h.001h-.02c-.308 0-.587.127-.786.332c-.205.2-.332.478-.332.787v.021v-.001v.118c.03.223.119.421.25.583l-.002-.002c.133.163.304.289.501.364l.008.003v6.569l-1.874-.996V7.865a.292.292 0 0 0-.048-.16l.001.001a.87.87 0 0 0-.095-.119l-1.306-.998a.825.825 0 0 0 .07-.196l.001-.006c.015-.067.024-.143.024-.222v-.024c0-.308-.127-.587-.332-.786a1.097 1.097 0 0 0-.786-.331H7.52h.001h-.02c-.308 0-.587.127-.786.332c-.205.2-.332.478-.332.787v.021v-.001v.024c0 .305.125.581.326.78c.2.205.478.332.787.332h.021h-.001h.015a.858.858 0 0 0 .288-.049l-.006.002c.11-.041.2-.081.287-.125l-.015.007l1.162.925v3.035a.29.29 0 0 0 .06.178l-.001-.001a.42.42 0 0 0 .152.129l.002.001l2.419 1.28V15.1l-4.055-1.874l.071-1.47v-.024a.29.29 0 0 0-.06-.178l.001.001a.42.42 0 0 0-.152-.129l-.002-.001l-1.851-.97a.522.522 0 0 0 .024-.157v-.2a1.127 1.127 0 0 0-.366-.719l-.001-.001a1.093 1.093 0 0 0-.752-.299h-.018h.001h-.007c-.305 0-.58.127-.775.332c-.208.2-.338.481-.338.792v.015v-.001v.118c.029.285.164.534.366.71l.001.001c.193.177.451.285.735.285h.131a1.45 1.45 0 0 0 .33-.083l-.01.003c.104-.04.195-.092.275-.156l-.002.002l1.707.88l-.047 1.47v.008c0 .075.017.145.048.208l-.001-.003a.305.305 0 0 0 .164.142l.002.001l4.577 2.134v6.869q-.308 0-.605-.024l-.605-.047l.071-4.364a.349.349 0 0 0-.06-.191l.001.001a.364.364 0 0 0-.175-.142l-.002-.001l-2.87-1.328v-.125c0-.305-.127-.58-.332-.775a1.095 1.095 0 0 0-.787-.332H6.49h.001h-.114a1.082 1.082 0 0 0-.71.366l-.001.001a1.113 1.113 0 0 0-.285.746v.025v-.001v.007c0 .305.127.58.332.775c.195.205.47.332.775.332h.039c.156 0 .305-.033.439-.094l-.007.003c.14-.067.261-.147.369-.242l-.002.002l2.656 1.21v4.008a10.611 10.611 0 0 1-3.534-1.343l.048.027a10.85 10.85 0 0 1-2.773-2.354l-.014-.017a11.1 11.1 0 0 1-1.824-3.112l-.026-.076a10.267 10.267 0 0 1-.676-3.699v-.111c0-1.494.314-2.915.88-4.201l-.026.067a11.06 11.06 0 0 1 2.324-3.44A11.019 11.019 0 0 1 7.73 2.065l.071-.028a10.269 10.269 0 0 1 4.139-.856h.061h-.003h.064c1.494 0 2.915.314 4.201.88l-.067-.026a11.078 11.078 0 0 1 3.44 2.32a11 11 0 0 1 2.296 3.369l.028.071c.54 1.218.854 2.639.854 4.134v.067v-.003v.064c0 1.444-.292 2.82-.82 4.072l.026-.069a11.1 11.1 0 0 1-2.175 3.373l.005-.006a10.852 10.852 0 0 1-3.172 2.32l-.065.028c-1.16.568-2.516.932-3.948 1.009l-.026.001z'%3E%3C/path%3E%3C/svg%3E") + } + + .app-icon.-r180 > svg { + transform: rotate(180deg); + } + + .app-icon.-double > svg:nth-child(2) { + margin-left: -0.5em; + } + +/* app footer */ +.app-footer { + padding: 2em; + font-size: 0.8em; + color: var(--aside-text); + } + + .app-footer > p { + margin: 0; + } + + /* stylelint-disable-next-line rscss/no-descendant-combinator */ + .app-footer a { + color: var(--aside-text); + } + \ No newline at end of file diff --git a/app/tools/tasks/styles/base.css b/app/tools/tasks/styles/base.css new file mode 100644 index 00000000..fa3e5a6c --- /dev/null +++ b/app/tools/tasks/styles/base.css @@ -0,0 +1,491 @@ +:root { + /* Named color scheme */ + --white: #fefefe; + --black: #0a0a0b; + --grey1: #545e75; + --grey2: #b0b0b8; + --grey3: #607D8B; + --grey4: #e4ecf0; + --blue: #2597f4; + --red: #ce2d4f; + --bar: #263238; + + /* UI color mapping */ + --scrollbar-color-thumb : var(--grey1); + --scrollbar-color-track : transparent; + --scrollbar-width : thin; + --scrollbar-width-legacy : 8px; + + --main-text: var(--white); + --main-bg: var(--black); + --main-border: var(--grey1); + --main-border-light: var(--grey3); + --header-text: var(--grey2); + --header-bg: var(--bar); + --highlight-text: var(--blue); + --disabled-text: var(--grey2); + --aside-text: var(--grey3); + --button-text: var(--grey3); + --button-bg: transparent; + --button-active-text: var(--white); + --button-active-bg: #1b2429; /* var(--grey2); */ + --button-highlight-text: var(--blue); + --bar-active-bg: var(--bar); +} + +html { + box-sizing: border-box; +} + +*, +*::before, +*::after { + box-sizing: inherit; +} +/* Modern browsers with `scrollbar-*` support */ +@supports (scrollbar-width: auto) { + * { + scrollbar-color: var(--scrollbar-color-thumb) var(--scrollbar-color-track); + scrollbar-width: var(--scrollbar-width); + } +} +/* Legacy browsers with `::-webkit-scrollbar-*` support */ +@supports selector(::-webkit-scrollbar) { + * ::-webkit-scrollbar-thumb { + background: var(--scrollbar-color-thumb); + } + * ::-webkit-scrollbar-track { + background: var(--scrollbar-color-track); + } + * ::-webkit-scrollbar { + max-width: var(--scrollbar-width-legacy); + max-height: var(--scrollbar-width-legacy); + } +} +html, +body { + margin: 0; + padding: 0; + height: 100%; + background: var(--main-bg); + color: var(--main-text); +} + +body { + font: normal 16px/1.5 system-ui, sans-serif; +} + + + +/* Todo app */ + +.todo-app { + overflow: hidden; +} +/* Todo day */ + +.todo-day { + padding: 0 0.5em; +} + +.todo-day > .header { + text-align: center; + padding: 2em 0; +} + +.todo-day > .header > .dayofweek { + text-transform: uppercase; + margin: 0 0 0.25em; + font-size: 1.5em; +} + +.todo-day > .header > .date { + text-transform: uppercase; + font-weight: normal; + margin: 0.25em 0 0; + font-size: 0.8em; + color: var(--aside-text); +} + +.todo-day.-past { + color: var(--disabled-text); +} + +.todo-day.-past > .header > .date { + color: var(--disabled-text); +} + +.todo-day.-today > .header > .dayofweek { + color: var(--highlight-text); +} + +.todo-day.-today > .header > .date { + color: var(--highlight-text); +} + + +/* Todo frame */ + +.todo-frame { + position: relative; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.todo-frame > .leftcontrols, +.todo-frame > .rightcontrols { + position: absolute; + top: 0; + width: 52px; + padding: 1.5em 0; + text-align: center; +} + +.todo-frame > .leftcontrols { + left: 0; +} + +.todo-frame > .rightcontrols { + right: 0; +} + +.todo-frame > .leftcontrols > p, +.todo-frame > .rightcontrols > p { + margin: 0 0 0.5em; +} + +.todo-frame > .rightcontrols > .datepicker { + position: absolute; + right: 10px; +} + +.todo-frame > .container { + position: absolute; + overflow: hidden; + inset: 0 52px; +} + +.todo-frame > .container > .card { + position: absolute; + top: 0; + left: 0; + width: 100%; + transition: + transform 0.2s ease-out, + opacity 0.2s ease-out; +} + +.todo-frame.-animated { + transition: height 0.2s ease-out; +} + +@media (width >= 600px) { + .todo-frame > .container { + right: 70px; + left: 70px; + } + + .todo-frame > .leftcontrols, + .todo-frame > .rightcontrols { + width: 70px; + } + + .todo-frame > .container > .card { + width: 50%; + } +} + +@media (width >= 768px) { + .todo-frame > .container > .card { + width: 33.333%; + } +} + +@media (width >= 1024px) { + .todo-frame > .container > .card { + width: 25%; + } +} + +@media (width >= 1280px) { + .todo-frame > .container > .card { + width: 20%; + } +} + + +/* Todo item */ + +.todo-list { + border:1px dotted var(--grey1); + border-radius: 5px; + max-height: 25vh; + overflow: hidden; + overflow-y: auto; + padding: .5em 0; + scrollbar-gutter: stable; +} +.todo-item { + position: relative; + font-size: 0.8em; + line-height: 1.5em; + margin: 0; + padding: 0.25em 0; + background: var(--main-bg); + transition: + transform 0.2s ease-out, + opacity 0.2s ease-out; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + +} +.todo-item .label { + cursor: + url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 14 14'%3E%3Cpath fill='none' stroke='white' stroke-linecap='round' stroke-linejoin='round' d='m13.478 11.832l-.46-2.757a2.573 2.573 0 0 0-2.961-2.114l-2.171.362l-.683-4.09a1.194 1.194 0 0 0-1.374-.98v0c-.65.108-1.09.723-.98 1.374l.894 5.36l-.363.133a1.715 1.715 0 0 0-.643 2.803l.184.19l.954.988M1.75.5L.5 1.75L1.75 3M.5 1.75h3M10.25.5l1.25 1.25L10.25 3m1.25-1.25h-3'/%3E%3C/svg%3E") + 16 16, pointer; +} + +.todo-item > .checkbox { + position: absolute; + top: 0; + left: 0; + width: 30px; + height: 2em; + line-height: 2em; +} + +.todo-item > .checkbox > input { + vertical-align: middle; + cursor: pointer; + appearance: none; + border: 1px solid #1b2429; + margin: 0; + font: inherit; + color: currentColor; + width: 1.15em; + height: 1.15em; + border: 1px solid #607D8B; + border-radius: 0.15em; + /* transform: translateY(0.5em); */ + margin: .5em 0 0 .5em; + display: grid; + place-content: center; +} +.todo-item > .checkbox > input::before { + content: ""; + width: 0.65em; + height: 0.65em; + transform: scale(0); + transition: 120ms transform ease-in-out; + box-shadow: inset 1em 1em var(--form-control-color);transform-origin: bottom left; + clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); +} +.todo-item.-done > .checkbox > input::before { + background: var(--blue); + border-color:var(--blue); + transform: translateY(1px) scale(1); + transform-origin: bottom left; + clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); +} +.todo-item > .checkbox > input:hover { +} + +.todo-item > .label { + margin: 0 0 0 30px; + padding-bottom: 0.25em; + border-bottom: 1px solid var(--main-border-light); + overflow: hidden; + text-overflow: ellipsis; +} + +.todo-item > .form { + display: none; + margin: 0 0 0 30px; + padding-right: 24px; + border-bottom: 1px solid var(--main-border); +} + +.todo-item > .form > .input { + border: 0; + border-radius: 0; + outline: 0; + padding: 0 0 0.25em; + width: 100%; + font-family: inherit; + font-size: inherit; + line-height: 1.5em; + background: transparent; +} + +.todo-item > .form > .save { + position: absolute; + top: 0.5em; + right: 0; +} + +.todo-item.-done > .label { + color: var(--disabled-text); + text-decoration: line-through; +} + +.todo-item.-editing > .label { + display: none; +} + +.todo-item.-editing > .form { + display: block; + background:var(--grey1); + color: var(--highlight-text); +} + + + +.todo-item.-dragging { + opacity: 0.7; + background:var(--blue); + color: var(--highlight-text); +} + +.todo-item.-placeholder { + transition: none !important; + visibility: hidden; +} + +/* Todo item input */ + +.todo-item-input { + position: relative; + margin: 0 0 0 30px; + padding: 0 30px 0 0; + border-bottom: 1px solid var(--main-border-light); + font-size: 0.8em; + line-height: 1.5em; + transition: transform 0.2s ease-out; +} + +.todo-item-input > .input { + border: 0; + border-radius: 3px; + outline: 0; + padding: 0.25em 0; + width: 100%; + font-family: inherit; + font-size: inherit; + line-height: 1.5em; + background: transparent; + color: var(--grey1); +} +.todo-item-input:focus-within{ + background: #263238; + /* box-shadow: 0px 1px 4px 0px #607D8B, 1px 0px 4px 0px #607D8B; */ +} +.todo-item-input:focus-within > .input:focus-visible { + background: #37474f; + color: #ccc; + outline-offset: 0px; + +} + +.todo-item-input > .save { + position: absolute; + top: 0.25em; + right: 0; +} +.todo-item.-editing._nodrag > .form > input[type="text"]:focus { + background: #263238; /* var(--grey1); */ + color: var(--white); +} + + +/* Todo custom list */ + +.todo-custom-list { + padding: 0 0.5em; + transition: + transform 0.2s ease-out, + opacity 0.2s ease-out, + box-shadow 0.2s ease-out; +} + +.todo-custom-list > .header { + text-align: center; + padding: 2em 0; +} + +.todo-custom-list > .header > .title { + margin: 0 0 10px; + font-size: 1.25em; + font-weight: 300; + line-height: normal; + cursor: + url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 14 14'%3E%3Cpath fill='none' stroke='white' stroke-linecap='round' stroke-linejoin='round' d='m13.478 11.832l-.46-2.757a2.573 2.573 0 0 0-2.961-2.114l-2.171.362l-.683-4.09a1.194 1.194 0 0 0-1.374-.98v0c-.65.108-1.09.723-.98 1.374l.894 5.36l-.363.133a1.715 1.715 0 0 0-.643 2.803l.184.19l.954.988M1.75.5L.5 1.75L1.75 3M.5 1.75h3M10.25.5l1.25 1.25L10.25 3m1.25-1.25h-3'/%3E%3C/svg%3E") + 16 16, pointer; +} + +.todo-custom-list > .header > .form { + display: none; + margin: 0 0 10px; + font-size: 1em; + line-height: 1em; +} + +.todo-custom-list > .header > .form > .input { + border: 0; + border-radius: 5px; + font-family: inherit; + font-size: 1.5em; + font-weight: bold; + line-height: normal; + outline: 0; + padding: 0; + text-align: center; + vertical-align: middle; + width: 70%; +} + +.todo-custom-list > .header > .form > .delete { + position: absolute; + right: 0.5em; + top: 2.5em; +} + +.todo-custom-list.-editing > .header > .title { + display: none; +} + +.todo-custom-list.-editing > .header > .form { + display: block; +} +.todo-custom-list.-editing > .header > .form > input[type="text"]:focus { + background: #263238; /* var(--bar-active-bg); */ + color: var(--main-text); +} + +.todo-custom-list.-editing input[type=text]:focus { + background: var(--bar-active-bg); + color: var(--main-text); +} + + +.todo-custom-list.-dragging { + box-shadow: + 10px 0 12px -14px rgb(0 0 0 / 30%), + -10px 0 12px -14px rgb(0 0 0 / 30%); + background: var(--blue); + opacity: 0.8; +} + +.todo-custom-list.-placeholder { + transition: none !important; + visibility: hidden; +} + + diff --git a/app/web-app/graphite.webp b/app/web-app/graphite.webp new file mode 100644 index 0000000000000000000000000000000000000000..ca8a3789ba1a544528d4c9c96a32eca1d267c56d GIT binary patch literal 29336 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�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 G0000wo=uGa literal 0 HcmV?d00001 diff --git a/app/web-app/livecodes.webp b/app/web-app/livecodes.webp new file mode 100644 index 0000000000000000000000000000000000000000..aa642bb8a19b959fdd732adfdaaabe0610e6ac9e GIT binary patch literal 33144 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 literal 0 HcmV?d00001 diff --git a/app/web-app/livecodes/readme.md b/app/web-app/livecodes/readme.md new file mode 100644 index 00000000..e69de29b diff --git a/app/web-app/tldraw.webp b/app/web-app/tldraw.webp new file mode 100644 index 0000000000000000000000000000000000000000..e717e5d2fa18153b7e58a015bbb04bc25e096cf9 GIT binary patch literal 31246 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 literal 0 HcmV?d00001 diff --git a/src/template/foot.html b/src/template/foot.html index d97dceae..39fa95c2 100644 --- a/src/template/foot.html +++ b/src/template/foot.html @@ -20,7 +20,7 @@
SuMoTuWeThFrSa