chore: remove orphaned legacy files and move QR scanner to elements/
- Trashed 10 unreferenced files: core .legacy types, bak files, element_modal_v1, element_websocket_v2, AE_MetadataFooter_not_ref, element_qr_scanner_v2 - Removed empty placeholder dirs: ae_db/, hooks/ - Moved element_qr_scanner_v3.svelte from lib root into elements/ - Updated 2 import paths for QR scanner (badges + leads) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,205 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
open?: boolean;
|
||||
title?: string;
|
||||
autoclose?: boolean;
|
||||
size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl' | '7xl';
|
||||
placement?: 'top-center' | 'center' | 'bottom-center';
|
||||
class_li?: string; // Additional classes for the dialog
|
||||
header?: import('svelte').Snippet;
|
||||
children?: import('svelte').Snippet;
|
||||
footer?: import('svelte').Snippet;
|
||||
}
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
title = '',
|
||||
autoclose = true,
|
||||
size = 'md',
|
||||
placement = 'center',
|
||||
class_li = '',
|
||||
header,
|
||||
children,
|
||||
footer
|
||||
}: Props = $props();
|
||||
|
||||
let dialog_element: HTMLDialogElement;
|
||||
|
||||
// Open/close dialog reactively
|
||||
$effect(() => {
|
||||
if (dialog_element) {
|
||||
if (open) {
|
||||
dialog_element.showModal();
|
||||
} else {
|
||||
dialog_element.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
// Handle backdrop click to close (if autoclose is true)
|
||||
dialog_element.addEventListener('click', (event) => {
|
||||
if (autoclose && event.target === dialog_element) {
|
||||
open = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Escape key to close
|
||||
const handle_keydown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape' && open) {
|
||||
event.preventDefault(); // Prevent default browser escape behavior (e.g., page back)
|
||||
open = false;
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', handle_keydown);
|
||||
|
||||
onDestroy(() => {
|
||||
window.removeEventListener('keydown', handle_keydown);
|
||||
});
|
||||
});
|
||||
|
||||
// Determine max-width based on size prop
|
||||
let max_width_class = $derived(
|
||||
size === 'sm'
|
||||
? 'max-w-sm'
|
||||
: size === 'md'
|
||||
? 'max-w-md'
|
||||
: size === 'lg'
|
||||
? 'max-w-lg'
|
||||
: size === 'xl'
|
||||
? 'max-w-xl'
|
||||
: size === '2xl'
|
||||
? 'max-w-2xl'
|
||||
: size === '3xl'
|
||||
? 'max-w-3xl'
|
||||
: size === '4xl'
|
||||
? 'max-w-4xl'
|
||||
: size === '5xl'
|
||||
? 'max-w-5xl'
|
||||
: size === '6xl'
|
||||
? 'max-w-6xl'
|
||||
: size === '7xl'
|
||||
? 'max-w-7xl'
|
||||
: 'max-w-md'
|
||||
);
|
||||
|
||||
// Determine placement classes
|
||||
let placement_class = $derived(
|
||||
placement === 'top-center'
|
||||
? 'justify-center items-start pt-[5vh]'
|
||||
: placement === 'center'
|
||||
? 'justify-center items-center'
|
||||
: placement === 'bottom-center'
|
||||
? 'justify-center items-end pb-[5vh]'
|
||||
: 'justify-center items-center' // Default to center
|
||||
);
|
||||
</script>
|
||||
|
||||
<dialog
|
||||
bind:this={dialog_element}
|
||||
class="
|
||||
p-0 bg-transparent overflow-visible
|
||||
backdrop:bg-black/50 backdrop:backdrop-blur-sm
|
||||
"
|
||||
onclose={() => (open = false)}
|
||||
>
|
||||
<div
|
||||
class="
|
||||
bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg shadow-xl
|
||||
flex flex-col
|
||||
mx-auto
|
||||
{max_width_class}
|
||||
w-full
|
||||
{class_li}
|
||||
"
|
||||
>
|
||||
<!-- Modal Header -->
|
||||
{#if title || header}
|
||||
<header
|
||||
class="flex items-center justify-between border-b border-gray-200 p-4 dark:border-gray-700"
|
||||
>
|
||||
{#if header}
|
||||
{@render header()}
|
||||
{:else}
|
||||
<h3 class="text-xl font-semibold">{title}</h3>
|
||||
{/if}
|
||||
<button
|
||||
onclick={() => (open = false)}
|
||||
class="rounded-full p-1 hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
title="Close modal"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</header>
|
||||
{/if}
|
||||
|
||||
<!-- Modal Body -->
|
||||
<main class="max-h-[70vh] overflow-y-auto p-4">
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
<!-- Modal Footer -->
|
||||
{#if footer}
|
||||
<footer class="border-t border-gray-200 p-4 dark:border-gray-700">
|
||||
{@render footer()}
|
||||
</footer>
|
||||
{/if}
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<style lang="postcss">
|
||||
dialog {
|
||||
display: flex; /* Override default to allow flexbox positioning */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
dialog[open] {
|
||||
opacity: 0;
|
||||
animation: fade-in 0.15s forwards ease-out;
|
||||
}
|
||||
|
||||
dialog:not([open]) {
|
||||
opacity: 1;
|
||||
animation: fade-out 0.15s forwards ease-out;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-out {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
pointer-events: none; /* Disable interaction while fading out */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
143
src/lib/elements/element_qr_scanner_v3.svelte
Normal file
143
src/lib/elements/element_qr_scanner_v3.svelte
Normal file
@@ -0,0 +1,143 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* src/lib/elements/element_qr_scanner_v3.svelte
|
||||
* QR Scanner v3 — Svelte 5 runes, auto-starts, no manual permission step.
|
||||
*
|
||||
* html5-qrcode's .start() handles camera permission internally.
|
||||
* A unique viewfinder ID is generated per instance so multiple scanners
|
||||
* can coexist on the same page without collision.
|
||||
*
|
||||
* Props:
|
||||
* start_qr_scanner (bindable) — true = scan, false = stop
|
||||
* on_qr_scan_result — callback fired with { detail: { result, entry_method } }
|
||||
* qr_fps — scan frames per second (default 10)
|
||||
* qr_facing_mode — 'environment' (rear) or 'user' (front)
|
||||
*/
|
||||
import { onDestroy, untrack } from 'svelte';
|
||||
import { Html5Qrcode, Html5QrcodeSupportedFormats } from 'html5-qrcode';
|
||||
|
||||
interface Props {
|
||||
start_qr_scanner?: boolean;
|
||||
on_qr_scan_result?: (event: { detail: { result: string; entry_method: string } }) => void;
|
||||
qr_fps?: number;
|
||||
qr_facing_mode?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
start_qr_scanner = $bindable(true),
|
||||
on_qr_scan_result,
|
||||
qr_fps = 10,
|
||||
qr_facing_mode = 'environment'
|
||||
}: Props = $props();
|
||||
|
||||
// Unique DOM ID per instance — prevents conflicts when multiple scanners mount
|
||||
const viewfinder_id = `qr_vf_${Math.random().toString(36).substring(2, 9)}`;
|
||||
|
||||
let scanner: Html5Qrcode | null = null;
|
||||
let status = $state<'idle' | 'starting' | 'scanning' | 'error'>('idle');
|
||||
let error_msg = $state('');
|
||||
|
||||
// React to start_qr_scanner prop changes from the parent
|
||||
$effect(() => {
|
||||
const should_scan = start_qr_scanner;
|
||||
|
||||
untrack(() => {
|
||||
if (should_scan && (status === 'idle' || status === 'error')) {
|
||||
start_scanning();
|
||||
} else if (!should_scan && status === 'scanning') {
|
||||
stop_scanning();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
stop_scanning();
|
||||
});
|
||||
|
||||
async function start_scanning() {
|
||||
if (status === 'starting' || status === 'scanning') return;
|
||||
status = 'starting';
|
||||
error_msg = '';
|
||||
|
||||
try {
|
||||
scanner = new Html5Qrcode(viewfinder_id, {
|
||||
formatsToSupport: [Html5QrcodeSupportedFormats.QR_CODE],
|
||||
verbose: false
|
||||
});
|
||||
|
||||
await scanner.start(
|
||||
{ facingMode: qr_facing_mode },
|
||||
{
|
||||
fps: qr_fps,
|
||||
// Use a percentage of the viewfinder so it scales on any screen size
|
||||
qrbox: (w: number, h: number) => {
|
||||
const side = Math.floor(Math.min(w, h) * 0.82);
|
||||
return { width: side, height: side };
|
||||
}
|
||||
},
|
||||
on_scan_success,
|
||||
on_scan_error
|
||||
);
|
||||
|
||||
status = 'scanning';
|
||||
} catch (e: any) {
|
||||
status = 'error';
|
||||
if (e?.name === 'NotAllowedError') {
|
||||
error_msg = 'Camera access denied. Please allow camera in your browser settings and try again.';
|
||||
} else {
|
||||
error_msg = 'Could not start camera. Please try again.';
|
||||
}
|
||||
console.warn('[QR v3] Scanner start failed:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function stop_scanning() {
|
||||
if (!scanner) return;
|
||||
const s = scanner;
|
||||
scanner = null;
|
||||
try {
|
||||
await s.stop();
|
||||
await s.clear();
|
||||
} catch {
|
||||
// Ignore cleanup errors — component may be unmounting
|
||||
}
|
||||
status = 'idle';
|
||||
}
|
||||
|
||||
function on_scan_success(decoded_text: string) {
|
||||
// Stop scanning before notifying parent so the camera shuts down cleanly
|
||||
stop_scanning().then(() => {
|
||||
start_qr_scanner = false;
|
||||
});
|
||||
|
||||
if (on_qr_scan_result) {
|
||||
on_qr_scan_result({ detail: { result: decoded_text, entry_method: 'QR' } });
|
||||
}
|
||||
}
|
||||
|
||||
function on_scan_error(_msg: string) {
|
||||
// Called on every frame that doesn't contain a QR code — expected, not an error
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Viewfinder fills whatever container the parent provides -->
|
||||
<div class="qr-scanner-v3 w-full h-full flex flex-col items-center justify-center">
|
||||
<div id={viewfinder_id} class="w-full h-full"></div>
|
||||
|
||||
{#if status === 'starting'}
|
||||
<div class="absolute inset-0 flex items-center justify-center bg-surface-900/40 rounded-xl">
|
||||
<p class="text-sm font-semibold opacity-70 animate-pulse">Starting camera...</p>
|
||||
</div>
|
||||
{:else if status === 'error'}
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center gap-4 bg-surface-900/80 rounded-xl p-6 text-center">
|
||||
<p class="text-sm text-error-400 font-semibold leading-snug">{error_msg}</p>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm preset-filled-primary"
|
||||
onclick={start_scanning}
|
||||
>
|
||||
Try Again
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,597 +0,0 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
log_lvl?: number;
|
||||
ws_connect?: boolean; // If true then we should be trying to connect to the WS server.
|
||||
ws_connect_status?: null | string;
|
||||
ws_server?: string;
|
||||
ws_retry_delay?: number;
|
||||
ws_retry_count?: number;
|
||||
base_url?: any;
|
||||
group_id?: string;
|
||||
client_id?: any;
|
||||
cmd?: null | string;
|
||||
msg?: null | string;
|
||||
type?: null | string; // msg, cmd, json, hello, bye
|
||||
trigger_send?: any;
|
||||
trigger_connect?: boolean;
|
||||
trigger_disconnect?: boolean;
|
||||
classes?: string;
|
||||
hide__ws_element?: boolean;
|
||||
hide__ws_form?: boolean;
|
||||
hide__ws_messages?: boolean;
|
||||
hide__ws_commands?: boolean;
|
||||
|
||||
ws_conn_status?: any;
|
||||
ws_recv_status?: any;
|
||||
ws_sent_status?: any;
|
||||
}
|
||||
|
||||
let {
|
||||
log_lvl = 0,
|
||||
ws_connect = $bindable(false),
|
||||
ws_connect_status = $bindable(null),
|
||||
ws_server = 'dev-api.oneskyit.com',
|
||||
ws_retry_delay = 3500,
|
||||
ws_retry_count = 0,
|
||||
base_url = `wss://${ws_server}/ws`,
|
||||
group_id = $bindable('ae-grp-99'),
|
||||
client_id = $bindable(Date.now()),
|
||||
cmd = $bindable(null),
|
||||
msg = $bindable(null),
|
||||
type = null,
|
||||
trigger_send = $bindable(null),
|
||||
trigger_connect = $bindable(false),
|
||||
trigger_disconnect = $bindable(false),
|
||||
classes = 'container p-1 bg-pink-100 text-xs mx-auto pb-16 mb-20 sm:mb-12 md:mb-8',
|
||||
hide__ws_element = $bindable(false),
|
||||
hide__ws_form = $bindable(true),
|
||||
hide__ws_messages = $bindable(false),
|
||||
hide__ws_commands = $bindable(false),
|
||||
|
||||
ws_conn_status = $bindable(null),
|
||||
ws_recv_status = $bindable(null),
|
||||
ws_sent_status = $bindable(null)
|
||||
}: Props = $props();
|
||||
|
||||
// import { run, prevent_default } from 'svelte/legacy';
|
||||
// import { createEventDispatcher, onMount } from 'svelte';
|
||||
|
||||
// *** Set initial variables
|
||||
// const dispatch = createEventDispatcher();
|
||||
|
||||
// JSON formatted data
|
||||
let ws_data: {
|
||||
client_id: string | null;
|
||||
target: string;
|
||||
type: string;
|
||||
msg: string | null;
|
||||
cmd: string | null;
|
||||
} = $state({
|
||||
client_id: null, // The device or browser ID if available.
|
||||
// 'src': null, // Sending client
|
||||
// 'account_id': null, // Essentially the person ID or user ID if available.
|
||||
// 'dest': null, // Destination client
|
||||
target: 'echo', // echo, dm (direct), grp (group), all (broadcast)
|
||||
type: 'cmd', // msg, cmd, json, hello, bye
|
||||
// 'grp': null, // Destination group
|
||||
msg: null, // Message string
|
||||
cmd: null // Command string
|
||||
// 'data': null,
|
||||
// 'b64': null,
|
||||
});
|
||||
|
||||
let ws_received_list_cmd: any[] = $state([]);
|
||||
let ws_received_list_other: any[] = $state([]);
|
||||
let ws_received_list_msg: string[] = [];
|
||||
|
||||
// onMount(async () => {
|
||||
// console.log('** Component Mounted: ** Element Websocket v2');
|
||||
// });
|
||||
|
||||
// *** Functions and Logic
|
||||
|
||||
function ws_connect_group_id({ group_id, client_id }: { group_id: string, client_id: any }) {
|
||||
if (!group_id) {
|
||||
group_id = 'ae-grp-99';
|
||||
console.log(
|
||||
`WS: No group_id specified! Setting to default: ${group_id}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!client_id) {
|
||||
client_id = Date.now();
|
||||
console.log(
|
||||
`WS: No client_id specified! Setting to current timestamp: ${client_id}`
|
||||
);
|
||||
// return false;
|
||||
}
|
||||
console.log(
|
||||
`WS Connect URL: ${base_url}/group/${group_id}/client/${client_id}`
|
||||
);
|
||||
let ws_connection = new WebSocket(
|
||||
`${base_url}/group/${group_id}/client/${client_id}`
|
||||
);
|
||||
|
||||
ws_connection.onopen = function () {
|
||||
console.log('WS: connected');
|
||||
|
||||
ws_connect_status = 'connected';
|
||||
|
||||
// dispatch('ws_conn', {
|
||||
// 'status': 'connected'
|
||||
// });
|
||||
ws_conn_status = 'connected';
|
||||
|
||||
ws_retry_count = 0;
|
||||
|
||||
// ws_connection.send(JSON.stringify({
|
||||
// client_id: client_id,
|
||||
// target: 'echo',
|
||||
// type: 'hello',
|
||||
// group_id: group_id,
|
||||
// msg: 'You are connected!'
|
||||
// }));
|
||||
|
||||
ws_connection.send(
|
||||
JSON.stringify({
|
||||
client_id: client_id,
|
||||
target: 'all',
|
||||
type: 'hello',
|
||||
group_id: group_id,
|
||||
msg: `Client ${client_id.toString().slice(-5)} connected!`
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
ws_connection.onmessage = function (event) {
|
||||
if (log_lvl) {
|
||||
console.log('WS: message received', event);
|
||||
}
|
||||
|
||||
let ws_recv_data = JSON.parse(event.data);
|
||||
if (log_lvl) {
|
||||
console.log('WS: Received data:', ws_recv_data);
|
||||
}
|
||||
|
||||
if (client_id == ws_recv_data.client_id) {
|
||||
if (log_lvl) {
|
||||
console.log(
|
||||
'WS: Message received was sent by self and is an echo that can be ignored.'
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ws_recv_data.type == 'cmd') {
|
||||
console.log(`WS: Type CMD: ${ws_recv_data.cmd}`);
|
||||
ws_received_list_cmd.unshift(ws_recv_data); // Add to the beginning of the list
|
||||
// ws_received_list_cmd.push(ws_recv_data); // Add to the end of the list
|
||||
ws_received_list_cmd = ws_received_list_cmd; // trigger Svelte update -2024-10-04
|
||||
} else {
|
||||
console.log('WS: Type other');
|
||||
ws_received_list_other.unshift(ws_recv_data); // Add to the beginning of the list
|
||||
// ws_received_list_other.push(ws_recv_data); // Add to the end of the list
|
||||
ws_received_list_other = ws_received_list_other; // trigger Svelte update -2024-10-04
|
||||
}
|
||||
|
||||
// dispatch('ws_recv', {
|
||||
// // 'client_id': ws_data.client_id, // The device or browser ID if available.
|
||||
// 'src': ws_recv_data.client_id, // The device or browser ID if available.
|
||||
// // 'account_id': ws_recv_data.account_id, // Essentially the person ID or user ID if available.
|
||||
// 'dest': group_id, // Destination client
|
||||
// 'target': ws_recv_data.target, // echo, dm (direct), grp (group), all (broadcast)
|
||||
// 'type': ws_recv_data.type, // Message type (msg, cmd, json, hello, bye)
|
||||
// // 'grp': ws_recv_data.grp, // Destination group
|
||||
// 'msg': ws_recv_data.msg, // Message string
|
||||
// 'cmd': ws_recv_data.cmd, // Command string
|
||||
// });
|
||||
ws_recv_status = {
|
||||
// 'client_id': ws_data.client_id, // The device or browser ID if available.
|
||||
src: ws_recv_data.client_id, // The device or browser ID if available.
|
||||
// 'account_id': ws_recv_data.account_id, // Essentially the person ID or user ID if available.
|
||||
dest: group_id, // Destination client
|
||||
target: ws_recv_data.target, // echo, dm (direct), grp (group), all (broadcast)
|
||||
type: ws_recv_data.type, // Message type (msg, cmd, json, hello, bye)
|
||||
// 'grp': ws_recv_data.grp, // Destination group
|
||||
msg: ws_recv_data.msg, // Message string
|
||||
cmd: ws_recv_data.cmd // Command string
|
||||
};
|
||||
};
|
||||
|
||||
ws_connection.onclose = function (event) {
|
||||
console.log('WS: connection closed');
|
||||
|
||||
ws_connection.send(
|
||||
JSON.stringify({
|
||||
client_id: client_id,
|
||||
target: 'all',
|
||||
type: 'hello',
|
||||
group_id: group_id,
|
||||
msg: `Client ${client_id} is disconnecting!`
|
||||
})
|
||||
);
|
||||
|
||||
ws_connect_status = 'disconnected';
|
||||
|
||||
let fake_ws_recv_data = {
|
||||
client_id: client_id,
|
||||
target: 'local',
|
||||
type: 'bye',
|
||||
group_id: group_id,
|
||||
msg: `LOCAL Client ${client_id} has disconnected!`
|
||||
};
|
||||
ws_received_list_other.unshift(fake_ws_recv_data);
|
||||
ws_received_list_other = ws_received_list_other; // trigger Svelte update -2024-10-04
|
||||
|
||||
// dispatch('ws_conn', {
|
||||
// 'status': 'disconnected'
|
||||
// });
|
||||
ws_conn_status = 'disconnected';
|
||||
|
||||
if (ws_connect) {
|
||||
if (ws_retry_count >= 10) {
|
||||
ws_retry_delay = ws_retry_delay += 4999; // Set to a very long time
|
||||
ws_retry_delay = Math.min(ws_retry_delay, 120000); // Max of 2 minutes
|
||||
console.log(
|
||||
`WS: Retry count exceeded. Increasing the delay. ws_retry_count=${ws_retry_count} ws_retry_delay=${ws_retry_delay}`
|
||||
);
|
||||
}
|
||||
setTimeout(function () {
|
||||
console.log('WS: Disconnected... Try again!');
|
||||
ws_retry_count += 1;
|
||||
ws_connect_group_id({
|
||||
group_id: group_id,
|
||||
client_id: client_id
|
||||
});
|
||||
console.log('WS: Again done?');
|
||||
}, ws_retry_delay);
|
||||
}
|
||||
};
|
||||
|
||||
ws_connection.onerror = function (event) {
|
||||
console.log('WS: connection error???');
|
||||
|
||||
ws_connection.send(
|
||||
JSON.stringify({
|
||||
client_id: client_id,
|
||||
target: 'all',
|
||||
type: 'hello',
|
||||
group_id: group_id,
|
||||
msg: `Client ${client_id} is having trouble?!`
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
// NOTE WARNING: Uncommenting this seems to break FastAPI somehow???
|
||||
// NOTE: from FastAPI log: RuntimeError: Unexpected ASGI message 'websocket.send', after sending 'websocket.close'.
|
||||
// ws_connection.onerror = function(err) {
|
||||
// console.error('WS socket error: ', err.message, 'Closing socket');
|
||||
// // ws_connection.close();
|
||||
// };
|
||||
|
||||
return ws_connection;
|
||||
}
|
||||
|
||||
// Start the WS function
|
||||
let ws_group: any = $state(null);
|
||||
|
||||
function handle_send_ws_data() {
|
||||
console.log(ws_data);
|
||||
if (!ws_data) {
|
||||
return false;
|
||||
}
|
||||
if (!ws_group) {
|
||||
console.log('WS: No connection!');
|
||||
return false;
|
||||
}
|
||||
let ws_data_json_str = JSON.stringify(ws_data);
|
||||
let resp = ws_group.send(ws_data_json_str);
|
||||
console.log(`WS: Send data response:`, resp);
|
||||
|
||||
// dispatch('ws_sent', {
|
||||
// // 'client_id': ws_data.client_id, // The device or browser ID if available.
|
||||
// 'src': ws_data.client_id, // The device or browser ID if available.
|
||||
// // 'account_id': ws_data.account_id, // Essentially the person ID or user ID if available.
|
||||
// 'dest': group_id, // Destination client
|
||||
// 'group_id': group_id,
|
||||
// 'target': ws_data.target, // echo, dm (direct), grp (group), all (broadcast)
|
||||
// 'type': ws_data.type, // Message type (msg, cmd, json, hello, bye)
|
||||
// // 'grp': ws_data.grp, // Destination group
|
||||
// 'msg': ws_data.msg, // Message string
|
||||
// 'cmd': ws_data.cmd, // Command string
|
||||
// });
|
||||
ws_sent_status = {
|
||||
// 'client_id': ws_data.client_id, // The device or browser ID if available.
|
||||
src: ws_data.client_id, // The device or browser ID if available.
|
||||
// 'account_id': ws_data.account_id, // Essentially the person ID or user ID if available.
|
||||
dest: group_id, // Destination client
|
||||
group_id: group_id,
|
||||
target: ws_data.target, // echo, dm (direct), grp (group), all (broadcast)
|
||||
type: ws_data.type, // Message type (msg, cmd, json, hello, bye)
|
||||
// 'grp': ws_data.grp, // Destination group
|
||||
msg: ws_data.msg, // Message string
|
||||
cmd: ws_data.cmd // Command string
|
||||
};
|
||||
|
||||
cmd = '';
|
||||
msg = '';
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (ws_connect && group_id) {
|
||||
console.log('HERE!!!!!');
|
||||
ws_group = ws_connect_group_id({
|
||||
group_id: group_id,
|
||||
client_id: client_id
|
||||
});
|
||||
// } else if (!ws_connect) {
|
||||
// console.log('HERE!!!!!');
|
||||
// log_lvl = 1;
|
||||
// if (log_lvl) {
|
||||
// console.log(`WS: WS not set to connect. Need to close WS Group connection.`);
|
||||
// }
|
||||
// ws_group?.close();
|
||||
// ws_connect_status = 'disconnected';
|
||||
} else {
|
||||
console.log('HERE!!!!!');
|
||||
log_lvl = 1;
|
||||
if (log_lvl) {
|
||||
console.log(
|
||||
`WS: Not connecting. ws_connect=${ws_connect} group_id=${group_id}`
|
||||
);
|
||||
}
|
||||
ws_group?.close();
|
||||
ws_connect_status = 'disconnected';
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (trigger_send && cmd) {
|
||||
trigger_send = null;
|
||||
console.log('WS: Send triggered!');
|
||||
console.log(cmd);
|
||||
ws_data.target = 'group';
|
||||
ws_data.type = 'cmd';
|
||||
ws_data.cmd = cmd;
|
||||
handle_send_ws_data();
|
||||
cmd = '';
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (trigger_connect) {
|
||||
trigger_connect = false;
|
||||
if (!ws_connect) {
|
||||
ws_connect = true;
|
||||
}
|
||||
console.log('WS: Connect triggered!');
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (trigger_disconnect) {
|
||||
console.log('WS: Disconnect triggered!');
|
||||
trigger_disconnect = false;
|
||||
if (ws_connect) {
|
||||
ws_connect = false;
|
||||
}
|
||||
if (ws_group) {
|
||||
ws_group.close();
|
||||
ws_group = null;
|
||||
ws_connect_status = 'disconnected';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
return function (event: T) {
|
||||
event.preventDefault();
|
||||
fn(event);
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<section
|
||||
class:hidden={!ws_connect || hide__ws_element}
|
||||
class="ae_element__websocket container p-1 bg-pink-100 text-xs mx-auto pb-16 mt-32 mb-32 relative"
|
||||
>
|
||||
<span class="absolute top-0 right-0 flex flex-col gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
hide__ws_form = !hide__ws_form;
|
||||
}}
|
||||
class="btn btn-sm text-xs hover:preset-filled-tertiary-500"
|
||||
class:preset-tonal-tertiary={hide__ws_form}
|
||||
class:preset-filled-tertiary-500={!hide__ws_form}
|
||||
>
|
||||
{#if hide__ws_form}
|
||||
Show Form
|
||||
{:else}
|
||||
Hide Form?
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
hide__ws_messages = !hide__ws_messages;
|
||||
}}
|
||||
class="btn btn-sm text-xs hover:preset-filled-tertiary-500"
|
||||
class:preset-tonal-tertiary={hide__ws_messages}
|
||||
class:preset-filled-tertiary-500={!hide__ws_messages}
|
||||
>
|
||||
{#if hide__ws_messages}
|
||||
Show Messages
|
||||
{:else}
|
||||
Hide Messages?
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
hide__ws_commands = !hide__ws_commands;
|
||||
}}
|
||||
class="btn btn-sm text-xs hover:preset-filled-tertiary-500"
|
||||
class:preset-tonal-tertiary={hide__ws_commands}
|
||||
class:preset-filled-tertiary-500={!hide__ws_commands}
|
||||
>
|
||||
{#if hide__ws_commands}
|
||||
Show Commands
|
||||
{:else}
|
||||
Hide Commands?
|
||||
{/if}
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<header>
|
||||
<h1 class="h6 text-center">Websocket Messages & Commands</h1>
|
||||
</header>
|
||||
|
||||
<!-- <form on:submit|prevent_default={handle_send_message}>
|
||||
<select bind:value={type}>
|
||||
<option value="">None</option>
|
||||
<option value="echo">Echo</option>
|
||||
<option value="dm">Direct Message</option>
|
||||
<option value="group">Group Message</option>
|
||||
<option value="all">Broadcast to All</option>
|
||||
<option value="cmd">Command</option>
|
||||
</select>
|
||||
|
||||
<select bind:value={group_id}>
|
||||
<option value="">None</option>
|
||||
<option value="test_grp_123">123</option>
|
||||
<option value="test_grp_999">999</option>
|
||||
<option value="test_grp_poster">A Poster Group</option>
|
||||
</select>
|
||||
|
||||
<input type="text" bind:value={message_text} placeholder="Your message"/>
|
||||
|
||||
<button>Send</button>
|
||||
</form> -->
|
||||
|
||||
{#if !hide__ws_form}
|
||||
<form onsubmit={prevent_default(handle_send_ws_data)}>
|
||||
<select bind:value={ws_data.type} class="input text-sm w-24">
|
||||
<option value="">None</option>
|
||||
<option value="echo">Echo</option>
|
||||
<option value="dm">Direct Message</option>
|
||||
<option value="group">Group Message</option>
|
||||
<option value="all">Broadcast to All</option>
|
||||
<option value="cmd">Command</option>
|
||||
</select>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={group_id}
|
||||
placeholder="Group ID"
|
||||
class="input text-sm w-36"
|
||||
/>
|
||||
<!-- <select bind:value={group_id}>
|
||||
<option value="">None</option>
|
||||
<option value="test_grp_123">123</option>
|
||||
<option value="test_grp_999">999</option>
|
||||
<option value="test_grp_poster">A Poster Group</option>
|
||||
</select> -->
|
||||
|
||||
<!-- <input type="text" bind:value={dm_client_id} placeholder="Direct message client ID"/> -->
|
||||
|
||||
<input
|
||||
type="text"
|
||||
bind:value={ws_data.cmd}
|
||||
placeholder="Your command"
|
||||
class="input text-sm w-36"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={ws_data.msg}
|
||||
placeholder="Your message"
|
||||
class="input text-xs w-96"
|
||||
/>
|
||||
|
||||
<button type="submit" class="btn btn-sm preset-tonal-warning"
|
||||
>Send Group</button
|
||||
>
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
<!-- <pre>
|
||||
ae_load:event_session=jEG9APQRUs8 (Poster Session #3: Work Never Ends - Pythagoras)
|
||||
ae_open:event_file=CHqU5sW7xbc (jpg)
|
||||
ae_open:event_file=Kljq0uiTlXt (video)
|
||||
</pre> -->
|
||||
|
||||
<!-- <hr> -->
|
||||
|
||||
<section class:hidden={hide__ws_messages}>
|
||||
<h2 class="text-center underline">
|
||||
Messages [grp, client, target, type]
|
||||
</h2>
|
||||
|
||||
<ol class="list-decimal list-outside max-h-24 overflow-y-auto messages">
|
||||
{#each ws_received_list_other as msg_entry, index (index)}
|
||||
<li class="ml-4">
|
||||
<div class="flex flex-row justify-between gap-1 w-full">
|
||||
<span>
|
||||
[{msg_entry.group_id || 'No Group ID'}]
|
||||
{msg_entry.client_id.toString().slice(-5) ||
|
||||
'No Client ID'}
|
||||
–
|
||||
{msg_entry.target || 'No Target'}
|
||||
|
|
||||
<!-- – -->
|
||||
{msg_entry.type || 'No Type'}:
|
||||
</span>
|
||||
<span class="justify-self-end">
|
||||
"{msg_entry.msg}"
|
||||
</span>
|
||||
<!-- <br>{JSON.stringify(msg_entry)} -->
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ol>
|
||||
</section>
|
||||
<!-- End Messages -->
|
||||
|
||||
<hr />
|
||||
|
||||
<section class:hidden={hide__ws_commands}>
|
||||
<h2 class="text-center underline">Commands</h2>
|
||||
|
||||
<ol class="list-decimal list-outside max-h-24 overflow-y-auto commands">
|
||||
{#each ws_received_list_cmd as cmd_entry, index (index)}
|
||||
<li class="ml-4">
|
||||
<div class="flex flex-row justify-between gap-1 w-full">
|
||||
<span>
|
||||
[{cmd_entry.group_id || 'No Group ID'}]
|
||||
{cmd_entry.client_id.toString().slice(-5) ||
|
||||
'No Client ID'}
|
||||
—
|
||||
{cmd_entry.target || 'No Target'}
|
||||
|
|
||||
<!-- — -->
|
||||
{cmd_entry.type || 'No Type'}:
|
||||
</span>
|
||||
<span class="justify-self-end">
|
||||
"{cmd_entry.cmd}"
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ol>
|
||||
</section>
|
||||
<!-- End Commands -->
|
||||
</section>
|
||||
|
||||
<style>
|
||||
/* .websocket_element {
|
||||
background-color: white;
|
||||
border-top: dashed medium gray;
|
||||
color: gray;
|
||||
font-size: .7em;
|
||||
}
|
||||
.websocket_element header {
|
||||
font-size: .7em;
|
||||
}
|
||||
.websocket_element h2 {
|
||||
font-size: .9em;
|
||||
} */
|
||||
</style>
|
||||
Reference in New Issue
Block a user