mirror of
https://github.com/Discreetly/frontend.git
synced 2026-01-09 12:58:03 -05:00
refactored for better UI/UX
This commit is contained in:
876
package-lock.json
generated
876
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -28,14 +28,17 @@
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-svelte": "^2.30.0",
|
||||
"lightningcss": "^1.21.7",
|
||||
"postcss": "^8.4.24",
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"prettier": "^2.8.0",
|
||||
"prettier-plugin-svelte": "^2.10.1",
|
||||
"rollup-plugin-sizes": "^1.0.5",
|
||||
"svelte": "^4.0.5",
|
||||
"svelte-check": "^3.4.5",
|
||||
"svelte-kit": "^1.2.0",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"terser": "^5.19.2",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^4.3.6"
|
||||
@@ -48,7 +51,7 @@
|
||||
"@semaphore-protocol/group": "^3.10.1",
|
||||
"@semaphore-protocol/identity": "^3.10.1",
|
||||
"date-fns": "^2.30.0",
|
||||
"discreetly-interfaces": "^0.1.37",
|
||||
"discreetly-interfaces": "^0.1.38",
|
||||
"libsodium-wrappers": "^0.7.11",
|
||||
"poseidon-lite": "^0.2.0",
|
||||
"qr-scanner": "^1.4.2",
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { selectedServer, selectedRoom, currentRoomsStore } from '$lib/stores';
|
||||
import { updateMessages } from '$lib/utils';
|
||||
|
||||
function setRoom(roomId: string) {
|
||||
$selectedRoom[$selectedServer] = roomId;
|
||||
updateMessages($selectedServer, roomId);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { RLNProver } from 'rlnjs';
|
||||
import { Group } from '@semaphore-protocol/group';
|
||||
import type { MessageI } from 'discreetly-interfaces';
|
||||
import type { MessageI, MessageInterfaces } from 'discreetly-interfaces';
|
||||
import type { IdentityStoreI, RoomI } from '$lib/types';
|
||||
import type { RLNFullProof, MerkleProof } from 'rlnjs';
|
||||
import { getMerkleProof } from '$lib//services/bandada';
|
||||
import { updateRooms } from '$lib/utils';
|
||||
import { get } from 'svelte/store';
|
||||
import { selectedServer, roomsStore } from '$lib/stores';
|
||||
import { calculateSignalHash } from './signalHash';
|
||||
import { calculateSignalHash } from 'discreetly-interfaces';
|
||||
import getRateCommitmentHash from './rateCommitmentHasher';
|
||||
|
||||
const wasmPath = '/rln/circuit.wasm';
|
||||
@@ -32,7 +32,19 @@ async function merkleProofFromRoom(
|
||||
identityCommitment: bigint
|
||||
) {
|
||||
const roomFromStore = get(roomsStore)[roomId];
|
||||
const identities = roomFromStore.identities ? roomFromStore.identities.map((i) => BigInt(i)) : [];
|
||||
let identities: bigint[];
|
||||
try {
|
||||
identities = roomFromStore.identities
|
||||
? roomFromStore.identities.map((i) => {
|
||||
// This removes any non-numeric characters from the string
|
||||
// In particular there was a bug where the `n` at the end of a bigint wasn't removed and it wasn't parsing correctly.
|
||||
return BigInt(String(i).replace(/\D/g, ''));
|
||||
})
|
||||
: [];
|
||||
} catch (err) {
|
||||
console.debug(roomFromStore.identities);
|
||||
throw new Error('Could not parse identities from room');
|
||||
}
|
||||
const group = new Group(RLN_IDENIFIER, 20, identities);
|
||||
let mp: MerkleProof;
|
||||
try {
|
||||
@@ -55,17 +67,17 @@ async function merkleProofFromRoom(
|
||||
|
||||
/**
|
||||
*
|
||||
* @param room
|
||||
* @param message
|
||||
* @param identity
|
||||
* @param epoch
|
||||
* @param messageId
|
||||
* @param messageLimit
|
||||
* @param {RoomI} room
|
||||
* @param {MessageInterfaces} message
|
||||
* @param {IdentityStoreI} identity
|
||||
* @param {bigint | number} epoch
|
||||
* @param {number} messageId
|
||||
* @param {number} messageLimit
|
||||
* @returns Message with proof attached
|
||||
*/
|
||||
async function genProof(
|
||||
room: RoomI,
|
||||
message: string,
|
||||
message: MessageInterfaces,
|
||||
identity: IdentityStoreI,
|
||||
epoch: bigint | number,
|
||||
messageId: bigint | number,
|
||||
@@ -78,7 +90,7 @@ async function genProof(
|
||||
const userMessageLimit = BigInt(messageLimit);
|
||||
const identitySecret = BigInt(identity._secret);
|
||||
const identityCommitment = BigInt(identity._commitment);
|
||||
const messageHash: bigint = calculateSignalHash(message);
|
||||
const messageHash: bigint = calculateSignalHash(JSON.stringify(message));
|
||||
const rateCommitment: bigint = getRateCommitmentHash(identityCommitment, userMessageLimit);
|
||||
|
||||
let merkleProof: MerkleProof;
|
||||
@@ -93,8 +105,16 @@ async function genProof(
|
||||
break;
|
||||
case 'BANDADA_GROUP':
|
||||
if (room.bandadaAddress === undefined) throw new Error('Bandada address not defined');
|
||||
merkleProof = await getMerkleProof(room.bandadaAddress, rateCommitment);
|
||||
throw new Error('Bandada not implemented yet');
|
||||
try {
|
||||
merkleProof = await getMerkleProof(
|
||||
room.bandadaAddress,
|
||||
room.bandadaGroupId!,
|
||||
rateCommitment
|
||||
);
|
||||
break;
|
||||
} catch (err) {
|
||||
throw new Error('GetMerkleProof failed' + err);
|
||||
}
|
||||
case 'CONTRACT':
|
||||
//TODO
|
||||
throw new Error('RLN contracts not implemented yet');
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { hexlify } from '@ethersproject/bytes';
|
||||
import { toUtf8Bytes } from '@ethersproject/strings';
|
||||
import { keccak256 } from '@ethersproject/keccak256';
|
||||
|
||||
/**
|
||||
* Hashes a signal string with Keccak256.
|
||||
* @param signal The RLN signal.
|
||||
* @returns The signal hash.
|
||||
*/
|
||||
export function calculateSignalHash(signal: string): bigint {
|
||||
const converted = hexlify(toUtf8Bytes(signal));
|
||||
return BigInt(keccak256(converted)) >> BigInt(8);
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { MerkleProof } from 'rlnjs';
|
||||
import type { BandadaGroupI } from 'discreetly-interfaces';
|
||||
import { get } from './api';
|
||||
|
||||
// https://api.bandada.pse.dev/groups/{group}/members/{member}/proof
|
||||
export function getMerkleProof(
|
||||
bandadaGroup: BandadaGroupI,
|
||||
bandadaServerAddress: string,
|
||||
groupId: string,
|
||||
identityCommitment: string | bigint
|
||||
): Promise<MerkleProof> {
|
||||
const endpoint = `groups/${bandadaGroup.groupID}/members/${identityCommitment}/proof`;
|
||||
return get([bandadaGroup.url, endpoint])
|
||||
const endpoint = `groups/${groupId}/members/${identityCommitment}/proof`;
|
||||
return get([bandadaServerAddress, endpoint])
|
||||
.then((res) => {
|
||||
return res as MerkleProof;
|
||||
})
|
||||
|
||||
@@ -9,98 +9,34 @@ export interface State {
|
||||
export interface EpochDetails {
|
||||
epoch: number;
|
||||
timestamp: number; // Unix epoch time
|
||||
local: string;
|
||||
relative: string;
|
||||
}
|
||||
|
||||
// class RateLimiter {
|
||||
// private numberMessages: number;
|
||||
// private milliSecondsPerEpoch: number;
|
||||
// private lastEpochMessageWasSent: number;
|
||||
// private remainingMessages: number;
|
||||
|
||||
// constructor(numberMessages: number, milliSecondsPerEpoch: number) {
|
||||
// this.numberMessages = numberMessages;
|
||||
// this.milliSecondsPerEpoch = milliSecondsPerEpoch;
|
||||
// this.lastEpochMessageWasSent = this.getCurrentEpoch();
|
||||
// this.remainingMessages = this.numberMessages;
|
||||
// }
|
||||
|
||||
// getCurrentEpoch(): number {
|
||||
// return Math.floor(Date.now() / this.milliSecondsPerEpoch);
|
||||
// }
|
||||
|
||||
// private updateState(): State {
|
||||
// const currentEpoch = this.getCurrentEpoch();
|
||||
// if (currentEpoch > this.lastEpochMessageWasSent) {
|
||||
// this.remainingMessages = this.numberMessages;
|
||||
// this.lastEpochMessageWasSent = currentEpoch;
|
||||
// }
|
||||
// return {
|
||||
// currentEpoch,
|
||||
// lastEpochMessageWasSent: this.lastEpochMessageWasSent,
|
||||
// remainingMessages: this.remainingMessages
|
||||
// };
|
||||
// }
|
||||
|
||||
// public getRemainingMessages(): number {
|
||||
// this.updateState();
|
||||
// return this.remainingMessages;
|
||||
// }
|
||||
|
||||
// public useMessage(): number {
|
||||
// this.updateState();
|
||||
// if (this.remainingMessages > 0) {
|
||||
// this.remainingMessages--;
|
||||
// }
|
||||
// return this.remainingMessages > 0 ? this.remainingMessages : -1;
|
||||
// }
|
||||
|
||||
// public getEpochFromTimestamp(timestamp: number = Date.now()): EpochDetails {
|
||||
// const epoch = Math.floor(timestamp / this.milliSecondsPerEpoch);
|
||||
// const local = new Date(timestamp).toLocaleString('en-US', {
|
||||
// hour: 'numeric',
|
||||
// minute: 'numeric',
|
||||
// hour12: true
|
||||
// });
|
||||
// return { epoch, timestamp, local };
|
||||
// }
|
||||
|
||||
// public getTimestampFromEpoch(epoch: number = this.getCurrentEpoch()): string {
|
||||
// const time = epoch * this.milliSecondsPerEpoch;
|
||||
// return new Date(time).toLocaleString('en-US', {
|
||||
// hour: 'numeric',
|
||||
// minute: 'numeric',
|
||||
// hour12: true
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
export function getEpochFromTimestamp(
|
||||
ratelimit: number,
|
||||
timestamp: number = Date.now()
|
||||
): EpochDetails {
|
||||
const epoch = Math.floor(timestamp / ratelimit);
|
||||
const local = new Date(timestamp).toLocaleString('en-US', {
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
hour12: true
|
||||
});
|
||||
return { epoch, timestamp, local };
|
||||
let relative = '';
|
||||
try {
|
||||
relative = formatRelative(new Date(timestamp), new Date());
|
||||
} catch (err) {
|
||||
relative = 'Unknown';
|
||||
console.debug(`${err.message}: ${epoch} * ${ratelimit} = ${timestamp}`);
|
||||
}
|
||||
return { epoch, relative, timestamp };
|
||||
}
|
||||
|
||||
export function getTimestampFromEpoch(
|
||||
epoch: number,
|
||||
ratelimit: number
|
||||
): { DateString: string; unixEpochTime: number } {
|
||||
let DateString = '';
|
||||
let unixEpochTime = 0;
|
||||
export function getTimestampFromEpoch(ratelimit: number, epoch: number): EpochDetails {
|
||||
let relative = '';
|
||||
let timestamp = 0;
|
||||
try {
|
||||
unixEpochTime = epoch * ratelimit;
|
||||
DateString = formatRelative(new Date(unixEpochTime), new Date());
|
||||
timestamp = epoch * ratelimit;
|
||||
relative = formatRelative(new Date(timestamp), new Date());
|
||||
} catch (err) {
|
||||
DateString = 'Unknown';
|
||||
console.debug(err);
|
||||
relative = 'Unknown';
|
||||
//console.debug(`${err.message}: ${epoch} * ${ratelimit} = ${timestamp}`);
|
||||
}
|
||||
|
||||
return { DateString, unixEpochTime };
|
||||
return { epoch, relative, timestamp };
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { RoomI } from '$lib/types';
|
||||
import { roomsStore, selectedRoom, selectedServer, serverStore } from '$lib/stores';
|
||||
import { roomsStore, selectedRoom, selectedServer, serverStore, messageStore } from '$lib/stores';
|
||||
import { get } from 'svelte/store';
|
||||
import {
|
||||
getIdentityRoomIds as getRoomIdsByIdentityCommitment,
|
||||
getRoomById
|
||||
getRoomById,
|
||||
getMessages
|
||||
} from '$lib/services/server';
|
||||
import { getCommitment } from '.';
|
||||
|
||||
@@ -69,3 +70,12 @@ export async function updateRooms(
|
||||
}
|
||||
return acceptedRoomNames;
|
||||
}
|
||||
|
||||
export function updateMessages(server: string, roomId: string) {
|
||||
getMessages(server, roomId).then((messages) => {
|
||||
messageStore.update((store) => {
|
||||
store[roomId] = messages;
|
||||
return store;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,25 +4,54 @@
|
||||
import InputPrompt from './InputPrompt.svelte';
|
||||
import Conversation from './Conversation.svelte';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { selectedServer, currentSelectedRoom, messageStore } from '$lib/stores';
|
||||
import { selectedServer, currentSelectedRoom, messageStore, rateLimitStore } from '$lib/stores';
|
||||
import { io } from 'socket.io-client';
|
||||
import type { Socket } from 'socket.io-client';
|
||||
import type { MessageI } from 'discreetly-interfaces';
|
||||
import { getEpochFromTimestamp, getTimestampFromEpoch } from '$lib/utils';
|
||||
import { getMessages } from '$lib/services/server';
|
||||
import { getEpochFromTimestamp, getTimestampFromEpoch, updateMessages } from '$lib/utils';
|
||||
import Loading from '$lib/components/loading.svelte';
|
||||
|
||||
let scrollChatToBottom: () => {};
|
||||
let socket: Socket;
|
||||
let connected: boolean = false;
|
||||
let lastRoom = '';
|
||||
$: currentEpoch = 0;
|
||||
$: timeLeftInEpoch = '0';
|
||||
$: userMessageLimit = $currentSelectedRoom.userMessageLimit ?? 1;
|
||||
$: roomRateLimit = $currentSelectedRoom.rateLimit ?? 0;
|
||||
$: if (!$rateLimitStore[$currentSelectedRoom.roomId!.toString()]) {
|
||||
$rateLimitStore[$currentSelectedRoom.roomId!.toString()] = {
|
||||
lastEpoch: currentEpoch,
|
||||
messagesSent: 0
|
||||
};
|
||||
}
|
||||
$: currentRateLimit = $rateLimitStore[$currentSelectedRoom.roomId!.toString()];
|
||||
$: messagesLeft = () => {
|
||||
if (currentRateLimit.lastEpoch !== currentEpoch) {
|
||||
currentRateLimit.lastEpoch = currentEpoch;
|
||||
currentRateLimit.messagesSent = 0;
|
||||
return userMessageLimit;
|
||||
} else {
|
||||
return userMessageLimit - currentRateLimit.messagesSent;
|
||||
}
|
||||
};
|
||||
$: messageId = userMessageLimit - messagesLeft();
|
||||
$: try {
|
||||
if (lastRoom) {
|
||||
socket.emit('leavingRoom', lastRoom);
|
||||
}
|
||||
socket.emit('joiningRoom', $currentSelectedRoom?.roomId.toString());
|
||||
console.debug('Joining room', $currentSelectedRoom?.roomId.toString());
|
||||
} catch {
|
||||
} finally {
|
||||
}
|
||||
|
||||
function updateEpoch() {
|
||||
currentEpoch = Math.floor(Date.now() / $currentSelectedRoom.rateLimit!);
|
||||
timeLeftInEpoch = (
|
||||
($currentSelectedRoom.rateLimit! -
|
||||
(Date.now() -
|
||||
getTimestampFromEpoch(currentEpoch, $currentSelectedRoom.rateLimit!).unixEpochTime)) /
|
||||
getTimestampFromEpoch($currentSelectedRoom.rateLimit!, currentEpoch).timestamp)) /
|
||||
1000
|
||||
).toFixed(1);
|
||||
}
|
||||
@@ -41,7 +70,7 @@
|
||||
console.debug('socket-io-transport-closed', reason);
|
||||
});
|
||||
|
||||
socket.emit('joiningRoom', $currentSelectedRoom?.roomId);
|
||||
socket.emit('joiningRoom', $currentSelectedRoom?.roomId.toString());
|
||||
});
|
||||
|
||||
socket.on('disconnected', () => {
|
||||
@@ -74,8 +103,8 @@
|
||||
}
|
||||
if (!data.epoch) {
|
||||
data.epoch = getEpochFromTimestamp(
|
||||
+data.timeStamp!,
|
||||
$currentSelectedRoom.rateLimit!
|
||||
$currentSelectedRoom.rateLimit!,
|
||||
+data.timeStamp!
|
||||
).epoch;
|
||||
}
|
||||
$messageStore[roomId] = [...$messageStore[roomId], data];
|
||||
@@ -84,13 +113,15 @@
|
||||
}
|
||||
scrollChatToBottom();
|
||||
}
|
||||
socket.on('Members', (data: string) => {
|
||||
console.log(data);
|
||||
});
|
||||
});
|
||||
|
||||
getMessages($selectedServer, $currentSelectedRoom?.roomId.toString()).then((messages) => {
|
||||
console.log(messages);
|
||||
$messageStore[$currentSelectedRoom?.roomId.toString()] = messages;
|
||||
});
|
||||
updateMessages($selectedServer, $currentSelectedRoom?.roomId.toString());
|
||||
|
||||
scrollChatToBottom();
|
||||
|
||||
setInterval(() => {
|
||||
updateEpoch();
|
||||
}, 100);
|
||||
@@ -101,14 +132,33 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="chat" class="grid grid-rows-[auto,1fr,auto]">
|
||||
<!-- Header -->
|
||||
<ChatRoomHeader {currentEpoch} {timeLeftInEpoch} />
|
||||
<!-- Conversation -->
|
||||
<Conversation bind:scrollChatBottom={scrollChatToBottom} />
|
||||
<!-- Prompt -->
|
||||
<InputPrompt {socket} {connected} {currentEpoch} />
|
||||
</div>
|
||||
{#if $currentSelectedRoom}
|
||||
<div id="chat" class="grid grid-rows-[auto,1fr,auto]">
|
||||
<!-- Header -->
|
||||
<ChatRoomHeader
|
||||
{currentEpoch}
|
||||
{timeLeftInEpoch}
|
||||
{userMessageLimit}
|
||||
{messageId}
|
||||
{messagesLeft}
|
||||
{roomRateLimit}
|
||||
/>
|
||||
<!-- Conversation -->
|
||||
<Conversation bind:scrollChatBottom={scrollChatToBottom} {roomRateLimit} />
|
||||
<!-- Prompt -->
|
||||
<InputPrompt
|
||||
{socket}
|
||||
{connected}
|
||||
{currentEpoch}
|
||||
{userMessageLimit}
|
||||
{messageId}
|
||||
{currentRateLimit}
|
||||
{messagesLeft}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<Loading />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
#chat {
|
||||
|
||||
@@ -1,11 +1,64 @@
|
||||
<script lang="ts">
|
||||
import { currentSelectedRoom } from '$lib/stores';
|
||||
import { currentSelectedRoom, rateLimitStore } from '$lib/stores';
|
||||
import { onMount } from 'svelte';
|
||||
export let currentEpoch: number;
|
||||
export let timeLeftInEpoch: string;
|
||||
export let userMessageLimit: number;
|
||||
export let roomRateLimit: number;
|
||||
export let messagesLeft: () => number;
|
||||
export let messageId: number;
|
||||
$: roomId = $currentSelectedRoom?.roomId!.toString();
|
||||
$: roomName = $currentSelectedRoom?.name ?? 'Select Room';
|
||||
$: userMessageLimit = $currentSelectedRoom.userMessageLimit ?? 1;
|
||||
$: currentRateLimit = $currentSelectedRoom.rateLimit ?? 0;
|
||||
$: actions(messagesLeft(), userMessageLimit);
|
||||
|
||||
function actions(msgsRemaining: number, totalMsgs: number) {
|
||||
const d = document.getElementById('ActionPoints');
|
||||
let canvas = d?.querySelector('canvas') as HTMLCanvasElement;
|
||||
|
||||
const circleRadius = 8;
|
||||
const circleSpacing = 5;
|
||||
const startX = circleRadius;
|
||||
|
||||
if (!canvas) {
|
||||
canvas = document.createElement('canvas');
|
||||
canvas.height = 20;
|
||||
d?.appendChild(canvas);
|
||||
}
|
||||
|
||||
canvas.width = (circleRadius * 2 + circleSpacing) * totalMsgs - circleSpacing;
|
||||
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear previous drawing
|
||||
|
||||
let x = startX;
|
||||
const y = canvas.height / 2;
|
||||
|
||||
for (let i = 0; i < msgsRemaining; i++) {
|
||||
ctx.fillStyle = msgsRemaining === 1 ? '#fa5f5f' : '#45a164';
|
||||
ctx.strokeStyle = msgsRemaining === 1 ? '#bc4747' : '#34794b'; // Outline color
|
||||
ctx.lineWidth = 1; // Outline width
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, circleRadius, 0, Math.PI * 2, true);
|
||||
ctx.fill();
|
||||
ctx.stroke(); // Draw the outline
|
||||
x += circleRadius * 2 + circleSpacing;
|
||||
}
|
||||
|
||||
for (let i = msgsRemaining; i < totalMsgs; i++) {
|
||||
ctx.fillStyle = '#73888a';
|
||||
ctx.strokeStyle = '#1a1f1f'; // Outline color
|
||||
ctx.lineWidth = 1; // Outline width
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, circleRadius, 0, Math.PI * 2, true);
|
||||
ctx.fill();
|
||||
ctx.stroke(); // Draw the outline
|
||||
x += circleRadius * 2 + circleSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
actions(messagesLeft(), userMessageLimit);
|
||||
});
|
||||
</script>
|
||||
|
||||
<header
|
||||
@@ -14,13 +67,17 @@
|
||||
<h2 class="h5 text-primary-500" title={roomId}>
|
||||
{roomName}
|
||||
</h2>
|
||||
<div class="hidden md:block text-xs md:text-sm">
|
||||
|
||||
<div class="hidden md:inline text-xs md:text-sm">
|
||||
<small title={roomId}
|
||||
>you can send {userMessageLimit} messages every {currentRateLimit / 1000} seconds</small
|
||||
>you can send {userMessageLimit} messages every {roomRateLimit / 1000} seconds / {messageId} of
|
||||
{messagesLeft()}
|
||||
used</small
|
||||
>
|
||||
<br class="hidden md:inline" />
|
||||
<small class="code" title={String(currentEpoch)}
|
||||
>Epoch: {currentEpoch} / Time Left in Epoch: {timeLeftInEpoch}s</small
|
||||
>
|
||||
</div>
|
||||
<div id="ActionPoints" />
|
||||
</header>
|
||||
|
||||
@@ -1,18 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { currentRoomMessages, currentSelectedRoom } from '$lib/stores';
|
||||
import { getTimestampFromEpoch } from '$lib/utils/rateLimit';
|
||||
import { getEpochFromTimestamp, getTimestampFromEpoch } from '$lib/utils/rateLimit';
|
||||
import type { MessageI } from 'discreetly-interfaces';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
$: rateLimit = $currentSelectedRoom?.rateLimit!;
|
||||
export let roomRateLimit: number;
|
||||
|
||||
let elemChat: HTMLElement;
|
||||
|
||||
// For some reason, eslint thinks ScrollBehavior is undefined...
|
||||
// eslint-disable-next-line no-undef
|
||||
export function scrollChatBottom(behavior: ScrollBehavior = 'smooth'): void {
|
||||
export function scrollChatBottom(behavior: ScrollBehavior = 'smooth', delay = 1): void {
|
||||
setTimeout(() => {
|
||||
elemChat.scrollTo({ top: elemChat.scrollHeight, behavior });
|
||||
}, 1);
|
||||
}, delay);
|
||||
}
|
||||
|
||||
function getTime(bubble: MessageI): string {
|
||||
let r = getTimestampFromEpoch(roomRateLimit, Number(bubble.epoch)).relative;
|
||||
if (r === 'Unknown') {
|
||||
r = getEpochFromTimestamp(roomRateLimit, Number(bubble.timeStamp)).relative;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
scrollChatBottom('smooth', 500);
|
||||
});
|
||||
</script>
|
||||
|
||||
<section
|
||||
@@ -25,10 +39,14 @@
|
||||
<div class="flex flex-col items-start">
|
||||
<div class="card p-2 md:p-4 space-y-1 md:space-y-2 bg-surface-200-700-token">
|
||||
<header class="flex justify-between items-center text-xs md:text-sm">
|
||||
<small class="opacity-50 text-primary-500 mr-2 md:mr-4"
|
||||
>{getTimestampFromEpoch(Number(bubble.epoch), rateLimit).DateString}</small
|
||||
>
|
||||
<small class="hidden md:block opacity-50 text-primary-500">epoch: {bubble.epoch}</small>
|
||||
<small class="opacity-50 text-primary-500 mr-2 md:mr-4">{getTime(bubble)}</small>
|
||||
{#if bubble.epoch}
|
||||
<small class="hidden md:block opacity-50 text-primary-500"
|
||||
>epoch: {bubble.epoch}</small
|
||||
>
|
||||
{:else}
|
||||
<small class="hidden md:block opacity-70 text-error-500">SYSTEM MESSAGE</small>
|
||||
{/if}
|
||||
</header>
|
||||
<p class="text-primary-500">{bubble.message}</p>
|
||||
</div>
|
||||
|
||||
@@ -8,28 +8,14 @@
|
||||
export let socket: Socket;
|
||||
export let connected: boolean;
|
||||
export let currentEpoch: number;
|
||||
export let userMessageLimit: number;
|
||||
export let currentRateLimit: { lastEpoch: number; messagesSent: number };
|
||||
export let messageId: number;
|
||||
export let messagesLeft: () => number;
|
||||
|
||||
let messageText = '';
|
||||
let sendingMessage: boolean = false;
|
||||
$: if (!$rateLimitStore[$currentSelectedRoom.roomId!.toString()]) {
|
||||
$rateLimitStore[$currentSelectedRoom.roomId!.toString()] = {
|
||||
lastEpoch: currentEpoch,
|
||||
messagesSent: 0
|
||||
};
|
||||
}
|
||||
|
||||
$: currentRateLimit = $rateLimitStore[$currentSelectedRoom.roomId!.toString()];
|
||||
$: userMessageLimit = $currentSelectedRoom.userMessageLimit ?? 1;
|
||||
$: messagesLeft = () => {
|
||||
if (currentRateLimit.lastEpoch !== currentEpoch) {
|
||||
currentRateLimit.lastEpoch = currentEpoch;
|
||||
currentRateLimit.messagesSent = 0;
|
||||
return userMessageLimit;
|
||||
} else {
|
||||
return userMessageLimit - currentRateLimit.messagesSent;
|
||||
}
|
||||
};
|
||||
$: messageId = userMessageLimit - messagesLeft();
|
||||
$: placeholderText = () => {
|
||||
if (!connected) {
|
||||
return 'Connecting...';
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import sizes from 'rollup-plugin-sizes';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()]
|
||||
plugins: [sveltekit()],
|
||||
build: {
|
||||
minify: 'terser',
|
||||
cssMinify: 'lightningcss',
|
||||
rollupOptions: {
|
||||
plugins: [sizes()],
|
||||
output: {
|
||||
manualChunks: {
|
||||
'@sveltejs/kit': ['@sveltejs/kit']
|
||||
},
|
||||
compact: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user