diff --git a/apps/cory/todo.json b/apps/cory/todo.json new file mode 100644 index 00000000..5f0bdabc --- /dev/null +++ b/apps/cory/todo.json @@ -0,0 +1 @@ +{"type":"tildefriends-app","files":{"app.js":"&QUR1tKa15B5Or8AfPX/8Zs87teSeX0Mh/HF7PEPBom0=.sha256","index.html":"&QXhwvxhHc9fa8iL6088hGDu9FgWdY7wkXgvU2BMNv0A=.sha256","lit-core.min.js":"&tP9KhbgwF1chFqPtkNZ12Yx9AfkpnSjFiPcX5Pw5J9g=.sha256","script.js":"&9RTYD4Le7b6WvqJ8M2TRhZEM0oo6gRVbquZfbvtVIBQ=.sha256"}} \ No newline at end of file diff --git a/apps/cory/todo/app.js b/apps/cory/todo/app.js new file mode 100644 index 00000000..2553b860 --- /dev/null +++ b/apps/cory/todo/app.js @@ -0,0 +1,82 @@ +import * as tfrpc from '/tfrpc.js'; + +let g_db; + +tfrpc.register(async function todo_get_all() { + let names = await todo_get_names(); + let result = []; + for (let name of names) { + result.push({ + name: name, + items: await todo_get(name), + }); + } + return result; +}); + +async function todo_get_names() { + return JSON.parse((await g_db.get('files')) ?? '[]'); +} + +async function todo_add(list) { + let exchanged = false; + let tries = 10; + while (!exchanged && tries-- > 0) { + let original = await g_db.get('files'); + let names = JSON.parse(original ?? '[]'); + let set = new Set(names); + set.add(list); + names = JSON.stringify([...set].sort()); + exchanged = original === names || await g_db.exchange('files', original, names); + } + return exchanged; +} +tfrpc.register(todo_add); + +async function todo_remove(list) { + let exchanged = false; + let tries = 10; + while (!exchanged && tries-- > 0) { + let original = await g_db.get('files'); + let names = JSON.parse(original ?? '[]'); + let set = new Set(names); + set.delete(list); + names = JSON.stringify([...set].sort()); + exchanged = original === names || await g_db.exchange('files', original, names); + } + await g_db.remove('list:' + list); + return exchanged; +} +tfrpc.register(todo_remove); + +tfrpc.register(async function todo_rename(old_name, new_name) { + if (await g_db.get('list:' + new_name)) { + throw RuntimeError(`${new_name} already exists.`); + } + let list = await todo_get(old_name); + await todo_set(new_name, list); + await todo_add(new_name); + await todo_remove(old_name); +}); + +async function todo_get(list) { + try { + let value = await g_db.get('list:' + list); + return JSON.parse(value ?? '[]'); + } catch (error) { + print(error); + return []; + } +} + +async function todo_set(list, value) { + await g_db.set('list:' + list, JSON.stringify(value)); +} +tfrpc.register(todo_set); + +async function main() { + g_db = await database('todo'); + await app.setDocument(utf8Decode(getFile('index.html'))); +} + +main(); \ No newline at end of file diff --git a/apps/cory/todo/index.html b/apps/cory/todo/index.html new file mode 100644 index 00000000..605816b8 --- /dev/null +++ b/apps/cory/todo/index.html @@ -0,0 +1,11 @@ + + + + TODO + + +

TODO

+ + + + \ No newline at end of file diff --git a/apps/cory/todo/lit-core.min.js b/apps/cory/todo/lit-core.min.js new file mode 100644 index 00000000..f11e9db0 --- /dev/null +++ b/apps/cory/todo/lit-core.min.js @@ -0,0 +1,29 @@ +/** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const t=window,i=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,s=Symbol(),e=new WeakMap;class o{constructor(t,i,e){if(this._$cssResult$=!0,e!==s)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=i}get styleSheet(){let t=this.i;const s=this.t;if(i&&void 0===t){const i=void 0!==s&&1===s.length;i&&(t=e.get(s)),void 0===t&&((this.i=t=new CSSStyleSheet).replaceSync(this.cssText),i&&e.set(s,t))}return t}toString(){return this.cssText}}const n=t=>new o("string"==typeof t?t:t+"",void 0,s),r=(t,...i)=>{const e=1===t.length?t[0]:i.reduce(((i,s,e)=>i+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(s)+t[e+1]),t[0]);return new o(e,t,s)},h=(s,e)=>{i?s.adoptedStyleSheets=e.map((t=>t instanceof CSSStyleSheet?t:t.styleSheet)):e.forEach((i=>{const e=document.createElement("style"),o=t.litNonce;void 0!==o&&e.setAttribute("nonce",o),e.textContent=i.cssText,s.appendChild(e)}))},l=i?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let i="";for(const s of t.cssRules)i+=s.cssText;return n(i)})(t):t +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */;var a;const u=window,c=u.trustedTypes,d=c?c.emptyScript:"",v=u.reactiveElementPolyfillSupport,p={toAttribute(t,i){switch(i){case Boolean:t=t?d:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,i){let s=t;switch(i){case Boolean:s=null!==t;break;case Number:s=null===t?null:Number(t);break;case Object:case Array:try{s=JSON.parse(t)}catch(t){s=null}}return s}},f=(t,i)=>i!==t&&(i==i||t==t),m={attribute:!0,type:String,converter:p,reflect:!1,hasChanged:f};class y extends HTMLElement{constructor(){super(),this.o=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this.l=null,this.u()}static addInitializer(t){var i;this.finalize(),(null!==(i=this.v)&&void 0!==i?i:this.v=[]).push(t)}static get observedAttributes(){this.finalize();const t=[];return this.elementProperties.forEach(((i,s)=>{const e=this.p(s,i);void 0!==e&&(this.m.set(e,s),t.push(e))})),t}static createProperty(t,i=m){if(i.state&&(i.attribute=!1),this.finalize(),this.elementProperties.set(t,i),!i.noAccessor&&!this.prototype.hasOwnProperty(t)){const s="symbol"==typeof t?Symbol():"__"+t,e=this.getPropertyDescriptor(t,s,i);void 0!==e&&Object.defineProperty(this.prototype,t,e)}}static getPropertyDescriptor(t,i,s){return{get(){return this[i]},set(e){const o=this[t];this[i]=e,this.requestUpdate(t,o,s)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)||m}static finalize(){if(this.hasOwnProperty("finalized"))return!1;this.finalized=!0;const t=Object.getPrototypeOf(this);if(t.finalize(),void 0!==t.v&&(this.v=[...t.v]),this.elementProperties=new Map(t.elementProperties),this.m=new Map,this.hasOwnProperty("properties")){const t=this.properties,i=[...Object.getOwnPropertyNames(t),...Object.getOwnPropertySymbols(t)];for(const s of i)this.createProperty(s,t[s])}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(t){const i=[];if(Array.isArray(t)){const s=new Set(t.flat(1/0).reverse());for(const t of s)i.unshift(l(t))}else void 0!==t&&i.push(l(t));return i}static p(t,i){const s=i.attribute;return!1===s?void 0:"string"==typeof s?s:"string"==typeof t?t.toLowerCase():void 0}u(){var t;this._=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this.g(),this.requestUpdate(),null===(t=this.constructor.v)||void 0===t||t.forEach((t=>t(this)))}addController(t){var i,s;(null!==(i=this.S)&&void 0!==i?i:this.S=[]).push(t),void 0!==this.renderRoot&&this.isConnected&&(null===(s=t.hostConnected)||void 0===s||s.call(t))}removeController(t){var i;null===(i=this.S)||void 0===i||i.splice(this.S.indexOf(t)>>>0,1)}g(){this.constructor.elementProperties.forEach(((t,i)=>{this.hasOwnProperty(i)&&(this.o.set(i,this[i]),delete this[i])}))}createRenderRoot(){var t;const i=null!==(t=this.shadowRoot)&&void 0!==t?t:this.attachShadow(this.constructor.shadowRootOptions);return h(i,this.constructor.elementStyles),i}connectedCallback(){var t;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(t=this.S)||void 0===t||t.forEach((t=>{var i;return null===(i=t.hostConnected)||void 0===i?void 0:i.call(t)}))}enableUpdating(t){}disconnectedCallback(){var t;null===(t=this.S)||void 0===t||t.forEach((t=>{var i;return null===(i=t.hostDisconnected)||void 0===i?void 0:i.call(t)}))}attributeChangedCallback(t,i,s){this._$AK(t,s)}$(t,i,s=m){var e;const o=this.constructor.p(t,s);if(void 0!==o&&!0===s.reflect){const n=(void 0!==(null===(e=s.converter)||void 0===e?void 0:e.toAttribute)?s.converter:p).toAttribute(i,s.type);this.l=t,null==n?this.removeAttribute(o):this.setAttribute(o,n),this.l=null}}_$AK(t,i){var s;const e=this.constructor,o=e.m.get(t);if(void 0!==o&&this.l!==o){const t=e.getPropertyOptions(o),n="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==(null===(s=t.converter)||void 0===s?void 0:s.fromAttribute)?t.converter:p;this.l=o,this[o]=n.fromAttribute(i,t.type),this.l=null}}requestUpdate(t,i,s){let e=!0;void 0!==t&&(((s=s||this.constructor.getPropertyOptions(t)).hasChanged||f)(this[t],i)?(this._$AL.has(t)||this._$AL.set(t,i),!0===s.reflect&&this.l!==t&&(void 0===this.C&&(this.C=new Map),this.C.set(t,s))):e=!1),!this.isUpdatePending&&e&&(this._=this.T())}async T(){this.isUpdatePending=!0;try{await this._}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var t;if(!this.isUpdatePending)return;this.hasUpdated,this.o&&(this.o.forEach(((t,i)=>this[i]=t)),this.o=void 0);let i=!1;const s=this._$AL;try{i=this.shouldUpdate(s),i?(this.willUpdate(s),null===(t=this.S)||void 0===t||t.forEach((t=>{var i;return null===(i=t.hostUpdate)||void 0===i?void 0:i.call(t)})),this.update(s)):this.P()}catch(t){throw i=!1,this.P(),t}i&&this._$AE(s)}willUpdate(t){}_$AE(t){var i;null===(i=this.S)||void 0===i||i.forEach((t=>{var i;return null===(i=t.hostUpdated)||void 0===i?void 0:i.call(t)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}P(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._}shouldUpdate(t){return!0}update(t){void 0!==this.C&&(this.C.forEach(((t,i)=>this.$(i,this[i],t))),this.C=void 0),this.P()}updated(t){}firstUpdated(t){}} +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +var _;y.finalized=!0,y.elementProperties=new Map,y.elementStyles=[],y.shadowRootOptions={mode:"open"},null==v||v({ReactiveElement:y}),(null!==(a=u.reactiveElementVersions)&&void 0!==a?a:u.reactiveElementVersions=[]).push("1.4.2");const b=window,g=b.trustedTypes,w=g?g.createPolicy("lit-html",{createHTML:t=>t}):void 0,S=`lit$${(Math.random()+"").slice(9)}$`,$="?"+S,C=`<${$}>`,T=document,P=(t="")=>T.createComment(t),x=t=>null===t||"object"!=typeof t&&"function"!=typeof t,A=Array.isArray,k=t=>A(t)||"function"==typeof(null==t?void 0:t[Symbol.iterator]),E=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,M=/-->/g,U=/>/g,N=RegExp(">|[ \t\n\f\r](?:([^\\s\"'>=/]+)([ \t\n\f\r]*=[ \t\n\f\r]*(?:[^ \t\n\f\r\"'`<>=]|(\"|')|))|$)","g"),R=/'/g,O=/"/g,V=/^(?:script|style|textarea|title)$/i,j=t=>(i,...s)=>({_$litType$:t,strings:i,values:s}),z=j(1),L=j(2),I=Symbol.for("lit-noChange"),H=Symbol.for("lit-nothing"),B=new WeakMap,D=T.createTreeWalker(T,129,null,!1),q=(t,i)=>{const s=t.length-1,e=[];let o,n=2===i?"":"",r=E;for(let i=0;i"===l[0]?(r=null!=o?o:E,a=-1):void 0===l[1]?a=-2:(a=r.lastIndex-l[2].length,h=l[1],r=void 0===l[3]?N:'"'===l[3]?O:R):r===O||r===R?r=N:r===M||r===U?r=E:(r=N,o=void 0);const c=r===N&&t[i+1].startsWith("/>")?" ":"";n+=r===E?s+C:a>=0?(e.push(h),s.slice(0,a)+"$lit$"+s.slice(a)+S+c):s+S+(-2===a?(e.push(void 0),i):c)}const h=n+(t[s]||"")+(2===i?"":"");if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return[void 0!==w?w.createHTML(h):h,e]};class J{constructor({strings:t,_$litType$:i},s){let e;this.parts=[];let o=0,n=0;const r=t.length-1,h=this.parts,[l,a]=q(t,i);if(this.el=J.createElement(l,s),D.currentNode=this.el.content,2===i){const t=this.el.content,i=t.firstChild;i.remove(),t.append(...i.childNodes)}for(;null!==(e=D.nextNode())&&h.length0){e.textContent=g?g.emptyScript:"";for(let s=0;s2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=H}get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}_$AI(t,i=this,s,e){const o=this.strings;let n=!1;if(void 0===o)t=W(this,t,i,0),n=!x(t)||t!==this._$AH&&t!==I,n&&(this._$AH=t);else{const e=t;let r,h;for(t=o[0],r=0;r{var e,o;const n=null!==(e=null==s?void 0:s.renderBefore)&&void 0!==e?e:i;let r=n._$litPart$;if(void 0===r){const t=null!==(o=null==s?void 0:s.renderBefore)&&void 0!==o?o:null;n._$litPart$=r=new F(i.insertBefore(P(),t),t,void 0,null!=s?s:{})}return r._$AI(t),r}; +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */var ot,nt;const rt=y;class ht extends y{constructor(){super(...arguments),this.renderOptions={host:this},this.et=void 0}createRenderRoot(){var t,i;const s=super.createRenderRoot();return null!==(t=(i=this.renderOptions).renderBefore)&&void 0!==t||(i.renderBefore=s.firstChild),s}update(t){const i=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this.et=et(i,this.renderRoot,this.renderOptions)}connectedCallback(){var t;super.connectedCallback(),null===(t=this.et)||void 0===t||t.setConnected(!0)}disconnectedCallback(){var t;super.disconnectedCallback(),null===(t=this.et)||void 0===t||t.setConnected(!1)}render(){return I}}ht.finalized=!0,ht._$litElement$=!0,null===(ot=globalThis.litElementHydrateSupport)||void 0===ot||ot.call(globalThis,{LitElement:ht});const lt=globalThis.litElementPolyfillSupport;null==lt||lt({LitElement:ht});const at={_$AK:(t,i,s)=>{t._$AK(i,s)},_$AL:t=>t._$AL};(null!==(nt=globalThis.litElementVersions)&&void 0!==nt?nt:globalThis.litElementVersions=[]).push("3.2.2"); +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const ut=!1;export{o as CSSResult,ht as LitElement,y as ReactiveElement,rt as UpdatingElement,at as _$LE,it as _$LH,h as adoptStyles,r as css,p as defaultConverter,l as getCompatibleStyle,z as html,ut as isServer,I as noChange,f as notEqual,H as nothing,et as render,i as supportsAdoptingStyleSheets,L as svg,n as unsafeCSS}; +//# sourceMappingURL=lit-core.min.js.map diff --git a/apps/cory/todo/script.js b/apps/cory/todo/script.js new file mode 100644 index 00000000..8074b20e --- /dev/null +++ b/apps/cory/todo/script.js @@ -0,0 +1,182 @@ +import {LitElement, html} from './lit-core.min.js'; +import * as tfrpc from '/static/tfrpc.js'; + +class TodosElement extends LitElement { + static get properties() { + return { + lists: {type: Array} + }; + } + + constructor() { + super(); + this.lists = []; + let self = this; + tfrpc.rpc.todo_get_all().then(function(lists) { + self.lists = lists; + }).catch(function(error) { + console.log(error); + }); + } + + async new_list() { + await tfrpc.rpc.todo_add('new list'); + await this.refresh(); + } + + async refresh() { + this.lists = await tfrpc.rpc.todo_get_all(); + } + + render() { + return html` +
+
+ ${this.lists.map(x => html` + + `)} +
+ +
`; + } +} + +class TodoListElement extends LitElement { + static get properties() { + return { + name: {type: String}, + items: {type: Array}, + editing: {type: Number}, + editing_name: {type: Boolean}, + }; + } + + constructor() { + super(); + this.items = []; + } + + save() { + tfrpc.rpc.todo_set(this.name, this.items).catch(function(error) { + console.log(error); + }); + } + + remove_item(item) { + let index = this.items.indexOf(item); + this.items = [].concat(this.items.slice(0, index), this.items.slice(index + 1)); + this.save(); + } + + handle_check(event, item) { + item.x = event.srcElement.checked; + this.save(); + } + + input_blur(item) { + this.save(); + this.editing = undefined; + } + + input_change(event, item) { + item.text = event.srcElement.value; + } + + input_keydown(event, item) { + if (event.key === 'Enter' || event.key === 'Escape') { + item.text = event.srcElement.value; + this.editing = undefined; + } + } + + updated() { + let edit = this.renderRoot.getElementById('edit'); + if (edit) { + edit.select(); + } + } + + render_item(item) { + let index = this.items.indexOf(item); + let self = this; + if (index === this.editing) { + return html` +
self.handle_check(x, item)}> + self.input_change(event, item)} + @keydown=${event => self.input_keydown(event, item)} + @blur=${x => self.input_blur(item)}> + self.remove_item(item)}>x
+ `; + } else { + return html` +
self.handle_check(x, item)}> + self.editing = index}>${item.text} + self.remove_item(item)} style="cursor: pointer">❎
+ `; + } + } + + add_item() { + this.items = [].concat(this.items || [], [{text: 'new item'}]); + this.editing = this.items.length - 1; + this.save(); + } + + async remove_list() { + if (confirm(`Are you sure you want to remove "${this.name}"?`)) { + await tfrpc.rpc.todo_remove(this.name); + this.dispatchEvent(new Event('change')); + } + } + + rename(new_name) { + let self = this; + return tfrpc.rpc.todo_rename(this.name, new_name).then(function() { + self.dispatchEvent(new Event('change')); + self.editing_name = false; + }).catch(function(error) { + console.log(error); + alert(error.message); + self.editing_name = false; + }); + } + + name_blur(new_name) { + this.rename(new_name); + } + + name_keydown(event, item) { + let self = this; + if (event.key == 'Enter' || event.key === 'Escape') { + let new_name = event.srcElement.value; + this.rename(new_name); + } + } + + render() { + let self = this; + let name = this.editing_name ? + html` self.name_keydown(event)} + @blur=${event => self.name_blur(event.srcElement.value)} + value=${this.name}>` : + html`

this.editing_name = true}>${this.name}

`; + return html` +
+ ${name} + ${(this.items || []).map(x => self.render_item(x))} + + +
+ `; + } +} + +customElements.define('tf-todo-list', TodoListElement); +customElements.define('tf-todos', TodosElement); \ No newline at end of file