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