forked from cory/tildefriends
		
	+todo
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4058 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
		
							
								
								
									
										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);
 | 
			
		||||
		Reference in New Issue
	
	Block a user