+todo
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4058 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
parent
d48b8b0ae1
commit
acc14f7318
1
apps/cory/todo.json
Normal file
1
apps/cory/todo.json
Normal file
@ -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"}}
|
82
apps/cory/todo/app.js
Normal file
82
apps/cory/todo/app.js
Normal file
@ -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();
|
11
apps/cory/todo/index.html
Normal file
11
apps/cory/todo/index.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>TODO</title>
|
||||
</head>
|
||||
<body style="color: #fff">
|
||||
<h1>TODO</h1>
|
||||
<tf-todos></tf-todos>
|
||||
</body>
|
||||
<script src="script.js" type="module"></script>
|
||||
</html>
|
29
apps/cory/todo/lit-core.min.js
vendored
Normal file
29
apps/cory/todo/lit-core.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
182
apps/cory/todo/script.js
Normal file
182
apps/cory/todo/script.js
Normal file
@ -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`
|
||||
<div>
|
||||
<div style="display: flex">
|
||||
${this.lists.map(x => html`
|
||||
<tf-todo-list name=${x.name} .items=${x.items} @change=${this.refresh}></tf-todo-list>
|
||||
`)}
|
||||
</div>
|
||||
<input type="button" @click=${this.new_list} value="+ List"></input>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
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`
|
||||
<div><input type="checkbox" ?checked=${item.x} @change=${x => self.handle_check(x, item)}></input>
|
||||
<input
|
||||
id="edit"
|
||||
type="text"
|
||||
value=${item.text}
|
||||
@change=${event => self.input_change(event, item)}
|
||||
@keydown=${event => self.input_keydown(event, item)}
|
||||
@blur=${x => self.input_blur(item)}></input>
|
||||
<span @click=${x => self.remove_item(item)}>x</span></div>
|
||||
`;
|
||||
} else {
|
||||
return html`
|
||||
<div><input type="checkbox" ?checked=${item.x} @change=${x => self.handle_check(x, item)}></input>
|
||||
<span @click=${x => self.editing = index}>${item.text}</span>
|
||||
<span @click=${x => self.remove_item(item)} style="cursor: pointer">❎</span></div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
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`<input
|
||||
type="text"
|
||||
id="edit"
|
||||
@keydown=${event => self.name_keydown(event)}
|
||||
@blur=${event => self.name_blur(event.srcElement.value)}
|
||||
value=${this.name}></input>` :
|
||||
html`<h2 @click=${x => this.editing_name = true}>${this.name}</h2>`;
|
||||
return html`
|
||||
<div style="border: 3px solid black; padding: 8px; margin: 8px; border-radius: 8px; background-color: #444">
|
||||
${name}
|
||||
${(this.items || []).map(x => self.render_item(x))}
|
||||
<button @click=${self.add_item}>+ Item</button>
|
||||
<button @click=${self.remove_list}>- List</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('tf-todo-list', TodoListElement);
|
||||
customElements.define('tf-todos', TodosElement);
|
Loading…
Reference in New Issue
Block a user