Refactor: Organize src/lib into subdirectories and move files accordingly.
This commit is contained in:
27
src/lib/app_components/analytics.svelte
Normal file
27
src/lib/app_components/analytics.svelte
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
export let log_lvl: number = 0;
|
||||
|
||||
export let site_google_tracking_id: string = '';
|
||||
if (log_lvl) {
|
||||
console.log(`AE Analytics: site_google_tracking_id = `, site_google_tracking_id);
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.gtag = function gtag(): void {
|
||||
window.dataLayer.push(arguments);
|
||||
};
|
||||
window.gtag('js', new Date());
|
||||
window.gtag('config', site_google_tracking_id);
|
||||
if (log_lvl) {
|
||||
console.log(`AE Analytics: Google Analytics Tracking ID = `, site_google_tracking_id);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<script
|
||||
async
|
||||
src="https://www.googletagmanager.com/gtag/js?id={site_google_tracking_id}">
|
||||
</script>
|
||||
</svelte:head>
|
||||
620
src/lib/app_components/e_app_access_type.svelte
Normal file
620
src/lib/app_components/e_app_access_type.svelte
Normal file
@@ -0,0 +1,620 @@
|
||||
<script lang="ts">
|
||||
|
||||
// *** Import Svelte specific
|
||||
import { onDestroy, onMount, tick } from 'svelte';
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
|
||||
// *** Import other supporting libraries
|
||||
// import { liveQuery } from "dexie";
|
||||
import {
|
||||
ShieldEllipsis, ShieldMinus, ShieldPlus, ShieldUser,
|
||||
User, UserCheck
|
||||
} from '@lucide/svelte';
|
||||
|
||||
// *** Import Aether specific variables and functions
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
import { ae_loc, ae_sess, ae_api, slct, slct_trigger } from '$lib/ae_stores';
|
||||
// import { core_func } from '$lib/ae_core/ae_core_functions';
|
||||
// Ideally the Event related stores should not be imported here?
|
||||
import { events_loc } from '$lib/ae_events_stores';
|
||||
// import { db_events } from "$lib/db_events";
|
||||
|
||||
// export let hidden: boolean = false;
|
||||
|
||||
// *** Setup Svelte properties
|
||||
interface Props {
|
||||
log_lvl?: number;
|
||||
hide?: null|boolean;
|
||||
focus_input: boolean;
|
||||
expand?: boolean;
|
||||
show_passcode_input: boolean;
|
||||
trigger_clear_access: null|boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
log_lvl = $bindable(0),
|
||||
hide = $bindable(false),
|
||||
focus_input = $bindable(false),
|
||||
expand = $bindable(false),
|
||||
show_passcode_input = $bindable(false),
|
||||
trigger_clear_access = $bindable(null),
|
||||
}: Props = $props();
|
||||
|
||||
|
||||
let entered_passcode: null|string = $state(null);
|
||||
let checked_passcode: null|string = $state(null);
|
||||
// let password_checked: boolean = $state(false);
|
||||
// let entered_passcode: null|string = '';
|
||||
// let show_passcode_input: boolean = $state(false);
|
||||
// let show_passcode_input: boolean = false;
|
||||
|
||||
// let trigger: null|string|boolean = null;
|
||||
let trigger: null|string|boolean = $state(null);
|
||||
|
||||
// const dispatch = createEventDispatcher();
|
||||
|
||||
|
||||
// WARNING: There is a bug (I think) around here related to the entered_passcode not being cleared. There seems to be something different about how Svelte handles state in this component compared to the others. This might be related to the `$effect` or `$derived` usage. Maybe there are conflicting things trying to update the $ae_loc store at the same time.
|
||||
onMount(() => {
|
||||
log_lvl = 2;
|
||||
if (log_lvl > 1) {
|
||||
console.log('** Element Mounted: ** Element Access Type');
|
||||
}
|
||||
entered_passcode = '';
|
||||
trigger = null;
|
||||
|
||||
// /** @type {HTMLElement | null} */
|
||||
// const to_focus = document.getElementById('access_passcode_input');
|
||||
// to_focus?.focus();
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (log_lvl > 1) {
|
||||
console.log('** Element Destroyed: ** Element Access Type');
|
||||
}
|
||||
|
||||
// Clean up any references or listeners if needed
|
||||
entered_passcode = null; // Clear the entered passcode
|
||||
show_passcode_input = false;
|
||||
|
||||
// Reset trigger
|
||||
trigger = null;
|
||||
});
|
||||
|
||||
|
||||
// afterNavigate(() => {
|
||||
// /** @type {HTMLElement | null} */
|
||||
// const to_focus = document.getElementById('access_passcode_input');
|
||||
// to_focus?.focus();
|
||||
// });
|
||||
|
||||
$effect(() => {
|
||||
if (entered_passcode && entered_passcode.length >= 5 && entered_passcode != checked_passcode) {
|
||||
checked_passcode = entered_passcode;
|
||||
if (log_lvl) {
|
||||
console.log(`entered_passcode=${entered_passcode}`);
|
||||
}
|
||||
handle_check_access_type_passcode();
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (trigger && $ae_loc.access_type) {
|
||||
trigger = false;
|
||||
if (log_lvl) {
|
||||
console.log(`$ae_loc.access_type=${$ae_loc.access_type}`);
|
||||
}
|
||||
|
||||
let access_checks_results = ae_util.process_permission_checks($ae_loc.access_type);
|
||||
|
||||
$ae_loc = {...$ae_loc, ...access_checks_results};
|
||||
$ae_loc = $ae_loc;
|
||||
$ae_loc.sys_menu.expand = false;
|
||||
} else if (trigger) {
|
||||
trigger = false;
|
||||
if (log_lvl) {
|
||||
console.log(`$ae_loc.access_type=not set`);
|
||||
}
|
||||
|
||||
// Send an empty string to reset the permissions. This is the same as sending 'anonymous'.
|
||||
let access_checks_results = ae_util.process_permission_checks('');
|
||||
|
||||
$ae_loc = {...$ae_loc, ...access_checks_results};
|
||||
$ae_loc = $ae_loc;
|
||||
}
|
||||
});
|
||||
|
||||
// This does not seem to work. I feel like it should though...?
|
||||
$effect(() => {
|
||||
if (!hide && focus_input) {
|
||||
focus_input = false;
|
||||
log_lvl = 2;
|
||||
// await tick();
|
||||
// document.getElementById('access_passcode_input')?.focus();
|
||||
if (log_lvl > 1) {
|
||||
console.log('Effect: Setting focus on the passcode input field');
|
||||
}
|
||||
/** @type {HTMLElement | null} */
|
||||
const to_focus = document.getElementById('access_passcode_input');
|
||||
to_focus?.focus();
|
||||
}
|
||||
});
|
||||
|
||||
$effect(async () => {
|
||||
if (trigger_clear_access) {
|
||||
trigger_clear_access = false;
|
||||
if (log_lvl) {
|
||||
console.log(`trigger_clear_access=${trigger_clear_access}`);
|
||||
}
|
||||
handle_clear_access();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function handle_check_access_type_passcode() {
|
||||
if (log_lvl > 1) {
|
||||
console.log(`*** handle_check_access_type_passcode() *** passcode list:`, $ae_loc.site_access_code_kv);
|
||||
}
|
||||
|
||||
// Reminder: super > manager > administrator > trusted > public > authenticated > anonymous
|
||||
|
||||
if (entered_passcode && entered_passcode.length >= 5) {
|
||||
|
||||
if ($ae_loc.site_access_code_kv.super.length >= 8 && $ae_loc.site_access_code_kv.super == entered_passcode) {
|
||||
console.log('Super passcode matched');
|
||||
|
||||
window.localStorage.setItem('access_type', 'super');
|
||||
|
||||
$ae_loc.access_type = 'super';
|
||||
} else if ($ae_loc.site_access_code_kv.manager.length >= 5 && $ae_loc.site_access_code_kv.manager == entered_passcode) {
|
||||
console.log('Manager passcode matched');
|
||||
|
||||
window.localStorage.setItem('access_type', 'manager');
|
||||
|
||||
$ae_loc.access_type = 'manager';
|
||||
} else if ($ae_loc.site_access_code_kv.administrator.length >= 5 && $ae_loc.site_access_code_kv.administrator == entered_passcode) {
|
||||
console.log('Administrator passcode matched');
|
||||
|
||||
window.localStorage.setItem('access_type', 'administrator');
|
||||
|
||||
$ae_loc.access_type = 'administrator';
|
||||
} else if ($ae_loc.site_access_code_kv.trusted.length >= 5 && $ae_loc.site_access_code_kv.trusted == entered_passcode) {
|
||||
console.log('Trusted passcode matched');
|
||||
|
||||
window.localStorage.setItem('access_type', 'trusted');
|
||||
|
||||
$ae_loc.access_type = 'trusted';
|
||||
} else if ($ae_loc.site_access_code_kv.public.length >= 5 && $ae_loc.site_access_code_kv.public == entered_passcode) {
|
||||
console.log('Public passcode matched');
|
||||
|
||||
window.localStorage.setItem('access_type', 'public');
|
||||
|
||||
$ae_loc.access_type = 'public';
|
||||
} else if ($ae_loc.site_access_code_kv.authenticated == entered_passcode) {
|
||||
console.log('Authenticated passcode matched');
|
||||
|
||||
window.localStorage.setItem('access_type', 'authenticated');
|
||||
|
||||
$ae_loc.access_type = 'authenticated';
|
||||
} else {
|
||||
if (log_lvl > 1) {
|
||||
console.log('Entered passcode does not match any of the site access codes.');
|
||||
}
|
||||
|
||||
if ($ae_loc.access_type != 'anonymous') {
|
||||
console.log('Access type is not anonymous');
|
||||
}
|
||||
// window.localStorage.setItem('access_type', 'anonymous');
|
||||
|
||||
// $ae_loc.access_type = 'anonymous';
|
||||
|
||||
// trigger = 'process_permission_check';
|
||||
|
||||
// $ae_loc = $ae_loc; // Trigger Svelte just in case
|
||||
// ae_loc.set($ae_loc);
|
||||
// console.log($ae_loc);
|
||||
|
||||
// dispatch_access_type_changed();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
entered_passcode = '';
|
||||
trigger = 'process_permission_check';
|
||||
|
||||
|
||||
$ae_loc.app_cfg.show_element__menu = false;
|
||||
$ae_loc.app_cfg.show_element__menu_btn = true;
|
||||
|
||||
// WARNING 2024-08-21: For some reason the config element does not auto show or hide when the access type changes.
|
||||
if (!$ae_loc.iframe && $ae_loc.authenticated_access) {
|
||||
$ae_loc.app_cfg.show_element__access_type = true;
|
||||
$ae_loc.app_cfg.show_element__cfg = true;
|
||||
} else if ($ae_loc.iframe && $ae_loc.trusted_access) {
|
||||
$ae_loc.app_cfg.show_element__access_type = true;
|
||||
$ae_loc.app_cfg.show_element__cfg = true;
|
||||
} else {
|
||||
$ae_loc.app_cfg.show_element__access_type = true;
|
||||
$ae_loc.app_cfg.show_element__cfg = false;
|
||||
}
|
||||
|
||||
// dispatch_access_type_changed();
|
||||
|
||||
return true;
|
||||
} else {
|
||||
if (log_lvl > 1) {
|
||||
console.log('Entered passcode too short.');
|
||||
}
|
||||
|
||||
// $ae_loc.access_type = null; // 'anonymous';
|
||||
|
||||
// dispatch_access_type_changed()
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function handle_clear_access() {
|
||||
// console.log('handle_clear_access()');
|
||||
// NOTE: I think it makes since to reset this to anonymous even if logged in as an admin or similar.
|
||||
window.localStorage.setItem('access_type', 'anonymous');
|
||||
|
||||
// $ae_loc.access_type = null; // 'anonymous';
|
||||
// Revert back to the user's access type after quick access (temporarily escalate permissions) is turned off.
|
||||
$ae_loc.access_type = $ae_loc.user_access_type ?? 'anonymous';
|
||||
trigger = 'process_permission_check';
|
||||
|
||||
entered_passcode = ''; // Clear the entered passcode
|
||||
show_passcode_input = true;
|
||||
|
||||
$ae_loc.app_cfg.show_element__menu = false;
|
||||
$ae_loc.app_cfg.show_element__menu_btn = true;
|
||||
|
||||
$ae_loc.edit_mode = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<section
|
||||
id="AE-Quick-Access-Type"
|
||||
class="
|
||||
ae_access_type
|
||||
hidden-print
|
||||
|
||||
bg-blue-100 text-gray-900
|
||||
dark:bg-blue-800 dark:text-gray-200
|
||||
|
||||
flex flex-col flex-wrap gap-1
|
||||
items-end justify-center
|
||||
|
||||
w-full
|
||||
p-1
|
||||
border-2 border-gray-200
|
||||
|
||||
duration-300 delay-150 hover:delay-1000 hover:ease-out
|
||||
transition-all
|
||||
"
|
||||
class:hidden={hide}
|
||||
>
|
||||
|
||||
<!-- class:hidden={!$ae_sess.show__sign_in_out__fields} -->
|
||||
<header
|
||||
class="ae_header hidden"
|
||||
>
|
||||
<h2 class="text-sm text-center font-semibold">
|
||||
Passcode Sign In
|
||||
</h2>
|
||||
</header>
|
||||
|
||||
<!-- Show list of authorized sessions and presentations for a user -->
|
||||
<!-- {#if $ae_loc.access_type == 'administrator'}
|
||||
{#if $events_loc.auth__kv.session}
|
||||
Sessions:
|
||||
<ul>
|
||||
{#each Object.entries($events_loc.auth__kv.session) as [key, value]}
|
||||
<li><a href="/events/{event_id}/session/{key}">
|
||||
{key}
|
||||
</a></li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{/if} -->
|
||||
|
||||
|
||||
<div class="transition-all">
|
||||
{#if $ae_loc.trusted_access && $ae_loc.edit_mode}
|
||||
{#if $ae_loc.manager_access}
|
||||
{#if $ae_loc?.sync_local_config}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
$ae_loc.sync_local_config = false;
|
||||
$events_loc.pres_mgmt.sync_local_config = false;
|
||||
|
||||
$ae_loc.lock_config = false;
|
||||
$events_loc.pres_mgmt.lock_config = false;
|
||||
|
||||
// dispatch_sync_local_config_changed();
|
||||
// tick();
|
||||
return false;
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-success border border-success-500 hover:preset-filled-success-500 transition-all hover:transition-all *:hover:inline"
|
||||
title="Syncing the local configuration with the remote configuration."
|
||||
>
|
||||
<span class="fas fa-sync m-1"></span>
|
||||
<span class="hidden">
|
||||
Sync
|
||||
</span>
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
$ae_loc.sync_local_config = true;
|
||||
$events_loc.pres_mgmt.sync_local_config = true;
|
||||
|
||||
$ae_loc.lock_config = true;
|
||||
$events_loc.pres_mgmt.lock_config = true;
|
||||
|
||||
// dispatch_sync_local_config_changed();
|
||||
// tick();
|
||||
return true;
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-warning border border-warning-500 hover:preset-filled-warning-500 transition-all hover:transition-all *:hover:inline"
|
||||
title="Currently not syncing with the remote server. Re-sync the local configuration with the remote configuration?"
|
||||
>
|
||||
<span class="fas fa-unlink m-1"></span>
|
||||
<span class="hidden">
|
||||
Re-sync?
|
||||
</span>
|
||||
</button>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<!-- {#if $ae_loc.edit_mode}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
$ae_loc.edit_mode = false;
|
||||
// dispatch_edit_mode_changed();
|
||||
}}
|
||||
class="btn btn-sm px-2 variant-ghost-success hover:variant-filled-success transition-all"
|
||||
title="Edit mode is currently enabled. Click to disable."
|
||||
>
|
||||
<span class="fas fa-toggle-on mx-1"></span>
|
||||
Edit Mode On
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
$ae_loc.edit_mode = true;
|
||||
// dispatch_edit_mode_changed();
|
||||
}}
|
||||
class="btn btn-sm px-2 variant-ghost-warning hover:variant-filled-warning transition-all space-x-1 *:hover:inline"
|
||||
title="Edit mode is currently disabled. Click to enable."
|
||||
>
|
||||
<span class="fas fa-toggle-off mx-1"></span>
|
||||
Edit
|
||||
<span class="hidden">
|
||||
Mode?
|
||||
</span>
|
||||
</button>
|
||||
{/if} -->
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row flex-wrap gap-1 items-end justify-end w-full transition-all">
|
||||
|
||||
{#if $ae_loc?.access_type && $ae_loc?.access_type == 'anonymous' && 1==3}
|
||||
<span>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
// handle_check_access_type_passcode();
|
||||
trigger = true;
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-success hover:preset-filled-warning-500 access_type_unlock_btn transition-all"
|
||||
title="Anonymous public access is currently set. Access mode is disabled/locked."
|
||||
>
|
||||
<span class="fas fa-lock mx-1"></span>
|
||||
<span class="lock_icon">Locked</span>
|
||||
|
||||
<span class="fas fa-unlock mx-1 unlock_icon hidden"></span>
|
||||
{#if (show_passcode_input)}
|
||||
<span class="unlock_text">Cancel</span>
|
||||
{:else}
|
||||
<span class="unlock_text">Access?</span>
|
||||
{/if}
|
||||
</button>
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
{#if ($ae_loc?.access_type && $ae_loc?.access_type != 'anonymous')}
|
||||
<span class="flex flex-row gap-1 items-center justify-center">
|
||||
<!-- <span class="fas fa-unlock mx-1"></span> -->
|
||||
<ShieldPlus class="inline-block" />
|
||||
|
||||
<span
|
||||
class="*:hover:inline"
|
||||
title={`Current access type/level: ${$ae_loc.access_type}`}
|
||||
>
|
||||
{#if $ae_loc.access_type == 'super'}
|
||||
<span class="fas fa-hat-wizard m-1"></span>
|
||||
<span class="hidden">Super</span>
|
||||
{:else if $ae_loc.access_type == 'manager'}
|
||||
<span class="fas fa-user-shield m-1"></span>
|
||||
<span class="hidden">Manager</span>
|
||||
{:else if $ae_loc.access_type == 'administrator'}
|
||||
<span class="fas fa-user-ninja m-1"></span>
|
||||
<span class="hidden">Administrator</span>
|
||||
{:else if $ae_loc.access_type == 'trusted'}
|
||||
<span class="fas fa-user-check m-1"></span>
|
||||
<span class="hidden">Trusted Access</span>
|
||||
{:else if $ae_loc.access_type == 'public'}
|
||||
Public
|
||||
<span class="hidden">Access</span>
|
||||
{:else if $ae_loc.access_type == 'authenticated'}
|
||||
Authenticated
|
||||
<span class="hidden">Access</span>
|
||||
{:else if $ae_loc.access_type == 'anonymous'}
|
||||
Anonymous Access
|
||||
{:else}
|
||||
Unknown Access
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
{#if $ae_loc?.user_access_type && $ae_loc?.access_type == $ae_loc?.user_access_type && !show_passcode_input}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
// handle_clear_access();
|
||||
// trigger_clear_access = true;
|
||||
show_passcode_input = true;
|
||||
}}
|
||||
class="btn btn-sm variant-outline-surface hover:preset-tonal-warning border border-warning-500 transition-all"
|
||||
title={`Current user access level: "${$ae_loc.user_access_type}". Click use passcode for a different access level.`}
|
||||
>
|
||||
<!-- <span class="fas fa-lock mx-1"></span> -->
|
||||
<!-- <ShieldMinus /> -->
|
||||
<ShieldEllipsis class="inline-block" />
|
||||
Passcode?
|
||||
</button>
|
||||
{:else if (!show_passcode_input)}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
// handle_clear_access();
|
||||
trigger_clear_access = true;
|
||||
show_passcode_input = true;
|
||||
// show_passcode_input = true;
|
||||
}}
|
||||
class="btn btn-sm variant-outline-warning hover:preset-tonal-warning border border-warning-500 transition-all"
|
||||
title={`Current access level: "${$ae_loc.access_type}". Click to clear the temporary access level.`}
|
||||
>
|
||||
<!-- <span class="fas fa-lock mx-1"></span> -->
|
||||
<ShieldMinus class="inline-block" />
|
||||
Clear?
|
||||
</button>
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
{#if (show_passcode_input)}
|
||||
<span class="flex flex-row gap-1 items-center justify-between w-full">
|
||||
<span>
|
||||
<ShieldEllipsis class="inline-block text-gray-500" />
|
||||
<span class="unlock_text text-sm">Passcode:</span>
|
||||
</span>
|
||||
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<input
|
||||
id="access_passcode_input"
|
||||
bind:value={entered_passcode}
|
||||
class="input w-32 transition-all"
|
||||
class:hidden={!show_passcode_input}
|
||||
type="text"
|
||||
placeholder="Passcode"
|
||||
autofocus={show_passcode_input}
|
||||
/>
|
||||
<!-- <div class="current_text transition-all">{$ae_loc.access_type}</div> -->
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
/* BEGIN: AE's Svelte Quick Access Type component */
|
||||
/*
|
||||
#xxx-AE-Quick-Access-Type {
|
||||
// position: absolute;
|
||||
position: fixed;
|
||||
// position: relative;
|
||||
// position: static;
|
||||
// position: sticky;
|
||||
// top: 1em;
|
||||
bottom: 1.5rem;
|
||||
right: 0rem;
|
||||
|
||||
padding: .5rem;
|
||||
|
||||
// lightyellow
|
||||
// background-color: hsla(60,100%,90%,.30);
|
||||
// background-color: rgba(var(--color-surface-500) / .5);
|
||||
|
||||
border-top: solid thin hsla(0,0%,0%,.25);
|
||||
border-left: solid thin hsla(0,0%,0%,.25);
|
||||
border-bottom: solid thin hsla(0,0%,0%,.25);
|
||||
border-top-left-radius: .5em;
|
||||
border-bottom-left-radius: .5em;
|
||||
|
||||
opacity: .15;
|
||||
// opacity: 1;
|
||||
|
||||
font-size: .75rem;
|
||||
|
||||
z-index: 5;
|
||||
|
||||
// NOTE: transition when no longer hovering
|
||||
transition-property: opacity, background-color;
|
||||
transition-delay: 1.25s;
|
||||
transition-duration: .75s;
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
#xxx-AE-Quick-Access-Type:hover {
|
||||
|
||||
border-top: solid thin hsla(0,0%,0%,.95);
|
||||
border-left: solid thin hsla(0,0%,0%,.95);
|
||||
border-bottom: solid thin hsla(0,0%,0%,.95);
|
||||
|
||||
opacity: 1;
|
||||
|
||||
// NOTE: transition when hover starts
|
||||
transition-property: opacity, background-color;
|
||||
transition-delay: .5s;
|
||||
transition-duration: .25s;
|
||||
transition-timing-function: ease-in;
|
||||
}
|
||||
*/
|
||||
|
||||
/* #Access-Type .unlock_text {
|
||||
transition: width 2s, height 2s, background-color 2s, transform 2s;
|
||||
} */
|
||||
/* END: Svelte Access Type component */
|
||||
|
||||
.access_type_unlock_btn:hover .lock_icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.access_type_unlock_btn:hover .unlock_icon {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
.access_type_unlock_btn .unlock_text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.access_type_unlock_btn:hover .unlock_text {
|
||||
display: initial;
|
||||
/* outline: solid thin red; */
|
||||
}
|
||||
|
||||
/*
|
||||
.ae_access_type .current_text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ae_access_type:hover .current_text {
|
||||
display: initial;
|
||||
outline: solid thin red;
|
||||
}
|
||||
*/
|
||||
</style>
|
||||
352
src/lib/app_components/e_app_cfg.svelte
Normal file
352
src/lib/app_components/e_app_cfg.svelte
Normal file
@@ -0,0 +1,352 @@
|
||||
<script lang="ts">
|
||||
|
||||
import {
|
||||
Settings
|
||||
} from '@lucide/svelte';
|
||||
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
import { ae_loc, ae_sess, ae_api, slct, slct_trigger } from '$lib/ae_stores';
|
||||
|
||||
// import Element_theme from '$lib/e_app_theme.svelte';
|
||||
|
||||
let notes: null|string = null;
|
||||
let all: boolean = false;
|
||||
|
||||
interface Props {
|
||||
log_lvl?: number;
|
||||
hide?: null|boolean;
|
||||
expand?: boolean;
|
||||
// export let theme_mode: null|string = null;
|
||||
// set_theme_mode?: null|boolean;
|
||||
// export let theme_name: null|string = null;
|
||||
// set_theme_name?: null|boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
log_lvl = $bindable(0),
|
||||
hide = $bindable(false),
|
||||
expand = $bindable(false),
|
||||
// set_theme_mode = null,
|
||||
// set_theme_name = null
|
||||
}: Props = $props();
|
||||
|
||||
// const dispatch = createEventDispatcher();
|
||||
|
||||
|
||||
// onMount(() => {
|
||||
// // console.log('** Element Mounted: ** Element App Config');
|
||||
// if (set_theme_mode) {
|
||||
// $slct_trigger = 'set_theme_mode';
|
||||
// }
|
||||
// if (set_theme_name) {
|
||||
// $slct_trigger = 'set_theme_name';
|
||||
// }
|
||||
// });
|
||||
|
||||
// $: if ($slct_trigger == 'set_theme_mode' && $ae_loc?.app_cfg?.theme_mode) {
|
||||
// console.log(`$ae_loc.app_cfg.theme_mode=${$ae_loc?.app_cfg?.theme_mode}`);
|
||||
// $slct_trigger = null;
|
||||
// if ($ae_loc.app_cfg.theme_mode == 'light') {
|
||||
// document.documentElement.classList.remove('dark');
|
||||
// document.documentElement.classList.add('light');
|
||||
// } else if ($ae_loc.app_cfg.theme_mode == 'dark') {
|
||||
// document.documentElement.classList.remove('light');
|
||||
// document.documentElement.classList.add('dark');
|
||||
// }
|
||||
// }
|
||||
|
||||
// $: if ($slct_trigger == 'set_theme_name' && $ae_loc?.app_cfg?.theme_name) {
|
||||
// console.log(`$ae_loc?.app_cfg?.theme_name=${$ae_loc?.app_cfg?.theme_name}`);
|
||||
// $slct_trigger = null;
|
||||
// // Update the body attribute named "data-theme" to the current theme name.
|
||||
// document.body.setAttribute('data-theme', $ae_loc?.app_cfg?.theme_name);
|
||||
// }
|
||||
|
||||
// $: if (entered_passcode && entered_passcode.length >= 5) {
|
||||
// console.log(`entered_passcode=${entered_passcode}`);
|
||||
// handle_check_access_type_passcode();
|
||||
// }
|
||||
|
||||
// $: if (trigger && $ae_loc.access_type) {
|
||||
// console.log(`$ae_loc.access_type=${$ae_loc.access_type}`);
|
||||
|
||||
// let access_checks_results = ae_util.process_permission_checks($ae_loc.access_type);
|
||||
|
||||
// $ae_loc = {...$ae_loc, ...access_checks_results};
|
||||
// } else if (trigger) {
|
||||
// console.log(`$ae_loc.access_type=not set`);
|
||||
|
||||
// // Send an empty string to reset the permissions. This is the same as sending 'anonymous'.
|
||||
// let access_checks_results = ae_util.process_permission_checks('');
|
||||
|
||||
// $ae_loc = {...$ae_loc, ...access_checks_results};
|
||||
// }
|
||||
|
||||
|
||||
function handle_something() {
|
||||
// console.log('*** handle_something() ***');
|
||||
|
||||
}
|
||||
|
||||
function handle_clear_storage(item: null|string) {
|
||||
// console.log('*** handle_clear_storage() ***');
|
||||
// window.localStorage.setItem('access_type', 'anonymous');
|
||||
// return true;
|
||||
}
|
||||
|
||||
// function dispatch_something_changed() {
|
||||
// console.log('*** dispatch_something_changed() ***');
|
||||
|
||||
// console.log(ae_util);
|
||||
// console.log($ae_loc);
|
||||
|
||||
// dispatch('access_type_changed', {
|
||||
// access_type: $ae_loc.access_type
|
||||
// });
|
||||
// }
|
||||
</script>
|
||||
|
||||
|
||||
<!-- transition duration-500 delay-1000 hover:duration-500 hover:delay-1000 hover:transition-all -->
|
||||
<section
|
||||
id="AE-App-Cfg"
|
||||
class="
|
||||
ae_app_cfg
|
||||
hidden-print
|
||||
|
||||
bg-blue-100 text-gray-900
|
||||
dark:bg-blue-800 dark:text-gray-200
|
||||
|
||||
flex flex-col flex-wrap gap-1
|
||||
items-end justify-center
|
||||
|
||||
w-72 max-w-72
|
||||
p-1
|
||||
border-2 border-gray-200
|
||||
|
||||
duration-300 delay-150 hover:delay-1000 hover:ease-out
|
||||
transition-all
|
||||
"
|
||||
class:hidden={hide}
|
||||
>
|
||||
|
||||
<header
|
||||
class:hidden={!expand}
|
||||
class="ae_header w-full"
|
||||
>
|
||||
<h2 class="text-sm text-center font-semibold">
|
||||
Config
|
||||
</h2>
|
||||
</header>
|
||||
|
||||
|
||||
<div
|
||||
class="ae_cfg_content text-xs space-y-4 my-4"
|
||||
class:hidden={!expand}
|
||||
data-sveltekit-preload-data="false"
|
||||
>
|
||||
|
||||
|
||||
<section class="space-y-2">
|
||||
<div>
|
||||
<h2 class="strong">Access Type:</h2>
|
||||
</div>
|
||||
{#if $ae_loc.access_type && $ae_loc.access_type != 'anonymous'}
|
||||
{#if $ae_loc.access_type == 'super'}
|
||||
<span class="fas fa-secret mx-1"></span> Super Access
|
||||
{:else if $ae_loc.access_type == 'manager'}
|
||||
<span class="fas fa-user-shield mx-1"></span> Manager Access
|
||||
{:else if $ae_loc.access_type == 'administrator'}
|
||||
<span class="fas fa-user-ninja mx-1"></span> Administrator Access
|
||||
{:else if $ae_loc.access_type == 'trusted'}
|
||||
<span class="fas fa-user-nurse mx-1"></span> Trusted Access
|
||||
{:else if $ae_loc.access_type == 'authenticated'}
|
||||
<span class="fas fa-user-friends mx-1"></span> Authenticated Access
|
||||
{:else if $ae_loc.access_type == 'anonymous'}
|
||||
<span class="fas fa-users mx-1"></span> Anonymous Access
|
||||
{:else}
|
||||
<span class="fas fa-unlock mx-1"></span> Unknown Access
|
||||
{/if}
|
||||
|
||||
<!-- <button
|
||||
class="btn btn-sm variant-glass-secondary access_type_lock_btn hover:transition-all"
|
||||
on:click={() => {
|
||||
handle_clear_access();
|
||||
}}
|
||||
title="Access mode is currently enabled/unlocked. Click to exit and lock."
|
||||
>
|
||||
<span class="fas fa-lock mx-1"></span> Lock
|
||||
</button> -->
|
||||
{:else}
|
||||
Not logged in
|
||||
{/if}
|
||||
</section>
|
||||
<!-- END: Access Type -->
|
||||
|
||||
|
||||
|
||||
|
||||
<section class="space-y-2">
|
||||
|
||||
<h2 class="strong">Utilities:</h2>
|
||||
<a
|
||||
class="btn btn-sm preset-tonal-secondary"
|
||||
href="/hosted_files"
|
||||
>
|
||||
<span class="fas fa-code mx-1"></span>
|
||||
Util: Convert Videos
|
||||
</a>
|
||||
|
||||
{#if $ae_loc.iframe}
|
||||
<a
|
||||
class="btn btn-sm preset-tonal-secondary"
|
||||
href="/?iframe=false"
|
||||
>
|
||||
<span class="fas fa-code mx-1"></span>
|
||||
Exit iframe Mode
|
||||
</a>
|
||||
{:else}
|
||||
<a
|
||||
class="btn btn-sm preset-tonal-secondary"
|
||||
href="/?iframe=true"
|
||||
>
|
||||
<span class="fas fa-code mx-1"></span>
|
||||
Use iframe Mode
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-sm preset-tonal-warning"
|
||||
title="Reload and clear the page cache"
|
||||
onclick={() => {
|
||||
// $ae_loc.
|
||||
window.location.reload(true);
|
||||
}}
|
||||
>
|
||||
<span class="fas fa-sync mx-1"></span>
|
||||
Reload
|
||||
&
|
||||
<span class="fas fa-trash mx-1"></span>
|
||||
Clear Cache
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm preset-tonal-warning"
|
||||
title="Clear the browser storage for this page"
|
||||
onclick={() => {
|
||||
if (!confirm('Are you sure you want to clear the local and session storage?')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear the local and session storage
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
|
||||
// Clear Indexed DB as well
|
||||
indexedDB.deleteDatabase('ae_core_db');
|
||||
indexedDB.deleteDatabase('ae_events_db');
|
||||
|
||||
window.location.reload();
|
||||
// alert('Local and Session Storage cleared and Indexed DBs deleted. You will probably want to refresh the page.');
|
||||
}}
|
||||
>
|
||||
<span class="fas fa-eraser mx-1"></span>
|
||||
Clear Storage & DB
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<!-- END: Utilities -->
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- class:justify-between={expand}
|
||||
class:justify-end={!expand} -->
|
||||
<div
|
||||
class="flex flex-row gap-2 items-center justify-between w-full"
|
||||
>
|
||||
<!-- {#if !expand} -->
|
||||
<span>
|
||||
{#if $ae_loc.access_type && $ae_loc.access_type != 'anonymous'}
|
||||
{#if $ae_loc.access_type == 'super'}
|
||||
<span class="fas fa-secret mx-1"></span> Super Access
|
||||
{:else if $ae_loc.access_type == 'manager'}
|
||||
<span class="fas fa-user-shield mx-1"></span> Manager Access
|
||||
{:else if $ae_loc.access_type == 'administrator'}
|
||||
<span class="fas fa-user-ninja mx-1"></span> Administrator Access
|
||||
{:else if $ae_loc.access_type == 'trusted'}
|
||||
<span class="fas fa-user-nurse mx-1"></span> Trusted Access
|
||||
{:else if $ae_loc.access_type == 'authenticated'}
|
||||
<span class="fas fa-user-friends mx-1"></span> Authenticated Access
|
||||
{:else if $ae_loc.access_type == 'anonymous'}
|
||||
<span class="fas fa-users mx-1"></span> Anonymous Access
|
||||
{:else}
|
||||
<span class="fas fa-unlock mx-1"></span> Unknown Access
|
||||
{/if}
|
||||
|
||||
<!-- <button
|
||||
class="btn btn-sm variant-glass-secondary access_type_lock_btn hover:transition-all"
|
||||
on:click={() => {
|
||||
handle_clear_access();
|
||||
}}
|
||||
title="Access mode is currently enabled/unlocked. Click to exit and lock."
|
||||
>
|
||||
<span class="fas fa-lock mx-1"></span> Lock
|
||||
</button> -->
|
||||
{:else}
|
||||
Not logged in
|
||||
{/if}
|
||||
</span>
|
||||
<!-- {/if} -->
|
||||
|
||||
<button
|
||||
class="
|
||||
ae_cfg_btn
|
||||
btn btn-sm text-sm
|
||||
preset-tonal-warning
|
||||
group transition-all
|
||||
"
|
||||
onclick={() => {
|
||||
expand = !expand;
|
||||
}}
|
||||
>
|
||||
<!-- <span class="fas fa-cog m-1"></span> -->
|
||||
<span class="inline-block" title="Settings">
|
||||
<Settings class="m-1" />
|
||||
</span>
|
||||
<span
|
||||
class="
|
||||
cfg_text
|
||||
hidden
|
||||
group-hover:inline
|
||||
"
|
||||
>
|
||||
Settings
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<style lang="postcss">
|
||||
|
||||
.ae_cfg_btn .cfg_text {
|
||||
/* display: none; */
|
||||
}
|
||||
|
||||
.ae_cfg_btn:hover .cfg_text {
|
||||
/* display: initial; */
|
||||
/* outline: solid thin red; */
|
||||
}
|
||||
|
||||
/* .access_type .current_text {
|
||||
display: none;
|
||||
} */
|
||||
|
||||
/* .access_type:hover .current_text {
|
||||
display: initial;
|
||||
} */
|
||||
/* END: AE's Svelte App Config component */
|
||||
</style>
|
||||
119
src/lib/app_components/e_app_clipboard.svelte
Normal file
119
src/lib/app_components/e_app_clipboard.svelte
Normal file
@@ -0,0 +1,119 @@
|
||||
<script lang="ts">
|
||||
|
||||
interface Props {
|
||||
children?: import('svelte').Snippet;
|
||||
log_lvl?: number;
|
||||
value: any;
|
||||
success?: boolean;
|
||||
btn_text?: string;
|
||||
btn_title?: string;
|
||||
btn_class?: string;
|
||||
hide_icon?: boolean;
|
||||
hide_text?: boolean;
|
||||
icon_name?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
children,
|
||||
log_lvl = 0,
|
||||
value = $bindable(''),
|
||||
success = $bindable(false),
|
||||
btn_text = 'Copy to Clipboard',
|
||||
btn_title = 'Copy to Clipboard',
|
||||
btn_class = 'btn btn-sm preset-tonal-warning text-warning-500 m-1',
|
||||
hide_icon = false,
|
||||
hide_text = false,
|
||||
icon_name = 'copy', // copy, check, link
|
||||
}: Props = $props();
|
||||
|
||||
// *** Import Svelte specific
|
||||
// import { browser } from '$app/environment';
|
||||
|
||||
// *** Import other supporting libraries
|
||||
import {
|
||||
// ArrowBigRight,
|
||||
// CircleX,
|
||||
CircleCheck,
|
||||
Copy,
|
||||
// Eye, EyeOff,
|
||||
// Key,
|
||||
Link,
|
||||
// LogIn, LogOut, LockKeyhole,
|
||||
// Mail, MailCheck,
|
||||
// Menu,
|
||||
// RefreshCw, RefreshCcw, RefreshCcwDot,
|
||||
// ShieldEllipsis, ShieldMinus, ShieldPlus, ShieldUser,
|
||||
// User, UserCheck
|
||||
} from '@lucide/svelte';
|
||||
|
||||
if (log_lvl) {
|
||||
console.log(`Clipboard component initialized with value:`, value);
|
||||
}
|
||||
|
||||
// Select your trigger element
|
||||
// const elemButton: HTMLButtonElement | null = document.querySelector('[data-button]');
|
||||
|
||||
// Add a click event handler to the trigger
|
||||
// elemButton?.addEventListener('click', () => {
|
||||
// // Call the Clipboard API
|
||||
// navigator.clipboard
|
||||
// // Use the `writeText` method write content to the clipboard
|
||||
// .writeText(value)
|
||||
// // Handle confirmation
|
||||
// .then(() => {
|
||||
// if (log_lvl) {
|
||||
// console.log(`Clipboard write successful: ${value}`);
|
||||
// }
|
||||
// success = true;
|
||||
// });
|
||||
// });
|
||||
</script>
|
||||
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-button
|
||||
onclick={() => {
|
||||
// if (browser) {
|
||||
// Call the Clipboard API
|
||||
navigator.clipboard
|
||||
// Use the `writeText` method write content to the clipboard
|
||||
.writeText(value)
|
||||
// Handle confirmation
|
||||
.then(() => {
|
||||
if (log_lvl) {
|
||||
console.log(`Clipboard write successful: ${value}`);
|
||||
}
|
||||
success = true;
|
||||
});
|
||||
// } else {
|
||||
// if (log_lvl) {
|
||||
// console.log(`Clipboard write attempted in non-browser environment.`);
|
||||
// }
|
||||
// }
|
||||
}}
|
||||
class={btn_class}
|
||||
title={btn_title}
|
||||
>
|
||||
<!-- {@render btn_text} -->
|
||||
{#if icon_name === 'link'}
|
||||
<Link
|
||||
class="mx-1 {hide_icon ? 'hidden' : 'inline-block' }"
|
||||
size="1.2em"
|
||||
/>
|
||||
{:else if icon_name === 'check'}
|
||||
<CircleCheck
|
||||
class="mx-1 {hide_icon ? 'hidden' : 'inline-block' }"
|
||||
size="1.2em"
|
||||
/>
|
||||
{:else}
|
||||
<Copy
|
||||
class="mx-1 {hide_icon ? 'hidden' : 'inline-block' }"
|
||||
size="1.2em"
|
||||
/>
|
||||
{/if}
|
||||
<span class="{hide_text ? 'hidden' : 'inline-block' }">
|
||||
{btn_text}
|
||||
</span>
|
||||
{@render children?.()}
|
||||
</button>
|
||||
294
src/lib/app_components/e_app_codemirror_v5.svelte
Normal file
294
src/lib/app_components/e_app_codemirror_v5.svelte
Normal file
@@ -0,0 +1,294 @@
|
||||
<script lang="ts">
|
||||
// This will be the wrapper for the CodeMirror editor. It should hide most of the configuration requirements.
|
||||
// WARNING: This has not been fully updated to Svelte version 5. It is a work in progress.
|
||||
// *** Import Svelte version 5 specific
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import {
|
||||
EditorView, keymap, highlightSpecialChars, drawSelection,
|
||||
highlightActiveLine, dropCursor, rectangularSelection,
|
||||
crosshairCursor,
|
||||
gutter, GutterMarker, highlightActiveLineGutter, lineNumbers,
|
||||
placeholder as placeholderExt,
|
||||
} from "@codemirror/view"
|
||||
import { EditorState, RangeSet, StateEffect, type Extension, Text } from '@codemirror/state';
|
||||
import {
|
||||
defaultKeymap, history, historyKeymap, indentWithTab,
|
||||
} from "@codemirror/commands"
|
||||
import { indentUnit } from '@codemirror/language';
|
||||
import { languages } from '@codemirror/language-data';
|
||||
// import {
|
||||
// defaultHighlightStyle, syntaxHighlighting, indentOnInput,
|
||||
// bracketMatching, foldGutter, foldKeymap
|
||||
// } from "@codemirror/language"
|
||||
// import {
|
||||
// defaultHighlightStyle, syntaxHighlighting, indentOnInput,
|
||||
// bracketMatching, foldGutter, foldKeymap
|
||||
// } from "@codemirror/language"
|
||||
// import {
|
||||
// searchKeymap, highlightSelectionMatches
|
||||
// } from "@codemirror/search"
|
||||
// import {
|
||||
// autocompletion, completionKeymap, closeBrackets,
|
||||
// closeBracketsKeymap
|
||||
// } from "@codemirror/autocomplete"
|
||||
// import {lintKeymap} from "@codemirror/lint"
|
||||
|
||||
// import { } from '@codemirror/gutter'; // Merged into @codemirror/view
|
||||
import { basicSetup } from 'codemirror';
|
||||
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
|
||||
// import { css } from '@codemirror/lang-css';
|
||||
// import { javascript } from '@codemirror/lang-javascript';
|
||||
// import { json } from '@codemirror/lang-json';
|
||||
// import { html } from '@codemirror/lang-html';
|
||||
import { oneDark } from "@codemirror/theme-one-dark";
|
||||
|
||||
|
||||
|
||||
// Props
|
||||
export let content: string = 'test test test test';
|
||||
export let new_content: string = '';
|
||||
|
||||
// export let language: Extension = markdown(); // javascript()
|
||||
export let theme_mode: string = 'light'; // 'dark' | 'light'
|
||||
export let theme: Extension = EditorView.baseTheme(); // EditorView.baseTheme(); // oneDark
|
||||
|
||||
export let extensions: Extension[] = [];
|
||||
|
||||
export let editable: boolean = true;
|
||||
export let readonly: boolean = false;
|
||||
|
||||
export let placeholder: string = 'Start typing...';
|
||||
|
||||
export let show_line_numbers: boolean = false;
|
||||
export let wrap_lines: boolean = true;
|
||||
export let use_tab: boolean = true;
|
||||
export let tab_size: number = 4;
|
||||
let classes = "";
|
||||
export { classes as class };
|
||||
|
||||
let editor_element: HTMLDivElement;
|
||||
let editorView: EditorView;
|
||||
|
||||
// theme = [
|
||||
// // baseTheme,
|
||||
// theme,
|
||||
// ];
|
||||
// console.log(`Theme:`, theme);
|
||||
|
||||
if (theme_mode == 'dark') {
|
||||
theme = oneDark;
|
||||
} else {
|
||||
// theme = EditorView.baseTheme({
|
||||
// "&": {
|
||||
// color: "black",
|
||||
// backgroundColor: "white"
|
||||
// },
|
||||
// ".cm-cursor, .cm-dropCursor": { borderLeftColor: "#000" },
|
||||
// "&.cm-focused .cm-selectionBackground, ::selection": {
|
||||
// backgroundColor: "#B7D5FF"
|
||||
// },
|
||||
// "&.cm-focused .cm-selectionForeground": { color: "black" }
|
||||
// });
|
||||
}
|
||||
|
||||
if (editable) {
|
||||
extensions.push(EditorView.editable.of(true));
|
||||
} else {
|
||||
// extensions.push(EditorState.editable.of(false));
|
||||
}
|
||||
if (readonly) {
|
||||
extensions.push(EditorState.readOnly.of(true));
|
||||
} else {
|
||||
// extensions.push(EditorState.readOnly.of(false));
|
||||
}
|
||||
|
||||
if (placeholder) { extensions.push(placeholderExt(placeholder)); }
|
||||
|
||||
if (show_line_numbers) {
|
||||
// extensions.push(lineNumbers({ class: "line-numbers" }));
|
||||
} else {
|
||||
// extensions.push(gutter({ class: "hidden-gutter" }));
|
||||
|
||||
// extensions.push(lineNumbers(false));
|
||||
// extensions.push(gutter(false));
|
||||
// extensions.pop();
|
||||
// extensions.slice(extensions.indexOf(lineNumbers), 1);
|
||||
|
||||
}
|
||||
|
||||
if (wrap_lines) { extensions.push(EditorView.lineWrapping); }
|
||||
|
||||
if (use_tab) { extensions.push(keymap.of([indentWithTab])); }
|
||||
if (tab_size) { extensions.push(indentUnit.of(" ".repeat(tab_size))); }
|
||||
|
||||
|
||||
// Enable spell check
|
||||
extensions.push(EditorView.contentAttributes.of({ spellcheck: "true" }));
|
||||
|
||||
// let languages = [
|
||||
// { name: 'CSS', mode: 'css' },
|
||||
// { name: 'HTML', mode: 'html' },
|
||||
// { name: 'JavaScript', mode: 'javascript' },
|
||||
// { name: 'Markdown', mode: 'markdown' },
|
||||
// { name: 'Python', mode: 'python' },
|
||||
// ];
|
||||
|
||||
// Reactive declaration for extensions
|
||||
$: editor_extensions = [
|
||||
basicSetup,
|
||||
// gutter({class: "hidden-gutter"}),
|
||||
// A line number gutter
|
||||
lineNumbers(false),
|
||||
// A gutter with code folding markers
|
||||
// foldGutter(false),
|
||||
// lineWrapping(false),
|
||||
// EditorView.lineWrapping, // Enable line wrapping
|
||||
|
||||
// EditorView.indentUnit.of(" ".repeat(tab_size)),
|
||||
// EditorView.tabSize.of(tab_size),
|
||||
// indentUnit.of(" ".repeat(tab_size)),
|
||||
|
||||
// EditorView.editable.of(editable),
|
||||
// EditorState.readOnly.of(readonly),
|
||||
|
||||
// EditorState.tabSize.of(tab_size),
|
||||
// keymap.of([indentWithTab]),
|
||||
// placeholderExt(placeholder),
|
||||
// language,
|
||||
markdown({base: markdownLanguage, codeLanguages: languages}),
|
||||
// javascript({typescript: true}),
|
||||
// json(),
|
||||
// css(),
|
||||
// html(),
|
||||
theme,
|
||||
|
||||
...extensions
|
||||
];
|
||||
|
||||
// baseTheme = {
|
||||
// ".cm-o-replacement": {
|
||||
// display: "inline-block",
|
||||
// width: ".5em",
|
||||
// height: ".5em",
|
||||
// borderRadius: ".25em"
|
||||
// },
|
||||
// "&light .cm-o-replacement": {
|
||||
// backgroundColor: "#04c"
|
||||
// },
|
||||
// "&dark .cm-o-replacement": {
|
||||
// backgroundColor: "#5bf"
|
||||
// }
|
||||
// },
|
||||
// let dimensions = {
|
||||
// width: '100%',
|
||||
// height: 'calc(100vh - 48px)' // Adjust for header and other elements
|
||||
// };
|
||||
// editorView.setSize(dimensions.width, dimensions.height);
|
||||
|
||||
|
||||
// Initialize CodeMirror on mount
|
||||
onMount(() => {
|
||||
editorView = new EditorView({
|
||||
state: EditorState.create({
|
||||
doc: content,
|
||||
extensions: editor_extensions,
|
||||
}),
|
||||
parent: editor_element, // document.body
|
||||
dispatch: (transaction) => {
|
||||
editorView.update([transaction]);
|
||||
if (transaction.docChanged) {
|
||||
new_content = editorView.state.doc.toString();
|
||||
const newContent = editorView.state.doc.toString();
|
||||
// dispatch('change', newContent);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Clean up on destroy
|
||||
onDestroy(() => {
|
||||
editorView?.destroy();
|
||||
});
|
||||
|
||||
// Update editor content when `content` prop changes
|
||||
$: if (editorView && editorView.state.doc.toString() !== content) {
|
||||
editorView.setState(
|
||||
EditorState.create({
|
||||
doc: content,
|
||||
extensions: editor_extensions
|
||||
})
|
||||
);
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if browser}
|
||||
<!-- flex flex-col flex-wrap items-center justify-center -->
|
||||
<div bind:this={editor_element} class="codemirror-wrapper h-100 max-h-full w-100 max-w-6xl {classes}"></div>
|
||||
{:else}
|
||||
<div class="scm-waiting {classes}">
|
||||
<div class="scm-waiting__loading scm-loading">
|
||||
<p class="scm-loading__text">Loading editor...</p>
|
||||
</div>
|
||||
|
||||
<pre class="scm-pre cm-editor">{content}</pre>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
/* .codemirror-wrapper :global(.cm-focused) {
|
||||
outline: none;
|
||||
} */
|
||||
|
||||
.codemirror-wrapper {
|
||||
flex-grow: 1;
|
||||
/* flex-shrink: 1; */
|
||||
/* flex-basis: 100%; */
|
||||
|
||||
/* font-size: 1rem; */
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
/* overflow: auto; */
|
||||
/* background-color: var(--cm-background); */
|
||||
|
||||
/* text-wrap: normal; */
|
||||
/* text-wrap: balance; */
|
||||
/* text-wrap: wrap; */
|
||||
/* text-wrap: break-word; */
|
||||
}
|
||||
.codemirror-wrapper :global(.cm-editor) {
|
||||
/* font-size: .8rem; */
|
||||
/* text-wrap: normal; */
|
||||
/* text-wrap: balance; */
|
||||
/* text-wrap: wrap; */
|
||||
/* text-wrap: break-word; */
|
||||
|
||||
/* max-width: 100%; */
|
||||
/* max-height: 100%; */
|
||||
|
||||
/* overflow: auto; */
|
||||
|
||||
/* width: 100%; */
|
||||
/* height: 100%; */
|
||||
|
||||
/* background-color: var(--cm-background); */
|
||||
/* color: var(--cm-text); */
|
||||
}
|
||||
|
||||
/* .codemirror-wrapper :global(.cm-gutters) {
|
||||
background-color: var(--cm-gutter-background);
|
||||
color: var(--cm-gutter-text);
|
||||
}
|
||||
.codemirror-wrapper :global(.cm-gutter) {
|
||||
background-color: var(--cm-gutter-background);
|
||||
color: var(--cm-gutter-text);
|
||||
}
|
||||
.codemirror-wrapper :global(.cm-gutterElement) {
|
||||
background-color: var(--cm-gutter-background);
|
||||
color: var(--cm-gutter-text);
|
||||
} */
|
||||
|
||||
</style>
|
||||
219
src/lib/app_components/e_app_debug_menu.svelte
Normal file
219
src/lib/app_components/e_app_debug_menu.svelte
Normal file
@@ -0,0 +1,219 @@
|
||||
<script lang="ts">
|
||||
// *** Import Svelte specific
|
||||
|
||||
// *** Import other supporting libraries
|
||||
import {
|
||||
Bug,
|
||||
CircleX, Info,
|
||||
ToggleLeft, ToggleRight,
|
||||
X
|
||||
} from '@lucide/svelte';
|
||||
|
||||
|
||||
// *** Import Aether specific variables and functions
|
||||
// import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
import { ae_loc, ae_sess, ae_api, slct, slct_trigger } from '$lib/ae_stores';
|
||||
|
||||
|
||||
// *** Setup Svelte properties
|
||||
interface Props {
|
||||
log_lvl?: number;
|
||||
hide?: null|boolean;
|
||||
expand?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
log_lvl = $bindable(0),
|
||||
hide = $bindable(false),
|
||||
expand = $bindable(false),
|
||||
}: Props = $props();
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<!-- App Debug Menu -->
|
||||
<!-- opacity-25
|
||||
hover:opacity-100 -->
|
||||
<!-- text-slate-400 hover:text-slate-800 -->
|
||||
<section
|
||||
class="
|
||||
ae_app__debug_menu
|
||||
|
||||
hidden-print
|
||||
z-50
|
||||
absolute bottom-0 left-0
|
||||
|
||||
flex
|
||||
text-sm sm:text-sm md:text-md lg:text-md xl:text-md 2xl:text-lg
|
||||
|
||||
dark:text-slate-400 dark:hover:text-slate-200
|
||||
bg-red-100/60 dark:bg-red-800/50
|
||||
hover:bg-red-200 hover:dark:bg-red-900
|
||||
|
||||
mx-1 my-2
|
||||
max-h-min
|
||||
max-w-lg
|
||||
transition-all
|
||||
transition-delay-1000
|
||||
transition-duration-1000
|
||||
ease-in
|
||||
|
||||
|
||||
border-2
|
||||
border-red-300 dark:border-red-700
|
||||
hover:border-red-500 hover:dark:border-red-500
|
||||
"
|
||||
class:top-0={expand}
|
||||
class:w-full={expand}
|
||||
class:hidden={hide}
|
||||
class:border-transparent={!expand}
|
||||
class:dark:border-transparent={!expand}
|
||||
class:hover:border-transparent={!expand}
|
||||
class:hover:bg-transparent={!expand}
|
||||
>
|
||||
|
||||
<div
|
||||
class:hidden={!expand}
|
||||
class:border-red-200={expand}
|
||||
class:dark:border-red-800={expand}
|
||||
class="
|
||||
|
||||
transition-all
|
||||
transition-delay-1000
|
||||
transition-duration-1000
|
||||
ease-in
|
||||
overflow-y-auto
|
||||
px-1 py-4
|
||||
opacity-50
|
||||
hover:opacity-100
|
||||
relative
|
||||
"
|
||||
>
|
||||
|
||||
<!-- flex flex-col items-center justify-center max-h-full outline -->
|
||||
<div>
|
||||
<!-- <span class="fas fa-bug mx-1"></span> -->
|
||||
<Bug class="inline-block mx-1" />
|
||||
<span>Debug</span>
|
||||
</div>
|
||||
|
||||
<pre class="text-xs">
|
||||
{JSON.stringify($ae_loc, null, 2)}
|
||||
</pre>
|
||||
|
||||
</div>
|
||||
|
||||
<span class="absolute top-0 right-0 flex flex-row gap-1 items-center justify-center">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
console.log('Debug ae_loc:', $ae_loc);
|
||||
$ae_loc.debug_mode = !$ae_loc?.debug_mode;
|
||||
}}
|
||||
class="
|
||||
btn btn-sm
|
||||
preset-outlined-surface-400-600 preset-filled-suface-200-800
|
||||
hover:preset-tonal-success
|
||||
transition-all
|
||||
"
|
||||
title="Turn debug content and styles off and on"
|
||||
>
|
||||
{#if $ae_loc?.debug_mode}
|
||||
<!-- <span class="fas fa-toggle-on mx-1"></span> -->
|
||||
<ToggleRight strokeWidth="2.5" color="green" class="inline-block mx-1" />
|
||||
<span>Debug</span>
|
||||
<span class="hidden">
|
||||
Mode On
|
||||
</span>
|
||||
{:else}
|
||||
<!-- <span class="fas fa-toggle-off mx-1"></span> -->
|
||||
<ToggleLeft strokeWidth="1" color="gray" class="inline-block mx-1" />
|
||||
<span>Debug?</span>
|
||||
<span class="hidden">
|
||||
Mode Off
|
||||
</span>
|
||||
{/if}
|
||||
<!-- <span class="fas fa-toggle-on mx-1"></span> -->
|
||||
<!-- <ToggleRight class="inline-block mx-1" /> -->
|
||||
<!-- <X class="inline-block mx-1" /> -->
|
||||
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
if (log_lvl) {
|
||||
console.log('Showing quick info/debug menu.');
|
||||
}
|
||||
expand = false;
|
||||
$ae_sess.debug_menu.hide_quick_info = false;
|
||||
|
||||
}}
|
||||
title="Show Quick Info"
|
||||
class="
|
||||
btn btn-sm
|
||||
preset-outlined-surface-400-600 preset-filled-suface-200-800
|
||||
hover:preset-tonal-success
|
||||
transition-all
|
||||
"
|
||||
>
|
||||
<!-- <span class="fas fa-info-circle mx-1"></span> -->
|
||||
<Info class="inline-block mx-1" />
|
||||
Quick Info
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
console.log('Debug ae_loc:', $ae_loc);
|
||||
// $ae_loc.debug_menu.expand = !$ae_loc?.debug_menu?.expand;
|
||||
expand = !expand;
|
||||
}}
|
||||
class="
|
||||
btn btn-sm
|
||||
preset-outlined-surface-400-600 preset-filled-suface-200-800
|
||||
hover:preset-tonal-warning
|
||||
transition-all
|
||||
"
|
||||
title="Turn debug content and styles off and on"
|
||||
>
|
||||
<!-- <span class="fas fa-toggle-on mx-1"></span> -->
|
||||
<!-- <ToggleRight class="inline-block mx-1" /> -->
|
||||
<CircleX class="inline-block mx-1" />
|
||||
<span class="hidden">
|
||||
Close
|
||||
</span>
|
||||
<span>Debug</span>
|
||||
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
console.log('Debug ae_loc:', $ae_loc);
|
||||
// $ae_loc.debug_menu.expand = !$ae_loc?.debug_menu?.expand;
|
||||
expand = !expand;
|
||||
}}
|
||||
id="AE-Quick-Debug"
|
||||
class="
|
||||
btn btn-icon
|
||||
text-xs
|
||||
p-1
|
||||
preset-outlined-surface-100-900
|
||||
hover:preset-filled-warning-100-900
|
||||
|
||||
transition-all
|
||||
fixed bottom-8 left-2
|
||||
text-neutral-300 hover:text-neutral-800
|
||||
dark:text-neutral-700 dark:hover:text-neutral-200
|
||||
"
|
||||
title="Turn debug content and styles off and on"
|
||||
>
|
||||
<!-- absolute bottom-2 left-2 -->
|
||||
<!-- fixed bottom-0 left-0 -->
|
||||
π
|
||||
</button>
|
||||
|
||||
</section>
|
||||
525
src/lib/app_components/e_app_help_tech.svelte
Normal file
525
src/lib/app_components/e_app_help_tech.svelte
Normal file
@@ -0,0 +1,525 @@
|
||||
<script lang="ts">
|
||||
|
||||
interface Props {
|
||||
log_lvl?: number;
|
||||
additional_kv?: key_val;
|
||||
e_success?: boolean;
|
||||
e_class?: string;
|
||||
e_title?: string;
|
||||
e_text?: string;
|
||||
e_class_h1?: string;
|
||||
e_class_h2?: string;
|
||||
e_class_form_hidden?: string;
|
||||
e_class_form_showing?: string;
|
||||
btn_text?: string;
|
||||
btn_title?: string;
|
||||
btn_class?: string;
|
||||
show_btn_class?: string;
|
||||
hide_icon?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
log_lvl = 0,
|
||||
additional_kv = $bindable({}),
|
||||
e_success = $bindable(false),
|
||||
e_class = '',
|
||||
e_title = 'Technical Help',
|
||||
e_text = 'Request technical help for this application.',
|
||||
e_class_h1 = $bindable(''),
|
||||
e_class_h2 = $bindable(''),
|
||||
e_class_form_hidden = $bindable(''),
|
||||
e_class_form_showing = $bindable(''),
|
||||
btn_text = 'Technical Help',
|
||||
btn_title = 'Technical support help',
|
||||
btn_class = '',
|
||||
show_btn_class = '',
|
||||
hide_icon = false,
|
||||
}: Props = $props();
|
||||
|
||||
// *** Import Svelte specific
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
// *** Import other supporting libraries
|
||||
import {
|
||||
// ArrowBigRight,
|
||||
BadgeQuestionMark,
|
||||
ChevronDown, ChevronRight,
|
||||
// CircleX,
|
||||
// Copy,
|
||||
// Eye, EyeOff,
|
||||
// Key,
|
||||
LifeBuoy,
|
||||
// LogIn, LogOut, LockKeyhole,
|
||||
// Mail, MailCheck,
|
||||
// Menu,
|
||||
RefreshCw, RefreshCcw, RefreshCcwDot,
|
||||
// ShieldEllipsis, ShieldMinus, ShieldPlus, ShieldUser,
|
||||
SquareX,
|
||||
// User, UserCheck
|
||||
} from '@lucide/svelte';
|
||||
|
||||
// *** Import Aether specific variables and functions
|
||||
import { ae_snip, ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger, type key_val } from '$lib/ae_stores';
|
||||
import { User } from 'lucide-svelte';
|
||||
import { api } from './api';
|
||||
|
||||
if (log_lvl) {
|
||||
console.log(`Help - technical support component loaded`);
|
||||
}
|
||||
|
||||
let help_tech_text: string = $state('');
|
||||
let hide_additional_info: boolean = $state(true);
|
||||
|
||||
function preventDefault(fn) {
|
||||
return function (event) {
|
||||
event.preventDefault();
|
||||
fn.call(this, event);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function send_help_tech_email() {
|
||||
if (log_lvl) {
|
||||
console.log(`*** send_help_tech_email() ***`);
|
||||
}
|
||||
|
||||
let subject = `Technical Notification - ${$ae_loc.name}`;
|
||||
|
||||
let body_html = `
|
||||
<div>
|
||||
Technical Notification,\n\n
|
||||
<ul>
|
||||
<li>Datetime: ${new Date().toISOString()}</li>
|
||||
<li>URL: ${window.location.href}</li>
|
||||
<li>Browser: ${navigator.userAgent}</li>
|
||||
<li>Viewport Size: ${window.innerWidth} x ${window.innerHeight}</li>
|
||||
<li>Screen Resolution: ${window.screen.width} x ${window.screen.height}</li>
|
||||
<li>In iframe: ${$ae_loc?.iframe}</li>
|
||||
<li>Theme Mode: ${$ae_loc?.theme_mode}</li>
|
||||
<li>Theme Name: ${$ae_loc?.theme_name}</li>
|
||||
<li>Account ID: ${$slct.account_id}</li>
|
||||
<li>Access Type: ${$ae_loc?.access_type}</li>
|
||||
${$ae_loc?.person_id ? `<li>person_id: ${$ae_loc?.person_id}</li>` : ''}
|
||||
${$ae_loc?.user_id ? `<li>user_id: ${$ae_loc?.user_id}</li>` : ''}
|
||||
${$ae_loc?.username ? `<li>username: ${$ae_loc?.username}</li>` : ''}
|
||||
${$ae_loc?.email ? `<li>email: ${$ae_loc?.email}</li>` : ''}
|
||||
<li>API Base URL: ${$ae_api.base_url}</li>
|
||||
</ul>
|
||||
<p>
|
||||
Help Request:\n\n
|
||||
${help_tech_text}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
api.send_email({
|
||||
api_cfg: $ae_api,
|
||||
from_email: $ae_loc.site_cfg_json?.noreply_email ?? 'noreply+tech@oneskyit.com',
|
||||
from_name: $ae_loc.site_cfg_json?.noreply_name ?? 'IT NoReply',
|
||||
// to_email: $ae_loc.site_cfg_json?.admin_email ?? 'admin+tech@oneskyit.com', // 'scott+idaabb@oneskyit.com',
|
||||
to_email: 'it+tech@oneskyit.com',
|
||||
// to_email: $idaa_slct.post_obj.email,
|
||||
// to_email: 'scott+idaabb@oneskyit.com',
|
||||
// to_name: $ae_loc.site_cfg_json?.admin_name ?? 'IT Tech',
|
||||
to_name: 'IT Tech',
|
||||
// to_name: $idaa_slct.post_obj.full_name ?? 'IDAA BB Poster',
|
||||
subject: subject,
|
||||
body_html: body_html,
|
||||
});
|
||||
|
||||
help_tech_text = ''; // Clear the text area after sending
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<!-- class:bg-radial-[at_55%_50%]={$ae_sess.show_help_tech}
|
||||
class:from-blue-400={$ae_sess.show_help_tech}
|
||||
class:to-transparent={$ae_sess.show_help_tech}
|
||||
class:to-90%={$ae_sess.show_help_tech} -->
|
||||
<div
|
||||
class="
|
||||
flex flex-row
|
||||
items-center justify-center
|
||||
rounded-lg shadow-2xl
|
||||
border-1 border-transparent
|
||||
transition-all
|
||||
{e_class}
|
||||
{!$ae_sess.show_help_tech ? e_class_form_hidden : e_class_form_showing}
|
||||
relative
|
||||
"
|
||||
class:w-xl={$ae_sess.show_help_tech}
|
||||
class:w-fit={!$ae_sess.show_help_tech}
|
||||
class:mx-auto={$ae_sess.show_help_tech}
|
||||
class:m-2={$ae_sess.show_help_tech}
|
||||
class:p-2={$ae_sess.show_help_tech}
|
||||
class:hover:border-blue-400={$ae_sess.show_help_tech}
|
||||
class:hover:dark:border-blue-600={$ae_sess.show_help_tech}
|
||||
class:hover:shadow-blue-200={$ae_sess.show_help_tech}
|
||||
class:hover:dark:shadow-blue-800={$ae_sess.show_help_tech}
|
||||
class:bg-blue-100={$ae_sess.show_help_tech}
|
||||
class:dark:bg-blue-900={$ae_sess.show_help_tech}
|
||||
|
||||
>
|
||||
|
||||
|
||||
{#if $ae_sess.show_help_tech}
|
||||
<div
|
||||
class="
|
||||
w-xl
|
||||
flex flex-col gap-1
|
||||
items-center justify-center
|
||||
p-2
|
||||
border rounded-xl border-gray-500/20
|
||||
bg-blue-200
|
||||
dark:bg-blue-800
|
||||
transition-all
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="
|
||||
d-flex align-items-center justify-content-between
|
||||
flex flex-row gap-1 items-center justify-between
|
||||
w-full
|
||||
"
|
||||
>
|
||||
<h1
|
||||
class="
|
||||
h1
|
||||
text-base font-semibold text-gray-800 dark:text-gray-200
|
||||
w-fit
|
||||
{e_class_h1}
|
||||
"
|
||||
>
|
||||
{#if e_success}
|
||||
<span class="text-lg text-green-800 dark:text-green-200 font-semibold">
|
||||
<BadgeQuestionMark class="inline-block mr-2" />
|
||||
Help Requested
|
||||
</span>
|
||||
{:else}
|
||||
<span class="text-lg text-gray-800 dark:text-gray-200 font-semibold">
|
||||
<BadgeQuestionMark class="inline-block mr-2" />
|
||||
<!-- Request Technical Help -->
|
||||
Notify Technical Support
|
||||
</span>
|
||||
<!-- <span class="text-gray-500">
|
||||
Send your help request with or without a description.
|
||||
</span> -->
|
||||
{/if}
|
||||
|
||||
<!-- Cancel button -->
|
||||
</h1>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => ($ae_sess.show_help_tech = false)}
|
||||
class="
|
||||
btn btn-base
|
||||
preset-tonal-tertiary
|
||||
preset-outlined-tertiary-100-900
|
||||
hover:preset-filled-tertiary-200-800
|
||||
transition-all
|
||||
{btn_class}
|
||||
"
|
||||
title="Close Help Request Form"
|
||||
>
|
||||
<!-- <span class="fas fa-times"></span> -->
|
||||
<SquareX size="1em" />
|
||||
<span class="sr-only">Close</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form
|
||||
class="
|
||||
m-1
|
||||
flex flex-col gap-1
|
||||
items-center justify-center
|
||||
w-md max-w-lg
|
||||
"
|
||||
onsubmit={preventDefault(() => {
|
||||
// Do stuff...
|
||||
send_help_tech_email();
|
||||
|
||||
// Hide the request form
|
||||
$ae_sess.show_help_tech = false;
|
||||
|
||||
alert('Notification sent to the IT team.');
|
||||
})}
|
||||
>
|
||||
|
||||
<textarea
|
||||
class="
|
||||
form-control
|
||||
w-full max-w-lg h-24 p-2
|
||||
border border-gray-300 rounded
|
||||
text-gray-950 dark:text-gray-50
|
||||
bg-white dark:bg-gray-500
|
||||
hover:dark:bg-gray-50
|
||||
hover:dark:text-gray-950
|
||||
"
|
||||
placeholder="Send with or without a description...."
|
||||
bind:value={help_tech_text}
|
||||
></textarea>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="
|
||||
btn btn-lg
|
||||
m-1
|
||||
preset-tonal-warning
|
||||
preset-outlined-warning-100-900
|
||||
hover:preset-filled-warning-200-800
|
||||
|
||||
|
||||
transition-all
|
||||
{btn_class}
|
||||
"
|
||||
title={btn_title}
|
||||
>
|
||||
{#if !hide_icon}
|
||||
<!-- <BadgeQuestionMark class="inline-block mr-2" /> -->
|
||||
<LifeBuoy class="inline-block m-1" />
|
||||
{/if}
|
||||
|
||||
{#if !help_tech_text}
|
||||
Notify Without Description
|
||||
{:else}
|
||||
Send Notification
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
</form>
|
||||
|
||||
<div
|
||||
class="
|
||||
text-sm text-gray-700 dark:text-gray-300 text-center italic
|
||||
"
|
||||
>
|
||||
This is intended for technical issues only. Please contact your organization's staff if you have a question about your membership, recorded content, meetings, or posts.
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="
|
||||
border border-gray-300 rounded p-2
|
||||
w-full
|
||||
max-w-2xl
|
||||
overflow-scroll
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="
|
||||
d-flex align-items-center justify-content-between
|
||||
flex flex-row gap-1 items-center justify-between
|
||||
w-full
|
||||
"
|
||||
>
|
||||
<h2
|
||||
class="
|
||||
h2
|
||||
text-base font-semibold text-gray-800 dark:text-gray-200
|
||||
{e_class_h2}
|
||||
"
|
||||
>
|
||||
<span class="text-base font-semibold text-gray-800 dark:text-gray-200">
|
||||
Additional Information Included
|
||||
</span>
|
||||
</h2>
|
||||
<!-- Button to expand and show additional information -->
|
||||
<button
|
||||
type="button"
|
||||
class="
|
||||
btn btn-sm preset-tonal-tertiary
|
||||
{btn_class}
|
||||
"
|
||||
onclick={() => (hide_additional_info = !hide_additional_info)}
|
||||
title="Toggle additional information"
|
||||
>
|
||||
<span>
|
||||
{#if hide_additional_info}
|
||||
<!-- <span class="fas fa-caret-right"></span> -->
|
||||
<ChevronRight size="1em" class="inline-block" />
|
||||
Show
|
||||
{:else}
|
||||
<!-- <span class="fas fa-caret-down"></span> -->
|
||||
<ChevronDown size="1em" class="inline-block" />
|
||||
Hide
|
||||
{/if}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<ul
|
||||
class="list-disc list-inside text-sm text-gray-800 dark:text-gray-200"
|
||||
class:hidden={hide_additional_info}
|
||||
>
|
||||
<li><span class="text-sm text-gray-500 dark:text-gray-400">Datetime =</span> {new Date().toISOString()}</li>
|
||||
<li><span class="text-sm text-gray-500 dark:text-gray-400">URL =</span> {window.location.href}</li>
|
||||
<li><span class="text-sm text-gray-500 dark:text-gray-400">Browser =</span> {navigator.userAgent}</li>
|
||||
<li><span class="text-sm text-gray-500 dark:text-gray-400">Viewport Size =</span> {window.innerWidth} x {window.innerHeight}</li>
|
||||
<li><span class="text-sm text-gray-500 dark:text-gray-400">Screen Resolution =</span> {window.screen.width} x {window.screen.height}</li>
|
||||
<li><span class="text-sm text-gray-500 dark:text-gray-400">Dark mode =</span> {window?.matchMedia?.('(prefers-color-scheme:dark)')?.matches ?? false}</li>
|
||||
<li><span class="text-sm text-gray-500 dark:text-gray-400">In iframe =</span> {$ae_loc?.iframe}</li>
|
||||
<li><span class="text-sm text-gray-500 dark:text-gray-400">Theme Mode =</span> {$ae_loc?.theme_mode}</li>
|
||||
<li><span class="text-sm text-gray-500 dark:text-gray-400">Theme Name =</span> {$ae_loc?.theme_name}</li>
|
||||
<li><span class="text-sm text-gray-500 dark:text-gray-400">Account ID =</span> {$slct.account_id}</li>
|
||||
<li><span class="text-sm text-gray-500 dark:text-gray-400">Access Type =</span> {$ae_loc?.access_type}</li>
|
||||
{#if $ae_loc?.person_id}
|
||||
<li><span class="text-sm text-gray-500 dark:text-gray-400">person_id =</span> {$ae_loc?.person_id}</li>
|
||||
<li><span class="text-sm text-gray-500 dark:text-gray-400">full_name =</span> {$ae_loc?.full_name}</li>
|
||||
{/if}
|
||||
{#if $ae_loc?.user_id}
|
||||
<li><span class="text-sm text-gray-500 dark:text-gray-400">user_id =</span> {$ae_loc?.user_id}</li>
|
||||
<li><span class="text-sm text-gray-500 dark:text-gray-400">username =</span> {$ae_loc?.username}</li>
|
||||
<li><span class="text-sm text-gray-500 dark:text-gray-400">email =</span> {$ae_loc?.email}</li>
|
||||
{/if}
|
||||
<li><span class="text-sm text-gray-500 dark:text-gray-400">API Base URL =</span> {$ae_api.base_url}</li>
|
||||
|
||||
{#if additional_kv && Object.keys(additional_kv).length > 0}
|
||||
<h2 class="text-base font-semibold text-gray-800">Component Info:</h2>
|
||||
<ul class="list-disc list-inside text-gray-800 text-sm">
|
||||
{#each Object.entries(additional_kv) as [key, value]}
|
||||
<li><span class="text-sm text-gray-500 dark:text-gray-400">{key} =</span> {value ?? '-- not set --'}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
|
||||
</ul>
|
||||
<div class="text-sm text-gray-700 dark:text-gray-300 text-center italic">
|
||||
This information will be included in the help request to assist technical support in diagnosing the issue.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="
|
||||
flex flex-row gap-2 items-center justify-around
|
||||
w-full
|
||||
mt-2
|
||||
"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
if ($ae_loc.edit_mode) {
|
||||
// Confirm before clearing
|
||||
if (!confirm("Are you sure you want to clear IndexedDB databases, localStorage, and sessionStorage? This will also reload the page.")) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Clearing IndexedDB, localStorage, sessionStorage, and reloading the page...");
|
||||
|
||||
// Clear Indexed DB
|
||||
indexedDB.deleteDatabase('ae_archives_db'); // Archives module
|
||||
indexedDB.deleteDatabase('ae_core_db');
|
||||
indexedDB.deleteDatabase('ae_events_db'); // Events module
|
||||
indexedDB.deleteDatabase('ae_journals_db'); // Journals module
|
||||
indexedDB.deleteDatabase('ae_posts_db'); // Posts module
|
||||
indexedDB.deleteDatabase('ae_sponsorships_db'); // Sponsorships module
|
||||
|
||||
// Clear localStorage and sessionStorage
|
||||
// Clearing the localStorage will force it to be re-created.
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
|
||||
goto('/', {invalidateAll: true});
|
||||
|
||||
// window.location.reload(true);
|
||||
} else {
|
||||
// Confirm before clearing
|
||||
if (!confirm("Are you sure you want to clear IndexedDB databases and some caches? This will also reload the page.")) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Clearing IndexedDB, localStorage, sessionStorage, and reloading the page...");
|
||||
|
||||
// Clear Indexed DB
|
||||
indexedDB.deleteDatabase('ae_archives_db'); // Archives module
|
||||
indexedDB.deleteDatabase('ae_core_db');
|
||||
indexedDB.deleteDatabase('ae_events_db'); // Events module
|
||||
indexedDB.deleteDatabase('ae_journals_db'); // Journals module
|
||||
indexedDB.deleteDatabase('ae_posts_db'); // Posts module
|
||||
indexedDB.deleteDatabase('ae_sponsorships_db'); // Sponsorships module
|
||||
|
||||
window.location.reload(true);
|
||||
}
|
||||
|
||||
// This does not seem to work fast enough or something?
|
||||
// goto('/', {invalidateAll: true});
|
||||
|
||||
// The page does usually seem to reload correctly?
|
||||
// window.location.reload(true); // true only works with Firefox
|
||||
// alert('Local and Session Storage cleared and Indexed DBs deleted. You will probably want to refresh the page.');
|
||||
}}
|
||||
class="
|
||||
btn btn-sm
|
||||
preset-tonal-surface
|
||||
preset-outlined-warning-100-900
|
||||
hover:preset-filled-warning-200-800
|
||||
transition-all
|
||||
{btn_class}
|
||||
"
|
||||
title="Clear App Data & Settings: Clear IndexedDB and reload. If in edit mode localStorage and sessionStorage will also be cleared."
|
||||
>
|
||||
<!-- <span class="fas fa-eraser mx-1"></span> -->
|
||||
<!-- <span class="fas fa-sync mx-1"></span> -->
|
||||
<RefreshCw size="1em" class="inline-block" />
|
||||
<span class="md:inline">Clear & Reload</span>
|
||||
</button>
|
||||
|
||||
<!-- Cancel button -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => ($ae_sess.show_help_tech = false)}
|
||||
class="
|
||||
btn btn-sm
|
||||
preset-tonal-tertiary
|
||||
preset-outlined-tertiary-100-900
|
||||
hover:preset-filled-tertiary-100-900
|
||||
transition-all
|
||||
{btn_class}
|
||||
"
|
||||
title="Close Help Request Form"
|
||||
>
|
||||
<!-- <span class="fas fa-times"></span> -->
|
||||
<SquareX size="1em" class="inline-block" />
|
||||
<span class="">Cancel</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{:else}
|
||||
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => ($ae_sess.show_help_tech = true)}
|
||||
class="
|
||||
btn btn-sm
|
||||
preset-filled-tertiary-400-600
|
||||
preset-outlined-tertiary-100-900
|
||||
hover:preset-filled-warning-500
|
||||
transition-all
|
||||
{btn_class}
|
||||
{show_btn_class}
|
||||
"
|
||||
title={btn_title}
|
||||
>
|
||||
{#if !hide_icon}
|
||||
<BadgeQuestionMark class="inline-block m-0.5" />
|
||||
{/if}
|
||||
<span class="hidden">
|
||||
{btn_text}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{/if}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
928
src/lib/app_components/e_app_sign_in_out.svelte
Normal file
928
src/lib/app_components/e_app_sign_in_out.svelte
Normal file
@@ -0,0 +1,928 @@
|
||||
<script lang="ts">
|
||||
|
||||
// *** Import Svelte specific
|
||||
import { browser } from '$app/environment';
|
||||
import { goto, invalidateAll } from '$app/navigation';
|
||||
import { Modal } from 'flowbite-svelte';
|
||||
|
||||
// *** Import other supporting libraries
|
||||
import {
|
||||
CircleX,
|
||||
Eye, EyeOff,
|
||||
Key,
|
||||
LogIn, LogOut, LockKeyhole,
|
||||
Mail, MailCheck,
|
||||
ShieldUser,
|
||||
User, UserCheck, UserLock
|
||||
} from '@lucide/svelte';
|
||||
|
||||
// *** Import Aether specific variables and functions
|
||||
import type { key_val } from '$lib/ae_stores';
|
||||
import { ae_loc, ae_sess, ae_api, slct, slct_trigger } from '$lib/ae_stores';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
import { core_func } from '$lib/ae_core/ae_core_functions';
|
||||
|
||||
// *** Setup Svelte properties
|
||||
interface Props {
|
||||
log_lvl?: number;
|
||||
data?: any;
|
||||
hidden: null|boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
log_lvl = $bindable(0),
|
||||
data = null,
|
||||
hidden = $bindable(true),
|
||||
}: Props = $props();
|
||||
|
||||
|
||||
let url_user_id = data.url.searchParams.get('user_id');
|
||||
let url_user_key = data.url.searchParams.get('user_key'); // Reminder that "key" is the site's auth key.
|
||||
let url_user_username = data.url.searchParams.get('username');
|
||||
let url_user_email = data.url.searchParams.get('user_email');
|
||||
|
||||
let ae_promises: key_val = {};
|
||||
|
||||
let trigger: null|boolean = $state(null); // Use $state to ensure reactivity
|
||||
let user_id = $state(null); // Use $state to ensure reactivity
|
||||
let user_obj: key_val = $state({}); // Use $state to ensure reactivity
|
||||
let person_id = $state(null); // Use $state to ensure reactivity
|
||||
let person_obj: key_val = $state({}); // Use $state to ensure reactivity
|
||||
|
||||
$effect(() => {
|
||||
if (user_id && person_id && trigger) {
|
||||
// alert(`Ready to sign in person! \n\nuser_id: ${user_id}\nperson_id: ${person_id}\n\nThis is a test alert to confirm the values are set.`);
|
||||
trigger = false;
|
||||
sign_in();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
let user_email = $state('scott.idem@gmail.com'); // Used for quick lookup of user by email address
|
||||
let new_password = $state('test12345');
|
||||
let confirm_password = $state('test12345');
|
||||
let is_changing_password = $state(false);
|
||||
let show_password_text = $state('password'); // password or text
|
||||
|
||||
|
||||
function sign_in() {
|
||||
$ae_loc.person_id = person_id; // Set the person_id in the ae_loc store
|
||||
$ae_loc.person = person_obj; // Store the full person object for reference
|
||||
$ae_loc.user_id = user_id; // Set the user_id in the ae_loc store
|
||||
$ae_loc.user = user_obj; // Store the full user object for reference
|
||||
|
||||
if (user_obj.super) {
|
||||
$ae_loc.access_type = 'super';
|
||||
} else if (user_obj.manager) {
|
||||
$ae_loc.access_type = 'manager';
|
||||
} else if (user_obj.administrator) {
|
||||
$ae_loc.access_type = 'administrator';
|
||||
} else if (user_obj.verified) {
|
||||
// They are "trusted" because the user has been verified. External staff usually start with this level.
|
||||
$ae_loc.access_type = 'trusted';
|
||||
} else if (user_obj.public) {
|
||||
$ae_loc.access_type = 'public';
|
||||
} else {
|
||||
$ae_loc.access_type = 'authenticated';
|
||||
}
|
||||
$ae_loc.user_access_type = $ae_loc.access_type; // Used to revert back to the user's access type after quick access (temporarily escalate permissions) is turned off.
|
||||
|
||||
let access_checks_results = ae_util.process_permission_checks($ae_loc.access_type);
|
||||
// WARNING: I think this is causing a loop in Svelte or something.
|
||||
// Last ten effects were:
|
||||
// Uncaught Svelte error: effect_update_depth_exceeded
|
||||
// Maximum update depth exceeded. This can happen when a reactive block or effect repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops
|
||||
// https://svelte.dev/e/effect_update_depth_exceeded
|
||||
$ae_loc = {
|
||||
...$ae_loc,
|
||||
...access_checks_results
|
||||
};
|
||||
// $ae_loc = {...access_checks_results};
|
||||
|
||||
console.log('Remove the sign in fields from the URL.');
|
||||
data.url.searchParams.delete('user_id');
|
||||
data.url.searchParams.delete('user_key');
|
||||
data.url.searchParams.delete('username');
|
||||
data.url.searchParams.delete('user_email');
|
||||
data.url.searchParams.delete('valid_email'); // Part of sign in email for possible future use
|
||||
|
||||
let new_url = data.url.toString();
|
||||
|
||||
// We need to set browser history and force all load functions to rerun.
|
||||
goto(new_url, {replaceState: true, invalidateAll: true});
|
||||
}
|
||||
|
||||
|
||||
function sign_out() {
|
||||
// Clear the session information
|
||||
$ae_loc.person_id = null;
|
||||
$ae_loc.person = {};
|
||||
$ae_loc.user_id = null;
|
||||
$ae_loc.user = {};
|
||||
|
||||
user_id = null; // Clear the user_id
|
||||
user_obj = {}; // Clear the user object
|
||||
person_id = null; // Clear the person_id
|
||||
person_obj = {}; // Clear the person object
|
||||
|
||||
$ae_loc.access_type = ''; // Clear the access type
|
||||
let access_checks_results = ae_util.process_permission_checks('');
|
||||
$ae_loc = {...$ae_loc, ...access_checks_results};
|
||||
|
||||
$ae_sess.auth__entered_user_id = null;
|
||||
$ae_sess.auth__entered_user_key = null;
|
||||
// $ae_sess.auth__entered_username = null; // Keeping the username
|
||||
$ae_sess.auth__entered_password = null;
|
||||
|
||||
indexedDB.deleteDatabase('ae_archives_db'); // Archives module
|
||||
indexedDB.deleteDatabase('ae_core_db');
|
||||
indexedDB.deleteDatabase('ae_events_db'); // Events module
|
||||
indexedDB.deleteDatabase('ae_journals_db'); // Journals module
|
||||
indexedDB.deleteDatabase('ae_posts_db'); // Posts module
|
||||
indexedDB.deleteDatabase('ae_sponsorships_db'); // Sponsorships module
|
||||
|
||||
// $ae_loc.allow_access = false;
|
||||
$ae_loc.authenticated_access = false;
|
||||
$ae_loc.edit_mode = false;
|
||||
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
|
||||
console.log('Remove the sign out fields from the URL.');
|
||||
data.url.searchParams.delete('user_id');
|
||||
data.url.searchParams.delete('user_key');
|
||||
data.url.searchParams.delete('username');
|
||||
data.url.searchParams.delete('user_email');
|
||||
data.url.searchParams.delete('valid_email'); // Part of sign in email for possible future use
|
||||
|
||||
let new_url = data.url.toString();
|
||||
|
||||
// We need to set browser history and force all load functions to rerun.
|
||||
// goto(new_url, {replaceState: true, invalidateAll: true});
|
||||
|
||||
// invalidateAll();
|
||||
window.location.reload();
|
||||
|
||||
console.log('Signed out successfully.');
|
||||
}
|
||||
|
||||
|
||||
if (browser) {
|
||||
if (url_user_id) {
|
||||
// Pre-fill the auth__entered_user_id if passed in via the URL.
|
||||
$ae_sess.auth__entered_user_id = url_user_id;
|
||||
}
|
||||
if (url_user_key) {
|
||||
// Pre-fill the auth__entered_key if passed in via the URL.
|
||||
$ae_sess.auth__entered_user_key = url_user_key;
|
||||
}
|
||||
if (url_user_username) {
|
||||
// Pre-fill the auth__entered_username if passed in via the URL.
|
||||
$ae_sess.auth__entered_username = url_user_username;
|
||||
}
|
||||
if (url_user_email) {
|
||||
// Pre-fill the auth__entered_email if passed in via the URL.
|
||||
$ae_sess.auth__entered_email = url_user_email;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Use core_func.send_email_auth_ae_obj__user_id
|
||||
function handle_send_auth_email({user_id}: {user_id: string}) {
|
||||
console.log('handle_send_auth_email()');
|
||||
|
||||
console.log($ae_loc.base_url); // URL origin
|
||||
console.log($ae_loc.hostname); // URL hostname
|
||||
|
||||
// This function creates a new auth_key and then sends an email to the user with the new auth key.
|
||||
ae_promises.send_email_auth_ae_obj__user_id = core_func.send_email_auth_ae_obj__user_id({
|
||||
api_cfg: $ae_api,
|
||||
account_id: $slct.account_id,
|
||||
user_id: user_id,
|
||||
base_url: $ae_loc.base_url,
|
||||
log_lvl: 2
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Use core_func.qry_ae_obj_li__user_email
|
||||
function handle_lookup_user_email({email}: {email: string}) {
|
||||
console.log('handle_lookup_user_email()');
|
||||
|
||||
// If a user is returned then send the new auth key email
|
||||
ae_promises.load__user_obj_li = core_func.qry_ae_obj_li__user_email({
|
||||
api_cfg: $ae_api,
|
||||
account_id: $slct.account_id,
|
||||
null_account_id: false,
|
||||
email: email,
|
||||
log_lvl: 1
|
||||
}).then((user_response) => {
|
||||
if (user_response?.user_id_random) {
|
||||
console.log(`User found for email:`, user_response);
|
||||
handle_send_auth_email({
|
||||
user_id: user_response.user_id_random
|
||||
});
|
||||
} else if (user_response.length > 0) {
|
||||
console.log(`Multiple users found for email:`, user_response);
|
||||
handle_send_auth_email({
|
||||
user_id: user_response[0].user_id_random
|
||||
});
|
||||
} else {
|
||||
alert('No user found with that email address.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function handle_change_password() {
|
||||
if (!new_password) {
|
||||
alert('Password cannot be empty.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (new_password !== confirm_password) {
|
||||
alert('Passwords do not match.');
|
||||
return;
|
||||
}
|
||||
|
||||
let use_user_id = $ae_loc.user_id;
|
||||
let use_password = new_password;
|
||||
let wait_for_lookup = true;
|
||||
|
||||
// Look up the user by email address to get their user ID
|
||||
if (user_email) {
|
||||
wait_for_lookup = true;
|
||||
// ae_promises.load__user_obj_li = await core_func.qry_ae_obj_li__user_email({
|
||||
// api_cfg: $ae_api,
|
||||
// account_id: $slct.account_id,
|
||||
// null_account_id: false,
|
||||
// email: user_email,
|
||||
// log_lvl: 1
|
||||
// }).then((user_response) => {
|
||||
// if (!user_response) {
|
||||
// // This means a 404 was returned
|
||||
// alert('No user found with that email address.');
|
||||
// return;
|
||||
// } else if (user_response?.user_id_random) {
|
||||
// console.log(`User found for email:`, user_response);
|
||||
// use_user_id = user_response.user_id_random;
|
||||
// } else if (user_response.length > 0) {
|
||||
// console.log(`Multiple users found for email:`, user_response);
|
||||
// use_user_id = user_response[0].user_id_random;
|
||||
// }
|
||||
// });
|
||||
|
||||
ae_promises.load__user_obj_li = await core_func.qry_ae_obj_li__user_email({
|
||||
api_cfg: $ae_api,
|
||||
account_id: $slct.account_id,
|
||||
null_account_id: false,
|
||||
email: user_email,
|
||||
log_lvl: 1
|
||||
});
|
||||
|
||||
if (!ae_promises.load__user_obj_li) {
|
||||
// This means a 404 was returned
|
||||
alert('No user found with that email address.');
|
||||
return;
|
||||
} else if (ae_promises.load__user_obj_li?.user_id_random) {
|
||||
console.log(`User found for email:`, ae_promises.load__user_obj_li);
|
||||
use_user_id = ae_promises.load__user_obj_li.user_id_random;
|
||||
} else if (ae_promises.load__user_obj_li.length > 0) {
|
||||
console.log(`Multiple users found for email:`, ae_promises.load__user_obj_li);
|
||||
use_user_id = ae_promises.load__user_obj_li[0].user_id_random;
|
||||
}
|
||||
} else {
|
||||
wait_for_lookup = false;
|
||||
}
|
||||
|
||||
is_changing_password = true;
|
||||
|
||||
if (user_email && wait_for_lookup) {
|
||||
console.log('Waiting for user lookup to complete...');
|
||||
// await ae_promises.load__user_obj_li;
|
||||
}
|
||||
|
||||
try {
|
||||
ae_promises.change_password = await core_func.auth_ae_obj__user_id_change_password({
|
||||
api_cfg: $ae_api,
|
||||
account_id: $ae_loc.account_id,
|
||||
user_id: use_user_id,
|
||||
password: use_password,
|
||||
log_lvl: log_lvl
|
||||
});
|
||||
// await core_func.auth_ae_obj__user_id_change_password({
|
||||
// api_cfg: $ae_api,
|
||||
// account_id: $ae_loc.account_id,
|
||||
// user_id: use_user_id,
|
||||
// password: use_password,
|
||||
// log_lvl: log_lvl
|
||||
// });
|
||||
// alert('Password changed successfully.');
|
||||
// $ae_sess.show__modal_change_password = false;
|
||||
} catch (error) {
|
||||
console.error('Error changing password:', error);
|
||||
alert('Unexpected Response. Failed to change password.');
|
||||
} finally {
|
||||
is_changing_password = false;
|
||||
}
|
||||
if (ae_promises.change_password) {
|
||||
console.log('Password changed successfully.');
|
||||
alert('Password changed successfully.');
|
||||
$ae_sess.show__modal_change_password = false;
|
||||
} else {
|
||||
console.error('Failed to change password.');
|
||||
alert('Failed to change password. Check password length.');
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<!-- It is important to keep in mind that a Person can only fully sign in if they have a linked User record. -->
|
||||
<!-- *:hover:inline -->
|
||||
<section
|
||||
id="AE-Sign-In-Out"
|
||||
class="
|
||||
ae_sign_in_out
|
||||
hidden-print
|
||||
|
||||
bg-blue-100 text-gray-900
|
||||
dark:bg-blue-800 dark:text-gray-200
|
||||
|
||||
flex flex-col flex-wrap gap-1
|
||||
items-end justify-center
|
||||
|
||||
w-72 max-w-72
|
||||
p-1
|
||||
border-2 border-gray-200
|
||||
|
||||
duration-300 delay-150 hover:delay-1000 hover:ease-out
|
||||
transition-all
|
||||
"
|
||||
class:hidden={hidden}
|
||||
>
|
||||
|
||||
|
||||
<header
|
||||
class:hidden={!$ae_sess.show__sign_in_out__fields}
|
||||
class="ae_header w-full "
|
||||
>
|
||||
<h2 class="text-sm text-center font-semibold">
|
||||
{#if $ae_loc?.person_id && $ae_loc?.user_id}
|
||||
<!-- <button
|
||||
type="button"
|
||||
class="btn btn-sm variant-outline-surface hover:variant-filled-surface"
|
||||
title="Show/Hide Sign-In/Out Options"
|
||||
onclick={() => {
|
||||
$ae_sess.show__sign_in_out__fields = !$ae_sess.show__sign_in_out__fields; // Toggle the visibility of the sign-in form
|
||||
}}
|
||||
> -->
|
||||
{$ae_loc?.person?.full_name_override ?? $ae_loc?.person?.full_name}
|
||||
<!-- <span class="text-sm text-gray-500">
|
||||
{$ae_loc?.user.username ?? '-- not set --'}
|
||||
</span> -->
|
||||
<!-- {#if log_lvl > 1}
|
||||
<span class="text-xs text-gray-500">({$ae_loc.person_id} / {$ae_loc.user_id})</span>
|
||||
{/if} -->
|
||||
<!-- </button> -->
|
||||
{:else}
|
||||
<!-- <LogIn class="mx-1" /> -->
|
||||
User Sign In
|
||||
|
||||
{/if}
|
||||
</h2>
|
||||
</header>
|
||||
|
||||
<span
|
||||
class:hidden={!$ae_sess.show__sign_in_out__fields}
|
||||
class="flex flex-col gap-1 transition-all w-full"
|
||||
>
|
||||
{#if !$ae_loc?.person_id && !$ae_loc?.user_id}
|
||||
|
||||
<!-- Form for user look up based on email address -->
|
||||
<form
|
||||
class="
|
||||
ae_sign_in_form
|
||||
flex flex-col gap-1 items-end justify-center
|
||||
p-1 my-1
|
||||
"
|
||||
onsubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
if ($ae_sess.auth__entered_email) {
|
||||
alert('Attempting to look up user by email address.');
|
||||
handle_lookup_user_email({
|
||||
email: $ae_sess.auth__entered_email
|
||||
});
|
||||
} else {
|
||||
alert('Please enter an email address to look up.');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="input max-w-48"
|
||||
placeholder="Email Address"
|
||||
value={$ae_sess.auth__entered_email ?? ''}
|
||||
oninput={(e) => $ae_sess.auth__entered_email = e.target.value}
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-sm preset-tonal-secondary border border-secondary-500 hover:preset-filled-secondary-500"
|
||||
title="Look up user by email and send sign in email"
|
||||
>
|
||||
<!-- <User class="mx-1" /> -->
|
||||
<!-- <Mail class="mx-1" /> -->
|
||||
<MailCheck class="mx-1" />
|
||||
Email Sign In
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- We need to get the person's linked User ID and auth_key or User username and password to sign in. -->
|
||||
<form
|
||||
class="
|
||||
ae_sign_in_form
|
||||
flex flex-col gap-1 items-end
|
||||
p-1 my-1
|
||||
"
|
||||
onsubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// WARNING: Logging in as a global user does not work yet. The API needs to be updated. Currently it returns multiple user records from the v_user view if there is more than one person record linked to the user ID.
|
||||
|
||||
if ($ae_sess.auth__entered_user_id && $ae_sess.auth__entered_user_key) {
|
||||
|
||||
// Try to use the user ID and user auth key passed in the URL params for authentication
|
||||
alert('Attempting to authenticate with User ID and Auth Key.');
|
||||
|
||||
ae_promises['user'] = await core_func.auth_ae_obj__user_id_user_auth_key({
|
||||
api_cfg: $ae_api,
|
||||
account_id: $ae_loc.account_id,
|
||||
// null_account_id: false, // Set to true to allow to authenticate as global user (Super or Manager)
|
||||
user_id: $ae_sess.auth__entered_user_id,
|
||||
user_auth_key: $ae_sess.auth__entered_user_key,
|
||||
log_lvl: 2,
|
||||
}).then((user_response) => {
|
||||
// console.log(`HERE:`, user_response);
|
||||
if (user_response.user_id_random) {
|
||||
console.log(`Successfully authenticated in with User ID and User Auth Key: ${user_response.username}`, user_response);
|
||||
user_obj = user_response; // Store the user object for later use
|
||||
user_id = user_obj.user_id_random; // Use the user_id_random for further API calls
|
||||
// person_id = user_obj.person_id_random;
|
||||
} else {
|
||||
alert('Failed to authenticate: ' + user_response.error);
|
||||
}
|
||||
}).then((response) => {
|
||||
if (!user_id) {
|
||||
// If we didn't get a user_id, return early
|
||||
console.error('No user_id obtained from auth_ae_obj__username_password');
|
||||
return;
|
||||
}
|
||||
|
||||
// Next we need to get the person's information. This is odd because we need to look it up based on the account_id and user_id. There should only be one account person record per user. 99% of the time the user's account ID will be the same as the person's account ID. The exception is for AE Global users (Super or Manager) who can have multiple accounts but only one person record per account.
|
||||
|
||||
// let params = {
|
||||
// user_id_random: user_id, // The user_id_random from the above authentication
|
||||
// }
|
||||
|
||||
// let params_json: key_val = {};
|
||||
|
||||
// params_json['and_qry'] = {};
|
||||
|
||||
// if (user_id) {
|
||||
// params_json['and_qry']['user_id_random'] = user_id;
|
||||
// }
|
||||
|
||||
// WARNING: This function returns a list. We only want the first one. There should be no more than 1 record returned.
|
||||
ae_promises['person'] = core_func.load_ae_obj_li__person({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_type: 'account',
|
||||
for_obj_id: $ae_loc.account_id,
|
||||
qry_user_id: user_id, // The user_id_random from the above authentication
|
||||
// params_json: params_json,
|
||||
// params: params,
|
||||
log_lvl: 1,
|
||||
}).then((person_response) => {
|
||||
if (person_response[0].person_id_random) {
|
||||
console.log(`Successfully loaded person for user_id_random (${user_id}):`, person_response[0]);
|
||||
person_obj = person_response[0];
|
||||
person_id = person_obj.person_id_random;
|
||||
|
||||
trigger = true; // Set trigger to true to indicate we can now sign in
|
||||
} else {
|
||||
alert('Failed to load person information: ' + person_response.error);
|
||||
}
|
||||
}).then(() => {
|
||||
// Once all promises are resolved, we can check if we have both user_id and person_id
|
||||
if (user_id && person_id) {
|
||||
// Set the session information
|
||||
// $ae_loc.person_id = person_id; // Set the person_id in the ae_loc store
|
||||
// $ae_loc.person = person_obj; // Store the full person object for reference
|
||||
// $ae_loc.user_id = user_id; // Set the user_id in the ae_loc store
|
||||
// $ae_loc.user = user_obj; // Store the full user object for reference
|
||||
|
||||
console.log(`Successfully authenticated and loaded user and person records:
|
||||
user_id: ${user_id},
|
||||
person_id: ${person_id}
|
||||
`);
|
||||
// window.location.reload(); // Reload to get the new session
|
||||
} else {
|
||||
console.error('Failed to authenticate and load data: missing user_id or person_id');
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
// console.log('DONE???', ae_promises);
|
||||
|
||||
} else if ($ae_sess.auth__entered_username && $ae_sess.auth__entered_password) {
|
||||
|
||||
// Try to use the username/password for authentication
|
||||
// alert('Attempting to authenticate with Username and Password.');
|
||||
ae_promises['user'] = await core_func.auth_ae_obj__username_password({
|
||||
api_cfg: $ae_api,
|
||||
account_id: $ae_loc.account_id,
|
||||
// null_account_id: false, // Set to true to allow to authenticate as global user (Super or Manager)
|
||||
username: $ae_sess.auth__entered_username,
|
||||
password: $ae_sess.auth__entered_password,
|
||||
log_lvl: 1,
|
||||
}).then((user_response) => {
|
||||
if (user_response.user_id_random) {
|
||||
console.log(`Successfully authenticated in with Username (${user_response.username}) and Password:`, user_response);
|
||||
user_obj = user_response; // Store the user object for later use
|
||||
user_id = user_obj.user_id_random; // Use the user_id_random for further API calls
|
||||
// person_id = user_obj.person_id_random;
|
||||
} else {
|
||||
alert('Failed to authenticate: ' + user_response.error);
|
||||
}
|
||||
}).then((response) => {
|
||||
if (!user_id) {
|
||||
// If we didn't get a user_id, return early
|
||||
console.error('No user_id obtained from auth_ae_obj__username_password');
|
||||
return;
|
||||
}
|
||||
|
||||
// Next we need to get the person's information. This is odd because we need to look it up based on the account_id and user_id. There should only be one account person record per user. 99% of the time the user's account ID will be the same as the person's account ID. The exception is for AE Global users (Super or Manager) who can have multiple accounts but only one person record per account.
|
||||
|
||||
// let params = {
|
||||
// user_id_random: user_id, // The user_id_random from the above authentication
|
||||
// }
|
||||
|
||||
// let params_json: key_val = {};
|
||||
|
||||
// params_json['and_qry'] = {};
|
||||
|
||||
// if (user_id) {
|
||||
// params_json['and_qry']['user_id_random'] = user_id;
|
||||
// }
|
||||
|
||||
// WARNING: This function returns a list. We only want the first one. There should be no more than 1 record returned.
|
||||
ae_promises['person'] = core_func.load_ae_obj_li__person({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_type: 'account',
|
||||
for_obj_id: $ae_loc.account_id,
|
||||
qry_user_id: user_id, // The user_id_random from the above authentication
|
||||
// params_json: params_json,
|
||||
// params: params,
|
||||
log_lvl: 1,
|
||||
}).then((person_response) => {
|
||||
if (person_response[0].person_id_random) {
|
||||
console.log(`Successfully loaded person for user_id_random (${user_id}):`, person_response[0]);
|
||||
person_obj = person_response[0];
|
||||
person_id = person_obj.person_id_random;
|
||||
|
||||
trigger = true; // Set trigger to true to indicate we can now sign in
|
||||
} else {
|
||||
alert('Failed to load person information: ' + person_response.error);
|
||||
}
|
||||
}).then(() => {
|
||||
// Once all promises are resolved, we can check if we have both user_id and person_id
|
||||
if (user_id && person_id) {
|
||||
// Set the session information
|
||||
// $ae_loc.person_id = person_id; // Set the person_id in the ae_loc store
|
||||
// $ae_loc.person = person_obj; // Store the full person object for reference
|
||||
// $ae_loc.user_id = user_id; // Set the user_id in the ae_loc store
|
||||
// $ae_loc.user = user_obj; // Store the full user object for reference
|
||||
|
||||
console.log(`Successfully authenticated and loaded user and person records:
|
||||
user_id: ${user_id},
|
||||
person_id: ${person_id}
|
||||
`);
|
||||
// window.location.reload(); // Reload to get the new session
|
||||
} else {
|
||||
console.error('Failed to authenticate and load data: missing user_id or person_id');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// console.log('DONE???', ae_promises);
|
||||
|
||||
} else {
|
||||
alert('Please enter either a User ID and Auth Key or Username and Password.');
|
||||
return false; // Prevent form submission if no credentials are provided
|
||||
}
|
||||
|
||||
}}
|
||||
>
|
||||
{#if $ae_sess.auth__entered_user_id}
|
||||
|
||||
<input
|
||||
type="text"
|
||||
class="input max-w-36"
|
||||
placeholder="User ID"
|
||||
value={$ae_sess.auth__entered_user_id ?? ''}
|
||||
oninput={(e) => $ae_sess.auth__entered_user_id = e.target.value}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="input max-w-36"
|
||||
placeholder="Auth Key"
|
||||
value={$ae_sess.auth__entered_user_key ?? ''}
|
||||
oninput={(e) => $ae_sess.auth__entered_user_key = e.target.value}
|
||||
>
|
||||
|
||||
{:else}
|
||||
|
||||
<input
|
||||
type="text"
|
||||
class="input max-w-48"
|
||||
placeholder="Username"
|
||||
value={$ae_sess.auth__entered_username ?? ''}
|
||||
oninput={(e) => $ae_sess.auth__entered_username = e.target.value}
|
||||
>
|
||||
<input
|
||||
type="password"
|
||||
class="input max-w-48"
|
||||
placeholder="Password"
|
||||
value={$ae_sess.auth__entered_password ?? ''}
|
||||
oninput={(e) => $ae_sess.auth__entered_password = e.target.value}
|
||||
>
|
||||
|
||||
{/if}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-sm preset-tonal-secondary border border-secondary-500 hover:preset-filled-secondary-500"
|
||||
title="Sign in with username and password"
|
||||
>
|
||||
<!-- <LogIn class="mx-1" /> -->
|
||||
<UserCheck class="mx-1" />
|
||||
{#if $ae_sess.auth__entered_user_id}
|
||||
User ID
|
||||
{:else}
|
||||
Username
|
||||
{/if}
|
||||
Sign In
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{:else}
|
||||
|
||||
<div class="flex flex-col gap-1 items-center justify-center">
|
||||
<span class="text-sm text-gray-500">
|
||||
{$ae_loc?.user?.username ?? '-- not set --'}
|
||||
</span>
|
||||
|
||||
<!-- Display change password option if the user is signed in and in Edit Mode -->
|
||||
{#if $ae_loc.edit_mode}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm preset-tonal-warning border border-warning-500 hover:preset-filled-warning-500"
|
||||
title="Change Password"
|
||||
onclick={() => {
|
||||
$ae_sess.show__modal_change_password = true;
|
||||
}}
|
||||
>
|
||||
<LockKeyhole class="mx-1" />
|
||||
<span class="hidden sm:inline">
|
||||
Change Password
|
||||
</span>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<!-- Display sign out option if the user is signed in -->
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm preset-tonal-warning border border-warning-500 hover:preset-filled-warning-500"
|
||||
title="Sign Out"
|
||||
onclick={async () => {
|
||||
if (confirm('Are you sure you want to sign out?')) {
|
||||
sign_out();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LogOut class="mx-1" />
|
||||
<span class="hidden sm:inline">
|
||||
Sign Out
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
<div
|
||||
class="flex flex-row gap-1 items-center justify-between w-full transition-all"
|
||||
>
|
||||
<span>
|
||||
{#if $ae_loc?.person_id && $ae_loc?.user_id}
|
||||
<span class="fas fa-user mx-1 text-gray-500"></span>
|
||||
{$ae_loc?.person?.full_name_override ?? $ae_loc?.person?.full_name}
|
||||
{:else}
|
||||
<!-- <span class="fas fa-user-x mx-1 text-gray-500"></span> -->
|
||||
<ShieldUser class="inline-block text-gray-500" />
|
||||
User sign in:
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="
|
||||
btn btn-sm text-xs
|
||||
variant-outline-warning hover:preset-tonal-warning
|
||||
border border-warning-500 group
|
||||
"
|
||||
title="Sign In"
|
||||
onclick={() => {
|
||||
$ae_sess.show__sign_in_out__fields = !$ae_sess.show__sign_in_out__fields; // Toggle the visibility of the sign-in form
|
||||
if (!$ae_sess.show__sign_in_out__fields) {
|
||||
$ae_sess.app_cfg.show_element__passcode_input = true;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if $ae_loc?.person_id && $ae_loc?.user_id}
|
||||
|
||||
{#if $ae_sess.show__sign_in_out__fields}
|
||||
<CircleX class="m-1" />
|
||||
<span
|
||||
class="
|
||||
cfg_text
|
||||
hidden
|
||||
group-hover:inline
|
||||
"
|
||||
>
|
||||
Hide Sign-In
|
||||
</span>
|
||||
{:else}
|
||||
<User class="mx-1 inline-block text-gray-500" />
|
||||
{$ae_loc?.person?.full_name_override ?? $ae_loc?.person?.full_name}
|
||||
{/if}
|
||||
|
||||
{:else}
|
||||
|
||||
{#if $ae_sess.show__sign_in_out__fields}
|
||||
<CircleX class="mx-1" />
|
||||
<span
|
||||
class="
|
||||
cfg_text
|
||||
hidden
|
||||
group-hover:inline
|
||||
"
|
||||
>
|
||||
Hide Sign-In
|
||||
</span>
|
||||
{:else}
|
||||
<!-- <LockKeyhole size="1.25em" class="mx-1 inline-block text-gray-500" /> -->
|
||||
<UserLock size="1.25em" class="m-1 inline-block text-gray-500" />
|
||||
<!-- <User class="mx-1 inline-block text-gray-500" /> -->
|
||||
<span
|
||||
class="
|
||||
cfg_text
|
||||
hidden
|
||||
group-hover:inline
|
||||
"
|
||||
>
|
||||
User Sign-In
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<!-- Change Password Modal -->
|
||||
<!-- They should be able to see their password (text) -->
|
||||
<!-- autocomplete="off" -->
|
||||
{#if $ae_sess.show__modal_change_password}
|
||||
<Modal
|
||||
title="Change Password"
|
||||
bind:open={$ae_sess.show__modal_change_password}
|
||||
autoclose={false}
|
||||
placement="top-center"
|
||||
size="md"
|
||||
class="top-center bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg border-gray-200 dark:border-gray-700 divide-gray-200 dark:divide-gray-700 shadow-md relative mx-auto w-full divide-y"
|
||||
>
|
||||
<div class="modal-box flex flex-col gap-2 items-center justify-center">
|
||||
<!-- If the user is a global Manger or Super then they can change the password for any user. Otherwise, they can only change their own password. Show email address field for a quick lookup to get the user.id. -->
|
||||
<div class="flex flex-col flex-wrap gap-2">
|
||||
<span class="text-sm text-gray-500">
|
||||
Your Username: {$ae_loc?.user?.username ?? '-- not set --'}
|
||||
</span>
|
||||
<span class="text-sm text-gray-500">
|
||||
Your Email: {$ae_loc?.user?.email ?? '-- not set --'}
|
||||
</span>
|
||||
<span class="text-sm text-gray-500">
|
||||
Your User ID: {$ae_loc?.user_id ?? '-- not set --'}
|
||||
</span>
|
||||
</div>
|
||||
{#if ($ae_loc?.manager_access)}
|
||||
<div class="flex flex-row flex-wrap gap-2">
|
||||
<input
|
||||
type="text"
|
||||
name="user_email"
|
||||
autocomplete="off"
|
||||
placeholder="User's email address"
|
||||
bind:value={user_email}
|
||||
class="input input-bordered w-48 max-w-full"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Allow for new password and confirm password fields. -->
|
||||
<div class="flex flex-row flex-wrap gap-2 items-center justify-center">
|
||||
<span class="flex flex-row flex-wrap gap-1">
|
||||
<input
|
||||
required
|
||||
type={show_password_text}
|
||||
autocomplete="new-password"
|
||||
name="new_password"
|
||||
placeholder="New Password"
|
||||
bind:value={new_password}
|
||||
class="input input-bordered w-48 max-w-full required:border-red-500"
|
||||
|
||||
/>
|
||||
<input
|
||||
required
|
||||
type={show_password_text}
|
||||
autocomplete="off"
|
||||
name="confirm_password"
|
||||
placeholder="Confirm Password"
|
||||
bind:value={confirm_password}
|
||||
class="input input-bordered w-48 max-w-full required:border-red-500"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<!-- Show/Hide Password Text -->
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-icon preset-tonal-secondary"
|
||||
onclick={() => {
|
||||
// const inputs = document.querySelectorAll('.modal-box input[type="password"]');
|
||||
// inputs.forEach((input) => {
|
||||
// input.type = input.type === 'password' ? 'text' : 'password';
|
||||
// });
|
||||
if (show_password_text === 'password') {
|
||||
show_password_text = 'text';
|
||||
} else {
|
||||
show_password_text = 'password';
|
||||
}
|
||||
}}
|
||||
title="Show/Hide Password Text"
|
||||
>
|
||||
{#if show_password_text === 'password'}
|
||||
<EyeOff class="mx-1"/>
|
||||
{:else}
|
||||
<Eye class="mx-1"/>
|
||||
{/if}
|
||||
<!-- <Eye class="mx-1"/> -->
|
||||
<!-- <span class="hidden sm:inline">
|
||||
Show/Hide
|
||||
</span> -->
|
||||
</button>
|
||||
|
||||
<div>
|
||||
<span class="text-sm text-warning-800 dark:text-gray-200">
|
||||
Password must be at <strong>least 10 characters</strong> long. It is recommend that you use a pass phrase (multiple words as your password) or a complex password (uppercase letter, lowercase letters, numbers, and special characters).
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row flex-wrap gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="btn preset-filled-warning-500"
|
||||
onclick={handle_change_password}
|
||||
disabled={is_changing_password}
|
||||
>
|
||||
<Key class="mx-1" />
|
||||
Change Password
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn preset-tonal-secondary"
|
||||
onclick={() => ($ae_sess.show__modal_change_password = false)}
|
||||
>
|
||||
<CircleX class="mx-1" />
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
{/if}
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
606
src/lib/app_components/e_app_sys_menu.svelte
Normal file
606
src/lib/app_components/e_app_sys_menu.svelte
Normal file
@@ -0,0 +1,606 @@
|
||||
<script lang="ts">
|
||||
// *** Import Svelte specific
|
||||
// import { tick } from 'svelte';
|
||||
// import { goto, invalidateAll } from '$app/navigation';
|
||||
|
||||
// *** Import other supporting libraries
|
||||
import {
|
||||
// ArrowBigRight,
|
||||
// Bug,
|
||||
CircleX,
|
||||
// Eye, EyeOff,
|
||||
// Key,
|
||||
// LogIn, LogOut, LockKeyhole,
|
||||
// Mail, MailCheck,
|
||||
Menu,
|
||||
// RefreshCw, RefreshCcwDot,
|
||||
ShieldEllipsis, ShieldMinus, ShieldPlus, ShieldUser,
|
||||
// ToggleLeft, ToggleRight,
|
||||
User, UserCheck,
|
||||
X
|
||||
} from '@lucide/svelte';
|
||||
|
||||
// *** Import Aether specific variables and functions
|
||||
// import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
import { ae_loc, ae_sess, ae_api, slct, slct_trigger } from '$lib/ae_stores';
|
||||
|
||||
import Element_access_type from '$lib/e_app_access_type.svelte';
|
||||
import Element_app_cfg from '$lib/e_app_cfg.svelte';
|
||||
import Element_sign_in_out from '$lib/e_app_sign_in_out.svelte';
|
||||
import Element_theme from '$lib/e_app_theme.svelte';
|
||||
|
||||
// *** Setup Svelte properties
|
||||
interface Props {
|
||||
log_lvl?: number;
|
||||
data: any;
|
||||
hide?: null|boolean;
|
||||
expand?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
log_lvl = $bindable(0),
|
||||
data = null,
|
||||
hide = $bindable(false),
|
||||
expand = $bindable(false),
|
||||
}: Props = $props();
|
||||
|
||||
let trigger_clear_access: null|boolean = $state(null);
|
||||
</script>
|
||||
|
||||
|
||||
<!-- App System Menu -->
|
||||
<!-- min-h-full
|
||||
max-h-max
|
||||
min-w-full
|
||||
max-w-max -->
|
||||
<!-- <section
|
||||
class="
|
||||
ae_app__menu
|
||||
hidden-print
|
||||
flex flex-col
|
||||
items-end justify-center
|
||||
gap-1
|
||||
absolute right-0 bottom-16
|
||||
hover:bottom-6
|
||||
bg-white dark:bg-gray-800
|
||||
border border-transparent
|
||||
hover:border hover:border-gray-200 hover:dark:border-gray-700
|
||||
rounded-lg
|
||||
p-2
|
||||
*:hover:inline
|
||||
opacity-40
|
||||
hover:opacity-100
|
||||
duration-50 delay-1000 hover:delay-100 hover:ease-out
|
||||
transition hover:transition-all
|
||||
z-10 hover:z-20
|
||||
"
|
||||
class:hidden={!expand_btn}
|
||||
> -->
|
||||
<!-- && !expand -->
|
||||
<!-- !$ae_loc
|
||||
?.sys_menu?.expand_btn -->
|
||||
<!-- bg-blue-100/60 dark:bg-blue-800/50 -->
|
||||
<!-- class:hover:border-transparent={!expand} -->
|
||||
<!-- mx-1 my-2 -->
|
||||
<!-- We need to be able to hide the menu button in certain situations. Mainly iframes. -->
|
||||
<section
|
||||
class="
|
||||
ae_app__sys_menu
|
||||
|
||||
hidden-print
|
||||
z-50
|
||||
absolute bottom-6 right-0
|
||||
|
||||
opacity-90
|
||||
hover:opacity-100
|
||||
|
||||
w-min
|
||||
max-w-md
|
||||
|
||||
flex flex-col-reverse gap-1
|
||||
items-end justify-center
|
||||
|
||||
text-sm sm:text-sm md:text-md lg:text-md xl:text-md 2xl:text-lg
|
||||
|
||||
bg-blue-200/90
|
||||
|
||||
border-2 rounded-lg
|
||||
border-blue-300/20 dark:border-blue-700/20
|
||||
hover:border-blue-500/20 hover:dark:border-blue-500/20
|
||||
|
||||
transition-all
|
||||
delay-500 hover:delay-200
|
||||
duration-500 hover:duration-200
|
||||
ease-in-out
|
||||
"
|
||||
class:top-0={expand && 1 == 3}
|
||||
class:opacity-100={expand}
|
||||
class:w-full={expand}
|
||||
class:hidden={hide}
|
||||
class:border-transparent={!expand}
|
||||
class:bg-transparent={!expand}
|
||||
>
|
||||
|
||||
<!-- class:hidden={!expand} -->
|
||||
<!-- class:preset-filled-warning-100-900={expand} -->
|
||||
<div
|
||||
class:opacity-50={!expand}
|
||||
class:opacity-100={expand}
|
||||
class:light:bg-blue-500={expand}
|
||||
class:dark:bg-blue-500={expand}
|
||||
class="
|
||||
hidden-print
|
||||
flex flex-col items-end justify-end
|
||||
gap-1
|
||||
|
||||
overflow-y-auto
|
||||
p-1
|
||||
|
||||
w-full
|
||||
|
||||
light:bg-blue-200/10
|
||||
dark:bg-blue-800/10
|
||||
hover:opacity-100
|
||||
|
||||
relative
|
||||
|
||||
transition-all
|
||||
delay-1000 hover:delay-500
|
||||
duration-200 hover:duration-200
|
||||
ease-in-out
|
||||
"
|
||||
title="
|
||||
ID: {$ae_loc?.person_id ?? '-- not set --'} / {$ae_loc?.user_id ?? '-- not set --'}
|
||||
Name: {$ae_loc?.person?.full_name ?? '-- not set --'}
|
||||
Username: {$ae_loc?.user?.username ?? '-- not set --'}
|
||||
Email: {$ae_loc?.user?.email ?? '-- not set --'}
|
||||
Access Type: {$ae_loc?.access_type ?? '-- not set --'}
|
||||
"
|
||||
>
|
||||
|
||||
{#if $ae_loc?.person_id}
|
||||
<div
|
||||
class="flex flex-row gap-1 items-center justify-end transition-all w-full group"
|
||||
>
|
||||
<User size="1em" class="mx-1 inline-block text-gray-500" />
|
||||
<span
|
||||
class:hidden={!expand}
|
||||
class="group-hover:inline-block"
|
||||
>
|
||||
{$ae_loc?.person?.informal_name ?? $ae_loc?.person?.given_name}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $ae_loc?.user_id}
|
||||
<!-- This is currently not set to show if not expanded. Saving space. -->
|
||||
<div
|
||||
class:hidden={!expand}
|
||||
class="flex flex-row gap-1 items-center justify-end transition-all w-full group"
|
||||
>
|
||||
<ShieldUser size="1em" class="mx-1 inline-block text-gray-500" />
|
||||
<span
|
||||
class:hidden={!expand}
|
||||
class="group-hover:inline-block"
|
||||
>
|
||||
{$ae_loc?.user?.username ?? '-- not set --'}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-row gap-1 items-center justify-end transition-all w-full group">
|
||||
{#if $ae_loc.access_type && $ae_loc.access_type != 'anonymous'}
|
||||
<span
|
||||
class="flex flex-row-reverse gap-1 group text-base"
|
||||
title={`Current access type/level: ${$ae_loc.access_type}`}
|
||||
>
|
||||
<!-- <span class="fas fa-unlock mx-1"></span> -->
|
||||
<!-- <ShieldPlus class="inline-block" /> -->
|
||||
|
||||
|
||||
{#if $ae_loc.access_type == 'super'}
|
||||
<span class="fas fa-hat-wizard m-1"></span>
|
||||
<span
|
||||
class:hidden={!expand}
|
||||
class="hidden group-hover:inline-block"
|
||||
>Super</span>
|
||||
{:else if $ae_loc.access_type == 'manager'}
|
||||
<span class="fas fa-user-shield m-1"></span>
|
||||
<span
|
||||
class:hidden={!expand}
|
||||
class="hidden group-hover:inline-block"
|
||||
>Manager</span>
|
||||
{:else if $ae_loc.access_type == 'administrator'}
|
||||
<span class="fas fa-user-ninja m-1"></span>
|
||||
<span
|
||||
class:hidden={!expand}
|
||||
class="hidden group-hover:inline-block"
|
||||
>Administrator</span>
|
||||
{:else if $ae_loc.access_type == 'trusted'}
|
||||
<span class="fas fa-user-check m-1"></span>
|
||||
<span
|
||||
class:hidden={!expand}
|
||||
class="hidden group-hover:inline-block"
|
||||
>Trusted Access</span>
|
||||
{:else if $ae_loc.access_type == 'public'}
|
||||
Public
|
||||
<span
|
||||
class:hidden={!expand}
|
||||
class="hidden group-hover:inline-block"
|
||||
>Access</span>
|
||||
{:else if $ae_loc.access_type == 'authenticated'}
|
||||
Authenticated
|
||||
<span
|
||||
class:hidden={!expand}
|
||||
class="hidden group-hover:inline-block"
|
||||
>Access</span>
|
||||
{:else if $ae_loc.access_type == 'anonymous'}
|
||||
Anonymous Access
|
||||
{:else}
|
||||
Unknown Access
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
{#if $ae_loc?.user_access_type && $ae_loc?.access_type == $ae_loc?.user_access_type}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
// handle_clear_access();
|
||||
// trigger_clear_access = true;
|
||||
// $ae_loc.app_cfg.show_element__passcode_input = !$ae_loc.app_cfg.show_element__passcode_input;
|
||||
|
||||
if (!expand) {
|
||||
expand = true;
|
||||
$ae_sess.sys_menu.expand = true;
|
||||
$ae_loc.sys_menu.hide_access_type = false;
|
||||
$ae_loc.sys_menu.expand_access_type = true;
|
||||
// $ae_loc.sys_menu.expand_btn = false;
|
||||
// $ae_loc.app_cfg.show_element__access_type = true;
|
||||
// $ae_loc.app_cfg.show_element__passcode_input = true;
|
||||
} else {
|
||||
// expand = true;
|
||||
// $ae_loc.sys_menu.expand = false;
|
||||
$ae_loc.sys_menu.hide_access_type = false;
|
||||
$ae_loc.sys_menu.expand_access_type = true;
|
||||
|
||||
// $ae_loc.sys_menu.expand_btn = true;
|
||||
}
|
||||
}}
|
||||
class="btn btn-sm text-xs variant-outline-surface hover:variant-ghost-warning transition-all group"
|
||||
title={`Current user access level: "${$ae_loc.user_access_type}". Click use passcode for a different access level.`}
|
||||
>
|
||||
<!-- <span class="fas fa-lock mx-1"></span> -->
|
||||
<!-- <ShieldMinus /> -->
|
||||
<ShieldEllipsis size="2em" class="inline-block" />
|
||||
<span class="hidden group-hover:inline-block">Passcode?</span>
|
||||
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
trigger_clear_access = true;
|
||||
|
||||
if (expand) {
|
||||
$ae_loc.sys_menu.hide_access_type = false;
|
||||
// $ae_loc.sys_menu.expand_access_type = false;
|
||||
expand = false;
|
||||
|
||||
// $ae_loc.sys_menu.expand = true;
|
||||
// $ae_loc.sys_menu.expand_btn = false;
|
||||
$ae_loc.app_cfg.show_element__access_type = true;
|
||||
$ae_sess.app_cfg.show_element__passcode_input = true;
|
||||
|
||||
// await tick();
|
||||
// console.log('Layout button click: Focus on passcode input!');
|
||||
// /** @type {HTMLElement | null} */
|
||||
// const to_focus = document.getElementById('access_passcode_input');
|
||||
// to_focus?.focus();
|
||||
} else {
|
||||
$ae_loc.sys_menu.hide_access_type = false;
|
||||
$ae_loc.sys_menu.expand_access_type = true;
|
||||
// $ae_loc.sys_menu.expand = false;
|
||||
// $ae_loc.sys_menu.expand_btn = true;
|
||||
}
|
||||
}}
|
||||
class="
|
||||
btn btn-sm text-xs
|
||||
flex-row-reverse
|
||||
variant-outline-surface hover:variant-ghost-warning
|
||||
transition-all group
|
||||
"
|
||||
title={`Current access level: "${$ae_loc.access_type}". Click to clear the temporary access level.`}
|
||||
>
|
||||
<!-- <span class="fas fa-lock mx-1"></span> Lock? -->
|
||||
<ShieldMinus class="inline-block" />
|
||||
<span class="hidden group-hover:inline-block">
|
||||
Clear?
|
||||
</span>
|
||||
</button>
|
||||
{/if}
|
||||
{:else}
|
||||
<button
|
||||
type="button"
|
||||
onclick={async () => {
|
||||
|
||||
expand = true;
|
||||
$ae_sess.sys_menu.expand = true;
|
||||
$ae_loc.sys_menu.hide_access_type = false;
|
||||
$ae_loc.sys_menu.expand_access_type = true;
|
||||
|
||||
$ae_sess.app_cfg.show_element__passcode_input = true;
|
||||
$ae_sess.sys_menu.focus_passcode_input = true;
|
||||
|
||||
// $ae_loc.sys_menu.expand = true;
|
||||
// $ae_loc.sys_menu.expand_btn = false;
|
||||
// $ae_loc.app_cfg.show_element__access_type = true;
|
||||
// $ae_loc.app_cfg.show_element__passcode_input = true;
|
||||
// await tick();
|
||||
console.log('Layout button click: Focus on passcode input!');
|
||||
/** @type {HTMLElement | null} */
|
||||
const to_focus = document.getElementById('access_passcode_input');
|
||||
to_focus?.focus();
|
||||
|
||||
}}
|
||||
class="
|
||||
btn btn-sm text-xs
|
||||
flex-row-reverse
|
||||
variant-outline-surface hover:variant-ghost-success
|
||||
transition-all group
|
||||
"
|
||||
title="Anonymous public access is currently set. You must Sign In or use a passcode to change your access level."
|
||||
>
|
||||
<!-- <span class="fas fa-lock mx-1 lock_icon"></span> -->
|
||||
<!-- <span class="">Unlock?</span> -->
|
||||
<ShieldUser class="inline-block" />
|
||||
<span class="hidden group-hover:inline-block">Auth?</span>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{#if $ae_loc.edit_mode}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
$ae_loc.edit_mode = false;
|
||||
// dispatch_edit_mode_changed();
|
||||
}}
|
||||
class="
|
||||
btn btn-base text-sm
|
||||
preset-tonal-warning
|
||||
preset-outlined-warning-800-200
|
||||
hover:preset-tonal-success
|
||||
transition-all group
|
||||
min-w-22 md:min-w-30 w-full max-w-fit
|
||||
gap-1
|
||||
"
|
||||
title="Click to turn off edit mode. Edit mode is currently on."
|
||||
>
|
||||
<span class="fas fa-toggle-on m-1 inline-block"></span>
|
||||
<span class="text-xs">Edit</span>
|
||||
<span class="hidden group-hover:inline-block group-hover:text-xs">
|
||||
Off
|
||||
</span>
|
||||
</button>
|
||||
{:else if $ae_loc.authenticated_access}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
$ae_loc.edit_mode = true;
|
||||
// dispatch_edit_mode_changed();
|
||||
}}
|
||||
class="
|
||||
btn btn-base text-sm
|
||||
preset-tonal-surface
|
||||
preset-outlined-warning-400-600
|
||||
hover:preset-tonal-warning
|
||||
transition-all group
|
||||
min-w-22 md:min-w-30 w-full max-w-fit
|
||||
gap-1
|
||||
"
|
||||
title="Click to torn on/enable edit mode. Edit mode is currently off/disabled."
|
||||
>
|
||||
<span class="fas fa-toggle-off m-1 inline-block"></span>
|
||||
<span class="text-xs">Edit</span>
|
||||
<span class="hidden group-hover:inline-block group-hover:text-xs">
|
||||
On?
|
||||
</span>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
|
||||
<!-- <div> -->
|
||||
<!-- class:visible={expand} -->
|
||||
<!-- class:invisible={!expand} -->
|
||||
<!-- class:hover:visible={true} -->
|
||||
<!-- invisible -->
|
||||
<button
|
||||
type="button"
|
||||
class:w-full={expand}
|
||||
class="
|
||||
btn btn-base text-sm
|
||||
preset-filled-tertiary-400-600
|
||||
preset-outlined-tertiary-400-600
|
||||
hover:preset-filled-success active:preset-filled-success
|
||||
px-1 py-1
|
||||
min-w-22 md:min-w-30 w-full max-w-fit
|
||||
transition-all group
|
||||
"
|
||||
title="Show/Hide the system menu"
|
||||
onclick={async () => {
|
||||
if (!expand) {
|
||||
expand = true;
|
||||
$ae_sess.sys_menu.expand = true;
|
||||
|
||||
// $ae_loc.sys_menu.expand = true;
|
||||
// $ae_loc.sys_menu.expand_btn = false;
|
||||
$ae_loc.app_cfg.show_element__access_type = true;
|
||||
|
||||
if ($ae_loc?.access_type == 'anonymous') {
|
||||
$ae_sess.sys_menu.focus_passcode_input = true;
|
||||
// $ae_sess.app_cfg.show_element__passcode_input = true;
|
||||
} else {
|
||||
// $ae_sess.app_cfg.show_element__passcode_input = false;
|
||||
$ae_loc.sys_menu.expand_user = false; // Not in use yet
|
||||
$ae_sess.show__sign_in_out__fields = false;
|
||||
}
|
||||
// $ae_loc.app_cfg.show_element__passcode_input = true;
|
||||
// await tick();
|
||||
// console.log('Layout button click: Focus on passcode input!');
|
||||
// /** @type {HTMLElement | null} */
|
||||
// const to_focus = document.getElementById('access_passcode_input');
|
||||
// to_focus?.focus();
|
||||
} else {
|
||||
expand = false;
|
||||
$ae_sess.sys_menu.expand = false;
|
||||
// $ae_loc.sys_menu.expand = false;
|
||||
// $ae_loc.sys_menu.expand_btn = true;
|
||||
// $ae_loc.app_cfg.show_element__passcode_input = false;
|
||||
}
|
||||
// $ae_loc.sys_menu.expand_btn = !expand_btn;
|
||||
}}
|
||||
>
|
||||
<!-- <span class=""> -->
|
||||
{#if expand}
|
||||
<CircleX class="m-1 inline-block" />
|
||||
{:else}
|
||||
<Menu class="m-1 inline-block" />
|
||||
{/if}
|
||||
<span class="text-xs hidden group-hover:inline-block">
|
||||
Menu
|
||||
</span>
|
||||
<!-- </span> -->
|
||||
</button>
|
||||
<!-- </div> -->
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Show menu on right side of page -->
|
||||
<!-- min-h-96 -->
|
||||
<!-- absolute right-0 bottom-10 -->
|
||||
<!-- opacity-50 -->
|
||||
<!-- hover:opacity-100 -->
|
||||
<!-- bg-white dark:bg-gray-800 -->
|
||||
<div
|
||||
class:preset-filled-warning-200-800={expand}
|
||||
class:hidden={!expand}
|
||||
class="
|
||||
ae_app__sys_menu
|
||||
|
||||
hidden-print
|
||||
flex flex-col
|
||||
items-end
|
||||
justify-end
|
||||
gap-2
|
||||
|
||||
min-w-48
|
||||
|
||||
bg-white dark:bg-gray-800
|
||||
|
||||
border border-gray-200 dark:border-gray-700
|
||||
rounded-lg
|
||||
px-1 py-2
|
||||
|
||||
transition-all
|
||||
delay-1000 hover:delay-100
|
||||
duration-100 hover:duration-200
|
||||
ease-in-out
|
||||
|
||||
z-20 hover:z-30
|
||||
"
|
||||
>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class:w-full={expand}
|
||||
class="
|
||||
btn btn-sm
|
||||
preset-filled-tertiary-400-600
|
||||
preset-outlined-tertiary-400-600
|
||||
hover:preset-filled-success active:preset-filled-success
|
||||
px-6 py-1
|
||||
transition-all group
|
||||
"
|
||||
title="Show/Hide the system menu"
|
||||
onclick={() => {
|
||||
if (!expand) {
|
||||
expand = true;
|
||||
$ae_sess.sys_menu.expand = true;
|
||||
// $ae_loc.sys_menu.expand_btn = false;
|
||||
// $ae_loc.sys_menu.expand_access_type = true;
|
||||
|
||||
// $ae_sess.app_cfg.show_element__passcode_input = true;
|
||||
$ae_sess.sys_menu.focus_passcode_input = true;
|
||||
} else {
|
||||
$ae_sess.sys_menu.expand = false;
|
||||
$ae_loc.sys_menu.expand_user = false; // Not in use yet
|
||||
$ae_sess.show__sign_in_out__fields = false;
|
||||
// $ae_loc.sys_menu.expand_btn = true;
|
||||
}
|
||||
// $ae_loc.sys_menu.expand_btn = !expand_btn;
|
||||
// $ae_loc.sys_menu.expand = !expand;
|
||||
}}
|
||||
>
|
||||
{#if expand}
|
||||
<CircleX class="m-1 inline-block" />
|
||||
<span class="hidden group-hover:inline-block">
|
||||
Hide Menu
|
||||
</span>
|
||||
{:else}
|
||||
<Menu class="m-1 inline-block" />
|
||||
<span class="hidden group-hover:inline-block">
|
||||
Menu
|
||||
</span>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<!-- {#if $ae_loc?.app_cfg?.show_element__cfg} -->
|
||||
<span
|
||||
class:hidden={!$ae_loc.edit_mode}
|
||||
>
|
||||
<Element_app_cfg
|
||||
hide={$ae_loc.sys_menu.hide_app_cfg}
|
||||
expand={$ae_loc.sys_menu.expand_app_cfg}
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class:hidden={!$ae_loc.edit_mode}
|
||||
>
|
||||
<Element_theme
|
||||
hide={$ae_loc.sys_menu.hide_app_cfg}
|
||||
expand={$ae_loc.sys_menu.expand_app_cfg}
|
||||
set_theme_mode={true}
|
||||
set_theme_name={true}
|
||||
/>
|
||||
</span>
|
||||
<!-- {/if} -->
|
||||
|
||||
{#if $ae_loc?.app_cfg?.show_element__sign_in_out}
|
||||
<!-- hide={$ae_loc.sys_menu.hide_user} -->
|
||||
<!-- expand={$ae_loc.sys_menu.expand_user} -->
|
||||
<Element_sign_in_out
|
||||
data={data}
|
||||
hidden={$ae_loc.iframe || !$ae_loc.app_cfg?.show_element__sign_in_out}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if !$ae_loc?.sys_menu?.hide_access_type && !$ae_loc?.iframe}
|
||||
<!-- hidden={$ae_loc?.iframe && !$ae_loc?.trusted_access && !expand} -->
|
||||
<Element_access_type
|
||||
bind:hide={$ae_loc.sys_menu.hide_access_type}
|
||||
bind:focus_input={$ae_sess.sys_menu.focus_passcode_input}
|
||||
bind:expand={$ae_loc.sys_menu.expand_access_type}
|
||||
bind:show_passcode_input={$ae_sess.app_cfg.show_element__passcode_input}
|
||||
bind:trigger_clear_access={trigger_clear_access}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
258
src/lib/app_components/e_app_theme.svelte
Normal file
258
src/lib/app_components/e_app_theme.svelte
Normal file
@@ -0,0 +1,258 @@
|
||||
<script lang="ts">
|
||||
|
||||
import {
|
||||
Moon, Sun
|
||||
} from '@lucide/svelte';
|
||||
|
||||
import { ae_loc, ae_sess, ae_api, slct, slct_trigger } from '$lib/ae_stores';
|
||||
|
||||
interface Props {
|
||||
log_lvl?: number;
|
||||
hide?: null|boolean;
|
||||
expand?: boolean;
|
||||
set_theme_mode: any;
|
||||
set_theme_name: any;
|
||||
}
|
||||
|
||||
let {
|
||||
log_lvl = $bindable(0),
|
||||
hide = $bindable(false),
|
||||
expand = $bindable(false),
|
||||
set_theme_mode,
|
||||
set_theme_name
|
||||
}: Props = $props();
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Change light and dark mode -->
|
||||
<!--
|
||||
if ($ae_loc.app_cfg.theme_mode == 'light') {
|
||||
document.documentElement.classList.remove('dark');
|
||||
document.documentElement.classList.add('light');
|
||||
} else if ($ae_loc.app_cfg.theme_mode == 'dark') {
|
||||
document.documentElement.classList.remove('light');
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
-->
|
||||
<section
|
||||
id="AE-App-Theme"
|
||||
class="
|
||||
ae_app_theme
|
||||
hidden-print
|
||||
|
||||
bg-blue-100 text-gray-900
|
||||
dark:bg-blue-800 dark:text-gray-200
|
||||
|
||||
flex flex-col flex-wrap gap-1
|
||||
items-end justify-center
|
||||
|
||||
w-72 max-w-72
|
||||
p-1
|
||||
border-2 border-gray-200
|
||||
|
||||
duration-300 delay-150 hover:delay-1000 hover:ease-out
|
||||
transition-all
|
||||
"
|
||||
class:hidden={hide}
|
||||
>
|
||||
|
||||
<div
|
||||
class:hidden={!expand}
|
||||
class="flex flex-row flex-wrap gap-2 items-center justify-between w-full"
|
||||
>
|
||||
<!-- Theme Name: -->
|
||||
<span class="text-sm font-semibold">
|
||||
{$ae_loc.theme_name}
|
||||
</span>
|
||||
<select
|
||||
onchange={(event) => {
|
||||
// $slct_trigger = 'set_theme_name';
|
||||
|
||||
let new_theme_name = event.target.value;
|
||||
// document.documentElement.theme = new_theme_name;
|
||||
|
||||
console.log(`$ae_loc?.theme_name=${$ae_loc?.theme_name}`);
|
||||
// $slct_trigger = null;
|
||||
// Update the body attribute named "data-theme" to the current theme name.
|
||||
// document.body.setAttribute('data-theme', new_theme_name);
|
||||
// document.body.setAttribute('data-theme', $ae_loc?.theme_name);
|
||||
|
||||
// NEW for Tailwind v4: Update the html attribute named "data-theme" to the current theme name.
|
||||
document.documentElement.setAttribute('data-theme', new_theme_name);
|
||||
|
||||
// if ($ae_loc.theme_mode == 'light') {
|
||||
// document.documentElement.classList.remove('dark');
|
||||
// document.documentElement.classList.add('light');
|
||||
// } else if ($ae_loc.theme_mode == 'dark') {
|
||||
// document.documentElement.classList.remove('light');
|
||||
// document.documentElement.classList.add('dark');
|
||||
// }
|
||||
}}
|
||||
bind:value={$ae_loc.theme_name}
|
||||
class="select w-32"
|
||||
title="Theme name"
|
||||
>
|
||||
<option value="">-- None --</option>
|
||||
<option value="cerberus">Cerberus</option>
|
||||
<option value="concord">Concord</option>
|
||||
<option value="crimson">Crimson</option>
|
||||
<option value="hamlindigo">Hamlindigo</option>
|
||||
<option value="modern">Modern</option>
|
||||
<option value="nouveau">Nouveau</option>
|
||||
<option value="rocket">Rocket</option>
|
||||
<option value="terminus">Terminus</option>
|
||||
<option value="vintage">Vintage</option>
|
||||
<option value="wintry">Wintry</option>
|
||||
<!-- <option value="ae_c_osit">OSIT</option> -->
|
||||
<option value="AE_OSIT_default">OSIT</option>
|
||||
<option value="AE_c_IDAA_light">IDAA - light</option>
|
||||
<option value="AE_c_LCI">LCI</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-row flex-wrap gap-2 items-center w-full"
|
||||
class:justify-between={expand}
|
||||
class:justify-end={!expand}
|
||||
>
|
||||
|
||||
{#if expand}
|
||||
<!-- Hide theme options -->
|
||||
<button
|
||||
class="
|
||||
btn btn-sm text-xs
|
||||
preset-tonal-secondary hover:preset-filled-secondary-500
|
||||
transition-all group
|
||||
"
|
||||
onclick={() => {
|
||||
expand = !expand;
|
||||
}}
|
||||
title="Hide Theme Options"
|
||||
>
|
||||
<!-- <span class="fas fa-compress-alt"></span> -->
|
||||
<span class="fas fa-compress-arrows-alt m-1"></span>
|
||||
<span
|
||||
class="
|
||||
hidden
|
||||
group-hover:inline-block
|
||||
text-xs
|
||||
"
|
||||
>
|
||||
Hide Theme Options
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="
|
||||
btn btn-sm text-xs
|
||||
preset-tonal-secondary hover:preset-filled-secondary-500
|
||||
transition-all group
|
||||
"
|
||||
onclick={() => {
|
||||
if ($ae_loc.theme_mode == 'light') {
|
||||
$ae_loc.theme_mode = 'dark';
|
||||
} else if ($ae_loc.theme_mode == 'dark') {
|
||||
$ae_loc.theme_mode = 'light';
|
||||
}
|
||||
|
||||
if ($ae_loc.theme_mode == 'light') {
|
||||
document.documentElement.classList.remove('dark');
|
||||
document.documentElement.classList.add('light');
|
||||
} else if ($ae_loc.theme_mode == 'dark') {
|
||||
document.documentElement.classList.remove('light');
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
}}
|
||||
title="Change light and dark mode"
|
||||
>
|
||||
<!-- <span class="fas fa-adjust"></span> -->
|
||||
{#if $ae_loc.theme_mode == 'light'}
|
||||
<Sun class="m-1" />
|
||||
<span class="hidden md:inline-block group-hover:inline-block">Light Mode</span>
|
||||
{:else if $ae_loc.theme_mode == 'dark'}
|
||||
<Moon class="m-1" />
|
||||
<span class="hidden group-hover:inline-block">Dark Mode</span>
|
||||
{/if}
|
||||
<span
|
||||
class="
|
||||
hidden
|
||||
group-hover:inline-block
|
||||
text-xs
|
||||
"
|
||||
>
|
||||
Change
|
||||
</span>
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
class="btn btn-sm preset-tonal-secondary hover:preset-filled-secondary-500 group"
|
||||
onclick={() => {
|
||||
if ($ae_loc.theme_mode == 'light') {
|
||||
$ae_loc.theme_mode = 'dark';
|
||||
} else if ($ae_loc.theme_mode == 'dark') {
|
||||
$ae_loc.theme_mode = 'light';
|
||||
}
|
||||
|
||||
if ($ae_loc.theme_mode == 'light') {
|
||||
document.documentElement.classList.remove('dark');
|
||||
document.documentElement.classList.add('light');
|
||||
} else if ($ae_loc.theme_mode == 'dark') {
|
||||
document.documentElement.classList.remove('light');
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
|
||||
expand = !expand;
|
||||
}}
|
||||
title="Change light and dark mode"
|
||||
>
|
||||
{#if $ae_loc.theme_mode == 'light'}
|
||||
<span class="inline-block" title="Light Mode">
|
||||
<Sun />
|
||||
</span>
|
||||
<span class="hidden group-hover:inline-block">Light Mode</span>
|
||||
{:else if $ae_loc.theme_mode == 'dark'}
|
||||
<span class="inline-block" title="Dark Mode">
|
||||
<Moon />
|
||||
</span>
|
||||
<span class="hidden group-hover:inline-block">Dark Mode</span>
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- <section class="space-y-2">
|
||||
<h2 class="strong">Theme:</h2> -->
|
||||
<!-- Light/Dark Theme: -->
|
||||
<!-- <div>
|
||||
<RadioGroup
|
||||
active="variant-glass-success"
|
||||
hover="hover:variant-ringed-surface"
|
||||
>
|
||||
<RadioItem
|
||||
onchange={() => {
|
||||
$slct_trigger = 'set_theme_mode';
|
||||
}}
|
||||
bind:group={$ae_loc.theme_mode}
|
||||
name="theme_light"
|
||||
value={'light'}
|
||||
>
|
||||
Light
|
||||
</RadioItem>
|
||||
<RadioItem
|
||||
onchange={() => {
|
||||
$slct_trigger = 'set_theme_mode';
|
||||
}}
|
||||
bind:group={$ae_loc.theme_mode}
|
||||
name="theme_dark"
|
||||
value={'dark'}
|
||||
>
|
||||
Dark
|
||||
</RadioItem>
|
||||
</RadioGroup>
|
||||
</div> -->
|
||||
|
||||
|
||||
|
||||
</section>
|
||||
Reference in New Issue
Block a user