forked from cory/tildefriends
		
	Compare commits
	
		
			10 Commits
		
	
	
		
			fae2771645
			...
			user_setti
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 58dbf42a3a | |||
| a1f221879b | |||
| 2a928dcafc | |||
| 5474c5a101 | |||
| 4b7261fa20 | |||
| 4992ff3a2d | |||
| 88ee0aa6f0 | |||
| 392206c19e | |||
| 
						
						
							
						
						f9e95e5733
	
				 | 
					
					
						|||
| 
						
						
							
						
						1444c945de
	
				 | 
					
					
						
@@ -1,5 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
	"type": "tildefriends-app",
 | 
			
		||||
	"emoji": "🪪",
 | 
			
		||||
	"previous": "&kgukkyDk1RxgfzgMH6H/0QeDPIuwPZypLuAFax21ljk=.sha256"
 | 
			
		||||
}
 | 
			
		||||
@@ -1,93 +0,0 @@
 | 
			
		||||
import * as tfrpc from '/tfrpc.js';
 | 
			
		||||
 | 
			
		||||
tfrpc.register(async function get_private_key(id) {
 | 
			
		||||
	return bip39Words(await ssb.getPrivateKey(id));
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function create_id(id) {
 | 
			
		||||
	return await ssb.createIdentity();
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function add_id(id) {
 | 
			
		||||
	return await ssb.addIdentity(bip39Bytes(id));
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function delete_id(id) {
 | 
			
		||||
	return await ssb.deleteIdentity(id);
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function reload() {
 | 
			
		||||
	await main();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
async function main() {
 | 
			
		||||
	let ids = await ssb.getIdentities();
 | 
			
		||||
	await app.setDocument(
 | 
			
		||||
		`<body style="color: #fff">
 | 
			
		||||
		<script>const handler = {};</script>
 | 
			
		||||
		<script type="module">
 | 
			
		||||
			import * as tfrpc from '/static/tfrpc.js';
 | 
			
		||||
			handler.export_id = async function export_id(event) {
 | 
			
		||||
				let id = event.srcElement.dataset.id;
 | 
			
		||||
				let element = document.createElement('textarea');
 | 
			
		||||
				element.value = await tfrpc.rpc.get_private_key(id);
 | 
			
		||||
				element.style = 'width: 100%; read-only: true';
 | 
			
		||||
				element.readOnly = true;
 | 
			
		||||
				event.srcElement.parentElement.appendChild(element);
 | 
			
		||||
				event.srcElement.onclick = event => handler.hide_id(event, element);
 | 
			
		||||
			}
 | 
			
		||||
			handler.add_id = async function add_id(event) {
 | 
			
		||||
				let id = document.getElementById('add_id').value;
 | 
			
		||||
				try {
 | 
			
		||||
					let new_id = await tfrpc.rpc.add_id(id);
 | 
			
		||||
					alert('Successfully imported: ' + new_id);
 | 
			
		||||
					await tfrpc.rpc.reload();
 | 
			
		||||
				} catch (e) {
 | 
			
		||||
					alert('Error importing identity: ' + e);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			handler.create_id = async function create_id(event) {
 | 
			
		||||
				try {
 | 
			
		||||
					let id = await tfrpc.rpc.create_id();
 | 
			
		||||
					alert('Successfully created: ' + id);
 | 
			
		||||
					await tfrpc.rpc.reload();
 | 
			
		||||
				} catch (e) {
 | 
			
		||||
					alert('Error creating identity: ' + e);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			handler.hide_id = function hide_id(event, element) {
 | 
			
		||||
				element.parentNode.removeChild(element);
 | 
			
		||||
				event.srcElement.onclick = handler.export_id;
 | 
			
		||||
			}
 | 
			
		||||
			handler.delete_id = async function delete_id(event) {
 | 
			
		||||
				let id = event.srcElement.dataset.id;
 | 
			
		||||
				try {
 | 
			
		||||
					if (prompt('Are you sure you want to delete "' + id + '"?  It cannot be recovered without the exported phrase.\\n\\nEnter the word "DELETE" to confirm you wish to delete it.') === 'DELETE') {
 | 
			
		||||
						if (await tfrpc.rpc.delete_id(id)) {
 | 
			
		||||
							alert('Successfully deleted ID: ' + id);
 | 
			
		||||
						}
 | 
			
		||||
						await tfrpc.rpc.reload();
 | 
			
		||||
					}
 | 
			
		||||
				} catch (e) {
 | 
			
		||||
					alert('Error deleting ID: ' + e);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		</script>
 | 
			
		||||
		<h1>SSB Identity Management</h1>
 | 
			
		||||
		<h2>Create a new identity</h2>
 | 
			
		||||
		<button id="create_id" onclick="handler.create_id()">Create Identity</button>
 | 
			
		||||
		<h2>Import an SSB Identity from 12 BIP39 English Words</h2>
 | 
			
		||||
		<textarea id="add_id" style="width: 100%" rows="4"></textarea><button id="add" onclick="handler.add_id(event)">Import Identity</button>
 | 
			
		||||
		<h2>Identities</h2>
 | 
			
		||||
		<ul>` +
 | 
			
		||||
			ids
 | 
			
		||||
				.map(
 | 
			
		||||
					(id) => `<li>
 | 
			
		||||
			<button onclick="handler.export_id(event)" data-id="${id}">Export Identity</button>
 | 
			
		||||
			<button onclick="handler.delete_id(event)" data-id="${id}">Delete Identity</button>
 | 
			
		||||
			${id}
 | 
			
		||||
		</li>`
 | 
			
		||||
				)
 | 
			
		||||
				.join('\n') +
 | 
			
		||||
			`	</ul>
 | 
			
		||||
	</body>`
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
main();
 | 
			
		||||
							
								
								
									
										1
									
								
								apps/user_settings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								apps/user_settings.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{"type": "tildefriends-app", "emoji": "⚙️"}
 | 
			
		||||
							
								
								
									
										60
									
								
								apps/user_settings/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								apps/user_settings/app.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
import * as tfrpc from '/tfrpc.js';
 | 
			
		||||
 | 
			
		||||
tfrpc.register(async function getIdentities() {
 | 
			
		||||
	return ssb.getIdentities();
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function createID(id) {
 | 
			
		||||
	return await ssb.createIdentity();
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function getPrivateKey(id) {
 | 
			
		||||
	return bip39Words(await ssb.getPrivateKey(id));
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function addID(id) {
 | 
			
		||||
	return await ssb.addIdentity(bip39Bytes(id));
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function deleteID(id) {
 | 
			
		||||
	return await ssb.deleteIdentity(id);
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function getThemes() {
 | 
			
		||||
	// TODO
 | 
			
		||||
	return ['solarized', 'gruvbox', 'light'];
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function getTheme() {
 | 
			
		||||
	// TODO
 | 
			
		||||
	return 'gruvbox';
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function setTheme() {
 | 
			
		||||
	// TODO
 | 
			
		||||
	console.warn('setTheme called - not implemented');
 | 
			
		||||
	return null;
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function reload() {
 | 
			
		||||
	await main();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
async function main() {
 | 
			
		||||
	// Get body.html
 | 
			
		||||
	const body = utf8Decode(await getFile('body.html'));
 | 
			
		||||
 | 
			
		||||
	// Build the document
 | 
			
		||||
	const document = `
 | 
			
		||||
	<!DOCTYPE html>
 | 
			
		||||
	<html>
 | 
			
		||||
		<head>
 | 
			
		||||
			<link rel="stylesheet" href="/static/tildefriends-v1.css"/>
 | 
			
		||||
			<script src="tf-theme-picker.js" type="module"></script>
 | 
			
		||||
			<script src="tf-password-form.js" type="module"></script>
 | 
			
		||||
			<script src="tf-delete-account-btn.js" type="module"></script>
 | 
			
		||||
			<script src="tf-identity-manager.js" type="module"></script>
 | 
			
		||||
		</head>
 | 
			
		||||
 | 
			
		||||
		<body class="flex-column">
 | 
			
		||||
			${body}
 | 
			
		||||
		</body>
 | 
			
		||||
	</html>`;
 | 
			
		||||
 | 
			
		||||
	// Send it to the browser
 | 
			
		||||
	app.setDocument(document);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
main();
 | 
			
		||||
							
								
								
									
										20
									
								
								apps/user_settings/body.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								apps/user_settings/body.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
<h1>Your settings</h1>
 | 
			
		||||
 | 
			
		||||
<div class="box flex-column">
 | 
			
		||||
	<h2>Appearance</h2>
 | 
			
		||||
 | 
			
		||||
	<tf-theme-picker></tf-theme-picker>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div class="box flex-column">
 | 
			
		||||
	<h2>Danger Zone</h2>
 | 
			
		||||
 | 
			
		||||
	<h3>Manage your identities</h3>
 | 
			
		||||
	<tf-identity-manager></tf-identity-manager>
 | 
			
		||||
 | 
			
		||||
	<h3>Change my password</h3>
 | 
			
		||||
	<tf-password-form></tf-password-form>
 | 
			
		||||
 | 
			
		||||
	<h3>Delete your account</h3>
 | 
			
		||||
	<tf-delete-account-btn></tf-delete-account-btn>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										120
									
								
								apps/user_settings/lit-all.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								apps/user_settings/lit-all.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								apps/user_settings/lit-all.min.js.map
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								apps/user_settings/lit-all.min.js.map
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										36
									
								
								apps/user_settings/tf-delete-account-btn.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								apps/user_settings/tf-delete-account-btn.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
import {LitElement, html} from './lit-all.min.js';
 | 
			
		||||
import * as tfrpc from '/static/tfrpc.js';
 | 
			
		||||
 | 
			
		||||
class TfDeleteAccountButtonElement extends LitElement {
 | 
			
		||||
	static get properties() {
 | 
			
		||||
		return {};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	deleteAccount() {
 | 
			
		||||
		const res = confirm(
 | 
			
		||||
			'Are you really sure you want to delete your account ?'
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		if (!res) return;
 | 
			
		||||
 | 
			
		||||
		console.warn('TODO');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		return html`
 | 
			
		||||
			<link rel="stylesheet" href="/static/tildefriends-v1.css" />
 | 
			
		||||
 | 
			
		||||
			<span>This action is irreversible !</span>
 | 
			
		||||
 | 
			
		||||
			<button class="red" @click=${this.deleteAccount}>
 | 
			
		||||
				[Not implemented] Delete my Tilde Friends account
 | 
			
		||||
			</button>
 | 
			
		||||
		`;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
customElements.define('tf-delete-account-btn', TfDeleteAccountButtonElement);
 | 
			
		||||
							
								
								
									
										118
									
								
								apps/user_settings/tf-identity-manager.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								apps/user_settings/tf-identity-manager.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
			
		||||
import {LitElement, html} from './lit-all.min.js';
 | 
			
		||||
import * as tfrpc from '/static/tfrpc.js';
 | 
			
		||||
 | 
			
		||||
class TfIdentityManagerElement extends LitElement {
 | 
			
		||||
	static get properties() {
 | 
			
		||||
		return {
 | 
			
		||||
			ids: {type: Array},
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super();
 | 
			
		||||
		this.ids = [];
 | 
			
		||||
		this.load();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async load() {
 | 
			
		||||
		this.ids = await tfrpc.rpc.getIdentities();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async createIdentity() {
 | 
			
		||||
		try {
 | 
			
		||||
			const id = await tfrpc.rpc.createID();
 | 
			
		||||
			alert('Successfully created: ' + id);
 | 
			
		||||
			await tfrpc.rpc.reload();
 | 
			
		||||
		} catch (err) {
 | 
			
		||||
			alert('Error creating identity: ' + err);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async importIdentity() {
 | 
			
		||||
		const words = this.renderRoot?.querySelector('#import-id-textarea').value;
 | 
			
		||||
		if (!words) return;
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			const newID = await tfrpc.rpc.addID(words);
 | 
			
		||||
 | 
			
		||||
			if (newID) alert('Successfully imported a new identity.');
 | 
			
		||||
			else alert('This identity already exists or is invalid.');
 | 
			
		||||
			await tfrpc.rpc.reload();
 | 
			
		||||
		} catch (err) {
 | 
			
		||||
			alert('Error importing identity: ' + err);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async exportIdentity(id) {
 | 
			
		||||
		alert(
 | 
			
		||||
			'Your private key is:\n' +
 | 
			
		||||
				(await tfrpc.rpc.getPrivateKey(id)) +
 | 
			
		||||
				'\nDo not share it with anyone!'
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async deleteIdentity(id) {
 | 
			
		||||
		try {
 | 
			
		||||
			if (
 | 
			
		||||
				prompt(
 | 
			
		||||
					'Are you sure you want to delete "' +
 | 
			
		||||
						id +
 | 
			
		||||
						'"?  It cannot be recovered without the exported phrase.\\n\\nEnter the word "DELETE" to confirm you wish to delete it.'
 | 
			
		||||
				) === 'DELETE'
 | 
			
		||||
			) {
 | 
			
		||||
				if (await tfrpc.rpc.deleteID(id)) {
 | 
			
		||||
					alert('Successfully deleted ID: ' + id);
 | 
			
		||||
				}
 | 
			
		||||
				await tfrpc.rpc.reload();
 | 
			
		||||
			}
 | 
			
		||||
		} catch (e) {
 | 
			
		||||
			alert('Error deleting ID: ' + e);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		return html` <link rel="stylesheet" href="/static/tildefriends-v1.css" />
 | 
			
		||||
			<style>
 | 
			
		||||
				.id-span {
 | 
			
		||||
					font-family: monospace;
 | 
			
		||||
					margin-left: 8px;
 | 
			
		||||
				}
 | 
			
		||||
			</style>
 | 
			
		||||
 | 
			
		||||
			<h4>Create a new identity</h4>
 | 
			
		||||
			<button id="create-id" class="green" @click=${this.createIdentity}>
 | 
			
		||||
				Create Identity
 | 
			
		||||
			</button>
 | 
			
		||||
 | 
			
		||||
			<h4>Import an SSB Identity from 12 BIP39 English Words</h4>
 | 
			
		||||
			<textarea id="import-id-textarea" style="width: 100%" rows="4"></textarea>
 | 
			
		||||
			<button class="green" @click=${this.importIdentity}>
 | 
			
		||||
				Import Identity
 | 
			
		||||
			</button>
 | 
			
		||||
 | 
			
		||||
			<h4>Warning !</h4>
 | 
			
		||||
			<strong
 | 
			
		||||
				>Anybody that knows your private key can gain total access over your
 | 
			
		||||
				account.</strong
 | 
			
		||||
			>
 | 
			
		||||
			<br /><br />
 | 
			
		||||
			Tilde Friends' contributors will never ask you for your private key !
 | 
			
		||||
 | 
			
		||||
			<ul>
 | 
			
		||||
				${this.ids.map(
 | 
			
		||||
					(id) =>
 | 
			
		||||
						html` <li>
 | 
			
		||||
							<button class="blue" @click=${() => this.exportIdentity(id)}>
 | 
			
		||||
								Export Identity
 | 
			
		||||
							</button>
 | 
			
		||||
							<button class="red" @click=${() => this.deleteIdentity(id)}>
 | 
			
		||||
								Delete Identity
 | 
			
		||||
							</button>
 | 
			
		||||
							<span class="id-span">${id}</span>
 | 
			
		||||
						</li>`
 | 
			
		||||
				)}
 | 
			
		||||
			</ul>`;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
customElements.define('tf-identity-manager', TfIdentityManagerElement);
 | 
			
		||||
							
								
								
									
										82
									
								
								apps/user_settings/tf-password-form.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								apps/user_settings/tf-password-form.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
import {LitElement, html} from './lit-all.min.js';
 | 
			
		||||
import * as tfrpc from '/static/tfrpc.js';
 | 
			
		||||
 | 
			
		||||
class TfPasswordFormElement extends LitElement {
 | 
			
		||||
	static get properties() {
 | 
			
		||||
		return {
 | 
			
		||||
			//selected: {type: String},
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Checks a password against different requirements
 | 
			
		||||
	 * @param {string} password the password to validate
 | 
			
		||||
	 * @returns
 | 
			
		||||
	 */
 | 
			
		||||
	validatePassword(password) {
 | 
			
		||||
		// TODO(tasiaiso)
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	submitPassword() {
 | 
			
		||||
		const currentPwd = this.shadowRoot.getElementById('current').value;
 | 
			
		||||
		const newPwd = this.shadowRoot.getElementById('new').value;
 | 
			
		||||
		const repeatPwd = this.shadowRoot.getElementById('Repeat').value;
 | 
			
		||||
 | 
			
		||||
		if (!(newPwd === repeatPwd)) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// TODO
 | 
			
		||||
		// tfrpc.changePassword()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		return html`
 | 
			
		||||
			<link rel="stylesheet" href="/static/tildefriends-v1.css" />
 | 
			
		||||
 | 
			
		||||
			<style>
 | 
			
		||||
				.grid {
 | 
			
		||||
					display: grid;
 | 
			
		||||
					grid-template-columns: auto auto;
 | 
			
		||||
				}
 | 
			
		||||
			</style>
 | 
			
		||||
 | 
			
		||||
			<div class="grid">
 | 
			
		||||
				<label for="current">Current password:</label>
 | 
			
		||||
				<input
 | 
			
		||||
					type="password"
 | 
			
		||||
					id="current"
 | 
			
		||||
					name="current"
 | 
			
		||||
					autocomplete="current-password"
 | 
			
		||||
				/>
 | 
			
		||||
 | 
			
		||||
				<label for="new">Enter new password:</label>
 | 
			
		||||
				<input
 | 
			
		||||
					type="password"
 | 
			
		||||
					id="new"
 | 
			
		||||
					name="new"
 | 
			
		||||
					autocomplete="new-password"
 | 
			
		||||
				/>
 | 
			
		||||
 | 
			
		||||
				<label for="repeat">Repeat new password:</label>
 | 
			
		||||
				<input
 | 
			
		||||
					type="password"
 | 
			
		||||
					id="repeat"
 | 
			
		||||
					name="repeat"
 | 
			
		||||
					autocomplete="new-password"
 | 
			
		||||
				/>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<button @click=${this.submitPassword} class="red">
 | 
			
		||||
				[Not implemented] Change my password
 | 
			
		||||
			</button>
 | 
			
		||||
		`;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
customElements.define('tf-password-form', TfPasswordFormElement);
 | 
			
		||||
							
								
								
									
										51
									
								
								apps/user_settings/tf-theme-picker.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								apps/user_settings/tf-theme-picker.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
import {LitElement, html, nothing} from './lit-all.min.js';
 | 
			
		||||
import * as tfrpc from '/static/tfrpc.js';
 | 
			
		||||
 | 
			
		||||
class TfThemePickerElement extends LitElement {
 | 
			
		||||
	static get properties() {
 | 
			
		||||
		return {
 | 
			
		||||
			selected: {type: String},
 | 
			
		||||
			themes: {type: Array},
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super();
 | 
			
		||||
		this.load();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async load() {
 | 
			
		||||
		this.themes = await tfrpc.rpc.getThemes();
 | 
			
		||||
		this.selected = await tfrpc.rpc.getTheme();
 | 
			
		||||
 | 
			
		||||
		let select = this.renderRoot?.querySelector('#theme-select');
 | 
			
		||||
		select.value = this.selected;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	changed(event) {
 | 
			
		||||
		this.selected = event.srcElement.value;
 | 
			
		||||
		console.log('selected theme', this.selected);
 | 
			
		||||
		// TODO
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		return html`
 | 
			
		||||
			<link rel="stylesheet" href="/static/tildefriends-v1.css" />
 | 
			
		||||
 | 
			
		||||
			<label for="theme">[Not implemented] Choose your theme:</label>
 | 
			
		||||
 | 
			
		||||
			<select
 | 
			
		||||
				name="theme"
 | 
			
		||||
				id="theme-select"
 | 
			
		||||
				?hidden=${!this.themes?.length}
 | 
			
		||||
				@change=${this.changed}
 | 
			
		||||
			>
 | 
			
		||||
				${(this.themes ?? []).map(
 | 
			
		||||
					(name) => html`<option value=${name}>${name}</option>`
 | 
			
		||||
				)}
 | 
			
		||||
			</select>
 | 
			
		||||
		`;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
customElements.define('tf-theme-picker', TfThemePickerElement);
 | 
			
		||||
							
								
								
									
										114
									
								
								core/tildefriends-v1.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								core/tildefriends-v1.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Tilde Friends core stylesheet
 | 
			
		||||
 *
 | 
			
		||||
 * This Software is an external library that is part of
 | 
			
		||||
 * Tilde Friends and is shared under the MIT license.
 | 
			
		||||
 *
 | 
			
		||||
 * Inject this file in your app at tildefriends.css
 | 
			
		||||
 * and use this tag to import it:
 | 
			
		||||
 * <link rel="stylesheet" href="/static/tildefriends-v1.css"/>
 | 
			
		||||
 *
 | 
			
		||||
 * v1.0 / 2024 M03 21
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
	color: white;
 | 
			
		||||
	font-family: sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button,
 | 
			
		||||
.button,
 | 
			
		||||
input[type='button'],
 | 
			
		||||
input[type='submit'],
 | 
			
		||||
select {
 | 
			
		||||
	border: none;
 | 
			
		||||
	border-radius: 8px;
 | 
			
		||||
	padding: 8px 12px;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	text-decoration: none;
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
	margin: 4px;
 | 
			
		||||
 | 
			
		||||
	&.red {
 | 
			
		||||
		background-color: #bd1e24;
 | 
			
		||||
		color: white;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.green {
 | 
			
		||||
		background-color: #18922d;
 | 
			
		||||
		color: white;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.blue {
 | 
			
		||||
		background-color: #0067a7;
 | 
			
		||||
		color: white;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.yellow {
 | 
			
		||||
		background-color: #ee9600;
 | 
			
		||||
		color: black;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&:hover {
 | 
			
		||||
		filter: brightness(0.75);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:link {
 | 
			
		||||
	color: #268bd2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:visited {
 | 
			
		||||
	color: #6c71c4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:hover {
 | 
			
		||||
	color: #859900;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:active {
 | 
			
		||||
	color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table {
 | 
			
		||||
	border-collapse: collapse;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
td,
 | 
			
		||||
th {
 | 
			
		||||
	border: 1px solid #ffffff40;
 | 
			
		||||
	text-align: left;
 | 
			
		||||
	padding: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tr:nth-child(even) {
 | 
			
		||||
	background-color: #ffffff20;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.flex {
 | 
			
		||||
	display: flex;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.flex-column {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.flex-row {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	flex-direction: row;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.inline-flex-row {
 | 
			
		||||
	display: inline-flex;
 | 
			
		||||
	flex-direction: row;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.box {
 | 
			
		||||
	background-color: #00000020;
 | 
			
		||||
	border: 1px solid grey;
 | 
			
		||||
	border-radius: 8px;
 | 
			
		||||
	padding: 16px;
 | 
			
		||||
	margin: 4px;
 | 
			
		||||
}
 | 
			
		||||
@@ -644,6 +644,7 @@ static void _httpd_endpoint_static(tf_http_request_t* request)
 | 
			
		||||
		"style.css",
 | 
			
		||||
		"tfrpc.js",
 | 
			
		||||
		"w3.css",
 | 
			
		||||
		"tildefriends-v1.css"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const char* k_map[][2] = {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user