Implement generic AE_ObjectFlags and AE_MetadataFooter components
- Created AE_ObjectFlags.svelte for standardized flag management across all modules. - Created AE_MetadataFooter.svelte for unified creation/update/original timestamp display. - Modularized Journal Entry view by replacing manual flag and footer logic with generic elements. - Improved code reusability and reduced main component complexity.
This commit is contained in:
65
src/lib/ae_elements/AE_MetadataFooter.svelte
Normal file
65
src/lib/ae_elements/AE_MetadataFooter.svelte
Normal file
@@ -0,0 +1,65 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* AE_MetadataFooter.svelte
|
||||
* GENERIC Aether Metadata Display
|
||||
* Reusable across all modules to standardize created/updated/original info.
|
||||
*/
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
import { ae_loc } from '$lib/stores/ae_stores';
|
||||
import { Clock, CalendarClock, Globe } from '@lucide/svelte';
|
||||
|
||||
interface Props {
|
||||
obj: any;
|
||||
showOriginal?: boolean;
|
||||
containerClass?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
obj,
|
||||
showOriginal = true,
|
||||
containerClass = "ae_meta flex flex-col gap-2 p-4 border-t border-surface-500/10 text-xs text-surface-500 w-full"
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<footer class={containerClass}>
|
||||
<!-- Original Date/Time Info (if applicable) -->
|
||||
{#if showOriginal && (obj?.original_datetime || obj?.original_timezone)}
|
||||
<div class="flex flex-row flex-wrap gap-x-4 gap-y-1 items-center bg-surface-500/5 p-2 rounded">
|
||||
<span class="flex items-center gap-1">
|
||||
<Globe size="1.1em" class="opacity-70" />
|
||||
<span class="font-bold uppercase tracking-tighter">Original:</span>
|
||||
{obj?.original_datetime ? ae_util.iso_datetime_formatter(obj.original_datetime, 'datetime_iso_12_no_seconds') : '--'}
|
||||
</span>
|
||||
{#if obj?.original_timezone}
|
||||
<span class="flex items-center gap-1">
|
||||
<span class="font-bold uppercase tracking-tighter">TZ:</span>
|
||||
{obj.original_timezone}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- System Timestamps -->
|
||||
<div class="flex flex-col sm:flex-row justify-between items-center gap-2 px-1">
|
||||
<div class="flex flex-wrap gap-4 justify-center sm:justify-start">
|
||||
<span class="flex items-center gap-1" title="Creation date">
|
||||
<CalendarClock size="1.1em" class="opacity-70 text-primary-500" />
|
||||
<span class="font-semibold uppercase tracking-tighter">Created:</span>
|
||||
{ae_util.iso_datetime_formatter(obj?.created_on, 'datetime_iso_12_no_seconds')}
|
||||
</span>
|
||||
{#if obj?.updated_on}
|
||||
<span class="flex items-center gap-1" title="Last update">
|
||||
<Clock size="1.1em" class="opacity-70 text-secondary-500" />
|
||||
<span class="font-semibold uppercase tracking-tighter">Updated:</span>
|
||||
{ae_util.iso_datetime_formatter(obj.updated_on, 'datetime_iso_12_no_seconds')}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if obj?.journal_entry_type || obj?.type}
|
||||
<span class="badge variant-soft-surface text-[10px] uppercase font-bold tracking-widest">
|
||||
Type: {obj?.journal_entry_type || obj?.type}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</footer>
|
||||
131
src/lib/ae_elements/AE_ObjectFlags.svelte
Normal file
131
src/lib/ae_elements/AE_ObjectFlags.svelte
Normal file
@@ -0,0 +1,131 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* AE_ObjectFlags.svelte
|
||||
* GENERIC Aether Object Flags & Visibility Toggles
|
||||
* Manages: alert, private, public, personal, professional, template
|
||||
*/
|
||||
import {
|
||||
Siren, MessageSquareWarning, Fingerprint,
|
||||
Globe, BookHeart, BriefcaseBusiness, NotepadTextDashed,
|
||||
Settings
|
||||
} from '@lucide/svelte';
|
||||
import { ae_loc } from '$lib/stores/ae_stores';
|
||||
|
||||
interface Props {
|
||||
// The object containing the flags (bindable)
|
||||
obj: any;
|
||||
|
||||
// Visibility configuration (optional overrides)
|
||||
showLabels?: boolean;
|
||||
hideAlert?: boolean;
|
||||
hidePrivate?: boolean;
|
||||
hidePublic?: boolean;
|
||||
hidePersonal?: boolean;
|
||||
hideProfessional?: boolean;
|
||||
hideTemplate?: boolean;
|
||||
|
||||
// Callbacks
|
||||
onToggle?: (prop: string, newValue: boolean) => void;
|
||||
|
||||
// Styling
|
||||
containerClass?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
obj = $bindable(),
|
||||
showLabels = true,
|
||||
hideAlert = false,
|
||||
hidePrivate = false,
|
||||
hidePublic = false,
|
||||
hidePersonal = false,
|
||||
hideProfessional = false,
|
||||
hideTemplate = false,
|
||||
onToggle,
|
||||
containerClass = "flex flex-row flex-wrap gap-1 items-center justify-evenly py-2 border-y border-surface-500/10"
|
||||
}: Props = $props();
|
||||
|
||||
function handle_toggle(prop: string) {
|
||||
obj[prop] = !obj[prop];
|
||||
if (onToggle) onToggle(prop, obj[prop]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class={containerClass}>
|
||||
{#if showLabels}
|
||||
<span class="text-xs text-surface-500 flex items-center gap-1 uppercase font-bold tracking-wider mr-2">
|
||||
<Settings size="1.1em" /> Flags:
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<!-- Alert Status -->
|
||||
{#if !hideAlert}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handle_toggle('alert')}
|
||||
class="btn-icon btn-icon-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition"
|
||||
title="Toggle Alert Status"
|
||||
>
|
||||
<Siren size="1.2em" class={obj?.alert ? 'text-error-500' : 'opacity-40'} />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<!-- Private / E2EE -->
|
||||
{#if !hidePrivate}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handle_toggle('private')}
|
||||
class="btn-icon btn-icon-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition"
|
||||
title="Toggle Private/Encrypted"
|
||||
>
|
||||
<Fingerprint size="1.2em" class={obj?.private ? 'text-success-500' : 'opacity-40'} />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<!-- Public Visibility -->
|
||||
{#if !hidePublic}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handle_toggle('public')}
|
||||
class="btn-icon btn-icon-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition"
|
||||
title="Toggle Public Visibility"
|
||||
>
|
||||
<Globe size="1.2em" class={obj?.public ? 'text-success-500' : 'opacity-40'} />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<!-- Personal Scope -->
|
||||
{#if !hidePersonal}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handle_toggle('personal')}
|
||||
class="btn-icon btn-icon-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition"
|
||||
title="Toggle Personal Scope"
|
||||
>
|
||||
<BookHeart size="1.2em" class={obj?.personal ? 'text-success-500' : 'opacity-40'} />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<!-- Professional Scope -->
|
||||
{#if !hideProfessional}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handle_toggle('professional')}
|
||||
class="btn-icon btn-icon-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition"
|
||||
title="Toggle Professional Scope"
|
||||
>
|
||||
<BriefcaseBusiness size="1.2em" class={obj?.professional ? 'text-success-500' : 'opacity-40'} />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<!-- Template Status -->
|
||||
{#if !hideTemplate && $ae_loc.edit_mode}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handle_toggle('template')}
|
||||
class="btn-icon btn-icon-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition"
|
||||
title="Toggle Template Mode"
|
||||
>
|
||||
<NotepadTextDashed size="1.2em" class={obj?.template ? 'text-success-500' : 'opacity-40'} />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -91,6 +91,9 @@
|
||||
import Comp_journal_entry_file_li from './ae_comp__journal_entry_obj_file_li.svelte';
|
||||
import Comp_hosted_files_download_button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
|
||||
import AE_AITools from '$lib/ae_elements/AE_AITools.svelte';
|
||||
import AE_ObjectFlags from '$lib/ae_elements/AE_ObjectFlags.svelte';
|
||||
import AE_MetadataFooter from '$lib/ae_elements/AE_MetadataFooter.svelte';
|
||||
import JournalEntry_Metadata from './JournalEntry_Metadata.svelte';
|
||||
|
||||
// *** Configuration
|
||||
let llm_api_token =
|
||||
@@ -1020,173 +1023,16 @@
|
||||
<!-- </div> -->
|
||||
|
||||
<!-- Journal Entry Options: alert, private, public, personal, professional, template -->
|
||||
<div
|
||||
class="
|
||||
flex flex-row flex-wrap gap-0.5 items-center justify-evenly
|
||||
"
|
||||
>
|
||||
<span class="text-sm text-gray-500">
|
||||
<Settings strokeWidth="1" class="mx-1 inline-block" />
|
||||
Flags:
|
||||
</span>
|
||||
|
||||
<!-- Entry alert status -->
|
||||
<!-- class:hidden={$lq__journal_obj?.cfg_json?.hide_btn_alert} -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
tmp_entry_obj.alert = !$lq__journal_entry_obj?.alert;
|
||||
update_journal_entry();
|
||||
}}
|
||||
class="btn-icon btn-icon-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition"
|
||||
title="Toggle alert status of this journal entry"
|
||||
>
|
||||
{#if $lq__journal_entry_obj?.alert}
|
||||
<Siren strokeWidth="2.5" color="red" />
|
||||
{:else}
|
||||
<MessageSquareWarning strokeWidth="1" color="gray" />
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<!-- Entry marked as private -->
|
||||
<!-- ($lq__journal_obj?.cfg_json?.hide_btn_private) || -->
|
||||
<button
|
||||
type="button"
|
||||
class:hidden={tmp_entry_obj?.private && !$ae_loc.edit_mode}
|
||||
onclick={() => {
|
||||
// Allow for toggle between private (encrypted) and not,
|
||||
// and if private, allow for decryption.
|
||||
if (
|
||||
!$lq__journal_entry_obj?.private &&
|
||||
tmp_entry_obj?.content
|
||||
) {
|
||||
// Handle converting to private (encrypted) content
|
||||
if (
|
||||
confirm(
|
||||
'Are you sure you want to encrypt and resave this journal entry?'
|
||||
)
|
||||
) {
|
||||
// Setting private to true will cause the update_journal_entry() function to encrypt the content.
|
||||
tmp_entry_obj.private = true;
|
||||
update_journal_entry();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (
|
||||
$lq__journal_entry_obj?.private &&
|
||||
tmp_entry_obj?.content_encrypted
|
||||
) {
|
||||
// Handle converting to not private (decrypted) content
|
||||
if (
|
||||
confirm(
|
||||
'Are you sure you want to decrypt and resave this journal entry unencrypted?'
|
||||
)
|
||||
) {
|
||||
// tmp_entry_obj.private = !$lq__journal_entry_obj?.private;
|
||||
// update_journal_entry();
|
||||
|
||||
// Setting private to false will cause the update_journal_entry() function to decrypt the content. This will also copy it to tmp_entry_obj.content.
|
||||
tmp_entry_obj.private = false;
|
||||
// tmp_entry_obj.
|
||||
// handle_decrypt_content();
|
||||
update_journal_entry();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}}
|
||||
class="btn-icon btn-icon-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition"
|
||||
title="Toggle private visibility of this journal entry"
|
||||
>
|
||||
{#if $lq__journal_entry_obj?.private}
|
||||
<Fingerprint strokeWidth="2.5" color="green" />
|
||||
<!-- Private (Encrypted) -->
|
||||
{:else}
|
||||
<Fingerprint strokeWidth="1" color="gray" />
|
||||
<!-- Not Private -->
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<!-- Entry allowed to be public -->
|
||||
<!-- class:hidden={$lq__journal_obj.cfg_json?.hide_btn_public} -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
if (tmp_entry_obj) {
|
||||
tmp_entry_obj.public = !$lq__journal_entry_obj?.public;
|
||||
update_journal_entry();
|
||||
}
|
||||
}}
|
||||
class="btn-icon btn-icon-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition"
|
||||
title="Toggle public visibility of this journal entry"
|
||||
>
|
||||
{#if $lq__journal_entry_obj?.public}
|
||||
<Globe strokeWidth="2.5" color="green" />
|
||||
{:else}
|
||||
<Globe strokeWidth="1" color="gray" />
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<!-- Entry marked as personal -->
|
||||
<!-- class:hidden={$lq__journal_obj.cfg_json?.hide_btn_personal} -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
if (tmp_entry_obj) {
|
||||
tmp_entry_obj.personal = !$lq__journal_entry_obj?.personal;
|
||||
update_journal_entry();
|
||||
}
|
||||
}}
|
||||
class="btn-icon btn-icon-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition"
|
||||
title="Toggle personal visibility of this journal entry"
|
||||
>
|
||||
{#if $lq__journal_entry_obj?.personal}
|
||||
<BookHeart strokeWidth="2.5" color="green" />
|
||||
{:else}
|
||||
<BookHeart strokeWidth="1" color="gray" />
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<!-- Entry marked as professional -->
|
||||
<!-- class:hidden={$lq__journal_obj.cfg_json?.hide_btn_professional} -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
tmp_entry_obj.professional =
|
||||
!$lq__journal_entry_obj?.professional;
|
||||
update_journal_entry();
|
||||
}}
|
||||
class="btn-icon btn-icon-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition"
|
||||
title="Toggle professional visibility of this journal entry"
|
||||
>
|
||||
{#if $lq__journal_entry_obj?.professional}
|
||||
<BriefcaseBusiness strokeWidth="2.5" color="green" />
|
||||
{:else}
|
||||
<BriefcaseBusiness strokeWidth="1" color="gray" />
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<!-- Toggle if entry should be used as a template entry (cloneable). Only visible in edit_mode. -->
|
||||
<button
|
||||
type="button"
|
||||
class:hidden={$lq__journal_obj?.cfg_json?.hide_btn_template ||
|
||||
!$ae_loc.edit_mode}
|
||||
onclick={() => {
|
||||
if (tmp_entry_obj) {
|
||||
tmp_entry_obj.template = !$lq__journal_entry_obj?.template;
|
||||
update_journal_entry();
|
||||
}
|
||||
}}
|
||||
class="btn-icon btn-icon-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition"
|
||||
title="Toggle if used as journal entry template"
|
||||
>
|
||||
{#if $lq__journal_entry_obj?.template}
|
||||
<NotepadTextDashed strokeWidth="2.5" color="green" />
|
||||
{:else}
|
||||
<NotepadTextDashed strokeWidth="1" color="gray" />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
<AE_ObjectFlags
|
||||
bind:obj={tmp_entry_obj}
|
||||
onToggle={() => update_journal_entry()}
|
||||
hideAlert={$lq__journal_obj?.cfg_json?.hide_btn_alert}
|
||||
hidePrivate={$lq__journal_obj?.cfg_json?.hide_btn_private}
|
||||
hidePublic={$lq__journal_obj?.cfg_json?.hide_btn_public}
|
||||
hidePersonal={$lq__journal_obj?.cfg_json?.hide_btn_personal}
|
||||
hideProfessional={$lq__journal_obj?.cfg_json?.hide_btn_professional}
|
||||
hideTemplate={$lq__journal_obj?.cfg_json?.hide_btn_template}
|
||||
/>
|
||||
|
||||
<!-- Copy and Clone Buttons -->
|
||||
<div>
|
||||
@@ -2232,31 +2078,7 @@
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<section class="ae_meta flex flex-row flex-wrap gap-1 items-center justify-between w-full">
|
||||
<span class="flex flex-row items-center justify-center text-sm text-gray-500">
|
||||
{#if !$ae_loc.edit_mode}
|
||||
<span class="">
|
||||
{ae_util.iso_datetime_formatter(
|
||||
$lq__journal_entry_obj?.created_on,
|
||||
'datetime_iso_12_no_seconds'
|
||||
)}
|
||||
{$lq__journal_entry_obj?.updated_on
|
||||
? ` | Last updated: ${ae_util.iso_datetime_formatter($lq__journal_entry_obj?.updated_on, 'datetime_iso_12_no_seconds')}`
|
||||
: ''}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="">
|
||||
{ae_util.iso_datetime_formatter(
|
||||
$lq__journal_entry_obj?.created_on,
|
||||
'datetime_iso_tz'
|
||||
)}
|
||||
{$lq__journal_entry_obj?.updated_on
|
||||
? ` | Last updated: ${ae_util.iso_datetime_formatter($lq__journal_entry_obj?.updated_on, 'datetime_iso_tz')}`
|
||||
: ''}
|
||||
</span>
|
||||
{/if}
|
||||
</span>
|
||||
</section>
|
||||
<AE_MetadataFooter obj={tmp_entry_obj} />
|
||||
|
||||
|
||||
{:else}
|
||||
|
||||
Reference in New Issue
Block a user