Compare commits

...

5 Commits

13 changed files with 512 additions and 0 deletions

4
apps/user_settings.json Normal file
View File

@ -0,0 +1,4 @@
{
"type": "tildefriends-app",
"emoji": "⚙️"
}

46
apps/user_settings/app.js Normal file
View File

@ -0,0 +1,46 @@
import * as tfrpc from '/tfrpc.js';
tfrpc.register(async function getIdentities() {
return ssb.getIdentities();
});
tfrpc.register(async function getPrivateKey(id) {
return bip39Words(await ssb.getPrivateKey(id));
});
tfrpc.register(async function getThemes() {
// TODO
return ['solarized', 'gruvbox', 'light'];
});
tfrpc.register(async function setTheme() {
// TODO
console.warn("setTheme called - not implemented")
return null;
});
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-latest.css"/>
<link rel="stylesheet" href="style.css"/>
<script src="script.js" type="module"></script>
<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();

View 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>Change my password</h3>
<tf-password-form></tf-password-form>
<h3>Manage your identities</h3>
<tf-identity-manager></tf-identity-manager>
<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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
/* */

View File

@ -0,0 +1 @@
/* */

View 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-latest.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);

View File

@ -0,0 +1,61 @@
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 exportIdentity(id) {
alert('Your private key is:\n' + (await tfrpc.rpc.getPrivateKey(id)));
}
render() {
return html`
<link rel="stylesheet" href="/static/tildefriends-latest.css"/>
<style>
.id-span {
font-family: monospace;
margin-left: 8px;
}
</style>
<h4>Create a new identity</h4>
<button id="create-id" class="btn-green">[Not implemented] Create Identity</button>
<h4>Import an SSB Identity from 12 BIP39 English Words</h4>
<textarea id="add-id" style="width: 100%" rows="4"></textarea>
<button class="green">[Not implemented] Import Identity</button>
<h4>Warning !</h4>
<strong>Anybody that has access to 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">[Not implemented] Delete Identity</button>
<span class="id-span">${id}</span>
</li>`
)}
</ul>`;
}
}
customElements.define('tf-identity-manager', TfIdentityManagerElement);

View File

@ -0,0 +1,68 @@
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-latest.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);

View File

@ -0,0 +1,40 @@
import {LitElement, html} 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();
}
changed(event) {
this.selected = event.srcElement.value;
console.log('selected theme', this.selected);
// TODO
}
render() {
return html`
<link rel="stylesheet" href="/static/tildefriends-latest.css"/>
<label for="theme">[Not implemented] Choose your theme:</label>
<select name="theme" ?hidden=${!this.themes?.length} @change=${this.changed}>
${(this.themes ?? []).map((id) => html`<option value=${id}>${id}</option>`)}
</select>
`;
}
}
customElements.define('tf-theme-picker', TfThemePickerElement);

113
core/tildefriends-v1.css Normal file
View File

@ -0,0 +1,113 @@
/*
* 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.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;
}

View File

@ -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] = {