mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2026-01-20 08:52:59 +00:00
feat: matrix
This commit is contained in:
@@ -56,3 +56,9 @@
|
||||
{@render children()}
|
||||
</main>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
main {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1 +1,184 @@
|
||||
<h2>WIP</h2>
|
||||
<script lang="ts">
|
||||
import { browser } from "$app/environment";
|
||||
import { onDestroy, onMount, setContext } from "svelte";
|
||||
import type {
|
||||
IndexedDBStore,
|
||||
IndexedDBCryptoStore,
|
||||
LoginResponse,
|
||||
} from "matrix-js-sdk";
|
||||
import MatrixTimeline from "$lib/chat/MatrixTimeline.svelte";
|
||||
import { matrixClient, currentRoomId } from "$lib/chat/chat";
|
||||
import MatrixRooms from "$lib/chat/MatrixRooms.svelte";
|
||||
import MatrixRoomMembers from "$lib/chat/MatrixRoomMembers.svelte";
|
||||
|
||||
let loggedIn = $state(false);
|
||||
let ready = $state(false);
|
||||
|
||||
let store: IndexedDBStore;
|
||||
let cryptoStore: IndexedDBCryptoStore;
|
||||
|
||||
onMount(async () => {
|
||||
if (!browser) return;
|
||||
const { createClient, IndexedDBStore, IndexedDBCryptoStore } = await import(
|
||||
"matrix-js-sdk"
|
||||
);
|
||||
|
||||
const storedLogin = getStoredLogin();
|
||||
|
||||
store = new IndexedDBStore({
|
||||
dbName: "matrix",
|
||||
indexedDB: window.indexedDB,
|
||||
});
|
||||
cryptoStore = new IndexedDBCryptoStore(window.indexedDB, "matrix-crypto");
|
||||
|
||||
$matrixClient = createClient({
|
||||
baseUrl: import.meta.env.VITE_MATRIX_URL,
|
||||
userId: storedLogin?.user_id,
|
||||
accessToken: storedLogin?.access_token,
|
||||
timelineSupport: true,
|
||||
store,
|
||||
cryptoStore,
|
||||
});
|
||||
|
||||
const loginToken = new URLSearchParams(window.location.search).get(
|
||||
"loginToken",
|
||||
);
|
||||
if (loginToken) {
|
||||
await handleLogin(await $matrixClient.loginWithToken(loginToken));
|
||||
window.history.replaceState({}, document.title, window.location.pathname);
|
||||
}
|
||||
|
||||
await postLogin();
|
||||
});
|
||||
|
||||
async function passwordLogin(event: SubmitEvent) {
|
||||
event.preventDefault();
|
||||
const form = event.target as HTMLFormElement;
|
||||
const username = (form.elements.namedItem("username") as HTMLInputElement)
|
||||
.value;
|
||||
const password = (form.elements.namedItem("password") as HTMLInputElement)
|
||||
.value;
|
||||
|
||||
await handleLogin(
|
||||
await $matrixClient.loginWithPassword(username, password),
|
||||
);
|
||||
await postLogin();
|
||||
}
|
||||
|
||||
async function handleLogin(response: LoginResponse) {
|
||||
localStorage.setItem("matrix-login", JSON.stringify(response));
|
||||
}
|
||||
|
||||
async function postLogin() {
|
||||
loggedIn = $matrixClient.isLoggedIn();
|
||||
|
||||
if (loggedIn) {
|
||||
await store.startup();
|
||||
await cryptoStore.startup();
|
||||
await $matrixClient.startClient();
|
||||
$matrixClient.once("sync", function (state, prevState, res) {
|
||||
ready = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getStoredLogin(): LoginResponse | undefined {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem("matrix-login")!);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
if ($matrixClient) {
|
||||
$matrixClient.stopClient();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if $matrixClient && loggedIn}
|
||||
{#if ready}
|
||||
<div class="chat">
|
||||
<div class="rooms">
|
||||
<button
|
||||
onclick={() => {
|
||||
$matrixClient.logout(true);
|
||||
$matrixClient.clearStores();
|
||||
localStorage.removeItem("matrix-login");
|
||||
window.location.reload();
|
||||
}}>logout</button
|
||||
>
|
||||
<MatrixRooms rooms={$matrixClient.getRooms()} />
|
||||
</div>
|
||||
{#if $currentRoomId}
|
||||
{@const room = $matrixClient.getRoom($currentRoomId)}
|
||||
{#key room}
|
||||
{#if room}
|
||||
<div class="timeline">
|
||||
<MatrixTimeline timeline={room.getLiveTimeline()} />
|
||||
</div>
|
||||
<div class="members">
|
||||
<MatrixRoomMembers members={room.getJoinedMembers()} />
|
||||
</div>
|
||||
{/if}
|
||||
{/key}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{:else if $matrixClient}
|
||||
{#await $matrixClient.loginFlows() then flows}
|
||||
{#each flows.flows as flow}
|
||||
{#if flow.type === "m.login.sso"}
|
||||
<a
|
||||
href={$matrixClient.getSsoLoginUrl(`${window.location.origin}/chat/`)}
|
||||
>
|
||||
{#each flow.identity_providers as idp}
|
||||
{#if idp.icon}
|
||||
<img src={$matrixClient.mxcUrlToHttp(idp.icon)} alt={idp.name} />
|
||||
{:else}
|
||||
{idp.name}
|
||||
{/if}
|
||||
{/each}
|
||||
</a>
|
||||
{:else if flow.type === "m.login.password"}
|
||||
<form onsubmit={passwordLogin}>
|
||||
<input name="username" type="text" placeholder="Username" />
|
||||
<input name="password" type="password" placeholder="Password" />
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
{/if}
|
||||
{/each}
|
||||
{/await}
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.chat {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
> *:not(:last-child) {
|
||||
border-right: 1px solid var(--md-sys-color-outline);
|
||||
}
|
||||
}
|
||||
|
||||
.room {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.timeline {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.rooms {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.members {
|
||||
width: 200px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -262,9 +262,7 @@
|
||||
{/if}
|
||||
{#each $items.slice(page * $pageSize - (page === 0 ? 0 : 1), (page + 1) * $pageSize - 1) as [chord] (JSON.stringify(chord?.id))}
|
||||
{#if chord}
|
||||
<tr>
|
||||
<ChordEdit {chord} onduplicate={() => (page = 0)} />
|
||||
</tr>
|
||||
<ChordEdit {chord} onduplicate={() => (page = 0)} />
|
||||
{/if}
|
||||
{/each}</tbody
|
||||
>
|
||||
@@ -397,7 +395,7 @@
|
||||
|
||||
table {
|
||||
height: fit-content;
|
||||
overflow: hidden;
|
||||
overflow-y: hidden;
|
||||
transition: all 1s ease;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -89,33 +89,37 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<th>
|
||||
<ChordActionEdit {chord} onsubmit={() => {}} />
|
||||
</th>
|
||||
<td>
|
||||
<ChordPhraseEdit {chord} />
|
||||
</td>
|
||||
<td class="table-buttons">
|
||||
{#if !chord.deleted}
|
||||
<button transition:slide class="icon compact" onclick={remove}
|
||||
>delete</button
|
||||
>
|
||||
{:else}
|
||||
<button transition:slide class="icon compact" onclick={restore}
|
||||
>restore_from_trash</button
|
||||
>
|
||||
{/if}
|
||||
<button disabled={chord.deleted} class="icon compact" onclick={duplicate}
|
||||
>content_copy</button
|
||||
>
|
||||
<button
|
||||
class="icon compact"
|
||||
class:disabled={chord.isApplied}
|
||||
onclick={restore}>undo</button
|
||||
>
|
||||
<div class="separator"></div>
|
||||
<button class="icon compact" onclick={share}>share</button>
|
||||
</td>
|
||||
<tr>
|
||||
<th>
|
||||
<ChordActionEdit {chord} onsubmit={() => {}} />
|
||||
</th>
|
||||
<td class="phrase-edit">
|
||||
<ChordPhraseEdit {chord} />
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-buttons">
|
||||
{#if !chord.deleted}
|
||||
<button transition:slide class="icon compact" onclick={remove}
|
||||
>delete</button
|
||||
>
|
||||
{:else}
|
||||
<button transition:slide class="icon compact" onclick={restore}
|
||||
>restore_from_trash</button
|
||||
>
|
||||
{/if}
|
||||
<button disabled={chord.deleted} class="icon compact" onclick={duplicate}
|
||||
>content_copy</button
|
||||
>
|
||||
<button
|
||||
class="icon compact"
|
||||
class:disabled={chord.isApplied}
|
||||
onclick={restore}>undo</button
|
||||
>
|
||||
<div class="separator"></div>
|
||||
<button class="icon compact" onclick={share}>share</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<style lang="scss">
|
||||
.separator {
|
||||
@@ -132,17 +136,29 @@
|
||||
transition: opacity 75ms ease;
|
||||
}
|
||||
|
||||
td {
|
||||
.phrase-edit {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
tr {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.table-buttons {
|
||||
opacity: 0;
|
||||
transition: opacity 75ms ease;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
transform: translate(100%, -50%);
|
||||
background: var(--md-sys-color-surface-variant);
|
||||
}
|
||||
|
||||
:global(tr):focus-within > .table-buttons,
|
||||
:global(tr):hover > .table-buttons {
|
||||
.icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
tr:hover .table-buttons {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
21
src/routes/(app)/config/chords/ChordEditActions.svelte
Normal file
21
src/routes/(app)/config/chords/ChordEditActions.svelte
Normal file
@@ -0,0 +1,21 @@
|
||||
<div class="table-buttons">
|
||||
{#if !chord.deleted}
|
||||
<button transition:slide class="icon compact" onclick={remove}
|
||||
>delete</button
|
||||
>
|
||||
{:else}
|
||||
<button transition:slide class="icon compact" onclick={restore}
|
||||
>restore_from_trash</button
|
||||
>
|
||||
{/if}
|
||||
<button disabled={chord.deleted} class="icon compact" onclick={duplicate}
|
||||
>content_copy</button
|
||||
>
|
||||
<button
|
||||
class="icon compact"
|
||||
class:disabled={chord.isApplied}
|
||||
onclick={restore}>undo</button
|
||||
>
|
||||
<div class="separator"></div>
|
||||
<button class="icon compact" onclick={share}>share</button>
|
||||
</div>
|
||||
0
src/routes/(app)/learn/Pick.svelte
Normal file
0
src/routes/(app)/learn/Pick.svelte
Normal file
0
src/routes/(app)/stats/+page.svelte
Normal file
0
src/routes/(app)/stats/+page.svelte
Normal file
Reference in New Issue
Block a user