Broken and needs a more ortodox restructuring, saving WIP

This commit is contained in:
2024-01-01 19:11:18 +01:00
parent ea4c12bf8d
commit 6725c71aa9

View File

@@ -1,19 +1,23 @@
<!DOCTYPE html> <!DOCTYPE html>
<script src="./SpaccDotWeb/SpaccDotWeb.js" module="SpaccDotWeb"></script> <script src="./SpaccDotWeb/SpaccDotWeb.js" module="SpaccDotWeb"></script>
<script src="//SpaccInc.gitlab.io/SpaccDotWeb/SpaccDotWeb.js" module="SpaccDotWeb"></script> <script src="//SpaccInc.gitlab.io/SpaccDotWeb/SpaccDotWeb.js" module="SpaccDotWeb"></script>
<script src="https://googlechrome.github.io/dialog-polyfill/dist/dialog-polyfill.js"></script>
<script module="Meta">({ <script module="Meta">({
Name: "🃏️ [Matrix] Sticker Helper", Name: "🃏️ [Matrix] Sticker Helper",
})</script> })</script>
<script module="Main" type="module"> <script module="Main" type="module">
import { html, render } from 'https://esm.sh/htm/preact/standalone';
//import axios from 'https://cdn.skypack.dev/axios';
const Spacc = SpaccDotWeb.AppInit(); const Spacc = SpaccDotWeb.AppInit();
</script>
<script module="App" type="module">
import { html, render } from 'https://esm.sh/htm/preact/standalone';
import axios from 'https://cdn.skypack.dev/axios';
const State = {}; const State = {};
const Defaults = { const Defaults = {
stickerSelectorUrl: "https://maunium.net/stickers-demo/", //"https://hub.octt.eu.org/Matrix/MauniumStickerPickerFork/", stickerSelectorUrl: "https://maunium.net/stickers-demo/",
appIdentity: "org.eu.octt.MatrixStickerHelper", appIdentity: "org.eu.octt.MatrixStickerHelper",
appInterface: "v1", appInterface: "v1",
}; };
@@ -37,6 +41,7 @@
dataIndex=${action.dataIndex} dataIndex=${action.dataIndex}
onclick=${action.onclick} onclick=${action.onclick}
disabled=${action.disabled} disabled=${action.disabled}
name=${action.name}
> >
${action.label} ${action.label}
</button> </button>
@@ -95,14 +100,6 @@
} }
function ClickImg (props) { function ClickImg (props) {
function deleteClick (that) {
const button = that.base.querySelector('button[name="delete"]');
button.innerHTML = '❌️ Confirm delete?';
button.onclick = function(){
}
}
return html`<div class="ClickImg"> return html`<div class="ClickImg">
<img <img
src=${props.src} src=${props.src}
@@ -118,19 +115,39 @@
} else { } else {
this.base.dataset.opened = '1'; this.base.dataset.opened = '1';
} }
this.base.querySelector('button[name="delete"]').innerHTML = '❌️';
}} }}
/> />
<button <${SecureButton}
name="delete" name="delete"
onclick=${() => deleteClick(this)} modalText='❌️ Confirm delete?'
onclick=${() => {
alert(1)
}}
> >
❌️ ❌️
</button> </${SecureButton}>
</div>` </div>`
} }
const Request = (url, options) => { function ShowModal (text, action, question) {
document.querySelector('#Modal > p').innerHTML = text;
document.querySelector('#Modal > button[name="confirm"]').onclick = action;
document.querySelector('#Modal').showModal();
}
function SecureButton (props) {
return html`
<button
class="SecureButton ${props.class}"
name=${props.name}
onclick=${() => ShowModal('❌️ Confirm delete?', props.onclick)}
>
${props.children}
</button>
`
}
function Request (url, options) {
const http = new XMLHttpRequest(); const http = new XMLHttpRequest();
http.onreadystatechange = function() { http.onreadystatechange = function() {
if (this.readyState === 4) { if (this.readyState === 4) {
@@ -144,65 +161,37 @@
http.send(options.body); http.send(options.body);
} }
const TryMatrixLoginAndSaveAndUse = (loginData) => { async function TryMatrixLoginAndSaveAndUse (loginData) {
/*fetch(`${loginData.homeserver}/_matrix/client/v3/login`, { try {
method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ const response = await axios.post(`${loginData.homeserver}/_matrix/client/v3/login`, {
type: "m.login.password", type: "m.login.password",
identifier: { identifier: {
type: "m.id.user", type: "m.id.user",
user: loginData.username, user: loginData.username,
}, },
password: loginData.password, password: loginData.password,
}) });
}).then(response => { State.account = {
if (response.ok) { homeserver: loginData.homeserver,
Config.accounts.push({ username: loginData.username,
homeserver: loginData.homeserver, token: response.data.access_token,
username: loginData.username, };
token: body.token, Config.accounts.push(State.account);
}); Config.Save();
Config.Save(); Render(CollectionScreen);
Render(); } catch(err) {
} else { alert(`Error trying to get a session token: ${err.response.request.response}`);
alert(`Error: ${response.json()}`); document.querySelector('#AccountLoginForm input[name="loginSave"]').disabled = false;
} }
})*/
Request(`${loginData.homeserver}/_matrix/client/v3/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
type: "m.login.password",
identifier: {
type: "m.id.user",
user: loginData.username,
},
password: loginData.password,
}),
callback: function(http) {
if (http.status === 200) {
State.account = {
homeserver: loginData.homeserver,
username: loginData.username,
token: JSON.parse(http.responseText).access_token,
};
Config.accounts.push(State.account);
Config.Save();
Render(CollectionScreen);
} else {
alert(`Error: ${http.responseText}`);
document.querySelector('#AccountLoginForm input[name="loginSave"]').disabled = false;
}
},
});
} }
const DataWidgetSaveRequest = (configUrl) => { async function DataWidgetSaveRequest (configUrl) {
document.querySelector('input[name="reinit"]').disabled = true; document.querySelector('input[name="reinit"]').disabled = true;
document.querySelector('button[name="commit"]').disabled = true; document.querySelector('button[name="commit"]').disabled = true;
State.dataWidgets.stickerpicker = { State.dataWidgets.stickerpicker = {
content: { content: {
type: "m.stickerpicker", type: "m.stickerpicker",
url: `${document.querySelector('input[name="sticker selector url"]').value}?&config=${configUrl}&theme=$theme`, url: `${document.querySelector('input[name="sticker selector url"]').value}?&config=${configUrl || ''}&theme=$theme`,
name: "Stickerpicker", name: "Stickerpicker",
creatorUserId: `${GetMatrixUserTag(State.account)}`, creatorUserId: `${GetMatrixUserTag(State.account)}`,
managedBy: [ managedBy: [
@@ -214,21 +203,17 @@
type: "m.widget", type: "m.widget",
id: "stickerpicker", id: "stickerpicker",
}; };
Request(`${State.account.homeserver}/_matrix/client/v3/user/${GetMatrixUserTag(State.account)}/account_data/m.widgets`, { try {
method: 'PUT', const response = await axios.put(`${State.account.homeserver}/_matrix/client/v3/user/${GetMatrixUserTag(State.account)}/account_data/m.widgets`, State.dataWidgets, {
headers: { "Content-Type": "application/json", "authorization": `Bearer ${State.account.token}` }, headers: { "Authorization": `Bearer ${State.account.token}` },
body: JSON.stringify(State.dataWidgets), });
callback: function(http) { console.log(response.data);
const data = JSON.parse(http.responseText); Render(CollectionScreen);
if (http.status === 200) { } catch(err) {
console.log(data); alert(`Error updating account widget data: ${err.response.request.response}`);
} else { document.querySelector('button[name="commit"]').disabled = false;
alert(`Error: ${http.responseText}`); }
document.querySelector('button[name="commit"]').disabled = false; document.querySelector('input[name="reinit"]').disabled = false;
}
document.querySelector('input[name="reinit"]').disabled = false;
},
});
} }
const GetMatrixUserTag = (account) => `@${account.username}:${account.homeserver.split('://')[1]}` const GetMatrixUserTag = (account) => `@${account.username}:${account.homeserver.split('://')[1]}`
@@ -238,7 +223,7 @@
for (let i=0; i<Config.accounts.length; i++) { for (let i=0; i<Config.accounts.length; i++) {
let account = Config.accounts[i]; let account = Config.accounts[i];
accountsActions.push({ accountsActions.push({
label: `🐱️ ${GetMatrixUserTag(account)}`, label: `🐱️ ${GetMatrixUserTag(account)}`,
onclick: () => { onclick: () => {
@@ -300,26 +285,25 @@
} }
function CollectionList (props) { function CollectionList (props) {
return html`<Fragment> let packElems = [];
for (const pack of (props.extras?.dataPacks?.packs || [])) {
packElems.push(html`
<img
src="${props.extras?.dataPacks?.homeserver_url}/_matrix/media/r0/thumbnail/${pack.stickers[0].info.thumbnail_url.split('mxc://')[1]}?&height=128&width=128&method=scale"
onclick=${null}
/>
`);
}
return html`<${CollectionViewMenu} optionsOpen=${false}>
<div id="CollectionList"><div> <div id="CollectionList"><div>
<img src="https://http.cat/100"/>
<img src="https://http.cat/101"/>
<img src="https://http.cat/100"/>
<img src="https://http.cat/101"/>
<img src="https://http.cat/100"/>
<img src="https://http.cat/101"/>
<img src="https://http.cat/100"/>
<img src="https://http.cat/101"/>
<img src="https://http.cat/100"/>
<img src="https://http.cat/101"/>
<img src="https://http.cat/100"/>
<img src="https://http.cat/101"/>
<button style=${{ verticalAlign: 'top', height: '64px' }}> <button style=${{ verticalAlign: 'top', height: '64px' }}>
Add new Create new pack
</button> </button>
${packElems}
</div></div> </div></div>
<${CollectionGrid}/> <${CollectionGrid}/>
</Fragment>` </${CollectionViewMenu}>`
} }
function CollectionGrid (props) { function CollectionGrid (props) {
@@ -333,12 +317,13 @@
<${ClickImg} src="https://http.cat/202"/> <${ClickImg} src="https://http.cat/202"/>
<${ClickImg} src="https://http.cat/203"/> <${ClickImg} src="https://http.cat/203"/>
<${ClickImg} src="https://http.cat/204"/> <${ClickImg} src="https://http.cat/204"/>
<${ClickImg} src="https://http.cat/205"/>
<button <button
style=${{ verticalAlign: 'top', width: '128px', height: '128px' }} style=${{ verticalAlign: 'top', width: '128px', height: '128px' }}
onclick=${() => this.base.querySelector('button > input[type="file"]').click()} onclick=${() => this.base.querySelector('button > input[type="file"]').click()}
> >
Add new Upload new
<br/><br/>
<small>(Only image files supported for now)</small>
<input <input
style=${{ display: 'none' }} style=${{ display: 'none' }}
type="file" type="file"
@@ -353,28 +338,80 @@
</div>` </div>`
} }
async function CollectionScreen (props) {
try {
const response = await axios.get(`${State.account.homeserver}/_matrix/client/v3/user/${GetMatrixUserTag(State.account)}/account_data/m.widgets`, {
headers: { "Authorization": `Bearer ${State.account.token}` },
});
Render(CollectionFetchData, response.data);
} catch(err) {
if (err.response.data.errcode === 'M_NOT_FOUND') {
Render(CollectionFetchData);
} else {
alert(`Error fetching account widget data: ${err.response.request.response}`);
Render();
}
}
}
async function CollectionFetchData (props) {
const packsUrl = (new URLSearchParams(props.dataWidgets?.stickerpicker?.content?.url?.split('?')[1])).get('config');
if (packsUrl) {
try {
const response = await axios.get(packsUrl).data;
Render(CollectionView, {
dataWidgets: props.extras,
dataPacks: response.data,
});
} catch(err) {
alert(`Error: ${err.response.request.response}`);
Render();
}
} else {
Render(CollectionView, {
dataWidgets: props.extras,
});
}
}
function CollectionView (props) { function CollectionView (props) {
const isManaged = props.extra?.stickerpicker?.content?.managedBy?.includes(`${Defaults.appIdentity}/${Defaults.appInterface}`); const isManaged = props.extras?.dataWidgets?.stickerpicker?.content?.managedBy?.includes(`${Defaults.appIdentity}/${Defaults.appInterface}`);
State.dataWidgets = props.extra; State.dataWidgets = props.extras?.dataWidgets;
if (!State.dataWidgets) State.dataWidgets = {}; if (!State.dataWidgets) State.dataWidgets = {};
new URLSearchParams(State.dataWidgets?.stickerpicker?.content?.url?.split('?')?[1]).get('config'); return html`<${CollectionViewMenu} optionsOpen=${!props.dataWidgets || !props.isManaged}>
${!props.extras?.dataWidgets && html`<p>
Your account must first be set-up to handle stickers.
</p>`}
${props.extras?.dataWidgets && !isManaged && html`
<p>
Your account is set-up with a sticker picker, but it's not marked as being managed by this app.
This could mean that you are currently using an incompatible sticker picker.
You can try to continue anyway if you think it should work, otherwise you should reinitialize sticker data.
</p>
<p>
<button
class="ActionButton"
onclick=${() => {
Render(CollectionList, props.extras?.dataPacks)
}}
>
⏭️ Continue
</button>
</p>
`}
${isManaged && Render(CollectionList, props.extras?.dataPacks)}
</${CollectionViewMenu}>`
}
return html`<Fragment> function CollectionViewMenu (props) {
return html`<Fragment class="CollectionViewMenu">
<${ActionForm} actionEntries=${[ <${ActionForm} actionEntries=${[
{ label: '🔙️ Go Back', onclick: () => Render() }, { label: '🔙️ Go Back', onclick: () => Render() },
{ name: 'commit', label: '📝️ Commit Changes', disabled: true, onclick: DataWidgetSaveRequest }, { name: 'commit', label: '📝️ Commit Changes', disabled: true, onclick: () => DataWidgetSaveRequest(packsUrl) },
]}/> ]}/>
<${CollectionList}/> ${props.children}
${!props.extras && html`<p> <details open=${props.optionsOpen}>
Your account is currently not set-up to handle stickers.
</p>`}
${props.extras && !isManaged && html`<p>
Your account is set-up with a sticker picker, but it's not marked as being managed by this app.
This could mean that you are currently using an incompatible sticker picker.
You can try to continue anyway if you think it should work, otherwise you should reinitialize sticker data.
</p>`}
<details open=${!props.extras || !isManaged}>
<summary> <summary>
<h3 style=${{ display: 'inline' }}> <h3 style=${{ display: 'inline' }}>
Options Options
@@ -385,7 +422,7 @@
the <code>stickerpicker</code> subfield in its <code>m.widgets</code> field. the <code>stickerpicker</code> subfield in its <code>m.widgets</code> field.
</p> </p>
<${TableForm} formEntries=${[ <${TableForm} formEntries=${[
{ name: 'reinit', label: '💡️ Reinitialize sticker data as new', type: 'button', onclick: DataWidgetSaveRequest }, { name: 'reinit', label: '💡️ Reinitialize sticker data as new', type: 'button', onclick: () => DataWidgetSaveRequest(this.base.querySelector('input[name="import json from url"]').value) },
{ label: 'or', type: 'hidden' }, { label: 'or', type: 'hidden' },
{ label: 'Import JSON from URL', placeholder: 'https://example.com', type: 'text', onInteract: () => { { label: 'Import JSON from URL', placeholder: 'https://example.com', type: 'text', onInteract: () => {
const thisElem = this.base.querySelector('input[name="import json from url"]'); const thisElem = this.base.querySelector('input[name="import json from url"]');
@@ -411,90 +448,91 @@
</Fragment>` </Fragment>`
} }
function CollectionScreen (props) {
Request(`${State.account.homeserver}/_matrix/client/v3/user/${GetMatrixUserTag(State.account)}/account_data/m.widgets`, {
headers: { "Content-Type": "application/json", "authorization": `Bearer ${State.account.token}` },
callback: function(http) {
const data = JSON.parse(http.responseText);
if (http.status === 200) {
Render(CollectionView, data);
} else {
if (data.errcode === 'M_NOT_FOUND') {
Render(CollectionView);
} else {
alert(`Error: ${http.responseText}`);
Render();
}
}
},
});
}
function App (props) { function App (props) {
return html` return html`<Fragment>
<${props.screen || (Config.accounts.length > 0 ? AccountChoice : AccountLogin)} extras=${props.extras}/> <div class="Dynamic">
<p> <${props.screen || (Config.accounts.length > 0 ? AccountChoice : AccountLogin)} extras=${props.extras}/>
🃏️ [Matrix] Sticker Helper WIP, created with ☕️ by <a href="https://hub.octt.eu.org">OctoSpacc</a>. </div>
Made possible by <a href="https://github.com/maunium/stickerpicker">Maunium sticker picker</a>. <div class="Static">
</p> <dialog id="Modal">
<style> <p></p>
:root { <button onclick=${() => document.querySelector('#Modal').close()}>🔙️ Back</button>
--margin: 8px; <button name="confirm">⏩️ Confirm</button>
} </dialog>
<p>
🃏️ [Matrix] Sticker Helper WIP, created with ☕️ by <a href="https://hub.octt.eu.org">OctoSpacc</a>.
Made possible by <a href="https://github.com/maunium/stickerpicker">Maunium sticker picker</a>.
</p>
<style>
:root {
--margin: 8px;
}
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
body, .ActionForm, .TableForm { body, .ActionForm, .TableForm {
margin: var(--margin); margin: var(--margin);
} }
#CollectionList { #CollectionList {
max-width: 100%; max-width: 100%;
overflow: auto; overflow: auto;
} }
#CollectionList > div { #CollectionList > div {
width: max-content; width: max-content;
} }
#CollectionList > div > * { #CollectionList > div > * {
width: 64px; width: 64px;
margin: var(--margin); margin: var(--margin);
} }
.ActionForm > *, .ActionButton,
.TableForm td > * { .ActionForm > *,
display: block; .TableForm td > * {
height: 2em; display: block;
width: 100%; height: 2em;
} width: 100%;
}
.ActionForm > * > * {
height: 2em; .ActionForm > * > * {
} height: 2em;
}
#CollectionGrid > *,
.ClickImg { #CollectionGrid > *,
display: inline-block; .ClickImg {
margin: 8px; display: inline-block;
} margin: 8px;
}
.ClickImg,
.ClickImg img { .ClickImg,
width: 128px; .ClickImg img {
} width: 128px;
}
.ClickImg[data-opened="1"], .ClickImg[data-opened="1"] > img { .ClickImg[data-opened="1"], .ClickImg[data-opened="1"] > img {
width: calc(100% - var(--margin)); width: calc(100% - var(--margin));
} }
</style> </style>
` </div>
</Fragment>`
} }
function Render (screen, extras) { function Render (screen, extras) {
render(html`<${App} screen=${screen} extras=${extras}/>`, document.body); render(html`<${App} screen=${screen} extras=${extras}/>`, document.body);
// idk why that section gets duplicated, it worked fine until a bit before, we do this to fix it
for (const type of ['.Static', '.CollectionViewMenu']) {
const elems = document.querySelectorAll(type);
for (let i=0; i<(elems.length-1); i++) {
elems[i].remove();
}
}
dialogPolyfill.registerDialog(document.querySelector('#Modal'));
} }
Render() Render()
</script> </script>