chore: migrate all FA icons to Lucide (@lucide/svelte)

- Replaced all active FontAwesome <span class="fas fa-*"> icons with
  Lucide components across 145 files (excluding /idaa/ which is intentional)
- Fixed merge script bug: consolidated lucide-svelte imports into @lucide/svelte
- Replaced dynamic toggle patterns (fa-toggle-on/off) with ToggleRight/ToggleLeft
- Replaced fa-eye/fa-eye-slash with Eye/EyeOff
- Replaced fa-bug/fa-bug-slash with Bug/BugOff
- Replaced fa-sync fa-spin with RefreshCw + animate-spin
- Replaced fa-microchip with Cpu
- Fixed {@const} placement in element_manage_event_file_li.svelte
- Removed obsolete CSS hover rules for .unlock_icon/.lock_icon
- svelte-check: 0 errors, 0 warnings
This commit is contained in:
Scott Idem
2026-03-16 18:07:43 -04:00
parent c9050264a5
commit b543c8a930
147 changed files with 587 additions and 754 deletions

View File

@@ -19,7 +19,7 @@
} from '$lib/stores/ae_stores';
import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
import { Check, Download, LoaderCircle, MinusCircle, Scissors } from '@lucide/svelte';
// Exports
// export let input_name = 'file_list';
@@ -221,7 +221,7 @@
class="btn btn-sm preset-tonal-warning hover:preset-filled-warning-500"
title={`Remove this file from list of videos:\n${hosted_file_obj.filename}\n[API] SHA256: ${hosted_file_obj?.hash_sha256?.slice(0, 10)}... Hosted ID: ${hosted_file_obj.hosted_file_id}`}
>
<span class="fas fa-minus-circle m-1"></span>
<MinusCircle size="1em" class="m-1" />
<span class="">Remove</span>
</button>
@@ -387,15 +387,15 @@
>
<!-- {#await ae_promises[hosted_file_id]} -->
{#if $ae_loc.files.processed_file_kv[hosted_file_id] && $ae_loc.files.processed_file_kv[hosted_file_id].submit_status == 'clipping'}
<span class="fas fa-spinner fa-spin m-1"></span>
<LoaderCircle size="1em" class="m-1 animate-spin" />
<span class="highlight">Clipping...</span>
{:else}
<!-- {#if ae_promises[hosted_file_id]} -->
{#if $ae_loc.files.processed_file_kv[hosted_file_id] && $ae_loc.files.processed_file_kv[hosted_file_id].submit_status == 'clipped'}
<span class="fas fa-check m-1"></span>
<Check size="1em" class="m-1" />
Clipped
{:else}
<span class="fas fa-cut m-1"></span>
<Scissors size="1em" class="m-1" />
Clip Video
{/if}
{/if}
@@ -405,13 +405,13 @@
</form>
{#await ae_promises[hosted_file_id]}
<span class="fas fa-spinner fa-spin m-1"></span>
<LoaderCircle size="1em" class="m-1 animate-spin" />
<span class="highlight"
>Processing... This may take a few minutes.</span
>
{:then}
{#if ae_promises[hosted_file_id]}
<span class="fas fa-download"></span> Ready to download below!
<Download size="1em" /> Ready to download below!
{:else}
<!-- <p>Fill out the form and select the video file to clip.</p> -->
{/if}

View File

@@ -6,11 +6,7 @@
* Supports General, AI, Performance, and IDAA-specific configurations.
*/
import { Modal } from 'flowbite-svelte';
import {
Palette, Mail, Brain, Timer,
ShieldCheck, CodeXml, Save,
Plus, Minus, Globe, ExternalLink
} from 'lucide-svelte';
import { Brain, CodeXml, ExternalLink, Globe, Mail, Minus, Palette, Plus, Save, ShieldCheck, Timer } from '@lucide/svelte';
import AE_Comp_Editor_CodeMirror from '$lib/elements/AE_Comp_Editor_CodeMirror.svelte';
import { ae_loc } from '$lib/stores/ae_stores';

View File

@@ -1,6 +1,5 @@
<script lang="ts">
import { Settings } from '@lucide/svelte';
import { Code, Eraser, LockOpen, RefreshCw, Settings, ShieldCheck, ShieldUser, Trash2, UserRound, Users } from '@lucide/svelte';
import { ae_util } from '$lib/ae_utils/ae_utils';
import { ae_loc, ae_sess, ae_api, slct, slct_trigger } from '$lib/stores/ae_stores';
import E_app_url_builder from '$lib/app_components/e_app_url_builder.svelte';
@@ -138,19 +137,19 @@
</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
<ShieldCheck size="1em" class="mx-1" /> Super Access
{:else if $ae_loc.access_type == 'manager'}
<span class="fas fa-user-shield mx-1"></span> Manager Access
<ShieldUser size="1em" class="mx-1" /> Manager Access
{:else if $ae_loc.access_type == 'administrator'}
<span class="fas fa-user-ninja mx-1"></span> Administrator Access
<UserRound size="1em" class="mx-1" /> Administrator Access
{:else if $ae_loc.access_type == 'trusted'}
<span class="fas fa-user-nurse mx-1"></span> Trusted Access
<UserRound size="1em" class="mx-1" /> Trusted Access
{:else if $ae_loc.access_type == 'authenticated'}
<span class="fas fa-user-friends mx-1"></span> Authenticated Access
<Users size="1em" class="mx-1" /> Authenticated Access
{:else if $ae_loc.access_type == 'anonymous'}
<span class="fas fa-users mx-1"></span> Anonymous Access
<Users size="1em" class="mx-1" /> Anonymous Access
{:else}
<span class="fas fa-unlock mx-1"></span> Unknown Access
<LockOpen size="1em" class="mx-1" /> Unknown Access
{/if}
<!-- <button
@@ -171,18 +170,18 @@
<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>
<Code size="1em" class="mx-1" />
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>
<Code size="1em" class="mx-1" />
Exit iframe Mode
</a>
{:else}
<a class="btn btn-sm preset-tonal-secondary" href="/?iframe=true">
<span class="fas fa-code mx-1"></span>
<Code size="1em" class="mx-1" />
Use iframe Mode
</a>
{/if}
@@ -196,9 +195,9 @@
window.location.reload();
}}
>
<span class="fas fa-sync mx-1"></span>
<RefreshCw size="1em" class="mx-1" />
Reload &
<span class="fas fa-trash mx-1"></span>
<Trash2 size="1em" class="mx-1" />
Clear Cache
</button>
<button
@@ -225,7 +224,7 @@
// 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>
<Eraser size="1em" class="mx-1" />
Clear Storage & DB
</button>
</div>
@@ -244,19 +243,19 @@ class:justify-end={!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
<ShieldCheck size="1em" class="mx-1" /> Super Access
{:else if $ae_loc.access_type == 'manager'}
<span class="fas fa-user-shield mx-1"></span> Manager Access
<ShieldUser size="1em" class="mx-1" /> Manager Access
{:else if $ae_loc.access_type == 'administrator'}
<span class="fas fa-user-ninja mx-1"></span> Administrator Access
<UserRound size="1em" class="mx-1" /> Administrator Access
{:else if $ae_loc.access_type == 'trusted'}
<span class="fas fa-user-nurse mx-1"></span> Trusted Access
<UserRound size="1em" class="mx-1" /> Trusted Access
{:else if $ae_loc.access_type == 'authenticated'}
<span class="fas fa-user-friends mx-1"></span> Authenticated Access
<Users size="1em" class="mx-1" /> Authenticated Access
{:else if $ae_loc.access_type == 'anonymous'}
<span class="fas fa-users mx-1"></span> Anonymous Access
<Users size="1em" class="mx-1" /> Anonymous Access
{:else}
<span class="fas fa-unlock mx-1"></span> Unknown Access
<LockOpen size="1em" class="mx-1" /> Unknown Access
{/if}
<!-- <button

View File

@@ -39,27 +39,7 @@
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 { BadgeQuestionMark, ChevronDown, ChevronRight, LifeBuoy, RefreshCw, SquareX } from '@lucide/svelte';
// *** Import Aether specific variables and functions
import {
ae_snip,
@@ -71,7 +51,6 @@
slct_trigger,
type key_val
} from '$lib/stores/ae_stores';
import { User } from 'lucide-svelte';
import { api } from '$lib/api/api';
if (log_lvl) {

View File

@@ -12,19 +12,7 @@
* (access_type, sign_in_out). Simple controls (theme, cfg utilities)
* are inlined here for clean panel layout without blue-bg conflicts.
*/
import {
Bug,
CircleX,
LogOut,
Menu,
Moon,
Sun,
ShieldEllipsis,
ShieldMinus,
ShieldUser,
User,
} from '@lucide/svelte';
import { Bug, CircleX, Eraser, LogOut, Maximize2, Menu, Minimize2, Moon, Palette, RefreshCw, ShieldEllipsis, ShieldMinus, ShieldUser, Sun, ToggleLeft, ToggleRight, User } from '@lucide/svelte';
import { ae_loc, ae_sess, ae_api } from '$lib/stores/ae_stores';
import Element_access_type from '$lib/app_components/e_app_access_type.svelte';
@@ -341,7 +329,7 @@
onclick={() => sec_appearance = !sec_appearance}
>
<span class="flex items-center gap-1">
<span class="fas fa-palette opacity-60"></span>
<Palette size="1em" class="opacity-60" />
Appearance
<span class="normal-case font-normal opacity-60 ml-1">
({$ae_loc?.theme_name ?? '—'} · {$ae_loc?.theme_mode ?? '—'})
@@ -425,12 +413,12 @@
<!-- iframe mode toggle -->
{#if $ae_loc.iframe}
<a class="btn btn-sm preset-tonal-secondary w-full justify-end" href="/?iframe=false">
<span class="fas fa-compress-arrows-alt opacity-60"></span>
<Minimize2 size="1em" class="opacity-60" />
Exit iframe Mode
</a>
{:else}
<a class="btn btn-sm preset-tonal-secondary w-full justify-end" href="/?iframe=true">
<span class="fas fa-expand-arrows-alt opacity-60"></span>
<Maximize2 size="1em" class="opacity-60" />
Enable iframe Mode
</a>
{/if}
@@ -442,7 +430,7 @@
onclick={() => window.location.reload()}
title="Hard reload the page"
>
<span class="fas fa-sync opacity-60"></span>
<RefreshCw size="1em" class="opacity-60" />
Reload Page
</button>
@@ -453,7 +441,7 @@
onclick={handle_clear_storage_db}
title="Clear localStorage, sessionStorage, and all IndexedDB tables then reload"
>
<span class="fas fa-eraser opacity-60"></span>
<Eraser size="1em" class="opacity-60" />
Clear Storage & DB
</button>
@@ -612,7 +600,7 @@
onclick={() => { $ae_loc.edit_mode = false; }}
title="Edit mode ON click to turn off"
>
<span class="fas fa-toggle-on text-sm inline-block"></span>
<ToggleRight size="1em" class="text-sm inline-block" />
<span class="btn-label max-w-0 overflow-hidden opacity-0 group-hover/edit:max-w-20 group-hover/edit:opacity-100 transition-all duration-300 ease-in-out text-xs pl-1">Edit</span>
</button>
{:else}
@@ -622,7 +610,7 @@
onclick={() => { $ae_loc.edit_mode = true; }}
title="Edit mode OFF click to turn on"
>
<span class="fas fa-toggle-off text-sm inline-block opacity-50"></span>
<ToggleLeft size="1em" class="text-sm inline-block opacity-50" />
<span class="btn-label max-w-0 overflow-hidden opacity-0 group-hover/edit:max-w-20 group-hover/edit:opacity-100 transition-all duration-300 ease-in-out text-xs pl-1">Edit</span>
</button>
{/if}

View File

@@ -18,9 +18,11 @@
ShieldMinus,
ShieldPlus,
ShieldUser,
// ToggleLeft, ToggleRight,
ToggleLeft, ToggleRight,
User,
UserCheck,
UserCog,
Wand2,
X
} from '@lucide/svelte';
@@ -191,22 +193,22 @@ max-w-max -->
<!-- <ShieldPlus class="inline-block" /> -->
{#if $ae_loc.access_type == 'super'}
<span class="fas fa-hat-wizard m-1"></span>
<Wand2 size="1em" class="m-1 inline-block" />
<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>
<ShieldUser size="1em" class="m-1 inline-block" />
<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>
<UserCog size="1em" class="m-1 inline-block" />
<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>
<UserCheck size="1em" class="m-1 inline-block" />
<span class:hidden={!expand} class="hidden group-hover:inline-block"
>Trusted Access</span
>
@@ -357,7 +359,7 @@ max-w-max -->
"
title="Click to turn off edit mode. Edit mode is currently on."
>
<span class="fas fa-toggle-on m-1 inline-block"></span>
<ToggleRight size="1em" class="m-1 inline-block" />
<span class="text-xs">Edit</span>
<span class="hidden group-hover:inline-block group-hover:text-xs"> Off </span>
</button>
@@ -379,7 +381,7 @@ max-w-max -->
"
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>
<ToggleLeft size="1em" class="m-1 inline-block" />
<span class="text-xs">Edit</span>
<span class="hidden group-hover:inline-block group-hover:text-xs"> On? </span>
</button>

View File

@@ -1,6 +1,5 @@
<script lang="ts">
import { Moon, Sun } from '@lucide/svelte';
import { Minimize2, Moon, Sun } from '@lucide/svelte';
import { ae_loc, ae_sess, ae_api, slct, slct_trigger } from '$lib/stores/ae_stores';
interface Props {
@@ -131,7 +130,7 @@ if ($ae_loc.app_cfg.theme_mode == 'light') {
title="Hide Theme Options"
>
<!-- <span class="fas fa-compress-alt"></span> -->
<span class="fas fa-compress-arrows-alt m-1"></span>
<Minimize2 size="1em" class="m-1" />
<span
class="
hidden

View File

@@ -9,6 +9,9 @@
Html5QrcodeSupportedFormats
} from 'html5-qrcode';
// *** Import Lucide icons
import { Camera, Crosshair, Keyboard, QrCode, Send } from '@lucide/svelte';
// *** Import Aether core variables and functions
import { api } from '$lib/api/api';
import { ae_api } from '$lib/stores/ae_stores';
@@ -380,7 +383,7 @@
}}
class="ae_btn__allow_camera btn btn-sm preset-tonal-primary"
>
<span class="fas fa-camera mx-1"></span>
<Camera size="1em" class="mx-1 inline-block" />
Allow Camera Access
</button>
@@ -393,7 +396,7 @@
}}
class="ae_btn__start btn btn-sm preset-tonal-primary"
>
<span class="fas fa-qrcode mx-1"></span>
<QrCode size="1em" class="mx-1 inline-block" />
Start Scanning
</button>
@@ -411,7 +414,7 @@
onclick={handle_stop_qr_scanning}
class="ae_btn__stop btn btn-sm preset-tonal-secondary"
>
<span class="fas fa-crosshairs fa-spin opacity-50 m-1"></span>
<Crosshair size="1em" class="animate-spin opacity-50 m-1" />
<!-- <span class="fas fa-stop-circle m-1"></span> -->
Stop
</button>
@@ -437,7 +440,7 @@
bind:value={qr_entered_text}
/>
<button onclick={handle_qr_manual_entry} class="btn btn-md preset-tonal-warning"
><span class="fas fa-paper-plane"></span> Submit Text</button
><Send size="1em" class="inline-block" /> Submit Text</button
>
<div class="search_by_text">
@@ -457,7 +460,7 @@
}}
class="btn btn-md preset-tonal-warning m-1"
>
<span class="fas fa-keyboard mx-1"></span> Enter Text
<Keyboard size="1em" class="mx-1 inline-block" /> Enter Text
</button>
{/if}
</div>
@@ -488,7 +491,7 @@
class:btn_default={disable_submit_badge_id_btn}
class:btn_primary={!disable_submit_badge_id_btn}
>
<span class="fas fa-paper-plane mx-1"></span> Submit Badge ID
<Send size="1em" class="mx-1 inline-block" /> Submit Badge ID
</button>
</form>
{:else}
@@ -499,7 +502,7 @@
}}
class="btn btn-md preset-tonal-secondary m-1"
>
<span class="fas fa-keyboard mx-1"></span> Enter Badge ID
<Keyboard size="1em" class="mx-1 inline-block" /> Enter Badge ID
</button>
{/if}
</div>

View File

@@ -11,8 +11,7 @@
import type { key_val } from '$lib/stores/ae_stores';
// Icons (Standardized to Lucide where possible, or FontAwesome placeholders)
import { Bold, Italic, List, Code, Maximize2 } from 'lucide-svelte';
import { Bold, Code, Italic, List, Maximize2 } from '@lucide/svelte';
interface Props {
content?: string;
new_content?: string;

View File

@@ -7,10 +7,7 @@
*/
import { onMount, untrack } from 'svelte';
import { browser } from '$app/environment';
import {
Bold, Italic, List, ListOrdered,
RemoveFormatting, Type, Code, AlignLeft
} from 'lucide-svelte';
import { AlignLeft, Bold, Code, Italic, List, ListOrdered, RemoveFormatting, Type } from '@lucide/svelte';
import { ae_util } from '$lib/ae_utils/ae_utils';
interface Props {

View File

@@ -10,6 +10,7 @@
// import { api } from '$lib/api';
// import { update_ae_obj_id_crud } from '$lib/ae_core/core__crud_generic';
import { update_ae_obj } from '$lib/ae_core/core__crud_generic';
import { Check, LoaderCircle, Pencil, Save, X } from '@lucide/svelte';
// import { ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/ae_stores';
// *** Import Aether core components
@@ -43,7 +44,7 @@
hide_edit_btn?: boolean;
outline_element?: boolean;
show_crud?: boolean;
btn_label?: any; // '<span class="fas fa-check mx-1"></span> Save'; // PATCH
btn_label?: any; // '<Check size="1em" class="mx-1" /> Save'; // PATCH
// export let remove_breaks = false;
class_li?: string;
children?: import('svelte').Snippet;
@@ -186,7 +187,7 @@
}}
title="Double click to edit property"
>
<span class="fas fa-edit"></span>
<Pencil size="1em" />
<span class="hidden">Edit</span>
</button>
</span>
@@ -208,7 +209,7 @@
}}
title="Close field editing"
>
<span class="fas fa-window-close"></span>
<X size="1em" />
<span class="hidden sm:inline">Close</span>
</button>
<!-- </span> -->
@@ -272,25 +273,25 @@
{#if btn_label}
{@html btn_label}
{:else}
<span class="fas fa-check mx-1"></span>
<Check size="1em" class="mx-1" />
<span>Save</span>
{/if}
<!-- <span>{patch_result}</span> -->
{:else if btn_label}
{@html btn_label}
{:else}
<span class="fas fa-save mx-1"></span>
<Save size="1em" class="mx-1" />
<span>Save</span>
{/if}
</button>
<div class="field_patch_result" class:show_crud>
{#await ae_promises.api_update__ae_obj}
<span class="fas fa-spinner fa-spin mx-1"></span>
<LoaderCircle size="1em" class="mx-1 animate-spin" />
<span>Processing...</span>
{:then}
{#if patch_result}
<span class="fas fa-check mx-1"></span>
<Check size="1em" class="mx-1" />
<span>{patch_result}</span>
{:else}
<!-- <div>Nothing to show yet...</div> -->

View File

@@ -41,7 +41,7 @@
outline_element?: boolean;
btn_label?: any; // '<span class="fas fa-check mx-1"></span> Save'; // PATCH
btn_label?: any; // '<Check size="1em" class="mx-1" /> Save'; // PATCH
// export let remove_breaks = false;
class_li?: string;
children?: import('svelte').Snippet;
@@ -102,7 +102,7 @@
// *** Import Aether core components
import AE_Comp_Editor_TipTap from '$lib/elements/AE_Comp_Editor_TipTap.svelte';
import { Check, LoaderCircle, Pencil, Save, X } from '@lucide/svelte';
// *** Import Aether module variables and functions
// *** Import Aether module components
@@ -270,7 +270,7 @@
}}
title="Double click to edit property"
>
<span class="fas fa-edit m-0 p-0"></span>
<Pencil size="1em" class="m-0 p-0" />
<span class="hidden">Edit</span>
</button>
</span>
@@ -313,7 +313,7 @@
}}
title="Close field editing. This does not save any changes."
>
<span class="fas fa-window-close"></span>
<X size="1em" />
<span class="hidden sm:inline">Close</span>
</button>
</span>
@@ -405,25 +405,25 @@
{#if btn_label}
{@html btn_label}
{:else}
<span class="fas fa-check mx-1"></span>
<Check size="1em" class="mx-1" />
<span>Save</span>
{/if}
<!-- <span>{patch_result}</span> -->
{:else if btn_label}
{@html btn_label}
{:else}
<span class="fas fa-save mx-1"></span>
<Save size="1em" class="mx-1" />
<span>Save</span>
{/if}
</button>
<div class="field_patch_result" class:hidden={!patch_status}>
{#await ae_promises.api_update__ae_obj}
<span class="fas fa-spinner fa-spin mx-1"></span>
<LoaderCircle size="1em" class="mx-1 animate-spin" />
<span>Processing...</span>
{:then}
{#if patch_status}
<span class="fas fa-check mx-1"></span>
<Check size="1em" class="mx-1" />
<span>{patch_status}</span>
{:else}
<!-- <div>Nothing to show yet...</div> -->

View File

@@ -2,7 +2,7 @@
// import { browser } from '$app/environment';
import { untrack } from 'svelte';
import type { Snippet } from 'svelte';
import { LoaderCircle, SquarePen, Save, X, Trash2, Check, CircleAlert } from 'lucide-svelte';
import { Check, CircleAlert, LoaderCircle, Save, SquarePen, Trash2, X } from '@lucide/svelte';
import type { key_val } from '$lib/stores/ae_stores';
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';

View File

@@ -10,7 +10,7 @@
import { ae_util } from '$lib/ae_utils/ae_utils';
import type { key_val } from '$lib/stores/ae_stores';
import type { ae_DataStore } from '$lib/types/ae_types';
import { LoaderCircle, Pencil, Save, Trash2 } from '@lucide/svelte';
interface Props {
log_lvl?: number;
expire_minutes?: number;
@@ -379,13 +379,13 @@
<div class="flex justify-between items-center pt-4">
<button type="button" class="btn variant-filled-error" onclick={handle_delete}>
<span class="fas fa-trash mr-2"></span> Delete
<Trash2 size="1em" class="mr-2" /> Delete
</button>
<div class="flex gap-2">
<button type="button" class="btn variant-soft" onclick={() => show_edit = false}>Cancel</button>
<button type="submit" class="btn variant-filled-primary">
<span class="fas fa-save mr-2"></span> Save
<Save size="1em" class="mr-2" /> Save
</button>
</div>
</div>
@@ -409,7 +409,7 @@
ondblclick={() => { show_edit = true; show_view = false; }}
title="Edit Data Store: {ds_code}"
>
<span class="fas fa-edit"></span>
<Pencil size="1em" />
</button>
{/if}
{:else if ds_loading_status === 'not found'}
@@ -424,7 +424,7 @@
{#if ds_loading_status === 'loading'}
<div class="absolute bottom-0 left-0 p-1 opacity-50">
<span class="fas fa-spinner fa-spin text-xs"></span>
<LoaderCircle size="1em" class="text-xs animate-spin" />
</div>
{/if}
</div>

View File

@@ -15,7 +15,7 @@
slct,
slct_trigger
} from '$lib/stores/ae_stores';
import { LoaderCircle, Minus, Upload } from '@lucide/svelte';
const dispatch = createEventDispatcher();
interface Props {
@@ -367,7 +367,7 @@
>
<label for={element_id} class="svelte_input_file_label text-center">
<div>
<span class="fas fa-upload"></span>
<Upload size="1em" />
<!-- Select files to upload -->
<!-- <span class="fas fa-file-archive"></span> -->
<strong>Upload your files</strong>
@@ -394,7 +394,7 @@
<div
class="file_list_status ae_warning preset-tonal-warning border border-warning-500 p-1 m-1"
>
<span class="fas fa-spinner fa-spin m-1"></span> Processing selected file
<LoaderCircle size="1em" class="m-1 animate-spin" /> Processing selected file
list...
</div>
{/if}
@@ -426,7 +426,7 @@
class="btn btn-md preset-tonal-warning hover:preset-filled-secondary-500 m-1"
title="Remove file from upload list"
>
<span class="fas fa-minus"></span>
<Minus size="1em" />
<span class="hidden">Remove</span>
</button>
</td>

View File

@@ -8,7 +8,7 @@
// import { api } from '$lib/api';
import { check_hosted_file_obj_w_hash } from '$lib/ae_core/core__check_hosted_file_obj_w_hash';
import { ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/stores/ae_stores';
import { LoaderCircle, Minus } from '@lucide/svelte';
interface Props {
// export let element_id = 'svelte_input_file_element';
container_class_li?: string[];
@@ -335,7 +335,7 @@
<div
class="file_list_status ae_warning preset-tonal-warning border border-warning-500 p-1 m-1"
>
<span class="fas fa-spinner fa-spin m-1"></span> Processing selected file list...
<LoaderCircle size="1em" class="m-1 animate-spin" /> Processing selected file list...
</div>
{/if}
@@ -366,7 +366,7 @@
class="btn btn-md preset-tonal-warning hover:preset-filled-secondary-500 m-1"
title="Remove file from upload list"
>
<span class="fas fa-minus"></span>
<Minus size="1em" />
<span class="hidden">Remove</span>
</button>
</td>

View File

@@ -25,7 +25,7 @@
} from '$lib/stores/ae_events_stores';
import { events_func } from '$lib/ae_events_functions';
import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
import { Check, Clock, Download, Eye, EyeOff, FileImage, FolderOpen, Laptop, LoaderCircle, Monitor, Pencil, RefreshCw, Save, Trash2, TriangleAlert } from '@lucide/svelte';
interface Props {
log_lvl?: number;
container_class_li?: string | Array<string>;
@@ -156,7 +156,7 @@
class:hidden={!$ae_loc.edit_mode || !$ae_loc.authenticated_access}
title="Refresh the list of files"
>
<span class="fas fa-sync-alt m-1"></span>
<RefreshCw size="1em" class="m-1" />
<div class="hidden">Files</div>
</button>
@@ -170,7 +170,7 @@
class:hidden={!$ae_loc.edit_mode || !$ae_loc.trusted_access}
title="Toggle direct download link and copy link button"
>
<span class="fas fa-download m-1"></span>
<Download size="1em" class="m-1" />
<div class="hidden">
{ae_tmp.show__direct_download ? 'Alt On' : 'Alt Download Off'}
</div>
@@ -193,7 +193,7 @@
'-- not set --'} (files: {$lq__event_file_obj_li?.length ??
'None'})"
>
<span class="fas fa-folder-open mx-1"></span>
<FolderOpen size="1em" class="mx-1" />
{@html $lq__event_file_obj_li
? `${$lq__event_file_obj_li.length}&times;`
: '-- none --'}
@@ -228,6 +228,7 @@
<tbody>
{#each $lq__event_file_obj_li as event_file_obj (event_file_obj.event_file_id)}
{@const ExtIcon = ae_util.file_extension_icon_lucide(event_file_obj.extension)}
<tr
class="ae_obj obj_event_file border-t border-b border-surface-200-800 hover:bg-surface-100-900 hover:border-surface-300-700 transition-colors duration-200"
class:dim={event_file_obj?.hide}
@@ -254,17 +255,17 @@
title="Convert this PDF to a high-res webp image for use in the Launcher poster display."
onclick={() => handle_convert_pdf_to_image(event_file_obj)}
>
<span class="fas fa-file-image mx-1"></span>
<FileImage size="1em" class="mx-1" />
Convert PDF → Image
</button>
{:else if convert_status_kv[event_file_obj.event_file_id] === 'converting'}
<span class="btn btn-sm preset-tonal-surface opacity-60 cursor-wait">
<span class="fas fa-spinner fa-spin mx-1"></span>
<LoaderCircle size="1em" class="mx-1 animate-spin" />
Converting…
</span>
{:else if convert_status_kv[event_file_obj.event_file_id] === 'done'}
<span class="btn btn-sm preset-tonal-success" title="Conversion complete. New webp hosted_file created: {convert_result_kv[event_file_obj.event_file_id]?.filename ?? ''}">
<span class="fas fa-check mx-1"></span>
<Check size="1em" class="mx-1" />
Done — {convert_result_kv[event_file_obj.event_file_id]?.filename ?? 'image created'}
</span>
{:else if convert_status_kv[event_file_obj.event_file_id] === 'error'}
@@ -276,7 +277,7 @@
convert_status_kv[event_file_obj.event_file_id] = 'idle';
}}
>
<span class="fas fa-exclamation-triangle mx-1"></span>
<TriangleAlert size="1em" class="mx-1" />
Failed — Retry?
</button>
{/if}
@@ -391,16 +392,12 @@
title="Save changes"
>
{#await ae_promises.update__event_file_obj}
<span
class="fas fa-spinner fa-spin mx-1"
></span>
<LoaderCircle size="1em" class="mx-1 animate-spin" />
<span class=""
>Saving {event_file_obj.extension}</span
>
{:then}
<span
class="fas fa-save mx-1"
></span>
<Save size="1em" class="mx-1" />
Save {event_file_obj.extension}
filename?
{/await}
@@ -444,8 +441,7 @@
event_file_obj.event_file_id}
title={`Rename this file? "${event_file_obj.filename}"}`}
>
<span class="fas fa-edit mx-1"
></span>
<Pencil size="1em" class="mx-1" />
{#if $events_sess.pres_mgmt?.show_field_edit__filename == event_file_obj.event_file_id}
Cancel?
{:else}
@@ -515,20 +511,15 @@
-->
{#await ae_promises.update__event_file_obj}
<span
class="fas fa-spinner fa-spin mx-1"
></span>
<LoaderCircle size="1em" class="mx-1 animate-spin" />
<span class=""
>Saving {event_file_obj.extension}</span
>
{:then}
{#if event_file_obj.hide}
<span class="fas fa-eye m-1"
></span> Unhide File
<Eye size="1em" class="m-1" /> Unhide File
{:else}
<span
class="fas fa-eye-slash m-1"
></span> Hide
<EyeOff size="1em" class="m-1" /> Hide
{/if}
{/await}
</button>
@@ -561,8 +552,7 @@
class="btn btn-sm preset-tonal-tertiary hover:preset-tonal-success"
title="Delete this file"
>
<span class="fas fa-trash-alt mx-1"
></span>
<Trash2 size="1em" class="mx-1" />
<!-- <span class="fas fa-minus mx-1"></span> -->
Delete
</button>
@@ -581,13 +571,9 @@
>
<div class="">
{#if event_file_obj.open_in_os == 'win'}
MS Windows <span
class="fab fa-windows"
></span>
MS Windows <Monitor size="1em" class="inline-block" />
{:else if event_file_obj.open_in_os == 'mac'}
Apple macOS <span
class="fab fa-apple"
></span>
Apple macOS <Laptop size="1em" class="inline-block" />
{/if}
</div>
@@ -698,11 +684,7 @@
Type:
<strong
>{event_file_obj.extension}
<span
class="fas fa-{ae_util.file_extension_icon(
event_file_obj.extension
)}"
></span>
<ExtIcon size="1em" class="inline-block" />
</strong>
<!-- {#if event_file_obj.open_in_os == 'win'}
<strong>
@@ -794,8 +776,7 @@
<!-- <span class="fas fa-cloud-upload-alt mx-1"></span> -->
<!-- Uploaded: -->
<!-- <span class="fas fa-calendar-day mx-1"></span> -->
<span class="fas fa-clock mx-1"
></span>
<Clock size="1em" class="mx-1" />
<strong>
{ae_util.iso_datetime_formatter(
event_file_obj.created_on,

View File

@@ -8,7 +8,7 @@
import { ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/stores/ae_stores';
import { db_core } from '$lib/ae_core/db_core';
import { core_func } from '$lib/ae_core/ae_core_functions';
import { CalendarDays, Download, FolderOpen, MinusCircle, Pencil, PlusCircle, RefreshCw, Trash2 } from '@lucide/svelte';
// export let allow_basic: boolean = false;
interface Props {
@@ -60,7 +60,7 @@
title="Files for {link_to_type ?? '-- not set --'}: {link_to_id ??
'-- not set --'} (files: {$lq__hosted_file_obj_li?.length ?? 'None'})"
>
<span class="fas fa-folder-open mx-1"></span>
<FolderOpen size="1em" class="mx-1" />
{@html $lq__hosted_file_obj_li
? `${$lq__hosted_file_obj_li.length}&times;`
: '-- none --'}
@@ -101,7 +101,7 @@
class:hidden={!$ae_loc.edit_mode || !$ae_loc.authenticated_access}
title="Refresh the list of files"
>
<span class="fas fa-sync-alt m-1"></span>
<RefreshCw size="1em" class="m-1" />
<div class="hidden">Files</div>
</button>
@@ -115,7 +115,7 @@
class:hidden={!$ae_loc.edit_mode || !$ae_loc.trusted_access}
title="Toggle direct download link and copy link button"
>
<span class="fas fa-download m-1"></span>
<Download size="1em" class="m-1" />
<div class="hidden">
{ae_tmp.show__direct_download ? 'Alt On' : 'Alt Download Off'}
</div>
@@ -163,10 +163,10 @@
title="Add/Remove file to/from the locally stored uploaded file list. This is referenced by other AE components."
>
{#if $ae_loc.files.uploaded_file_kv[hosted_file_obj.hosted_file_id]}
<span class="fas fa-minus-circle m-1"></span>
<MinusCircle size="1em" class="m-1" />
<span class="hidden">Remove</span>
{:else}
<span class="fas fa-plus-circle m-1"></span>
<PlusCircle size="1em" class="m-1" />
<span class="hidden">Add</span>
{/if}
</button>
@@ -198,7 +198,7 @@
class="btn btn-sm preset-tonal-secondary hover:preset-filled-secondary-500"
title="Delete a file from the host server."
>
<span class="fas fa-trash-alt m-1"></span>
<Trash2 size="1em" class="m-1" />
<span class="hidden">Delete</span>
</button>
@@ -212,19 +212,19 @@
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500"
title="Edit file details"
>
<span class="fas fa-edit m-1"></span>
<Pencil size="1em" class="m-1" />
<span class="hidden">Edit</span>
</button>
<span class="text-xs text-gray-500">
<span class="fas fa-calendar-day mx-1"></span>
<CalendarDays size="1em" class="mx-1" />
<span class="hidden">Created on:</span>
{ae_util.iso_datetime_formatter(
hosted_file_obj.created_on,
'datetime_medium_sec'
)}
{#if hosted_file_obj.updated_on}
<span class="fas fa-sync-alt mx-1"></span>
<RefreshCw size="1em" class="mx-1" />
Updated on
{ae_util.iso_datetime_formatter(
hosted_file_obj.updated_on,

View File

@@ -0,0 +1,93 @@
<script lang="ts">
/**
* src/lib/elements/element_pwa_install_prompt.svelte
*
* Reusable PWA install nudge. Drop this anywhere in the app to surface
* a platform-aware "Add to Home Screen" prompt.
*
* - Chrome / Android / Desktop Chrome: shows a button that triggers the
* native browser install flow (via the captured beforeinstallprompt event).
* - iOS Safari: shows manual step-by-step "Share → Add to Home Screen" instructions
* since iOS does not support the beforeinstallprompt API.
* - Already installed (standalone mode) or dismissed: renders nothing.
*
* The pwa_install singleton must be initialised first (done in root +layout.svelte).
*/
import { Download, Plus, Share2, Smartphone, X } from '@lucide/svelte';
import { pwa_install } from '$lib/pwa/pwa_install.svelte';
interface Props {
class?: string;
}
let { class: extra_class = '' }: Props = $props();
</script>
{#if pwa_install.should_show}
<div
class="relative w-full rounded-2xl overflow-hidden
border border-primary-500/25
bg-primary-500/8 dark:bg-primary-500/12
{extra_class}"
>
<!-- Dismiss button -->
<button
class="absolute top-2 right-2 p-2 rounded-lg
opacity-40 hover:opacity-90
hover:bg-surface-500/10
transition-opacity"
onclick={() => pwa_install.dismiss()}
aria-label="Dismiss install prompt"
>
<X size="1rem" />
</button>
<div class="p-4 pr-10 space-y-3">
<!-- Header row: icon + text -->
<div class="flex items-center gap-3">
<div class="shrink-0 p-2.5 rounded-xl bg-primary-500/15 text-primary-500">
<Smartphone size="1.35em" />
</div>
<div>
<p class="font-bold text-sm leading-tight">Install Aether Leads</p>
<p class="text-xs opacity-55 leading-snug mt-0.5">
Add to your home screen for the best mobile experience
</p>
</div>
</div>
{#if pwa_install.can_prompt}
<!-- Chrome / Android / Desktop Chrome — native one-tap install -->
<button
class="btn preset-filled-primary w-full"
onclick={() => pwa_install.prompt()}
>
<Download size="1em" />
<span>Add to Home Screen</span>
</button>
{:else}
<!-- iOS Safari — manual instructions (no API available) -->
<ol class="space-y-2 text-sm">
<li class="flex items-center gap-2.5">
<span class="shrink-0 flex items-center justify-center w-5 h-5 rounded-full
bg-primary-500/20 text-primary-600 dark:text-primary-300
text-[10px] font-bold leading-none">1</span>
<span class="opacity-75">
Tap the
<Share2 size="0.85em" class="inline align-middle mx-0.5 text-primary-500" />
<strong>Share</strong> button in Safari
</span>
</li>
<li class="flex items-center gap-2.5">
<span class="shrink-0 flex items-center justify-center w-5 h-5 rounded-full
bg-primary-500/20 text-primary-600 dark:text-primary-300
text-[10px] font-bold leading-none">2</span>
<span class="opacity-75">
Tap <strong>Add to Home Screen</strong>
<Plus size="0.85em" class="inline align-middle mx-0.5 text-primary-500" />
</span>
</li>
</ol>
{/if}
</div>
</div>
{/if}

View File

@@ -0,0 +1,142 @@
/**
* src/lib/pwa/pwa_install.svelte.ts
*
* Global PWA install prompt state using Svelte 5 universal reactivity.
*
* Usage:
* - Call `pwa_install.init()` once in the root +layout.svelte (browser $effect).
* - Import `pwa_install` in any component to read state and trigger the prompt.
*
* Platform behaviour:
* - Chrome / Android / Desktop Chrome: intercepts `beforeinstallprompt`,
* lets us show a custom button that calls the native install flow.
* - iOS Safari: `beforeinstallprompt` never fires; we detect iOS + not-standalone
* and show manual "Share → Add to Home Screen" instructions instead.
* - If already running in standalone mode (i.e. already installed) → hide everything.
*
* Dismiss: persists for DISMISS_DAYS days via localStorage key `ae_pwa_install_dismissed`.
*/
import { browser } from '$app/environment';
// `BeforeInstallPromptEvent` is non-standard and not included in TypeScript's default lib.
interface BeforeInstallPromptEvent extends Event {
prompt(): Promise<void>;
userChoice: Promise<{ outcome: 'accepted' | 'dismissed'; platform: string }>;
}
const DISMISS_KEY = 'ae_pwa_install_dismissed';
const DISMISS_DAYS = 7;
// --- Module-level reactive state (Svelte 5 universal reactivity) ---
let _deferred_prompt = $state<BeforeInstallPromptEvent | null>(null);
let _is_installed = $state(false);
let _is_dismissed = $state(false);
let _initialized = false; // plain boolean — not reactive, guards against double-init
// --- Private helpers (SSR-safe) ---
function _in_standalone(): boolean {
if (!browser) return false;
return (
window.matchMedia('(display-mode: standalone)').matches ||
// iOS Safari sets navigator.standalone (non-standard)
('standalone' in window.navigator && (window.navigator as any).standalone === true)
);
}
function _is_ios(): boolean {
if (!browser) return false;
// Cover iPhone/iPod/iPad (incl. iPadOS 13+ which reports MacIntel with touch points)
return (
/iphone|ipad|ipod/i.test(navigator.userAgent) ||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)
);
}
// --- Public API ---
export const pwa_install = {
/**
* True if the browser has captured a deferred install prompt
* (Chrome / Android / PWA-capable desktop browsers).
*/
get can_prompt(): boolean {
return _deferred_prompt !== null && !_is_installed && !_is_dismissed;
},
/**
* True if on iOS Safari and NOT already installed.
* We cannot trigger a native prompt on iOS so we show manual instructions instead.
*/
get is_ios_nudge(): boolean {
return _is_ios() && !_in_standalone() && !_is_dismissed && !_is_installed;
},
/** True if the install UI should be rendered at all. */
get should_show(): boolean {
return pwa_install.can_prompt || pwa_install.is_ios_nudge;
},
/** True once the app is confirmed installed (standalone mode or appinstalled event). */
get is_installed(): boolean {
return _is_installed;
},
/**
* Register browser event listeners. Call ONCE from root +layout.svelte.
* Safe to call multiple times (idempotent).
*/
init(): void {
if (!browser || _initialized) return;
_initialized = true;
// Already running as installed PWA — nothing to prompt.
if (_in_standalone()) {
_is_installed = true;
return;
}
// Restore dismiss state from localStorage (with expiry check).
const raw = localStorage.getItem(DISMISS_KEY);
if (raw) {
const days_since = (Date.now() - parseInt(raw, 10)) / 86_400_000;
if (days_since < DISMISS_DAYS) {
_is_dismissed = true;
} else {
// Expired — clear so the nudge can reappear.
localStorage.removeItem(DISMISS_KEY);
}
}
// Capture the deferred install prompt (Chrome/Android/Desktop Chrome).
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
_deferred_prompt = e as BeforeInstallPromptEvent;
});
// Fired by the browser after the user accepts the install prompt.
window.addEventListener('appinstalled', () => {
_is_installed = true;
_deferred_prompt = null;
});
},
/** Trigger the native install prompt. No-op if no deferred prompt is available. */
async prompt(): Promise<void> {
if (!_deferred_prompt) return;
// Grab and clear to prevent double-fire.
const evt = _deferred_prompt;
_deferred_prompt = null;
await evt.prompt();
const { outcome } = await evt.userChoice;
if (outcome === 'accepted') {
_is_installed = true;
}
},
/** Dismiss the nudge; it will not re-appear for DISMISS_DAYS days. */
dismiss(): void {
_is_dismissed = true;
if (browser) localStorage.setItem(DISMISS_KEY, Date.now().toString());
},
};