Refactor: Organize src/lib into subdirectories and move files accordingly.
This commit is contained in:
475
src/lib/elements/element_ae_crud.svelte
Normal file
475
src/lib/elements/element_ae_crud.svelte
Normal file
@@ -0,0 +1,475 @@
|
||||
<script lang="ts">
|
||||
// *** Import Svelte core
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
|
||||
// *** Import Aether core variables and functions
|
||||
import type { key_val } from '$lib/ae_stores';
|
||||
// import { api } from '$lib/api';
|
||||
import { update_ae_obj_id_crud } from '$lib/ae_core/core__crud_generic';
|
||||
// import { ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/ae_stores';
|
||||
|
||||
// *** Import Aether core components
|
||||
// *** Import Aether module variables and functions
|
||||
// *** Import Aether module components
|
||||
|
||||
// *** Export/Exposed variables and functions for component
|
||||
export let log_lvl: number = 0;
|
||||
export let trigger_patch: any = null;
|
||||
export let api_cfg: key_val = {'api_crud_super_key': null};
|
||||
// export let api_crud_super_key: null|string = api_cfg.api_crud_super_key;
|
||||
export let object_type: string;
|
||||
export let object_id: string;
|
||||
export let field_name: string;
|
||||
export let field_type: string = 'text'; // button, text, textarea, template (older method), select (in progress method)
|
||||
export let field_value: any;
|
||||
export let allow_null: boolean = false;
|
||||
export let select_option_li: key_val = {};
|
||||
export let val_empty_is_null: boolean = false; // This was added to help with a select option list. If the value is empty (''), it will be set to null.
|
||||
export let edit_label: string = 'Edit';
|
||||
export let display_inline: boolean = false;
|
||||
export let display_block_edit: boolean = false;
|
||||
|
||||
export let textarea_cols: number = 80;
|
||||
export let textarea_rows: number = 5;
|
||||
|
||||
// export let input_field_template: null|object = null;
|
||||
|
||||
export let hide_edit_btn = false;
|
||||
export let outline_element = false;
|
||||
export let show_crud = false;
|
||||
export let btn_label = null; // '<span class="fas fa-check mx-1"></span> Save'; // PATCH
|
||||
|
||||
// export let show_field_name = true;
|
||||
// export let show_original_value = true;
|
||||
// export let trim_string = false;
|
||||
// export let remove_breaks = false;
|
||||
|
||||
export let class_li: string = '';
|
||||
|
||||
// *** Set initial variables
|
||||
let ae_promises: key_val = {}; // Promise<any>;
|
||||
let patch_result: null|Promise<any>|key_val|string;
|
||||
|
||||
let original_field_value = field_value;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
|
||||
onMount(() => {
|
||||
if (log_lvl) {
|
||||
console.log(`Element AE CRUD: Object Type: ${object_type}; Object ID: ${object_id}; Field Name: ${field_name}; Field Value: ${field_value} (Original: ${original_field_value})`);
|
||||
// ; Super Key: ${api_crud_super_key}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$: if (trigger_patch == true) {
|
||||
console.log('AE CRUD: Patch triggered!');
|
||||
trigger_patch = null;
|
||||
handle_obj_field_patch(field_value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Updated 2024-03-22
|
||||
async function handle_obj_field_patch(new_field_value: any) {
|
||||
console.log('*** handle_obj_field_patch() ***');
|
||||
|
||||
patch_result = null;
|
||||
|
||||
// This was added to help with a select option list. If the value is empty (''), it will be set to null.
|
||||
if (val_empty_is_null && new_field_value == '') {
|
||||
new_field_value = null;
|
||||
}
|
||||
|
||||
// NOTE: Is this needed? The field_name, field_value, and fields parameters for update_ae_obj_id_crud() already take care of the data portion. They are added to data_list object as part of the JSON request.
|
||||
// NOTE: Currently this only handles one field and value at a time. This may need to be changed in the future to use the "fields" parameter as well.
|
||||
// let patch_data = {}
|
||||
|
||||
// if (remove_breaks) {
|
||||
// patch_data['field_value'] = field_value.replace(/(\r\n|\n|\r)/gm, "");
|
||||
// } else {
|
||||
// patch_data['field_value'] = field_value;
|
||||
// }
|
||||
|
||||
// patch_data[field_name] = field_value;
|
||||
// console.log(patch_data);
|
||||
|
||||
// let params = {};
|
||||
|
||||
ae_promises.api_update__ae_obj = core_func.handle_update_ae_obj_id_crud({
|
||||
api_cfg: api_cfg,
|
||||
object_type: object_type,
|
||||
object_id: object_id,
|
||||
field_name: field_name,
|
||||
new_field_value: new_field_value,
|
||||
params: {},
|
||||
try_cache: false,
|
||||
log_lvl: 0
|
||||
})
|
||||
.then(function (results) {
|
||||
console.log('Field PATCH Promise', results);
|
||||
|
||||
if (results) {
|
||||
console.log(`Patched - Field Name: ${field_name} with new Field Value: ${new_field_value}; Original Field Value: ${original_field_value}`);
|
||||
patch_result = 'PATCH complete';
|
||||
original_field_value = new_field_value;
|
||||
} else {
|
||||
console.log(`Not Patched - Field Name: ${field_name} with new Field Value: ${new_field_value}; Original Field Value: ${original_field_value}`);
|
||||
patch_result = 'PATCH failed';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('Something went wrong patching the record.');
|
||||
console.log(error);
|
||||
return false;
|
||||
})
|
||||
.finally(function () {
|
||||
console.log('Field PATCH Promise finally');
|
||||
// This dispatch() must be under "finally".
|
||||
dispatch(
|
||||
'ae_crud_updated',
|
||||
{
|
||||
'type': object_type,
|
||||
'id': object_id,
|
||||
'field_name': field_name,
|
||||
'field_value': new_field_value,
|
||||
'original_value': original_field_value,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return ae_promises.api_update__ae_obj;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div
|
||||
class="{class_li} ae_crud"
|
||||
class:show_crud
|
||||
class:display_inline
|
||||
class:outline_element
|
||||
>
|
||||
<span class="field_viewing_wrapper">
|
||||
<slot></slot>
|
||||
|
||||
<button
|
||||
class="btn btn-sm font-normal preset-tonal-warning hover:preset-tonal-error border border-error-500 field_show_btn"
|
||||
class:hide_edit_btn
|
||||
class:show_crud
|
||||
on:dblclick={() => {
|
||||
show_crud = true;
|
||||
}}
|
||||
title="Double click to edit property"
|
||||
>
|
||||
<span class="fas fa-edit"></span>
|
||||
<span class="hidden">Edit</span>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<div
|
||||
class:display_block_edit
|
||||
class="field_editing_wrapper font-normal min-w-content w-100 max-w-full"
|
||||
>
|
||||
<!-- <span class="grow flex flex-row items-center justify-between"> -->
|
||||
<span class="hidden">
|
||||
{edit_label}
|
||||
</span>
|
||||
|
||||
<button
|
||||
class="btn btn-md font-normal ae_btn_surface_outlined m-1"
|
||||
class:show_crud
|
||||
on:click={() => {
|
||||
show_crud = false;
|
||||
}}
|
||||
title="Close field editing"
|
||||
>
|
||||
<span class="fas fa-window-close"></span>
|
||||
<span class="hidden sm:inline">Close</span>
|
||||
</button>
|
||||
<!-- </span> -->
|
||||
|
||||
<span
|
||||
class="field_value grow min-w-content max-w-full"
|
||||
class:show_crud
|
||||
>
|
||||
{#if field_type == 'template'}
|
||||
<!-- <Element_input_v2 {...input_field_template} bind:value={field_value} /> -->
|
||||
{:else if field_type == 'button'}
|
||||
<!-- <input type="button" bind:value={field_value}> -->
|
||||
{field_value}
|
||||
{:else if field_type == 'select'}
|
||||
<select
|
||||
bind:value={field_value}
|
||||
class="select"
|
||||
>
|
||||
{#if select_option_li && Object.keys(select_option_li).length == 0}
|
||||
<option value="">-- not set --</option>
|
||||
{:else if select_option_li && Object.keys(select_option_li).length > 0}
|
||||
{#each Object.keys(select_option_li) as option}
|
||||
<option value={option}>{select_option_li[option]}</option>
|
||||
{/each}
|
||||
{:else}
|
||||
<option value="">-- no list --</option>
|
||||
{/if}
|
||||
</select>
|
||||
|
||||
{:else if field_type == 'text'}
|
||||
<input
|
||||
bind:value={field_value}
|
||||
class="input ae_btn_surface min-w-64 w-96 max-w-full"
|
||||
>
|
||||
{:else if field_type == 'textarea'}
|
||||
<textarea
|
||||
bind:value={field_value}
|
||||
cols={textarea_cols}
|
||||
rows={textarea_rows}
|
||||
class="textarea"
|
||||
></textarea>
|
||||
{:else}
|
||||
<input
|
||||
bind:value={field_value}
|
||||
class="input w-fit"
|
||||
>
|
||||
{/if}
|
||||
{#if allow_null}
|
||||
<button
|
||||
class="btn btn-sm ae_btn_warning m-1"
|
||||
on:click={() => {
|
||||
field_value = null;
|
||||
}}
|
||||
title="Set value to NULL"
|
||||
>
|
||||
Ø
|
||||
NULL
|
||||
</button>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<button
|
||||
class="btn btn-lg ae_btn_warning_outlined m-1"
|
||||
class:show_crud
|
||||
disabled={field_value == original_field_value}
|
||||
on:click={async () => {
|
||||
handle_obj_field_patch(field_value);
|
||||
}}
|
||||
title="Save new field value"
|
||||
>
|
||||
{#if field_value == original_field_value}
|
||||
{#if (btn_label)}
|
||||
{@html btn_label}
|
||||
{:else}
|
||||
<span class="fas fa-check mx-1"></span>
|
||||
<span>Save</span>
|
||||
{/if}
|
||||
<!-- <span>{patch_result}</span> -->
|
||||
{:else}
|
||||
{#if (btn_label)}
|
||||
{@html btn_label}
|
||||
{:else}
|
||||
<span class="fas fa-save mx-1"></span>
|
||||
<span>Save</span>
|
||||
{/if}
|
||||
{/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>
|
||||
<span>Processing...</span>
|
||||
{:then}
|
||||
{#if patch_result}
|
||||
<span class="fas fa-check mx-1"></span>
|
||||
<span>{patch_result}</span>
|
||||
{:else}
|
||||
<!-- <div>Nothing to show yet...</div> -->
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
/* .ae_crud .field_editing_wrapper {
|
||||
font-size: 1em;
|
||||
} */
|
||||
|
||||
|
||||
/* BEGIN: Svelte CRUD (update) component */
|
||||
/* .ae_crud {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
} */
|
||||
|
||||
.ae_crud.display_inline {
|
||||
/* outline: solid thin red; */
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.ae_crud.show_crud .field_viewing_wrapper .field_show_btn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ae_crud.outline_element .field_viewing_wrapper {
|
||||
outline: dotted thin hsla(0,50%,50%,0);
|
||||
transition: outline 3s 1s;
|
||||
}
|
||||
.ae_crud.outline_element .field_viewing_wrapper:hover {
|
||||
outline: dashed thin hsla(0,50%,50%,.9);
|
||||
transition: outline 1s .5s;
|
||||
}
|
||||
|
||||
.ae_crud .field_viewing_wrapper .field_show_btn.hide_edit_btn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ae_crud .field_viewing_wrapper .field_show_btn {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
/* color: hsla(0,0%,50%,.8); */
|
||||
opacity: .25;
|
||||
|
||||
/* NOTE: transition when hover ends */
|
||||
transition-property: opacity, background-color, border-color, color, height, width;
|
||||
transition-delay: 1.00s; /* no delay */
|
||||
transition-duration: .55s;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
.ae_crud .field_viewing_wrapper:hover .field_show_btn {
|
||||
/* outline: solid thin hsla(0,50%,50%,.9); */
|
||||
/* color: hsla(0,50%,50%,.9); */
|
||||
|
||||
opacity: 1;
|
||||
|
||||
/* NOTE: transition when hover starts */
|
||||
transition-property: opacity, background-color, border-color, color, height, width;
|
||||
transition-delay: .25s; /* no delay */
|
||||
transition-duration: .50s;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
.ae_crud .field_editing_wrapper {
|
||||
/* outline: dashed thin pink; */
|
||||
|
||||
display: none;
|
||||
/* position: relative; */
|
||||
|
||||
/* contain: layout; */
|
||||
/* contain: size; */
|
||||
/* contain: none; */
|
||||
contain: content;
|
||||
|
||||
box-shadow: .5em .5em .75em .75em hsla(0,0%,0%,0.5);
|
||||
|
||||
/* color: hsla(0,0%,50%,.8); */
|
||||
background-color: hsla(0,0%,50%,.5);
|
||||
border: dashed thin transparent;
|
||||
|
||||
/* font-size: 1em; */
|
||||
/* line-height: 1em; */
|
||||
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
|
||||
/* NOTE: transition when hover ends */
|
||||
transition-property: background-color, border-color, color, height, width;
|
||||
transition-delay: .75s; /* short delay */
|
||||
transition-duration: .50s;
|
||||
transition-timing-function: linear;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ae_crud.show_crud .field_editing_wrapper {
|
||||
/* display: initial; */
|
||||
display: block;
|
||||
contain: content;
|
||||
|
||||
/* background-color: yellow; */
|
||||
background-color: hsla(60,50%,80%,.95);
|
||||
border: solid thin hsla(0,50%,50%,.50);
|
||||
border-radius: .5em;
|
||||
|
||||
height: auto;
|
||||
/* height: 100%; */
|
||||
max-height: 100%;
|
||||
|
||||
min-width: fit-content;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
|
||||
/* NOTE: transition when hover starts */
|
||||
transition-property: background-color, border-color, height, width;
|
||||
transition-delay: .25s; /* no delay */
|
||||
transition-duration: .25s;
|
||||
transition-timing-function: linear;
|
||||
|
||||
/* position: absolute; */
|
||||
/* position: fixed; */
|
||||
/* position: relative; */
|
||||
/* top: 1em; */
|
||||
/* left: 1em; */
|
||||
/* right: 1em; */
|
||||
/* bottom: 1em; */
|
||||
|
||||
z-index: 1;
|
||||
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
|
||||
.ae_crud.show_crud.display_inline .field_editing_wrapper:hover {
|
||||
background-color: hsla(60,60%,90%,.80);
|
||||
z-index: 55;
|
||||
}
|
||||
|
||||
.ae_crud.show_crud.display_inline .field_editing_wrapper {
|
||||
/* display: block; */
|
||||
display: inline-block;
|
||||
/* display: inline; */
|
||||
|
||||
box-shadow: initial;
|
||||
background-color: hsla(60,50%,80%,.40);
|
||||
|
||||
padding: .25em;
|
||||
|
||||
position: initial;
|
||||
|
||||
z-index: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: .5em;
|
||||
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.ae_crud.show_crud.display_inline .field_editing_wrapper.display_block_edit {
|
||||
/* display: block; */
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: .5em;
|
||||
|
||||
flex-grow: 1;
|
||||
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ae_crud textarea {
|
||||
height: auto;
|
||||
}
|
||||
/* END: Svelte CRUD (update) component */
|
||||
</style>
|
||||
579
src/lib/elements/element_ae_crud_v2.svelte
Normal file
579
src/lib/elements/element_ae_crud_v2.svelte
Normal file
@@ -0,0 +1,579 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
log_lvl?: number;
|
||||
api_cfg?: key_val;
|
||||
// export let api_crud_super_key: null|string = api_cfg.api_crud_super_key;
|
||||
|
||||
trigger_patch?: any;
|
||||
patch_status?: string; // '', processing, complete, error
|
||||
patch_complete?: boolean|null|string; // null = not started, true = complete/success, false = error
|
||||
patch_result?: boolean|null|key_val; // Result of the patch operation
|
||||
|
||||
object_type: string;
|
||||
object_id: string;
|
||||
object_reload?: boolean;
|
||||
|
||||
field_name: string;
|
||||
field_type?: string; // button, text, textarea, template (older method), select (in progress method)
|
||||
|
||||
previous_field_value?: any;
|
||||
current_field_value?: any;
|
||||
new_field_value?: any;
|
||||
|
||||
allow_null?: boolean;
|
||||
select_option_kv?: key_val;
|
||||
val_empty_is_null?: boolean; // This was added to help with a select option list. If the value is empty (''), it will be set to null.
|
||||
|
||||
edit_label?: string;
|
||||
display_block?: boolean; // If true, the element will be displayed as a block element.
|
||||
// display_inline?: boolean;
|
||||
display_absolute_edit?: boolean; // If true, the edit form will be displayed in the top right corner of the element.
|
||||
// display_block_edit?: boolean;
|
||||
textarea_cols?: number;
|
||||
textarea_rows?: number;
|
||||
|
||||
// export let input_field_template: null|object = null;
|
||||
|
||||
hide_element?: boolean; // This hides the entire custom element.
|
||||
hide_edit_btn?: boolean; // This only hides the edit button.
|
||||
// hide_edit_form?: boolean;
|
||||
show_edit_form?: boolean; // This is used if the parent needs to make the edit form visible.
|
||||
|
||||
outline_element?: boolean;
|
||||
|
||||
btn_label?: any; // '<span class="fas fa-check mx-1"></span> Save'; // PATCH
|
||||
// export let remove_breaks = false;
|
||||
class_li?: string;
|
||||
children?: import('svelte').Snippet;
|
||||
}
|
||||
|
||||
let {
|
||||
log_lvl = 0,
|
||||
api_cfg = {'api_crud_super_key': null},
|
||||
|
||||
trigger_patch = null,
|
||||
patch_status = '',
|
||||
patch_complete = $bindable(null), // null = not started, true = complete/success, false = error
|
||||
patch_result = null,
|
||||
|
||||
object_type,
|
||||
object_id,
|
||||
object_reload = false,
|
||||
|
||||
field_name,
|
||||
field_type = 'text',
|
||||
|
||||
previous_field_value = $bindable(null),
|
||||
current_field_value = null,
|
||||
new_field_value = current_field_value,
|
||||
|
||||
allow_null = false,
|
||||
select_option_kv = $bindable({}),
|
||||
val_empty_is_null = false, // If the value is empty (''), it will be set to null.
|
||||
|
||||
edit_label = 'Edit',
|
||||
display_block = false,
|
||||
// display_inline = false,
|
||||
display_absolute_edit = false,
|
||||
// display_block_edit = false,
|
||||
textarea_cols = 80,
|
||||
textarea_rows = 5,
|
||||
|
||||
hide_element = $bindable(false),
|
||||
hide_edit_btn = false,
|
||||
// hide_edit_form = $bindable(true),
|
||||
show_edit_form = $bindable(),
|
||||
|
||||
outline_element = $bindable(false),
|
||||
|
||||
btn_label = null,
|
||||
class_li = '',
|
||||
children
|
||||
}: Props = $props();
|
||||
|
||||
// *** Import Svelte core
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
// *** Import Aether core variables and functions
|
||||
import type { key_val } from '$lib/ae_stores';
|
||||
// import { api } from '$lib/api';
|
||||
import { core_func } from '$lib/ae_core/ae_core_functions';
|
||||
// import { ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/ae_stores';
|
||||
|
||||
// *** Import Aether core components
|
||||
// *** Import Aether module variables and functions
|
||||
// *** Import Aether module components
|
||||
|
||||
// export let show_field_name = true;
|
||||
// export let show_original_value = true;
|
||||
// export let trim_string = false;
|
||||
|
||||
|
||||
// *** Set initial variables
|
||||
let ae_promises: key_val = $state({}); // Promise<any>;
|
||||
// let patch_result: null|Promise<any>|key_val|string = $state();
|
||||
|
||||
|
||||
$effect(() => {
|
||||
if (browser) {
|
||||
if (log_lvl) {
|
||||
console.log(`Element AE CRUD: Object Type: ${object_type}; Object ID: ${object_id}; Field Name: ${field_name}; New Field Value: ${new_field_value} (Current: ${current_field_value})`);
|
||||
// ; Super Key: ${api_crud_super_key}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if (select_option_kv) {
|
||||
// console.log(select_option_kv);
|
||||
// }
|
||||
|
||||
if (trigger_patch === true) {
|
||||
if (log_lvl) {
|
||||
console.log('AE CRUD: Patch triggered by trigger_patch = true');
|
||||
}
|
||||
trigger_patch = null;
|
||||
handle_obj_field_patch(new_field_value);
|
||||
|
||||
// if (new_field_value !== current_field_value) {
|
||||
// if (log_lvl > 1) {
|
||||
// console.log(`AE CRUD: field_value changed from ${current_field_value} to ${new_field_value}`);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Updated 2024-03-22
|
||||
async function handle_obj_field_patch(new_field_value: any) {
|
||||
if (log_lvl) {
|
||||
console.log(`AE CRUD: Patching Object Type: ${object_type}; Object ID: ${object_id}; Field Name: ${field_name}; New Field Value: ${new_field_value}; Current Field Value: ${current_field_value}`);
|
||||
// ; Super Key: ${api_crud_super_key}
|
||||
}
|
||||
|
||||
patch_status = 'Processing...';
|
||||
patch_complete = null;
|
||||
patch_result = null;
|
||||
|
||||
// This was added to help with a select option list. If the value is empty (''), it will be set to null.
|
||||
if (val_empty_is_null && new_field_value == '') {
|
||||
new_field_value = null;
|
||||
}
|
||||
|
||||
// NOTE: Is this needed? The field_name, field_value, and fields parameters for update_ae_obj_id_crud() already take care of the data portion. They are added to data_list object as part of the JSON request.
|
||||
// NOTE: Currently this only handles one field and value at a time. This may need to be changed in the future to use the "fields" parameter as well.
|
||||
// let patch_data = {}
|
||||
|
||||
// if (remove_breaks) {
|
||||
// patch_data['field_value'] = field_value.replace(/(\r\n|\n|\r)/gm, "");
|
||||
// } else {
|
||||
// patch_data['field_value'] = field_value;
|
||||
// }
|
||||
|
||||
// patch_data[field_name] = field_value;
|
||||
// console.log(patch_data);
|
||||
|
||||
// let params = {};
|
||||
|
||||
ae_promises.api_update__ae_obj = core_func.update_ae_obj_id_crud_v2({
|
||||
api_cfg: api_cfg,
|
||||
object_type: object_type,
|
||||
object_id: object_id,
|
||||
object_reload: object_reload,
|
||||
field_name: field_name,
|
||||
new_field_value: new_field_value,
|
||||
log_lvl: log_lvl
|
||||
})
|
||||
.then(function (results) {
|
||||
// console.log('Field PATCH Promise', results);
|
||||
|
||||
if (results) {
|
||||
// console.log(`Patched - Field Name: ${field_name} with New Field Value: ${new_field_value}; Original Field Value: ${current_field_value}`);
|
||||
patch_status = 'PATCH complete';
|
||||
current_field_value = new_field_value;
|
||||
} else {
|
||||
console.log(`Not Patched - Field Name: ${field_name} with New Field Value: ${new_field_value}; Original Field Value: ${current_field_value}`);
|
||||
patch_status = 'PATCH failed';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.catch(function (error) {
|
||||
patch_status = 'Error during PATCH';
|
||||
console.log('Something went wrong patching the record.');
|
||||
console.log(error);
|
||||
return false;
|
||||
})
|
||||
.finally(function () {
|
||||
// console.log('Field PATCH Promise finally');
|
||||
if (patch_status != 'PATCH complete') {
|
||||
patch_status = 'Error during PATCH';
|
||||
patch_complete = false;
|
||||
patch_result = false;
|
||||
} else {
|
||||
// patch_complete = true;
|
||||
patch_complete = object_id; // Return the object ID that was patched
|
||||
patch_result = {
|
||||
'type': object_type,
|
||||
'id': object_id,
|
||||
'field_name': field_name,
|
||||
'field_value': new_field_value,
|
||||
'original_value': current_field_value,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return ae_promises.api_update__ae_obj;
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<!-- relative -->
|
||||
<div
|
||||
class="{class_li} ae_crud
|
||||
font-normal
|
||||
transition-all duration-300 ease-in-out
|
||||
relative
|
||||
"
|
||||
class:hidden={hide_element}
|
||||
class:block={display_block}
|
||||
class:inline-block={!display_block}
|
||||
class:outline_element
|
||||
>
|
||||
|
||||
{@render children?.()}
|
||||
|
||||
<span
|
||||
class="field_viewing_wrapper
|
||||
"
|
||||
>
|
||||
<!-- {@render children?.()} -->
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class:hidden={show_edit_form || hide_edit_btn}
|
||||
class="
|
||||
btn btn-icon
|
||||
text-xs
|
||||
m-0 px-0.5
|
||||
preset-tonal-warning hover:preset-tonal-error
|
||||
preset-outlined-warning-100-900 hover:preset-outlined-warning-600-400
|
||||
opacity-50 hover:opacity-100
|
||||
transition-all
|
||||
"
|
||||
ondblclick={() => {
|
||||
// hide_edit_btn = true;
|
||||
// hide_edit_form = false;
|
||||
show_edit_form = true;
|
||||
}}
|
||||
title="Double click to edit property"
|
||||
>
|
||||
<span class="fas fa-edit m-0 p-0"></span>
|
||||
<span class="hidden">Edit</span>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<!-- class:display_block={display_block} -->
|
||||
<div
|
||||
class:hidden={!show_edit_form}
|
||||
class:absolute={display_absolute_edit}
|
||||
class:top-0={display_absolute_edit}
|
||||
class:right-0={display_absolute_edit}
|
||||
|
||||
|
||||
class:drop-shadow-2xl={display_absolute_edit}
|
||||
class="field_editing_wrapper_v2
|
||||
border-2 border-dashed hover:border-solid
|
||||
border-pink-400 dark:border-pink-600
|
||||
rounded-md
|
||||
min-w-fit w-full max-w-fit
|
||||
z-20
|
||||
p-1 m-0
|
||||
bg-pink-50/90 dark:bg-pink-900/90
|
||||
hover:bg-pink-50 hover:dark:bg-pink-900
|
||||
|
||||
flex flex-col md:flex-row flex-wrap gap-1
|
||||
items-center justify-center
|
||||
space-y-2
|
||||
transition-all duration-300 ease-in-out
|
||||
"
|
||||
>
|
||||
<span class="grow flex flex-row items-center justify-between">
|
||||
<span class="hidden md:inline font-semibold text-lg">
|
||||
{edit_label}
|
||||
</span>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-md ae_btn_surface_outlined m-1"
|
||||
onclick={() => {
|
||||
// hide_edit_btn = false;
|
||||
// hide_edit_form = true;
|
||||
show_edit_form = false;
|
||||
}}
|
||||
title="Close field editing. This does not save any changes."
|
||||
>
|
||||
<span class="fas fa-window-close"></span>
|
||||
<span class="hidden sm:inline">Close</span>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
New value: {new_field_value}
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="field_value
|
||||
grow
|
||||
min-w-fit w-full max-w-fit
|
||||
flex flex-row flex-wrap items-center justify-center gap-1
|
||||
"
|
||||
|
||||
>
|
||||
{#if field_type == 'template'}
|
||||
<!-- <Element_input_v2 {...input_field_template} bind:value={field_value} /> -->
|
||||
{:else if field_type == 'button'}
|
||||
<!-- <input type="button" bind:value={field_value}> -->
|
||||
{new_field_value}
|
||||
{:else if field_type == 'select'}
|
||||
<select
|
||||
bind:value={new_field_value}
|
||||
class="
|
||||
select
|
||||
p-1
|
||||
min-w-fit w-full
|
||||
border border-gray-300 dark:border-gray-600
|
||||
"
|
||||
>
|
||||
{#if select_option_kv && Object.keys(select_option_kv).length == 0}
|
||||
<option value="">-- not set --</option>
|
||||
{:else if select_option_kv && Object.keys(select_option_kv).length > 0}
|
||||
{#each Object.keys(select_option_kv) as option}
|
||||
<option value={option}>{select_option_kv[option]} {option}</option>
|
||||
{/each}
|
||||
{:else}
|
||||
<option value="">-- no list --</option>
|
||||
{/if}
|
||||
</select>
|
||||
|
||||
{:else if field_type == 'text'}
|
||||
<input
|
||||
bind:value={new_field_value}
|
||||
class="input ae_btn_surface min-w-64 w-96 max-w-full"
|
||||
>
|
||||
{:else if field_type == 'textarea'}
|
||||
<textarea
|
||||
bind:value={new_field_value}
|
||||
cols={textarea_cols}
|
||||
rows={textarea_rows}
|
||||
class="textarea"
|
||||
></textarea>
|
||||
{:else}
|
||||
<input
|
||||
bind:value={new_field_value}
|
||||
class="input w-fit"
|
||||
>
|
||||
{/if}
|
||||
{#if allow_null}
|
||||
<button
|
||||
class="btn btn-sm ae_btn_warning m-1"
|
||||
onclick={() => {
|
||||
new_field_value = null;
|
||||
}}
|
||||
title="Set value to NULL"
|
||||
>
|
||||
Ø
|
||||
NULL
|
||||
</button>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<button
|
||||
class="
|
||||
ae_btn_warning_outlined
|
||||
btn btn-lg
|
||||
m-1
|
||||
"
|
||||
disabled={new_field_value === current_field_value}
|
||||
onclick={async () => {
|
||||
handle_obj_field_patch(new_field_value);
|
||||
}}
|
||||
title="Save new field value"
|
||||
>
|
||||
{#if new_field_value === current_field_value}
|
||||
{#if (btn_label)}
|
||||
{@html btn_label}
|
||||
{:else}
|
||||
<span class="fas fa-check mx-1"></span>
|
||||
<span>Save</span>
|
||||
{/if}
|
||||
<!-- <span>{patch_result}</span> -->
|
||||
{:else}
|
||||
{#if (btn_label)}
|
||||
{@html btn_label}
|
||||
{:else}
|
||||
<span class="fas fa-save mx-1"></span>
|
||||
<span>Save</span>
|
||||
{/if}
|
||||
{/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>
|
||||
<span>Processing...</span>
|
||||
{:then}
|
||||
{#if patch_status}
|
||||
<span class="fas fa-check mx-1"></span>
|
||||
<span>{patch_status}</span>
|
||||
{:else}
|
||||
<!-- <div>Nothing to show yet...</div> -->
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
.ae_crud.outline_element .field_viewing_wrapper {
|
||||
outline: dotted thin hsla(0,50%,50%,0);
|
||||
transition: outline 3s 1s;
|
||||
}
|
||||
.ae_crud.outline_element .field_viewing_wrapper:hover {
|
||||
outline: dashed thin hsla(0,50%,50%,.9);
|
||||
transition: outline 1s .5s;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ae_crud .field_viewing_wrapper {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
/* color: hsla(0,0%,50%,.8); */
|
||||
opacity: .25;
|
||||
|
||||
/* NOTE: transition when hover ends */
|
||||
transition-property: opacity, background-color, border-color, color, height, width;
|
||||
transition-delay: 1.00s; /* no delay */
|
||||
transition-duration: .55s;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
.ae_crud .field_viewing_wrapper:hover {
|
||||
/* outline: solid thin hsla(0,50%,50%,.9); */
|
||||
/* color: hsla(0,50%,50%,.9); */
|
||||
|
||||
opacity: 1;
|
||||
|
||||
/* NOTE: transition when hover starts */
|
||||
transition-property: opacity, background-color, border-color, color, height, width;
|
||||
transition-delay: .25s; /* no delay */
|
||||
transition-duration: .50s;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
.ae_crud .field_editing_wrapper {
|
||||
/* outline: dashed thin pink; */
|
||||
|
||||
|
||||
/* position: relative; */
|
||||
|
||||
/* contain: layout; */
|
||||
/* contain: size; */
|
||||
/* contain: none; */
|
||||
contain: content;
|
||||
|
||||
box-shadow: .5em .5em .75em .75em hsla(0,0%,0%,0.5);
|
||||
|
||||
/* color: hsla(0,0%,50%,.8); */
|
||||
/* background-color: hsla(0,0%,50%,.5); */
|
||||
/* border: dashed thin transparent; */
|
||||
|
||||
/* background-color: yellow; */
|
||||
background-color: hsla(60,50%,80%,.95);
|
||||
border: solid thin hsla(0,50%,50%,.50);
|
||||
border-radius: .5em;
|
||||
|
||||
height: auto;
|
||||
/* height: 100%; */
|
||||
max-height: 100%;
|
||||
|
||||
min-width: fit-content;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
|
||||
/* font-size: 1em; */
|
||||
/* line-height: 1em; */
|
||||
|
||||
/* NOTE: transition when hover ends */
|
||||
transition-property: background-color, border-color, color, height, width;
|
||||
transition-delay: .75s; /* short delay */
|
||||
transition-duration: .50s;
|
||||
transition-timing-function: linear;
|
||||
|
||||
z-index: 1;
|
||||
|
||||
padding: .5em;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.ae_crud.show_crud.display_inline .field_editing_wrapper:hover {
|
||||
background-color: hsla(60,60%,90%,.80);
|
||||
z-index: 55;
|
||||
}
|
||||
|
||||
.ae_crud.show_crud.display_inline .field_editing_wrapper {
|
||||
/* display: block; */
|
||||
display: inline-block;
|
||||
/* display: inline; */
|
||||
|
||||
box-shadow: initial;
|
||||
background-color: hsla(60,50%,80%,.40);
|
||||
|
||||
padding: .25em;
|
||||
|
||||
position: initial;
|
||||
|
||||
z-index: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: .5em;
|
||||
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.ae_crud.show_crud.display_inline .field_editing_wrapper.display_block_edit {
|
||||
/* display: block; */
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: .5em;
|
||||
|
||||
flex-grow: 1;
|
||||
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ae_crud textarea {
|
||||
height: auto;
|
||||
}
|
||||
/* END: Svelte CRUD (update) component */
|
||||
</style>
|
||||
805
src/lib/elements/element_data_store.svelte
Normal file
805
src/lib/elements/element_data_store.svelte
Normal file
@@ -0,0 +1,805 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { api } from '$lib/api';
|
||||
import { ae_loc, ae_sess, ae_api, slct, slct_trigger, ae_trig } from '$lib/ae_stores';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
import type { key_val } from '$lib/ae_stores';
|
||||
|
||||
export let expire_minutes: number = 10;
|
||||
export let mount_reload_sec: number = 0;
|
||||
|
||||
export let ds_code: string;
|
||||
export let ds_name: null|string = null;
|
||||
export let ds_type: string = 'text';
|
||||
export let for_type: null|string = null;
|
||||
export let for_id: null|string = null;
|
||||
console.log(`ae_e_data_store ${ds_code} for_type=${for_type} for_id=${for_id} account_id=${$ae_loc.account_id}`);
|
||||
|
||||
// export let store: string = 'local';
|
||||
export let display: string = 'block'; // Avoid; Use class list instead
|
||||
export let class_li: string = ''; // : string[] = [];
|
||||
export let try_cache: boolean = true;
|
||||
|
||||
export let hide: boolean = false; // Hide the entire element
|
||||
export let show_edit: boolean = false;
|
||||
export let show_edit_btn: boolean = true;
|
||||
export let show_view: boolean = true;
|
||||
// export let show_delete_btn: boolean = false;
|
||||
|
||||
export let ds_loaded: boolean = false;
|
||||
|
||||
export let debug: boolean = false;
|
||||
|
||||
let ae_promises: key_val = {}; // Promise<any>;
|
||||
let ds_get_results: Promise<any>|key_val;
|
||||
let ds_loading_status: string = 'starting...';
|
||||
let ds_submit_results: Promise<any>|key_val;
|
||||
|
||||
let val_json: key_val;
|
||||
let val_html: key_val;
|
||||
let val_md: key_val;
|
||||
export let val_sql: null|key_val = null;
|
||||
let val_text: string;
|
||||
|
||||
|
||||
let ds_code_obj =
|
||||
{
|
||||
id: null,
|
||||
account_id: null,
|
||||
code: ds_code,
|
||||
name: ds_name,
|
||||
type: ds_type,
|
||||
for_type: null, // for_type
|
||||
for_id: null, // for_id
|
||||
access_read: null, // 'super', 'manager', 'administrator', 'trusted', 'authenticated', 'anonymous'
|
||||
access_write: null, // 'super', 'manager', 'administrator', 'trusted', 'authenticated', 'anonymous'
|
||||
access_delete: null, // 'super', 'manager', 'administrator', 'trusted', 'authenticated', 'anonymous'
|
||||
html: null,
|
||||
json: null,
|
||||
md: null,
|
||||
text: null,
|
||||
updated_on: null,
|
||||
chk_account_id: null,
|
||||
};
|
||||
|
||||
let ae_ds_tmp: key_val;
|
||||
if (browser && localStorage.getItem(`ae_ds__${ds_code}`)) {
|
||||
ae_ds_tmp = JSON.parse(localStorage.getItem(`ae_ds__${ds_code}`));
|
||||
} else {
|
||||
ae_ds_tmp = ds_code_obj;
|
||||
}
|
||||
|
||||
// Writable<key_val> = persisted(`ae_ds__${ds_code}`, ds_code_obj);
|
||||
// console.log(`ae_e_data_store cached: ${ds_code} = `, ae_ds_tmp);
|
||||
console.log(`ae_e_data_store cached: ${ds_code} account_id=${$ae_loc.account_id}`);
|
||||
|
||||
if (!ae_ds_tmp || !ae_ds_tmp.id) {
|
||||
ds_loading_status = '-- loading --';
|
||||
} else {
|
||||
// ae_ds_loc.set(ae_ds_tmp);
|
||||
}
|
||||
|
||||
$ae_sess.ds.submit_status = null;
|
||||
$ae_sess.ds.create_status = null;
|
||||
$ae_sess.ds.update_status = null;
|
||||
|
||||
let trigger: null|string = null;
|
||||
|
||||
// $: if (ae_ds_tmp) {
|
||||
// console.log(`ae_e_data_store ae_ds_loc = `, ae_ds_tmp);
|
||||
// }
|
||||
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
let ae_ds_loc_test: any;
|
||||
if (browser) {
|
||||
console.log('ae_e_data_store Browser detected.');
|
||||
ae_ds_loc_test = JSON.parse(localStorage.getItem(`ae_ds__${ds_code}`));
|
||||
console.log(`ae_e_data_store ae_ds_loc_test = `, ae_ds_loc_test);
|
||||
// ae_ds_tmp = {id: null};
|
||||
// ae_ds_tmp = ae_ds_loc_test;
|
||||
} else {
|
||||
console.log('ae_e_data_store Browser not detected.');
|
||||
}
|
||||
|
||||
// This is a quick check to make sure the data store is not stale. If it is, then we need to trigger a reload.
|
||||
if (browser && ae_ds_tmp && ae_ds_tmp.loaded_on && ae_ds_tmp.chk_account_id == $ae_loc.account_id) {
|
||||
console.log(`ae_e_data_store ${ds_code} loaded_on: ${ae_ds_tmp.loaded_on}`);
|
||||
let loaded_on = new Date(ae_ds_tmp.loaded_on);
|
||||
let now = new Date();
|
||||
let diff = now.getTime() - loaded_on.getTime();
|
||||
let diff_minutes = diff / (1000 * 60);
|
||||
if (diff_minutes > expire_minutes) {
|
||||
console.log(`ae_e_data_store: Data Store ${ds_code} stale. Last loaded on: ${loaded_on.toISOString()}`);
|
||||
// Wait for random number of milliseconds to avoid all data stores being reloaded at the same time.
|
||||
let random_ms = Math.floor(Math.random() * 500);
|
||||
console.log(`ae_e_data_store: Random number of milliseconds: ${random_ms}`);
|
||||
setTimeout(() => {
|
||||
trigger = 'load__ds__code';
|
||||
}, random_ms);
|
||||
}
|
||||
} else if (browser) {
|
||||
console.log('ae_e_data_store: No loaded_on date found and or the account_id check failed. Need to trigger reload.');
|
||||
trigger = 'load__ds__code';
|
||||
}
|
||||
|
||||
// This is a secondary check... The account_id should either be null or match the current account_id.
|
||||
if (!ae_ds_tmp || !ae_ds_tmp.account_id === null || $ae_loc.account_id == $ae_loc.account_id) {
|
||||
trigger = 'load__ds__code';
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
console.log('Element: Data Store element_data_store.svelte');
|
||||
// console.log('ae_ MOUNTED Browser detected.');
|
||||
|
||||
if (mount_reload_sec) {
|
||||
// Wait for random number of milliseconds to avoid all data stores being reloaded at the same time.
|
||||
let random_ms = Math.floor(Math.random() * mount_reload_sec * 1000);
|
||||
console.log(`ae_e_data_store: Random number of milliseconds: ${random_ms}`);
|
||||
setTimeout(() => {
|
||||
trigger = 'load__ds__code';
|
||||
}, random_ms);
|
||||
}
|
||||
});
|
||||
|
||||
// let ds_code_li = {}; //: key_val; // = ae_loc_tmp.ds;
|
||||
// console.log(`ae_ ds_code_li = `, ds_code_li);
|
||||
|
||||
$: if (trigger == 'load__ds__code' && ds_code && ds_type) {
|
||||
console.log(`ae_e_data_store: ae_ load__ds__code: ${ds_code} ds_type=${ds_type} for_type=${for_type} for_id=${for_id} ${try_cache}`);
|
||||
|
||||
trigger = null;
|
||||
|
||||
load_data_store({
|
||||
code: ds_code,
|
||||
type: ds_type,
|
||||
for_type: for_type,
|
||||
for_id: for_id,
|
||||
try_cache: try_cache
|
||||
});
|
||||
}
|
||||
|
||||
async function load_data_store(
|
||||
{
|
||||
code,
|
||||
type='text',
|
||||
for_type=null,
|
||||
for_id=null,
|
||||
try_cache=true
|
||||
}: {
|
||||
code: string,
|
||||
type: string,
|
||||
for_type: string|null,
|
||||
for_id: string|null,
|
||||
try_cache: boolean
|
||||
}
|
||||
) {
|
||||
|
||||
// let ds_code_val = await api.get_data_store_obj_w_code({
|
||||
ds_get_results = api.get_data_store_obj_w_code({
|
||||
api_cfg: $ae_api,
|
||||
data_store_code: code,
|
||||
data_type: type,
|
||||
log_lvl: 0
|
||||
|
||||
})
|
||||
.then( function (ds_results) {
|
||||
// console.log(`ae_ Data Store ${code} = `, ds_results);
|
||||
if (ds_results) {
|
||||
console.log(`ae_e_data_store: Got a result for code ${code}`);
|
||||
if (!ds_results.data_store_id_random) {
|
||||
console.log('Something went wrong? No data store ID found.');
|
||||
return false;
|
||||
}
|
||||
|
||||
ds_loaded = true;
|
||||
|
||||
// Set the loaded_on datetime to the current time for reference later. This will be used to determine if the data store is stale.
|
||||
ae_ds_tmp.loaded_on = new Date().toISOString();
|
||||
// Set the chk_account_id as a backup check to make sure the data store belongs to the account for the current site. This should not be needed, but here we are...
|
||||
ae_ds_tmp.chk_account_id = $ae_loc.account_id;
|
||||
|
||||
ae_ds_tmp.id = ds_results.data_store_id_random;
|
||||
ae_ds_tmp.account_id = ds_results.account_id_random;
|
||||
ae_ds_tmp.code = ds_results.code; // This will overwrite whatever was passed in.
|
||||
ae_ds_tmp.name = ds_results.name;
|
||||
ae_ds_tmp.type = ds_results.type; // This will overwrite whatever was passed in.
|
||||
if (type == 'html') {
|
||||
ae_ds_tmp.html = ds_results.text;
|
||||
val_html = ds_results.text;
|
||||
return ds_results.html;
|
||||
} else if (type == 'json') {
|
||||
ae_ds_tmp.json = ds_results.json;
|
||||
val_json = ds_results.json;
|
||||
return ds_results.json;
|
||||
} else if (type == 'md') {
|
||||
ae_ds_tmp.text = ds_results.text;
|
||||
val_md = ds_results.text;
|
||||
return ds_results.text;
|
||||
} else if (type == 'sql') {
|
||||
ae_ds_tmp.text = ds_results.text;
|
||||
val_sql = ds_results.text;
|
||||
return ds_results.text;
|
||||
} else {
|
||||
ae_ds_tmp.text = ds_results.text;
|
||||
val_text = ds_results.text;
|
||||
return ds_results.text;
|
||||
}
|
||||
} else {
|
||||
ds_loaded = false;
|
||||
ds_loading_status = '-- not found --';
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(`Something went wrong. for code ${code}`);
|
||||
console.log(error);
|
||||
ds_loading_status = '-- error --';
|
||||
return false;
|
||||
});
|
||||
// .finally(function (ds_val_result) {
|
||||
// // console.log(`ae_ ds_code_val = `, ds_val_result);
|
||||
|
||||
// // ae_ds_loc.set(ds_val_result);
|
||||
// // localStorage.setItem(ds_code, ds_val_result);
|
||||
// // sessionStorage.setItem(ds_code, ds_val_result);
|
||||
|
||||
// // return ds_val_result;
|
||||
// });
|
||||
// console.log(`ae_ ds_code_val = `, ds_code_val);
|
||||
}
|
||||
|
||||
|
||||
async function handle_submit_form(event: any) {
|
||||
// console.log('*** handle_submit_form() ***');
|
||||
|
||||
$ae_sess.ds.submit_status = 'processing';
|
||||
|
||||
|
||||
// Data in
|
||||
let form_data = new FormData(event.target);
|
||||
// console.log(form_data);
|
||||
|
||||
let data_store_di: key_val = ae_util.extract_prefixed_form_data({prefix: null, form_data: form_data, trim_values: true, bool_tf_str: true, log_lvl: 0});
|
||||
// console.log(data_store_di);
|
||||
|
||||
// Data out
|
||||
let data_store_do: key_val = {};
|
||||
|
||||
if (typeof data_store_di.ds_id_random !== 'undefined') {
|
||||
data_store_do['data_store_id_random'] = data_store_di.ds_id_random;
|
||||
}
|
||||
|
||||
// if (!$slct.data_store_id) {
|
||||
if (!ae_ds_tmp.id) {
|
||||
data_store_do['account_id_random'] = $ae_loc.account_id;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_account_id !== 'undefined' && data_store_di.ds_account_id && data_store_di.ds_use_account_id) {
|
||||
data_store_do['account_id_random'] = data_store_di.ds_account_id;
|
||||
} else if (data_store_di.ds_use_account_id && $ae_loc.account_id) {
|
||||
data_store_do['account_id_random'] = $ae_loc.account_id;
|
||||
} else {
|
||||
data_store_do['account_id_random'] = null;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_code !== 'undefined') {
|
||||
data_store_do['code'] = data_store_di.ds_code;
|
||||
} else {
|
||||
data_store_do['code'] = ds_code;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_name !== 'undefined') {
|
||||
data_store_do['name'] = data_store_di.ds_name;
|
||||
} else {
|
||||
data_store_do['name'] = ds_name;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_type !== 'undefined') {
|
||||
data_store_do['type'] = data_store_di.ds_type;
|
||||
} else {
|
||||
data_store_do['type'] = ds_type;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_for_type !== 'undefined' && data_store_di.ds_for_type) {
|
||||
data_store_do['for_type'] = data_store_di.ds_for_type;
|
||||
} else {
|
||||
data_store_do['for_type'] = null;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_for_id !== 'undefined' && data_store_di.ds_for_id) {
|
||||
data_store_do['for_id_random'] = data_store_di.ds_for_id;
|
||||
} else {
|
||||
data_store_do['for_id_random'] = null;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_access_read !== 'undefined') {
|
||||
data_store_do['access_read'] = data_store_di.ds_access_read;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_access_write !== 'undefined') {
|
||||
data_store_do['access_write'] = data_store_di.ds_access_write;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_access_delete !== 'undefined') {
|
||||
data_store_do['access_delete'] = data_store_di.ds_access_delete;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_value !== 'undefined') {
|
||||
if (data_store_di.ds_type == 'html') {
|
||||
data_store_do['text'] = data_store_di.ds_value;
|
||||
} else if (data_store_di.ds_type == 'json') {
|
||||
data_store_do['json'] = data_store_di.ds_value;
|
||||
} else if (data_store_di.ds_type == 'md') {
|
||||
data_store_do['text'] = data_store_di.ds_value;
|
||||
} else if (data_store_di.ds_type == 'sql') {
|
||||
data_store_do['text'] = data_store_di.ds_value;
|
||||
} else {
|
||||
data_store_do['text'] = data_store_di.ds_value;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_enable !== 'undefined') {
|
||||
data_store_do['enable'] = data_store_di.ds_enable;
|
||||
} else {
|
||||
data_store_do['enable'] = true;
|
||||
}
|
||||
|
||||
console.log(data_store_do);
|
||||
|
||||
if (!ae_ds_tmp.id) {
|
||||
// Create
|
||||
console.log(`ae_ Data Store Create:`, data_store_do);
|
||||
ds_submit_results = handle_create__data_store({
|
||||
obj_type: 'data_store',
|
||||
data: data_store_do
|
||||
})
|
||||
.then( function (ds_results) {
|
||||
console.log(`ae_ Data Store Create Results:`, ds_results);
|
||||
if (ds_results) {
|
||||
ae_ds_tmp.id = ds_results.data_store_id_random;
|
||||
ae_ds_tmp.updated_on = ds_results.updated_on;
|
||||
}
|
||||
return ds_results;
|
||||
})
|
||||
.finally(function (ds_val_result) {
|
||||
console.log(`ae_ ds_val_result = `, ds_val_result);
|
||||
trigger = 'load__ds__code';
|
||||
$ae_sess.ds.submit_status = 'created';
|
||||
});
|
||||
} else {
|
||||
// Update
|
||||
console.log(`ae_ Data Store Update:`, data_store_do);
|
||||
ds_submit_results = handle_update__data_store({
|
||||
obj_type: 'data_store',
|
||||
obj_id: ae_ds_tmp.id,
|
||||
data: data_store_do
|
||||
})
|
||||
.then( function (ds_results) {
|
||||
console.log(`ae_ Data Store Update Results:`, ds_results);
|
||||
if (ds_results) {
|
||||
ae_ds_tmp.updated_on = ds_results.updated_on;
|
||||
}
|
||||
return ds_results;
|
||||
// })
|
||||
// .finally(function (ds_val_result) {
|
||||
// // console.log(`ae_ ds_code_val = `, ds_val_result);
|
||||
})
|
||||
.finally(function () {
|
||||
// console.log(`ae_ ds_val_result = `, ds_val_result);
|
||||
console.log(`ae_ load__ds__code: ${ds_code} ${ds_type} ${for_type} ${for_id} ${try_cache}`);
|
||||
trigger = 'load__ds__code';
|
||||
$ae_sess.ds.submit_status = 'updated';
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
async function handle_create__data_store({
|
||||
obj_type,
|
||||
data
|
||||
}) {
|
||||
console.log('*** handle_create__data_store() ***');
|
||||
|
||||
$ae_sess.ds.create_status = 'starting';
|
||||
ae_promises.api_create__data_store_obj = api.create_ae_obj_crud({
|
||||
api_cfg: $ae_api,
|
||||
obj_type: obj_type,
|
||||
fields: data,
|
||||
key: $ae_api.api_crud_super_key,
|
||||
log_lvl: 1
|
||||
})
|
||||
.then(async function (create__obj_result) {
|
||||
if (!create__obj_result) {
|
||||
console.log('The result was null or false.');
|
||||
return false;
|
||||
}
|
||||
return create__obj_result;
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('Something went wrong.');
|
||||
console.log(error);
|
||||
return false;
|
||||
})
|
||||
.finally(function (create__obj_result) {
|
||||
$ae_sess.ds.create_status = 'finished';
|
||||
return create__obj_result;
|
||||
});
|
||||
|
||||
return ae_promises.api_create__data_store_obj;
|
||||
}
|
||||
|
||||
|
||||
async function handle_update__data_store({
|
||||
obj_type,
|
||||
obj_id,
|
||||
data
|
||||
}) {
|
||||
console.log('*** handle_update__data_store() ***');
|
||||
|
||||
$ae_sess.ds.update_status = 'starting';
|
||||
ae_promises.update__data_store_obj = api.update_ae_obj_id_crud({
|
||||
api_cfg: $ae_api,
|
||||
obj_type: obj_type,
|
||||
obj_id: obj_id,
|
||||
fields: data,
|
||||
key: $ae_api.api_crud_super_key,
|
||||
log_lvl: 1
|
||||
})
|
||||
.then(async function (update__obj_result) {
|
||||
if (!update__obj_result) {
|
||||
console.log('The result was null or false.');
|
||||
return false;
|
||||
}
|
||||
return update__obj_result;
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('Something went wrong.');
|
||||
console.log(error);
|
||||
return false;
|
||||
})
|
||||
.finally(function (update__obj_result) {
|
||||
$ae_sess.ds.update_status = 'finished';
|
||||
return update__obj_result;
|
||||
})
|
||||
|
||||
return ae_promises.update__data_store_obj;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div
|
||||
class="ae__elem__data_store relative {class_li}"
|
||||
class:hide={hide}
|
||||
>
|
||||
{#if ae_ds_tmp}
|
||||
|
||||
|
||||
{#if debug || $ae_loc.debug == 'debug'}
|
||||
|
||||
<pre>
|
||||
id: {ae_ds_tmp.id},
|
||||
code: {ae_ds_tmp.code},
|
||||
type: {ae_ds_tmp.type},
|
||||
for_type: {ae_ds_tmp.for_type},
|
||||
for_id: {ae_ds_tmp.for_id},
|
||||
access_read: {ae_ds_tmp.access_read},
|
||||
access_write: {ae_ds_tmp.access_write},
|
||||
access_delete: {ae_ds_tmp.access_delete},
|
||||
name: {ae_ds_tmp.name},
|
||||
html: {ae_ds_tmp.html},
|
||||
json: {ae_ds_tmp.json},
|
||||
md: {ae_ds_tmp.md},
|
||||
text: {ae_ds_tmp.text},
|
||||
updated_on: {ae_ds_tmp.updated_on},
|
||||
</pre>
|
||||
{/if}
|
||||
|
||||
{#if show_edit}
|
||||
<section class="edit z-50">
|
||||
<form
|
||||
class="ae__elem__data_store__form"
|
||||
on:submit|preventDefault={handle_submit_form}
|
||||
>
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
name="ds_id_random"
|
||||
value={ae_ds_tmp.id}
|
||||
/>
|
||||
|
||||
{#if $ae_loc.trusted_access}
|
||||
<label for="ds_use_account_id" class="label text-xs inline">Use Account ID
|
||||
<input
|
||||
type="checkbox"
|
||||
name="ds_use_account_id"
|
||||
class="checkbox"
|
||||
value="true"
|
||||
checked={ae_ds_tmp.account_id ? true : false}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
{#if $ae_loc.manager_access}
|
||||
<input
|
||||
type="text"
|
||||
name="ds_account_id"
|
||||
class="input max-w-48 text-xs"
|
||||
placeholder="Account ID"
|
||||
value={ae_ds_tmp.account_id}
|
||||
/>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
name="ds_code"
|
||||
class="input text-xs"
|
||||
placeholder="Data store code"
|
||||
value={ae_ds_tmp.code}
|
||||
required
|
||||
/>
|
||||
{/if}
|
||||
{#if $ae_loc.trusted_access}
|
||||
<input
|
||||
type="text"
|
||||
name="ds_name"
|
||||
class="input text-xs"
|
||||
placeholder="Data store name"
|
||||
value={ae_ds_tmp.name}
|
||||
required
|
||||
/>
|
||||
{/if}
|
||||
{#if $ae_loc.manager_access}
|
||||
<input
|
||||
type="text"
|
||||
name="ds_type"
|
||||
class="input max-w-48 text-xs"
|
||||
placeholder="Data store type (html, json, md, sql, text)"
|
||||
value={ae_ds_tmp.type}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="ds_for_type"
|
||||
class="input max-w-48 text-xs"
|
||||
placeholder="Data store For Type"
|
||||
value={ae_ds_tmp.for_type}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="ds_for_id"
|
||||
class="input max-w-48 text-xs"
|
||||
placeholder="Data store For ID"
|
||||
value={ae_ds_tmp.for_id}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="ds_access_read"
|
||||
class="input max-w-48 text-xs"
|
||||
placeholder="Access read"
|
||||
value={ae_ds_tmp.access_read}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="ds_access_write"
|
||||
class="input max-w-48 text-xs"
|
||||
placeholder="Access write"
|
||||
value={ae_ds_tmp.access_write}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="ds_access_delete"
|
||||
class="input max-w-48 text-xs"
|
||||
placeholder="Access delete"
|
||||
value={ae_ds_tmp.access_delete}
|
||||
/>
|
||||
{:else}
|
||||
Code: {ae_ds_tmp.code}
|
||||
<!-- Name: {ae_ds_tmp.name} -->
|
||||
Type: {ae_ds_tmp.type}
|
||||
{/if}
|
||||
|
||||
<!-- Handle HTML type -->
|
||||
{#if ae_ds_tmp.type == 'html' || ae_ds_tmp.type == null}
|
||||
<textarea
|
||||
name="ds_value"
|
||||
class="textarea type_html font-mono text-sm"
|
||||
cols="75"
|
||||
rows="25"
|
||||
placeholder="Enter the HTML here"
|
||||
>{ae_ds_tmp.type == 'html' && ae_ds_tmp.html ? ae_ds_tmp.html : ''}</textarea>
|
||||
<!-- Handle SQL type -->
|
||||
{:else if ae_ds_tmp.type == 'sql'}
|
||||
<textarea
|
||||
name="ds_value"
|
||||
class="textarea type_sql font-mono text-sm"
|
||||
cols="75"
|
||||
rows="25"
|
||||
placeholder="Enter the SQL here"
|
||||
>{ae_ds_tmp.type == 'sql' && ae_ds_tmp.text ? ae_ds_tmp.text : ''}</textarea>
|
||||
|
||||
<!-- Handle text type -->
|
||||
{:else if ae_ds_tmp.type == 'text'}
|
||||
<textarea
|
||||
name="ds_value"
|
||||
class="textarea type_text"
|
||||
cols="70"
|
||||
rows="10"
|
||||
placeholder="Enter the text here"
|
||||
>{ae_ds_tmp.type == 'text' ? ae_ds_tmp.text : ''}</textarea>
|
||||
{/if}
|
||||
<div class="flex gap-1 justify-center justify-evenly items-center p-1">
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn preset-tonal-warning"
|
||||
on:click={() => {
|
||||
if (confirm('Are you sure you want to delete this data store?')) {
|
||||
trigger = 'delete__ds__code';
|
||||
// $slct_trigger = 'delete__ds__code';
|
||||
}
|
||||
show_edit = false;
|
||||
show_view = true;
|
||||
}}
|
||||
>
|
||||
<span class="fas fa-trash mx-1"></span>
|
||||
Delete
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn preset-tonal-primary"
|
||||
on:click={() => {
|
||||
show_edit = false;
|
||||
show_view = true;
|
||||
}}
|
||||
>
|
||||
<span class="fas fa-times mx-1"></span>
|
||||
Close
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn preset-tonal-primary"
|
||||
disabled={ds_submit_results instanceof Promise && !ds_submit_results}
|
||||
on:click={() => {
|
||||
trigger = 'save__ds__code';
|
||||
// $slct_trigger = 'save__ds__code';
|
||||
}}
|
||||
>
|
||||
<span class="fas fa-save mx-1"></span>
|
||||
Save
|
||||
</button>
|
||||
|
||||
{#await ds_submit_results}
|
||||
<div class="modal-loading">
|
||||
<span class="fas fa-spinner fa-spin"></span>
|
||||
<span class="loading-text">
|
||||
Saving...
|
||||
</span>
|
||||
</div>
|
||||
{:then ds_submit_results}
|
||||
{#if ds_submit_results}
|
||||
<div>
|
||||
<span class="fas fa-check text-green-500"></span>
|
||||
<span class="saved-text">
|
||||
Saved
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
|
||||
<div
|
||||
class="ae_debug"
|
||||
class:hidden={!debug && $ae_loc.debug != 'debug'}
|
||||
>
|
||||
submit: {$ae_sess.ds.submit_status}
|
||||
create: {$ae_sess.ds.create_status}
|
||||
update: {$ae_sess.ds.update_status}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<!-- {#if mode == 'view'} -->
|
||||
|
||||
{#if !ae_ds_tmp.type && !ae_ds_tmp.html && !ae_ds_tmp.json && !ae_ds_tmp.md && !ae_ds_tmp.text}
|
||||
{#if $ae_loc.manager_access}
|
||||
<span class="preset-tonal-warning">No data found! Is the data store correct or new?</span>
|
||||
{:else}
|
||||
<!-- <span class="variant-soft">loading</span> -->
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if ae_ds_tmp.type == 'html' && ae_ds_tmp.html}
|
||||
{@html ae_ds_tmp.html}
|
||||
{:else if ae_ds_tmp.type == 'html'}
|
||||
{#if $ae_loc.manager_access}
|
||||
<span class="preset-tonal-warning">No HTML found! Is the data store type correct?</span>
|
||||
{:else}
|
||||
<!-- <span class="variant-soft">loading</span> -->
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if ae_ds_tmp.type == 'text' && ae_ds_tmp.text}
|
||||
{ae_ds_tmp.text}
|
||||
{:else if ae_ds_tmp.type == 'text'}
|
||||
{#if $ae_loc.manager_access}
|
||||
<span class="preset-tonal-warning">No text found! Is the data store type correct?</span>
|
||||
{:else}
|
||||
<!-- <span class="variant-soft">loading</span> -->
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="ae_btn_edit__ds btn hover:preset-tonal-warning text-xs absolute top-0 right-0 opacity-30 hover:opacity-100 transition delay-700 hover:delay-200 m-1 p-1"
|
||||
class:opacity-5={!$ae_loc.manager_access}
|
||||
class:hidden={!show_edit_btn || !$ae_loc.trusted_access}
|
||||
on:dblclick={() => {
|
||||
trigger = 'load__ds__code';
|
||||
show_edit = true;
|
||||
show_view = false;
|
||||
}}
|
||||
title="Double click to edit data store: {ds_code} with {ae_ds_tmp.account_id ? `account ID=${ae_ds_tmp.account_id}` : 'no account ID'}"
|
||||
>
|
||||
<span class="fas fa-edit mx-1"></span>
|
||||
Edit
|
||||
</button>
|
||||
|
||||
<!-- {/if} -->
|
||||
{:else}
|
||||
<!-- Nothing to see yet -->
|
||||
{/if}
|
||||
|
||||
<!-- Text:
|
||||
<pre>
|
||||
{val_text}
|
||||
</pre> -->
|
||||
|
||||
<!-- JSON:
|
||||
<pre>
|
||||
{val_json}
|
||||
</pre> -->
|
||||
|
||||
{#await ds_get_results}
|
||||
<div class="modal-loading text-xs absolute bottom-0 left-0 opacity-30 hover:opacity-100 transition delay-700 hover:delay-200">
|
||||
<span class="fas fa-spinner fa-spin"></span>
|
||||
<span class="loading-text">
|
||||
Loading...
|
||||
</span>
|
||||
</div>
|
||||
{/await}
|
||||
|
||||
<!-- {ds_loading_status} -->
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<style lang="postcss">
|
||||
/* .ae_btn_edit__ds {
|
||||
opacity: .5;
|
||||
} */
|
||||
|
||||
/* The section.edit should be above the rest of the content and centered on the page */
|
||||
section.edit {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 100;
|
||||
background-color: hsla(0, 0%, 100%, .95);
|
||||
padding: 1rem;
|
||||
border-radius: .5rem;
|
||||
box-shadow: 0 0 1rem hsla(0, 0%, 0%, .5);
|
||||
|
||||
min-width: 80%;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
875
src/lib/elements/element_data_store_v2.svelte
Normal file
875
src/lib/elements/element_data_store_v2.svelte
Normal file
@@ -0,0 +1,875 @@
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
// import { liveQuery } from "dexie"; // Use this in the future???
|
||||
import { Modal } from 'flowbite-svelte';
|
||||
|
||||
import { api } from '$lib/api';
|
||||
import { ae_loc, ae_sess, ae_api, slct, slct_trigger, ae_trig } from '$lib/ae_stores';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
import type { key_val } from '$lib/ae_stores';
|
||||
|
||||
export let log_lvl: number = 0;
|
||||
export let expire_minutes: number = 15;
|
||||
export let mount_reload_sec: number = 0;
|
||||
|
||||
export let ds_code: string;
|
||||
export let ds_name: null|string = null;
|
||||
export let ds_type: string = 'text';
|
||||
export let for_type: null|string = null;
|
||||
export let for_id: null|string = null;
|
||||
console.log(`ae_e_data_store ${ds_code} account_id=${$ae_loc.account_id} for_type=${for_type} for_id=${for_id}`);
|
||||
|
||||
// export let store: string = 'local';
|
||||
// export let display: string = 'block'; // Avoid; Use class list instead
|
||||
export let class_li: string = ''; // : string[] = [];
|
||||
export let try_cache: boolean = true;
|
||||
|
||||
export let hide: boolean = false; // Hide the entire element
|
||||
export let show_edit: boolean = false;
|
||||
export let show_edit_btn: boolean = false; // You must still be at least an admin (vs a manager).
|
||||
export let show_view: boolean = true;
|
||||
// export let show_delete_btn: boolean = false;
|
||||
|
||||
export let ds_loaded: boolean = false;
|
||||
|
||||
export let debug: boolean = false;
|
||||
|
||||
let ae_promises: key_val = {};
|
||||
// let ae_tmp: key_val = {};
|
||||
// let ae_triggers: key_val = {};
|
||||
|
||||
let ds_get_results: Promise<any>|key_val;
|
||||
export let ds_loading_status: string = 'starting';
|
||||
let ds_submit_results: Promise<any>|key_val;
|
||||
|
||||
let val_json: key_val;
|
||||
let val_html: key_val;
|
||||
let val_md: key_val;
|
||||
export let val_sql: null|key_val = null;
|
||||
let val_text: string;
|
||||
|
||||
|
||||
let ds_code_obj =
|
||||
{
|
||||
id: null,
|
||||
account_id: null,
|
||||
code: ds_code,
|
||||
name: ds_name,
|
||||
type: ds_type,
|
||||
for_type: null, // for_type
|
||||
for_id: null, // for_id
|
||||
access_read: null, // 'super', 'manager', 'administrator', 'trusted', 'authenticated', 'anonymous'
|
||||
access_write: null, // 'super', 'manager', 'administrator', 'trusted', 'authenticated', 'anonymous'
|
||||
access_delete: null, // 'super', 'manager', 'administrator', 'trusted', 'authenticated', 'anonymous'
|
||||
html: null,
|
||||
json: null,
|
||||
md: null,
|
||||
text: null,
|
||||
updated_on: null,
|
||||
chk_account_id: null,
|
||||
};
|
||||
|
||||
let ae_ds_tmp: key_val;
|
||||
if (browser && try_cache && localStorage.getItem(`ae_ds__${ds_code}`)) {
|
||||
if (log_lvl) {
|
||||
console.log(`ae_e_data_store: Found cached data for ${ds_code}`);
|
||||
}
|
||||
ae_ds_tmp = JSON.parse(localStorage.getItem(`ae_ds__${ds_code}`) ?? '{}');
|
||||
if (log_lvl) {
|
||||
console.log(`ae_e_data_store cached: ${ds_code} account_id=${$ae_loc.account_id}`, ae_ds_tmp);
|
||||
}
|
||||
} else {
|
||||
ae_ds_tmp = ds_code_obj;
|
||||
ds_loading_status = 'loading';
|
||||
}
|
||||
|
||||
$ae_sess.ds.submit_status = null;
|
||||
$ae_sess.ds.create_status = null;
|
||||
$ae_sess.ds.update_status = null;
|
||||
|
||||
let trigger: null|string = null;
|
||||
|
||||
// This is a quick check to make sure the data store is not stale. If it is, then we need to trigger a reload.
|
||||
if (browser && ae_ds_tmp && ae_ds_tmp.loaded_on && ae_ds_tmp.chk_account_id == $ae_loc.account_id) {
|
||||
console.log(`ae_e_data_store ${ds_code} loaded_on: ${ae_ds_tmp.loaded_on}`);
|
||||
let loaded_on = new Date(ae_ds_tmp.loaded_on);
|
||||
let now = new Date();
|
||||
let diff = now.getTime() - loaded_on.getTime();
|
||||
let diff_minutes = diff / (1000 * 60);
|
||||
if (diff_minutes > expire_minutes) {
|
||||
console.log(`ae_e_data_store: Data Store ${ds_code} stale. Last loaded on: ${loaded_on.toISOString()}`);
|
||||
// Wait for random number of milliseconds to avoid all data stores being reloaded at the same time.
|
||||
let random_ms = Math.floor(Math.random() * 500);
|
||||
console.log(`ae_e_data_store: Random number of milliseconds: ${random_ms}`);
|
||||
setTimeout(() => {
|
||||
trigger = 'load__ds__code';
|
||||
}, random_ms);
|
||||
}
|
||||
} else if (browser) {
|
||||
console.log('ae_e_data_store: No loaded_on date found and or the account_id check failed. Need to trigger reload.');
|
||||
trigger = 'load__ds__code';
|
||||
}
|
||||
|
||||
// This is a secondary check... The account_id should either be null or match the current account_id.
|
||||
if (!ae_ds_tmp || !ae_ds_tmp.account_id === null || $ae_loc.account_id == $ae_loc.account_id) {
|
||||
trigger = 'load__ds__code';
|
||||
}
|
||||
|
||||
if (browser && mount_reload_sec) {
|
||||
// Wait for random number of milliseconds to avoid all data stores being reloaded at the same time.
|
||||
let random_ms = Math.floor(Math.random() * mount_reload_sec * 1000);
|
||||
if (log_lvl) {
|
||||
console.log(`ae_e_data_store: Random number of milliseconds: ${random_ms}`);
|
||||
}
|
||||
setTimeout(() => {
|
||||
trigger = 'load__ds__code';
|
||||
}, random_ms);
|
||||
}
|
||||
|
||||
$: if (trigger == 'load__ds__code' && ds_code && ds_type) {
|
||||
if (log_lvl) {
|
||||
console.log(`ae_e_data_store: ae_ load__ds__code: ${ds_code} ds_type=${ds_type} for_type=${for_type} for_id=${for_id} ${try_cache}`);
|
||||
}
|
||||
|
||||
trigger = null;
|
||||
|
||||
load_data_store({
|
||||
code: ds_code,
|
||||
type: ds_type,
|
||||
for_type: for_type,
|
||||
for_id: for_id,
|
||||
try_cache: try_cache
|
||||
});
|
||||
}
|
||||
|
||||
async function load_data_store(
|
||||
{
|
||||
code,
|
||||
type = 'text',
|
||||
for_type = null,
|
||||
for_id = null,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
code: string,
|
||||
type?: string,
|
||||
for_type?: string|null,
|
||||
for_id?: string|null,
|
||||
try_cache?: boolean,
|
||||
log_lvl?: number
|
||||
}
|
||||
) {
|
||||
|
||||
// let ds_code_val = await api.get_data_store_obj_w_code({
|
||||
ds_get_results = api.get_data_store_obj_w_code({
|
||||
api_cfg: $ae_api,
|
||||
data_store_code: code,
|
||||
data_type: type,
|
||||
log_lvl: log_lvl
|
||||
})
|
||||
.then( function (ds_results) {
|
||||
// console.log(`ae_ Data Store ${code} = `, ds_results);
|
||||
if (ds_results) {
|
||||
if (log_lvl) {
|
||||
console.log(`ae_e_data_store: Got a result for code ${code}`);
|
||||
}
|
||||
if (!ds_results.data_store_id_random) {
|
||||
console.log('Something went wrong? No data store ID found.');
|
||||
return false;
|
||||
}
|
||||
|
||||
ds_loaded = true;
|
||||
ds_loading_status = 'loaded';
|
||||
|
||||
// Set the loaded_on datetime to the current time for reference later. This will be used to determine if the data store is stale.
|
||||
ae_ds_tmp.loaded_on = new Date().toISOString();
|
||||
// Set the chk_account_id as a backup check to make sure the data store belongs to the account for the current site. This should not be needed, but here we are...
|
||||
ae_ds_tmp.chk_account_id = $ae_loc.account_id;
|
||||
|
||||
ae_ds_tmp.id = ds_results.data_store_id_random;
|
||||
ae_ds_tmp.account_id = ds_results.account_id_random;
|
||||
ae_ds_tmp.code = ds_results.code; // This will overwrite whatever was passed in.
|
||||
ae_ds_tmp.name = ds_results.name;
|
||||
ae_ds_tmp.type = ds_results.type; // This will overwrite whatever was passed in.
|
||||
if (type == 'html') {
|
||||
ae_ds_tmp.html = ds_results.text;
|
||||
val_html = ds_results.text;
|
||||
return ds_results.html;
|
||||
} else if (type == 'json') {
|
||||
ae_ds_tmp.json = ds_results.json;
|
||||
val_json = ds_results.json;
|
||||
return ds_results.json;
|
||||
} else if (type == 'md') {
|
||||
ae_ds_tmp.text = ds_results.text;
|
||||
val_md = ds_results.text;
|
||||
return ds_results.text;
|
||||
} else if (type == 'sql') {
|
||||
ae_ds_tmp.text = ds_results.text;
|
||||
val_sql = ds_results.text;
|
||||
return ds_results.text;
|
||||
} else {
|
||||
ae_ds_tmp.text = ds_results.text;
|
||||
val_text = ds_results.text;
|
||||
return ds_results.text;
|
||||
}
|
||||
} else {
|
||||
ds_loaded = false;
|
||||
ds_loading_status = 'not found';
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
if (browser && try_cache) {
|
||||
if (log_lvl) {
|
||||
console.log(`ae_e_data_store: Caching data store ${code} in localStorage.`);
|
||||
}
|
||||
localStorage.setItem(`ae_ds__${code}`, JSON.stringify(ae_ds_tmp));
|
||||
} else {
|
||||
// console.log(`ae_e_data_store: Not in browser. Not caching.`);
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(`Something went wrong. for code ${code}`);
|
||||
console.log(error);
|
||||
ds_loading_status = 'error';
|
||||
return false;
|
||||
});
|
||||
// .finally(function (ds_val_result) {
|
||||
// console.log(`ae_ ds_code_val = `, ds_val_result);
|
||||
|
||||
// ae_ds_loc.set(ds_val_result);
|
||||
// localStorage.setItem(ds_code, ds_val_result);
|
||||
// sessionStorage.setItem(ds_code, ds_val_result);
|
||||
|
||||
// return ds_val_result;
|
||||
// });
|
||||
// console.log(`ae_ ds_code_val = `, ds_code_val);
|
||||
}
|
||||
|
||||
|
||||
async function handle_submit_form(event: any) {
|
||||
// console.log('*** handle_submit_form() ***');
|
||||
|
||||
$ae_sess.ds.submit_status = 'processing';
|
||||
|
||||
|
||||
// Data in
|
||||
let form_data = new FormData(event.target);
|
||||
// console.log(form_data);
|
||||
|
||||
let data_store_di: key_val = ae_util.extract_prefixed_form_data({prefix: null, form_data: form_data, trim_values: true, bool_tf_str: true, log_lvl: 0});
|
||||
// console.log(data_store_di);
|
||||
|
||||
// Data out
|
||||
let data_store_do: key_val = {};
|
||||
|
||||
if (typeof data_store_di.ds_id_random !== 'undefined') {
|
||||
data_store_do['data_store_id_random'] = data_store_di.ds_id_random;
|
||||
}
|
||||
|
||||
// if (!$slct.data_store_id) {
|
||||
if (!ae_ds_tmp.id) {
|
||||
data_store_do['account_id_random'] = $ae_loc.account_id;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_account_id !== 'undefined' && data_store_di.ds_account_id && data_store_di.ds_use_account_id) {
|
||||
data_store_do['account_id_random'] = data_store_di.ds_account_id;
|
||||
} else if (data_store_di.ds_use_account_id && $ae_loc.account_id) {
|
||||
data_store_do['account_id_random'] = $ae_loc.account_id;
|
||||
} else {
|
||||
data_store_do['account_id_random'] = null;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_code !== 'undefined') {
|
||||
data_store_do['code'] = data_store_di.ds_code;
|
||||
} else {
|
||||
data_store_do['code'] = ds_code;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_name !== 'undefined') {
|
||||
data_store_do['name'] = data_store_di.ds_name;
|
||||
} else {
|
||||
data_store_do['name'] = ds_name;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_type !== 'undefined') {
|
||||
data_store_do['type'] = data_store_di.ds_type;
|
||||
} else {
|
||||
data_store_do['type'] = ds_type;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_for_type !== 'undefined' && data_store_di.ds_for_type) {
|
||||
data_store_do['for_type'] = data_store_di.ds_for_type;
|
||||
} else {
|
||||
data_store_do['for_type'] = null;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_for_id !== 'undefined' && data_store_di.ds_for_id) {
|
||||
data_store_do['for_id_random'] = data_store_di.ds_for_id;
|
||||
} else {
|
||||
data_store_do['for_id_random'] = null;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_access_read !== 'undefined') {
|
||||
data_store_do['access_read'] = data_store_di.ds_access_read;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_access_write !== 'undefined') {
|
||||
data_store_do['access_write'] = data_store_di.ds_access_write;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_access_delete !== 'undefined') {
|
||||
data_store_do['access_delete'] = data_store_di.ds_access_delete;
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_value !== 'undefined') {
|
||||
if (data_store_di.ds_type == 'html') {
|
||||
data_store_do['text'] = data_store_di.ds_value;
|
||||
} else if (data_store_di.ds_type == 'json') {
|
||||
data_store_do['json'] = data_store_di.ds_value;
|
||||
} else if (data_store_di.ds_type == 'md') {
|
||||
data_store_do['text'] = data_store_di.ds_value;
|
||||
} else if (data_store_di.ds_type == 'sql') {
|
||||
data_store_do['text'] = data_store_di.ds_value;
|
||||
} else {
|
||||
data_store_do['text'] = data_store_di.ds_value;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof data_store_di.ds_enable !== 'undefined') {
|
||||
data_store_do['enable'] = data_store_di.ds_enable;
|
||||
} else {
|
||||
data_store_do['enable'] = true;
|
||||
}
|
||||
|
||||
if (log_lvl) {
|
||||
console.log(`ae_ Data Store data out:`, data_store_do);
|
||||
}
|
||||
|
||||
if (!ae_ds_tmp.id) {
|
||||
// Create
|
||||
console.log(`ae_ Data Store Create:`, data_store_do);
|
||||
ds_submit_results = handle_create__data_store({
|
||||
obj_type: 'data_store',
|
||||
data: data_store_do
|
||||
})
|
||||
.then( function (ds_results) {
|
||||
console.log(`ae_ Data Store Create Results:`, ds_results);
|
||||
if (ds_results) {
|
||||
ae_ds_tmp.id = ds_results.data_store_id_random;
|
||||
ae_ds_tmp.updated_on = ds_results.updated_on;
|
||||
}
|
||||
return ds_results;
|
||||
})
|
||||
.finally(function (ds_val_result) {
|
||||
console.log(`ae_ ds_val_result = `, ds_val_result);
|
||||
trigger = 'load__ds__code';
|
||||
$ae_sess.ds.submit_status = 'created';
|
||||
});
|
||||
} else {
|
||||
// Update
|
||||
console.log(`ae_ Data Store Update:`, data_store_do);
|
||||
ds_submit_results = handle_update__data_store({
|
||||
obj_type: 'data_store',
|
||||
obj_id: ae_ds_tmp.id,
|
||||
data: data_store_do
|
||||
})
|
||||
.then( function (ds_results) {
|
||||
console.log(`ae_ Data Store Update Results:`, ds_results);
|
||||
if (ds_results) {
|
||||
ae_ds_tmp.updated_on = ds_results.updated_on;
|
||||
}
|
||||
return ds_results;
|
||||
// })
|
||||
// .finally(function (ds_val_result) {
|
||||
// // console.log(`ae_ ds_code_val = `, ds_val_result);
|
||||
})
|
||||
.finally(function () {
|
||||
// console.log(`ae_ ds_val_result = `, ds_val_result);
|
||||
console.log(`ae_ load__ds__code: ${ds_code} ${ds_type} ${for_type} ${for_id} ${try_cache}`);
|
||||
trigger = 'load__ds__code';
|
||||
$ae_sess.ds.submit_status = 'updated';
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
async function handle_create__data_store(
|
||||
{
|
||||
obj_type,
|
||||
data
|
||||
} : {
|
||||
obj_type: string,
|
||||
data: key_val
|
||||
}
|
||||
) {
|
||||
if (log_lvl) {
|
||||
console.log('*** handle_create__data_store() ***');
|
||||
}
|
||||
|
||||
$ae_sess.ds.create_status = 'starting';
|
||||
ae_promises.api_create__data_store_obj = api.create_ae_obj_crud({
|
||||
api_cfg: $ae_api,
|
||||
obj_type: obj_type,
|
||||
fields: data,
|
||||
key: $ae_api.api_crud_super_key,
|
||||
log_lvl: log_lvl
|
||||
})
|
||||
.then(async function (create__obj_result) {
|
||||
if (!create__obj_result) {
|
||||
console.log('The result was null or false.');
|
||||
return false;
|
||||
}
|
||||
return create__obj_result;
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('Something went wrong.');
|
||||
console.log(error);
|
||||
return false;
|
||||
})
|
||||
.finally(function (create__obj_result) {
|
||||
$ae_sess.ds.create_status = 'finished';
|
||||
return create__obj_result;
|
||||
});
|
||||
|
||||
return ae_promises.api_create__data_store_obj;
|
||||
}
|
||||
|
||||
|
||||
async function handle_update__data_store(
|
||||
{
|
||||
obj_type,
|
||||
obj_id,
|
||||
data
|
||||
} : {
|
||||
obj_type: string,
|
||||
obj_id: string,
|
||||
data: key_val
|
||||
}
|
||||
) {
|
||||
if (log_lvl) {
|
||||
console.log('*** handle_update__data_store() ***');
|
||||
}
|
||||
|
||||
$ae_sess.ds.update_status = 'starting';
|
||||
ae_promises.update__data_store_obj = api.update_ae_obj_id_crud({
|
||||
api_cfg: $ae_api,
|
||||
obj_type: obj_type,
|
||||
obj_id: obj_id,
|
||||
fields: data,
|
||||
key: $ae_api.api_crud_super_key,
|
||||
log_lvl: log_lvl
|
||||
})
|
||||
.then(async function (update__obj_result) {
|
||||
if (!update__obj_result) {
|
||||
console.log('The result was null or false.');
|
||||
return false;
|
||||
}
|
||||
return update__obj_result;
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('Something went wrong.');
|
||||
console.log(error);
|
||||
return false;
|
||||
})
|
||||
.finally(function (update__obj_result) {
|
||||
$ae_sess.ds.update_status = 'finished';
|
||||
return update__obj_result;
|
||||
})
|
||||
|
||||
return ae_promises.update__data_store_obj;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div
|
||||
class="ae__elem__data_store relative {class_li}"
|
||||
class:hidden={hide}
|
||||
>
|
||||
{#if ae_ds_tmp}
|
||||
|
||||
|
||||
{#if debug || $ae_loc.debug == 'debug'}
|
||||
|
||||
<pre>
|
||||
id: {ae_ds_tmp.id},
|
||||
code: {ae_ds_tmp.code},
|
||||
type: {ae_ds_tmp.type},
|
||||
for_type: {ae_ds_tmp.for_type},
|
||||
for_id: {ae_ds_tmp.for_id},
|
||||
access_read: {ae_ds_tmp.access_read},
|
||||
access_write: {ae_ds_tmp.access_write},
|
||||
access_delete: {ae_ds_tmp.access_delete},
|
||||
name: {ae_ds_tmp.name},
|
||||
html: {ae_ds_tmp.html},
|
||||
json: {ae_ds_tmp.json},
|
||||
md: {ae_ds_tmp.md},
|
||||
text: {ae_ds_tmp.text},
|
||||
updated_on: {ae_ds_tmp.updated_on},
|
||||
</pre>
|
||||
|
||||
{/if}
|
||||
|
||||
<!-- {#if show_edit} -->
|
||||
<!-- <section class="edit z-50"> -->
|
||||
|
||||
<!-- Main modal -->
|
||||
<Modal
|
||||
title="{ae_ds_tmp.name} - {ae_ds_tmp.code}"
|
||||
bind:open={show_edit}
|
||||
autoclose={false}
|
||||
size="xl"
|
||||
placement="top-center"
|
||||
class="
|
||||
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 flex flex-col
|
||||
mx-auto divide-y
|
||||
w-full
|
||||
max-w-6xl
|
||||
"
|
||||
|
||||
>
|
||||
|
||||
<form
|
||||
class="flex flex-col gap-1"
|
||||
on:submit|preventDefault={handle_submit_form}
|
||||
>
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
name="ds_id_random"
|
||||
value={ae_ds_tmp.id}
|
||||
/>
|
||||
|
||||
<div class="flex flex-row gap-1">
|
||||
{#if $ae_loc.trusted_access}
|
||||
<label for="ds_use_account_id" class="label text-xs inline">Use Account ID
|
||||
<input
|
||||
type="checkbox"
|
||||
name="ds_use_account_id"
|
||||
class="checkbox"
|
||||
value="true"
|
||||
checked={ae_ds_tmp.account_id ? true : false}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
{#if $ae_loc.manager_access}
|
||||
<input
|
||||
type="text"
|
||||
name="ds_account_id"
|
||||
class="input max-w-48 text-xs"
|
||||
placeholder="Account ID"
|
||||
value={ae_ds_tmp.account_id}
|
||||
/>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
name="ds_code"
|
||||
class="input text-xs"
|
||||
placeholder="Data store code"
|
||||
value={ae_ds_tmp.code}
|
||||
required
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-1">
|
||||
{#if $ae_loc.trusted_access}
|
||||
<input
|
||||
type="text"
|
||||
name="ds_name"
|
||||
class="input text-xs"
|
||||
placeholder="Data store name"
|
||||
value={ae_ds_tmp.name}
|
||||
required
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-1">
|
||||
{#if $ae_loc.manager_access}
|
||||
<input
|
||||
type="text"
|
||||
name="ds_type"
|
||||
class="input max-w-48 text-xs"
|
||||
placeholder="Data store type (html, json, md, sql, text)"
|
||||
value={ae_ds_tmp.type}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="ds_for_type"
|
||||
class="input max-w-48 text-xs"
|
||||
placeholder="Data store For Type"
|
||||
value={ae_ds_tmp.for_type}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="ds_for_id"
|
||||
class="input max-w-48 text-xs"
|
||||
placeholder="Data store For ID"
|
||||
value={ae_ds_tmp.for_id}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="ds_access_read"
|
||||
class="input max-w-48 text-xs"
|
||||
placeholder="Access read"
|
||||
value={ae_ds_tmp.access_read}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="ds_access_write"
|
||||
class="input max-w-48 text-xs"
|
||||
placeholder="Access write"
|
||||
value={ae_ds_tmp.access_write}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="ds_access_delete"
|
||||
class="input max-w-48 text-xs"
|
||||
placeholder="Access delete"
|
||||
value={ae_ds_tmp.access_delete}
|
||||
/>
|
||||
{:else}
|
||||
Code: {ae_ds_tmp.code}
|
||||
<!-- Name: {ae_ds_tmp.name} -->
|
||||
Type: {ae_ds_tmp.type}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<!-- Handle HTML type -->
|
||||
{#if ae_ds_tmp.type == 'html' || ae_ds_tmp.type == null}
|
||||
<textarea
|
||||
name="ds_value"
|
||||
class="textarea type_html font-mono text-sm"
|
||||
cols="75"
|
||||
rows="25"
|
||||
placeholder="Enter the HTML here"
|
||||
>{ae_ds_tmp.type == 'html' && ae_ds_tmp.html ? ae_ds_tmp.html : ''}</textarea>
|
||||
<!-- Handle SQL type -->
|
||||
{:else if ae_ds_tmp.type == 'sql'}
|
||||
<textarea
|
||||
name="ds_value"
|
||||
class="textarea type_sql font-mono text-sm"
|
||||
cols="75"
|
||||
rows="25"
|
||||
placeholder="Enter the SQL here"
|
||||
>{ae_ds_tmp.type == 'sql' && ae_ds_tmp.text ? ae_ds_tmp.text : ''}</textarea>
|
||||
|
||||
<!-- Handle text type -->
|
||||
{:else if ae_ds_tmp.type == 'text'}
|
||||
<textarea
|
||||
name="ds_value"
|
||||
class="textarea type_text"
|
||||
cols="70"
|
||||
rows="10"
|
||||
placeholder="Enter the text here"
|
||||
>{ae_ds_tmp.type == 'text' ? ae_ds_tmp.text : ''}</textarea>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-1 justify-center justify-evenly items-center">
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn preset-tonal-warning"
|
||||
on:click={() => {
|
||||
if (confirm('Are you sure you want to delete this data store?')) {
|
||||
trigger = 'delete__ds__code';
|
||||
// $slct_trigger = 'delete__ds__code';
|
||||
}
|
||||
show_edit = false;
|
||||
show_view = true;
|
||||
}}
|
||||
>
|
||||
<span class="fas fa-trash mx-1"></span>
|
||||
Delete
|
||||
</button>
|
||||
|
||||
<!-- <button
|
||||
type="button"
|
||||
class="btn variant-soft-primary"
|
||||
on:click={() => {
|
||||
show_edit = false;
|
||||
show_view = true;
|
||||
}}
|
||||
>
|
||||
<span class="fas fa-times mx-1"></span>
|
||||
Close
|
||||
</button> -->
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn preset-tonal-primary border border-primary-500"
|
||||
disabled={ds_submit_results instanceof Promise && !ds_submit_results}
|
||||
on:click={() => {
|
||||
trigger = 'save__ds__code';
|
||||
// $slct_trigger = 'save__ds__code';
|
||||
}}
|
||||
>
|
||||
<span class="fas fa-save mx-1"></span>
|
||||
Save
|
||||
</button>
|
||||
|
||||
{#await ds_submit_results}
|
||||
<div class="modal-loading">
|
||||
<span class="fas fa-spinner fa-spin"></span>
|
||||
<span class="loading-text">
|
||||
Saving...
|
||||
</span>
|
||||
</div>
|
||||
{:then ds_submit_results}
|
||||
{#if ds_submit_results}
|
||||
<div>
|
||||
<span class="fas fa-check text-green-500"></span>
|
||||
<span class="saved-text">
|
||||
Saved
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
|
||||
<div
|
||||
class="ae_debug"
|
||||
class:hidden={!debug && $ae_loc.debug != 'debug'}
|
||||
>
|
||||
submit: {$ae_sess.ds.submit_status}
|
||||
create: {$ae_sess.ds.create_status}
|
||||
update: {$ae_sess.ds.update_status}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<div class="text-center w-full">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
console.log('Close modal edit data store.');
|
||||
show_edit = false;
|
||||
show_view = true;
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-warning hover:preset-tonal-warning border border-warning-500"
|
||||
>
|
||||
<span class="fas fa-times mx-1"></span>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
</Modal>
|
||||
|
||||
<!-- </section> -->
|
||||
<!-- {/if} -->
|
||||
|
||||
<!-- {#if mode == 'view'} -->
|
||||
|
||||
{#if !ae_ds_tmp.type && !ae_ds_tmp.html && !ae_ds_tmp.json && !ae_ds_tmp.md && !ae_ds_tmp.text}
|
||||
{#if $ae_loc.manager_access}
|
||||
<span class="preset-tonal-warning">No data found! Is the data store correct or new?</span>
|
||||
{:else}
|
||||
<!-- <span class="variant-soft">loading</span> -->
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if ae_ds_tmp.type == 'html' && ae_ds_tmp.html}
|
||||
{@html ae_ds_tmp.html}
|
||||
{:else if ae_ds_tmp.type == 'html'}
|
||||
{#if $ae_loc.manager_access}
|
||||
<span class="preset-tonal-warning">No HTML found! Is the data store type correct?</span>
|
||||
{:else}
|
||||
<!-- <span class="variant-soft">loading</span> -->
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if ae_ds_tmp.type == 'text' && ae_ds_tmp.text}
|
||||
{ae_ds_tmp.text}
|
||||
{:else if ae_ds_tmp.type == 'text'}
|
||||
{#if $ae_loc.manager_access}
|
||||
<span class="preset-tonal-warning">No text found! Is the data store type correct?</span>
|
||||
{:else}
|
||||
<!-- <span class="variant-soft">loading</span> -->
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="ae_btn_edit__ds btn hover:preset-tonal-warning text-xs absolute top-0 right-0 opacity-30 hover:opacity-100 transition delay-700 hover:delay-200 p-1 z-50"
|
||||
class:opacity-5={!$ae_loc.manager_access}
|
||||
class:hidden={!(
|
||||
($ae_loc.manager_access && $ae_loc.edit_mode)
|
||||
||
|
||||
(show_edit_btn && $ae_loc.administrator_access && $ae_loc.edit_mode)
|
||||
)}
|
||||
on:dblclick={() => {
|
||||
trigger = 'load__ds__code';
|
||||
show_edit = true;
|
||||
show_view = false;
|
||||
}}
|
||||
title="Double click to edit data store: {ds_code} with {ae_ds_tmp.account_id ? `account ID=${ae_ds_tmp.account_id}` : 'no account ID'}"
|
||||
>
|
||||
<span class="fas fa-edit mx-1"></span>
|
||||
Edit
|
||||
</button>
|
||||
|
||||
<!-- {/if} -->
|
||||
{:else}
|
||||
<!-- Nothing to see yet -->
|
||||
{/if}
|
||||
|
||||
<!-- Text:
|
||||
<pre>
|
||||
{val_text}
|
||||
</pre> -->
|
||||
|
||||
<!-- JSON:
|
||||
<pre>
|
||||
{val_json}
|
||||
</pre> -->
|
||||
|
||||
{#await ds_get_results}
|
||||
<div class="modal-loading text-xs absolute bottom-0 left-0 opacity-30 hover:opacity-100 transition delay-700 hover:delay-200">
|
||||
<span class="fas fa-spinner fa-spin"></span>
|
||||
<span class="loading-text">
|
||||
Loading...
|
||||
</span>
|
||||
</div>
|
||||
{/await}
|
||||
|
||||
<!-- {ds_loading_status} -->
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<style lang="postcss">
|
||||
/* .ae_btn_edit__ds {
|
||||
opacity: .5;
|
||||
} */
|
||||
|
||||
/* The section.edit should be above the rest of the content and centered on the page */
|
||||
/*
|
||||
section.edit {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 100;
|
||||
background-color: hsla(0, 0%, 100%, .95);
|
||||
padding: 1rem;
|
||||
border-radius: .5rem;
|
||||
box-shadow: 0 0 1rem hsla(0, 0%, 0%, .5);
|
||||
|
||||
min-width: 80%;
|
||||
}
|
||||
*/
|
||||
|
||||
/* .hide {
|
||||
display: none;
|
||||
} */
|
||||
</style>
|
||||
414
src/lib/elements/element_input_file.svelte
Normal file
414
src/lib/elements/element_input_file.svelte
Normal file
@@ -0,0 +1,414 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, onMount, tick } from 'svelte';
|
||||
|
||||
import type { key_val } from '$lib/ae_stores';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
// 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/ae_stores';
|
||||
|
||||
export let element_id = 'svelte_input_file_element';
|
||||
export let input_name = 'file_list';
|
||||
export let container_class_li: string[] = [];
|
||||
export let input_class_li: string[] = ['file_drop_area'];
|
||||
export let table_class_li: string[] = ['table', 'table-sm', 'table-striped', '' , 'text-sm'];
|
||||
|
||||
export let multiple: boolean = true;
|
||||
export let required: boolean = true;
|
||||
export let accept: string = 'audio/*, image/*, video/*, .bak, .cfg, .css, .csv, .doc, .docx, .gz, .htm, .html, .ini, .iso, .j2, .json, .key, .keynote, .md, .pdf, .ppt, .pptx, .rar, .rtf, .sql, .svelte, ttf, .txt, .xls, .xlsx, .xz, .zip, .bin, .dmg, .exe, .js, .msi, .php, .py, .sh';
|
||||
export let untrusted_extension_list = ['bin', 'dmg', 'exe', 'js', 'msi', 'php', 'py', 'sh'];
|
||||
export let legacy_extension_list = ['avi', 'doc', 'ppt', 'xls', 'wmv'];
|
||||
export let use_selected_file_table = true;
|
||||
|
||||
export let file_list_status: null|string = null;
|
||||
export let processed_file_list: any[] = [];
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let input_file_list: any = null;
|
||||
let input_file_list_processed: any[] = [];
|
||||
|
||||
onMount(() => {
|
||||
console.log('** Element Mounted: ** Element Input File');
|
||||
});
|
||||
|
||||
|
||||
$: if (input_file_list) {
|
||||
console.log(input_file_list);
|
||||
|
||||
process_file_list(input_file_list)
|
||||
.then(function (result) {
|
||||
// console.log(result);
|
||||
|
||||
if (!result || !result.length) {
|
||||
file_list_status = 'none';
|
||||
}
|
||||
|
||||
// Save the results to the file upload list to be displayed as a table.
|
||||
input_file_list_processed = result; // Includes file hash
|
||||
|
||||
dispatch(
|
||||
'input_file_list_updated',
|
||||
{
|
||||
element_id: element_id,
|
||||
input_file_list: input_file_list,
|
||||
input_file_list_processed: result, // Includes file hash
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function process_file_list(file_list) {
|
||||
console.log('*** process_file_list() ***');
|
||||
|
||||
file_list_status = 'processing';
|
||||
processed_file_list = [];
|
||||
|
||||
if (!file_list) {
|
||||
// file_list_processed = null;
|
||||
file_list_status = 'none';
|
||||
// await tick();
|
||||
return processed_file_list;
|
||||
}
|
||||
|
||||
// const forLoop = async _ => {
|
||||
// console.log('*** Start ***');
|
||||
|
||||
// for (let [i, file_item] of file_list.entries()) { // Not sure why this does not work???
|
||||
for await (const [i, file_item] of Array.prototype.entries.call(file_list)) {
|
||||
console.log(i, file_item);
|
||||
|
||||
// NOTE: The file list is readonly. The filenames can not be changed here.
|
||||
if (file_item.name.endsWith('.odpmac') || file_item.name.endsWith('.odpwin') || file_item.name.endsWith('.pptmac') || file_item.name.endsWith('.pptwin') || file_item.name.endsWith('.pptxmac') || file_item.name.endsWith('.pptxwin')) {
|
||||
console.log('This file extension may need to be fixed? API upload will take care of it.');
|
||||
// file_item.name = file_item.name.replace('.odpwin', '.odp');
|
||||
}
|
||||
|
||||
let file_data: key_val = {};
|
||||
|
||||
let filename = file_item.name;
|
||||
// console.log(filename);
|
||||
file_data['filename'] = filename;
|
||||
|
||||
let guessed_extension = ae_util.guess_file_extension(filename);
|
||||
file_data['guessed_extension'] = guessed_extension;
|
||||
|
||||
file_data['type'] = file_item.type;
|
||||
|
||||
let modified_date = new Date(file_item.lastModified);
|
||||
file_data['modified_date'] = modified_date;
|
||||
let modified_datetime_string = ae_util.iso_datetime_formatter(modified_date, 'datetime_medium');
|
||||
file_data['modified_datetime_string'] = modified_datetime_string;
|
||||
|
||||
let file_size_bytes = file_item.size;
|
||||
file_data['file_size_bytes'] = file_size_bytes;
|
||||
let file_size_string = ae_util.format_bytes(file_item.size, 2);
|
||||
file_data['file_size_string'] = file_size_string;
|
||||
|
||||
// // NOTE: Calculate the hash of the file before upload. Check if this exact file has already been uploaded.
|
||||
// let file_reader = new FileReader();
|
||||
// file_reader.onload = async function() {
|
||||
|
||||
// const hash_buffer = crypto.subtle.digest('SHA-256', file_reader.result);
|
||||
// let hash_hex_test = hash_buffer.then(async function (result_buffer) {
|
||||
// const hash_array = Array.from(new Uint8Array(result_buffer)); // convert buffer to byte array
|
||||
// const hash_hex = hash_array.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
// console.log(`File hash hex? ${hash_hex}`);
|
||||
// file_data['hash_sha256'] = hash_hex;
|
||||
// return hash_hex;
|
||||
// })
|
||||
// .catch(function (error) {
|
||||
// console.log('Something went wrong?', error);
|
||||
// });
|
||||
|
||||
// return hash_hex_test;
|
||||
|
||||
// // file_data['hash_hex_test'] = hash_hex_test;
|
||||
|
||||
// // const hash_buffer = await crypto.subtle.digest('SHA-256', file_reader.result);
|
||||
// // let hash_str = await new TextDecoder().decode(hash_buffer);
|
||||
// // console.log(`File hash string? ${hash_str}`);
|
||||
// // file_data['hash_str'] = hash_str;
|
||||
|
||||
// // const hash_array = Array.from(new Uint8Array(hash_buffer)); // convert buffer to byte array
|
||||
// // const hash_hex = hash_array.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
// // console.log(`File hash hex? ${hash_hex}`);
|
||||
|
||||
// // file_data['hash_sha256'] = hash_hex;
|
||||
// // return hash_hex_test;
|
||||
// // return hash_hex;
|
||||
// }
|
||||
|
||||
// // file_reader.then(function (result) {
|
||||
// // console.log(`File hash hex? ${result}`);
|
||||
// // return hash_hex;
|
||||
// // })
|
||||
// // .catch(function (error) {
|
||||
// // console.log('No results returned or failed.', error);
|
||||
// // });
|
||||
// // file_data['hash_sha256'] = file_reader.readAsArrayBuffer(file_item);
|
||||
|
||||
|
||||
|
||||
let warning_untrusted_extension = false;
|
||||
let warning_legacy_extension = false;
|
||||
let warning_size = false;
|
||||
let warning_message = null;
|
||||
|
||||
if (untrusted_extension_list.includes(guessed_extension)) {
|
||||
console.log('This is an untrusted extension. Going to warn.')
|
||||
warning_untrusted_extension = true;
|
||||
warning_message = 'It appears this an untrusted file type and is likely meant to be an executable or installable application. It is <strong>strongly</strong> recommended that this file not be used.';
|
||||
|
||||
} else if (legacy_extension_list.includes(guessed_extension)) {
|
||||
console.log('This is a legacy extension. Going to warn.')
|
||||
warning_legacy_extension = true;
|
||||
if (guessed_extension == 'ppt') {
|
||||
warning_message = 'It appears this is a legacy PowerPoint file and has not been officially supported since Office PowerPoint 2003. This file is known to have issues and may not work well. It is <strong>strongly</strong> recommended that this file be saved using the modern PPTX format.';
|
||||
} else if (guessed_extension == 'avi') {
|
||||
warning_message = 'It appears this is a video file using the AVI format. It is <strong>strongly</strong> recommended that this file be re-saved as an MP4, MOV, MKV, or MPG/MPEG. The file will also likely be much smaller.';
|
||||
} else if (guessed_extension == 'wmv') {
|
||||
warning_message = 'It appears this is a video file using Microsoft\'s WMV format. It is <strong>strongly</strong> recommended that this file be re-saved as an MP4, MOV, MKV, or MPG/MPEG.';
|
||||
} else {
|
||||
warning_message = 'It appears this is a legacy or not very well supported file format. It is <strong>strongly</strong> recommended that it be saved in an alternative format if possible.';
|
||||
}
|
||||
|
||||
} else if (file_size_bytes > 52428800) {
|
||||
// 50 MB = 52428800 bytes
|
||||
// 100 MB = 104857600 bytes
|
||||
console.log(`This is a large file size ${file_size_bytes / 1048576} MB (${file_size_bytes} bytes). Going to warn.`);
|
||||
warning_size = true;
|
||||
if (file_size_bytes > 2147483648) { // > 2 GB
|
||||
warning_message = `This file size (${file_size_string}) is very large and will take at <strong>least</strong> a few minutes to upload depending on your network connection. In some cases it may be worth compressing the file or embedded media. Most audio, image, and video files can be compressed without a significant loss in quality. Be sure you have a stable network connection, especially if you are uploading over a wireless connection. Many business (convention centers, hotels, restaurants, etc) cap upload speeds significantly.`;
|
||||
} else if (file_size_bytes > 209715200) { // > 200 MB
|
||||
warning_message = `This file size (${file_size_string}) is large and will likely take at <strong>least</strong> a few minutes to upload depending on your network connection. Be sure you have a stable network connection, especially if you are uploading over a wireless connection. Many business (convention centers, hotels, restaurants, etc) cap upload speeds significantly. In some cases it may be worth compressing the file or embedded media. Many audio, image, and video files can be compressed without a significant loss in quality.`;
|
||||
} else if (file_size_bytes > 104857600) { // 100 MB to 200 MB
|
||||
warning_message = `This file size (${file_size_string}) is large and will likely take a few minutes to upload depending on your network connection. Be sure you have a stable network connection. In some cases it may be worth compressing the file or embedded media. Many audio, image, and video files can be compressed without a significant loss in quality.`;
|
||||
} else { // 50 to 100 MB
|
||||
warning_message = `This file size (${file_size_string}) is large and may take a few minutes to upload depending on your network connection. In some cases it may be worth compressing the file or embedded media. Many audio, image, and video files can be compressed without a significant loss in quality.`;
|
||||
}
|
||||
|
||||
} else {
|
||||
}
|
||||
|
||||
file_data['warning_untrusted_extension'] = warning_untrusted_extension;
|
||||
file_data['warning_legacy_extension'] = warning_legacy_extension;
|
||||
file_data['warning_size'] = warning_size;
|
||||
|
||||
file_data['warning_message'] = warning_message;
|
||||
|
||||
file_data['uploaded'] = null;
|
||||
file_data['uploaded_bytes'] = null;
|
||||
|
||||
// input_file_list_processed.push(JSON.parse(JSON.stringify(file_data)));
|
||||
// input_file_list_processed = input_file_list_processed;
|
||||
|
||||
// console.log(get_file_hash(file_item));
|
||||
// console.log(await get_file_hash(file_item));
|
||||
|
||||
let file_hash = null;
|
||||
|
||||
// Only hash files less than 2 GB (2147483648 bytes)!!!
|
||||
console.log(`File size: ${file_size_bytes / 1048576} MB (${file_size_bytes} bytes)`);
|
||||
if (file_size_bytes < 2000000000) { // > 2 GB 2 147 483 648
|
||||
file_hash = await ae_util.get_file_hash(file_item);
|
||||
} else {
|
||||
// File size in MB
|
||||
console.log(`File is too large to hash. File size: ${file_size_bytes / 1048576} MB`);
|
||||
}
|
||||
|
||||
|
||||
if (file_hash) {
|
||||
console.log(`Found file hash to lookup: ${ae_util.shorten_string({string: file_hash})}`);
|
||||
file_data['hash_sha256'] = file_hash;
|
||||
|
||||
let check_hosted_file_obj_w_hash_result = await check_hosted_file_obj_w_hash({
|
||||
api_cfg: $ae_api,
|
||||
hosted_file_hash: file_hash
|
||||
});
|
||||
|
||||
// console.log(check_hosted_file_obj_w_hash_result);
|
||||
|
||||
if (check_hosted_file_obj_w_hash_result && check_hosted_file_obj_w_hash_result.hosted_file_found_check) {
|
||||
console.log('Matching hash!!!');
|
||||
file_data['hash_sha256_match'] = true;
|
||||
// $ae_events.pres_mgmt.new_upload_list[i].hash_sha256_match = true;
|
||||
// $ae_events = $ae_events;
|
||||
}
|
||||
} else {
|
||||
file_data['hash_sha256'] = null;
|
||||
file_data['hash_sha256_match'] = false;
|
||||
}
|
||||
|
||||
processed_file_list.push(file_data);
|
||||
// input_file_list_processed.push(file_data);
|
||||
}
|
||||
|
||||
file_list_status = 'ready';
|
||||
console.log(processed_file_list);
|
||||
|
||||
// return JSON.parse(JSON.stringify(processed_file_list));
|
||||
return processed_file_list
|
||||
}
|
||||
|
||||
|
||||
function remove_file_from_filelist(index) {
|
||||
console.log('*** remove_file_from_filelist() ***');
|
||||
|
||||
// Can not use something like this because it is readonly:
|
||||
const dt = new DataTransfer();
|
||||
// let input = document.getElementById(input_element_id);
|
||||
|
||||
let input_element = document.querySelector('input[type="file"].svelte_input_file_element');
|
||||
|
||||
if (!input_element) {
|
||||
console.error('Could not find the input element.');
|
||||
return false;
|
||||
}
|
||||
|
||||
let files = input_file_list;
|
||||
|
||||
if (!files || !files.length) {
|
||||
console.error('No files found in the file list.');
|
||||
file_list_status = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
if (index !== i) // Only include the file if it does not match the index value.
|
||||
dt.items.add(file);
|
||||
}
|
||||
|
||||
// NOTE: I thought just setting the input_element.files OR input_file_list would trigger the input_file_list change.
|
||||
// input_element.files = Object.assign({}, dt.files);
|
||||
input_element.files = dt.files; // Assign the updates list
|
||||
// input_file_list = null;
|
||||
input_file_list = dt.files; // I feel like this should not need to be done, but doing it anyways.
|
||||
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div class="svelte_element ae_element ae_input_file flex flex-col gap-1 items-center justify-center {container_class_li.join(' ')} text-center">
|
||||
<label
|
||||
for={element_id}
|
||||
class="svelte_input_file_label text-center"
|
||||
>
|
||||
<div>
|
||||
<span class="fas fa-upload"></span>
|
||||
<!-- Select files to upload -->
|
||||
<!-- <span class="fas fa-file-archive"></span> -->
|
||||
<strong>Upload your files</strong>
|
||||
<!-- (drag and drop) -->
|
||||
</div>
|
||||
<span>
|
||||
Presentation related files only<br>
|
||||
(PowerPoint, Keynote, PDF, mp4, Word Doc, Excel, txt, etc)
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<input
|
||||
id={element_id}
|
||||
type="file"
|
||||
bind:files={input_file_list}
|
||||
{multiple}
|
||||
{required}
|
||||
{accept}
|
||||
name={input_name}
|
||||
class="svelte_input_file_element {input_class_li.join(' ')}"
|
||||
/>
|
||||
|
||||
{#if file_list_status == 'processing'}
|
||||
<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...
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- {#await input_file_list_processed} -->
|
||||
<!-- {:then} -->
|
||||
{#if use_selected_file_table && input_file_list_processed && input_file_list_processed.length}
|
||||
<strong>Files selected for upload</strong>
|
||||
<table class="slct_file_list text-sm {table_class_li.join(' ')}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Remove</th>
|
||||
<th>Filename</th>
|
||||
<th>Modified</th>
|
||||
<th>Size</th>
|
||||
<!-- <th>Type</th> -->
|
||||
<th>Ext</th>
|
||||
<th>Hash</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{#each input_file_list_processed as file_list_item, file_index}
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="file_remove">
|
||||
<button
|
||||
on:click|preventDefault={() => { (remove_file_from_filelist(file_index)); }}
|
||||
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>
|
||||
<span class="hidden">Remove</span>
|
||||
</button>
|
||||
</td>
|
||||
<td class="file_filename">{file_list_item.filename}</td>
|
||||
<td class="file_last_modified">{file_list_item.modified_datetime_string}</td>
|
||||
<td
|
||||
class="file_size"
|
||||
class:bg-pink-200={file_list_item.warning_size}
|
||||
>
|
||||
{file_list_item.file_size_string}
|
||||
<!-- {#if $ae_sess.api_upload_kv[link_to_id]}
|
||||
<span class="text-xs">({$ae_sess.api_upload_kv[link_to_id].percent_completed}%)</span>
|
||||
{/if} -->
|
||||
</td>
|
||||
<!-- <td class="file_type" class:warning_file_untrusted_extension={file_list_item.warning_untrusted_extension} class:warning_file_legacy_extension={file_list_item.warning_legacy_extension}>{file_list_item.type}</td> -->
|
||||
<td
|
||||
class="file_extension"
|
||||
class:bg-red-200={file_list_item.warning_untrusted_extension}
|
||||
class:bg-pink-200={file_list_item.warning_legacy_extension}
|
||||
>
|
||||
{file_list_item.guessed_extension}
|
||||
</td>
|
||||
<td
|
||||
class="file_hash file_hash256"
|
||||
class:bg-pink-200={file_list_item.hash_sha256_match}
|
||||
>
|
||||
{ae_util.shorten_string({string: file_list_item.hash_sha256, begin_length: 5, end_length: 4, wildcard_length: 2})}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
{/each}
|
||||
|
||||
</table>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
th {
|
||||
text-align: center;
|
||||
/* font-size: smaller; */
|
||||
}
|
||||
td {
|
||||
text-align: center;
|
||||
}
|
||||
.file_last_modified {
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.file_size, .file_type {
|
||||
/* font-size: smaller; */
|
||||
}
|
||||
|
||||
.file_hash {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
</style>
|
||||
389
src/lib/elements/element_input_files_tbl.svelte
Normal file
389
src/lib/elements/element_input_files_tbl.svelte
Normal file
@@ -0,0 +1,389 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, onMount, tick } from 'svelte';
|
||||
|
||||
import type { key_val } from '$lib/ae_stores';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
// 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/ae_stores';
|
||||
|
||||
// export let element_id = 'svelte_input_file_element';
|
||||
export let container_class_li: string[] = [];
|
||||
export let table_class_li: string[] = ['table', 'table-sm', 'table-striped', '' , 'text-sm'];
|
||||
|
||||
|
||||
export let untrusted_extension_list = ['bin', 'dmg', 'exe', 'js', 'msi', 'php', 'py', 'sh'];
|
||||
export let legacy_extension_list = ['avi', 'doc', 'ppt', 'xls', 'wmv'];
|
||||
export let use_selected_file_table = true;
|
||||
|
||||
export let input_file_list: any = null;
|
||||
export let file_list_status: null|string = null;
|
||||
export let processed_file_list: any[] = [];
|
||||
|
||||
// const dispatch = createEventDispatcher();
|
||||
|
||||
// let input_file_list_processed: any[] = [];
|
||||
|
||||
onMount(() => {
|
||||
console.log('** Element Mounted: ** Element Input File');
|
||||
});
|
||||
|
||||
|
||||
$: if (input_file_list) {
|
||||
console.log(input_file_list);
|
||||
|
||||
process_file_list(input_file_list)
|
||||
.then(function (result) {
|
||||
// console.log(result);
|
||||
|
||||
if (!result || !result.length) {
|
||||
processed_file_list = [];
|
||||
file_list_status = 'none';
|
||||
}
|
||||
|
||||
// Save the results to the file upload list to be displayed as a table.
|
||||
// input_file_list_processed = result; // Includes file hash
|
||||
|
||||
// dispatch(
|
||||
// 'input_file_list_updated',
|
||||
// {
|
||||
// element_id: element_id,
|
||||
// input_file_list: input_file_list,
|
||||
// input_file_list_processed: result, // Includes file hash
|
||||
// }
|
||||
// );
|
||||
|
||||
});
|
||||
} else {
|
||||
processed_file_list = [];
|
||||
file_list_status = 'none';
|
||||
}
|
||||
|
||||
|
||||
async function process_file_list(file_list) {
|
||||
console.log('*** process_file_list() ***');
|
||||
|
||||
file_list_status = 'processing';
|
||||
processed_file_list = [];
|
||||
|
||||
if (!file_list) {
|
||||
// file_list_processed = null;
|
||||
file_list_status = 'none';
|
||||
// await tick();
|
||||
return processed_file_list;
|
||||
}
|
||||
|
||||
// const forLoop = async _ => {
|
||||
// console.log('*** Start ***');
|
||||
|
||||
// for (let [i, file_item] of file_list.entries()) { // Not sure why this does not work???
|
||||
for await (const [i, file_item] of Array.prototype.entries.call(file_list)) {
|
||||
console.log(i, file_item);
|
||||
|
||||
// NOTE: The file list is readonly. The filenames can not be changed here.
|
||||
if (file_item.name.endsWith('.odpmac') || file_item.name.endsWith('.odpwin') || file_item.name.endsWith('.pptmac') || file_item.name.endsWith('.pptwin') || file_item.name.endsWith('.pptxmac') || file_item.name.endsWith('.pptxwin')) {
|
||||
console.log('This file extension may need to be fixed? API upload will take care of it.');
|
||||
// file_item.name = file_item.name.replace('.odpwin', '.odp');
|
||||
}
|
||||
|
||||
let file_data: key_val = {};
|
||||
|
||||
let filename = file_item.name;
|
||||
// console.log(filename);
|
||||
file_data['filename'] = filename;
|
||||
|
||||
let guessed_extension = ae_util.guess_file_extension(filename);
|
||||
file_data['guessed_extension'] = guessed_extension;
|
||||
|
||||
file_data['type'] = file_item.type;
|
||||
|
||||
let modified_date = new Date(file_item.lastModified);
|
||||
file_data['modified_date'] = modified_date;
|
||||
let modified_datetime_string = ae_util.iso_datetime_formatter(modified_date, 'datetime_medium');
|
||||
file_data['modified_datetime_string'] = modified_datetime_string;
|
||||
|
||||
let file_size_bytes = file_item.size;
|
||||
file_data['file_size_bytes'] = file_size_bytes;
|
||||
let file_size_string = ae_util.format_bytes(file_item.size, 2);
|
||||
file_data['file_size_string'] = file_size_string;
|
||||
|
||||
// // NOTE: Calculate the hash of the file before upload. Check if this exact file has already been uploaded.
|
||||
// let file_reader = new FileReader();
|
||||
// file_reader.onload = async function() {
|
||||
|
||||
// const hash_buffer = crypto.subtle.digest('SHA-256', file_reader.result);
|
||||
// let hash_hex_test = hash_buffer.then(async function (result_buffer) {
|
||||
// const hash_array = Array.from(new Uint8Array(result_buffer)); // convert buffer to byte array
|
||||
// const hash_hex = hash_array.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
// console.log(`File hash hex? ${hash_hex}`);
|
||||
// file_data['hash_sha256'] = hash_hex;
|
||||
// return hash_hex;
|
||||
// })
|
||||
// .catch(function (error) {
|
||||
// console.log('Something went wrong?', error);
|
||||
// });
|
||||
|
||||
// return hash_hex_test;
|
||||
|
||||
// // file_data['hash_hex_test'] = hash_hex_test;
|
||||
|
||||
// // const hash_buffer = await crypto.subtle.digest('SHA-256', file_reader.result);
|
||||
// // let hash_str = await new TextDecoder().decode(hash_buffer);
|
||||
// // console.log(`File hash string? ${hash_str}`);
|
||||
// // file_data['hash_str'] = hash_str;
|
||||
|
||||
// // const hash_array = Array.from(new Uint8Array(hash_buffer)); // convert buffer to byte array
|
||||
// // const hash_hex = hash_array.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
// // console.log(`File hash hex? ${hash_hex}`);
|
||||
|
||||
// // file_data['hash_sha256'] = hash_hex;
|
||||
// // return hash_hex_test;
|
||||
// // return hash_hex;
|
||||
// }
|
||||
|
||||
// // file_reader.then(function (result) {
|
||||
// // console.log(`File hash hex? ${result}`);
|
||||
// // return hash_hex;
|
||||
// // })
|
||||
// // .catch(function (error) {
|
||||
// // console.log('No results returned or failed.', error);
|
||||
// // });
|
||||
// // file_data['hash_sha256'] = file_reader.readAsArrayBuffer(file_item);
|
||||
|
||||
|
||||
|
||||
let warning_untrusted_extension = false;
|
||||
let warning_legacy_extension = false;
|
||||
let warning_size = false;
|
||||
let warning_message = null;
|
||||
|
||||
if (untrusted_extension_list.includes(guessed_extension)) {
|
||||
console.log('This is an untrusted extension. Going to warn.')
|
||||
warning_untrusted_extension = true;
|
||||
warning_message = 'It appears this an untrusted file type and is likely meant to be an executable or installable application. It is <strong>strongly</strong> recommended that this file not be used.';
|
||||
|
||||
} else if (legacy_extension_list.includes(guessed_extension)) {
|
||||
console.log('This is a legacy extension. Going to warn.')
|
||||
warning_legacy_extension = true;
|
||||
if (guessed_extension == 'ppt') {
|
||||
warning_message = 'It appears this is a legacy PowerPoint file and has not been officially supported since Office PowerPoint 2003. This file is known to have issues and may not work well. It is <strong>strongly</strong> recommended that this file be saved using the modern PPTX format.';
|
||||
} else if (guessed_extension == 'avi') {
|
||||
warning_message = 'It appears this is a video file using the AVI format. It is <strong>strongly</strong> recommended that this file be re-saved as an MP4, MOV, MKV, or MPG/MPEG. The file will also likely be much smaller.';
|
||||
} else if (guessed_extension == 'wmv') {
|
||||
warning_message = 'It appears this is a video file using Microsoft\'s WMV format. It is <strong>strongly</strong> recommended that this file be re-saved as an MP4, MOV, MKV, or MPG/MPEG.';
|
||||
} else {
|
||||
warning_message = 'It appears this is a legacy or not very well supported file format. It is <strong>strongly</strong> recommended that it be saved in an alternative format if possible.';
|
||||
}
|
||||
|
||||
} else if (file_size_bytes > 52428800) {
|
||||
// 50 MB = 52428800 bytes
|
||||
// 100 MB = 104857600 bytes
|
||||
console.log(`This is a large file size ${file_size_bytes / 1048576} MB (${file_size_bytes} bytes). Going to warn.`);
|
||||
warning_size = true;
|
||||
if (file_size_bytes > 2147483648) { // > 2 GB
|
||||
warning_message = `This file size (${file_size_string}) is very large and will take at <strong>least</strong> a few minutes to upload depending on your network connection. In some cases it may be worth compressing the file or embedded media. Most audio, image, and video files can be compressed without a significant loss in quality. Be sure you have a stable network connection, especially if you are uploading over a wireless connection. Many business (convention centers, hotels, restaurants, etc) cap upload speeds significantly.`;
|
||||
} else if (file_size_bytes > 209715200) { // > 200 MB
|
||||
warning_message = `This file size (${file_size_string}) is large and will likely take at <strong>least</strong> a few minutes to upload depending on your network connection. Be sure you have a stable network connection, especially if you are uploading over a wireless connection. Many business (convention centers, hotels, restaurants, etc) cap upload speeds significantly. In some cases it may be worth compressing the file or embedded media. Many audio, image, and video files can be compressed without a significant loss in quality.`;
|
||||
} else if (file_size_bytes > 104857600) { // 100 MB to 200 MB
|
||||
warning_message = `This file size (${file_size_string}) is large and will likely take a few minutes to upload depending on your network connection. Be sure you have a stable network connection. In some cases it may be worth compressing the file or embedded media. Many audio, image, and video files can be compressed without a significant loss in quality.`;
|
||||
} else { // 50 to 100 MB
|
||||
warning_message = `This file size (${file_size_string}) is large and may take a few minutes to upload depending on your network connection. In some cases it may be worth compressing the file or embedded media. Many audio, image, and video files can be compressed without a significant loss in quality.`;
|
||||
}
|
||||
|
||||
} else {
|
||||
}
|
||||
|
||||
file_data['warning_untrusted_extension'] = warning_untrusted_extension;
|
||||
file_data['warning_legacy_extension'] = warning_legacy_extension;
|
||||
file_data['warning_size'] = warning_size;
|
||||
|
||||
file_data['warning_message'] = warning_message;
|
||||
|
||||
file_data['uploaded'] = null;
|
||||
file_data['uploaded_bytes'] = null;
|
||||
|
||||
// input_file_list_processed.push(JSON.parse(JSON.stringify(file_data)));
|
||||
// input_file_list_processed = input_file_list_processed;
|
||||
|
||||
// console.log(get_file_hash(file_item));
|
||||
// console.log(await get_file_hash(file_item));
|
||||
|
||||
let file_hash = null;
|
||||
|
||||
// Only hash files less than 2 GB (2147483648 bytes)!!!
|
||||
console.log(`File size: ${file_size_bytes / 1048576} MB (${file_size_bytes} bytes)`);
|
||||
if (file_size_bytes < 2000000000) { // > 2 GB 2 147 483 648
|
||||
file_hash = await ae_util.get_file_hash(file_item);
|
||||
} else {
|
||||
// File size in MB
|
||||
console.log(`File is too large to hash. File size: ${file_size_bytes / 1048576} MB`);
|
||||
}
|
||||
|
||||
|
||||
if (file_hash) {
|
||||
console.log(`Found file hash to lookup: ${ae_util.shorten_string({string: file_hash})}`);
|
||||
file_data['hash_sha256'] = file_hash;
|
||||
|
||||
let check_hosted_file_obj_w_hash_result = await check_hosted_file_obj_w_hash({
|
||||
api_cfg: $ae_api,
|
||||
hosted_file_hash: file_hash
|
||||
});
|
||||
|
||||
// console.log(check_hosted_file_obj_w_hash_result);
|
||||
|
||||
if (check_hosted_file_obj_w_hash_result && check_hosted_file_obj_w_hash_result.hosted_file_found_check) {
|
||||
console.log('Matching hash!!!');
|
||||
file_data['hash_sha256_match'] = true;
|
||||
// $ae_events.pres_mgmt.new_upload_list[i].hash_sha256_match = true;
|
||||
// $ae_events = $ae_events;
|
||||
}
|
||||
} else {
|
||||
file_data['hash_sha256'] = null;
|
||||
file_data['hash_sha256_match'] = false;
|
||||
}
|
||||
|
||||
processed_file_list.push(file_data);
|
||||
// input_file_list_processed.push(file_data);
|
||||
}
|
||||
|
||||
file_list_status = 'ready';
|
||||
console.log(processed_file_list);
|
||||
|
||||
// return JSON.parse(JSON.stringify(processed_file_list));
|
||||
return processed_file_list
|
||||
}
|
||||
|
||||
|
||||
function remove_file_from_filelist(index) {
|
||||
console.log('*** remove_file_from_filelist() ***');
|
||||
|
||||
// Can not use something like this because it is readonly:
|
||||
const dt = new DataTransfer();
|
||||
// let input = document.getElementById(input_element_id);
|
||||
|
||||
let input_element = document.querySelector('input[type="file"].svelte_input_file_element');
|
||||
|
||||
if (!input_element) {
|
||||
console.error('Could not find the input element.');
|
||||
return false;
|
||||
}
|
||||
|
||||
let files = input_file_list;
|
||||
|
||||
if (!files || !files.length) {
|
||||
console.error('No files found in the file list.');
|
||||
file_list_status = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
if (index !== i) // Only include the file if it does not match the index value.
|
||||
dt.items.add(file);
|
||||
}
|
||||
|
||||
// NOTE: I thought just setting the input_element.files OR input_file_list would trigger the input_file_list change.
|
||||
// input_element.files = Object.assign({}, dt.files);
|
||||
input_element.files = dt.files; // Assign the updates list
|
||||
// input_file_list = null;
|
||||
input_file_list = dt.files; // I feel like this should not need to be done, but doing it anyways.
|
||||
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div class="svelte_element ae_element ae_input_file flex flex-col gap-1 items-center justify-center {container_class_li.join(' ')} text-center">
|
||||
|
||||
|
||||
{#if file_list_status == 'processing'}
|
||||
<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...
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- {#await processed_file_list} -->
|
||||
<!-- {:then} -->
|
||||
{#if use_selected_file_table && processed_file_list && processed_file_list.length}
|
||||
<strong>Files selected for upload</strong>
|
||||
<table class="slct_file_list text-sm {table_class_li.join(' ')}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Remove</th>
|
||||
<th>Filename</th>
|
||||
<th>Modified</th>
|
||||
<th>Size</th>
|
||||
<!-- <th>Type</th> -->
|
||||
<th>Ext</th>
|
||||
<th>Hash</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{#each processed_file_list as file_list_item, file_index}
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="file_remove">
|
||||
<button
|
||||
on:click|preventDefault={() => { (remove_file_from_filelist(file_index)); }}
|
||||
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>
|
||||
<span class="hidden">Remove</span>
|
||||
</button>
|
||||
</td>
|
||||
<td class="file_filename text-wrap break-all md:break-words">
|
||||
{file_list_item.filename}
|
||||
</td>
|
||||
<td class="file_last_modified">{file_list_item.modified_datetime_string}</td>
|
||||
<td
|
||||
class="file_size"
|
||||
class:bg-pink-200={file_list_item.warning_size}
|
||||
>
|
||||
{file_list_item.file_size_string}
|
||||
{#if $ae_sess.api_upload_kv[file_list_item.hash_sha256]}
|
||||
<span class="text-xs">({$ae_sess.api_upload_kv[file_list_item.hash_sha256].percent_completed}%)</span>
|
||||
{/if}
|
||||
</td>
|
||||
<!-- <td class="file_type" class:warning_file_untrusted_extension={file_list_item.warning_untrusted_extension} class:warning_file_legacy_extension={file_list_item.warning_legacy_extension}>{file_list_item.type}</td> -->
|
||||
<td
|
||||
class="file_extension"
|
||||
class:bg-red-200={file_list_item.warning_untrusted_extension}
|
||||
class:bg-pink-200={file_list_item.warning_legacy_extension}
|
||||
>
|
||||
{file_list_item.guessed_extension}
|
||||
</td>
|
||||
<td
|
||||
class="file_hash file_hash256"
|
||||
class:bg-pink-200={file_list_item.hash_sha256_match}
|
||||
>
|
||||
{ae_util.shorten_string({string: file_list_item.hash_sha256, begin_length: 5, end_length: 4, wildcard_length: 2})}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
{/each}
|
||||
|
||||
</table>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
th {
|
||||
text-align: center;
|
||||
/* font-size: smaller; */
|
||||
}
|
||||
td {
|
||||
text-align: center;
|
||||
}
|
||||
.file_last_modified {
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.file_size, .file_type {
|
||||
/* font-size: smaller; */
|
||||
}
|
||||
|
||||
.file_hash {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
</style>
|
||||
574
src/lib/elements/element_input_v2.svelte
Normal file
574
src/lib/elements/element_input_v2.svelte
Normal file
@@ -0,0 +1,574 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
|
||||
import util from './utilities.js';
|
||||
|
||||
// import Select_element_lu from './element_select_lu.svelte';
|
||||
|
||||
|
||||
/* *** BEGIN *** Core input settings */
|
||||
|
||||
export let id_random: string = ''; // OSIT Aether specific
|
||||
export let obj_type: string = ''; // OSIT Aether specific
|
||||
export let obj_prop_name: string = ''; // OSIT Aether specific
|
||||
|
||||
|
||||
export let use_name_prefix: boolean = false;
|
||||
export let name: string = (use_name_prefix ? `${obj_type}__${obj_prop_name}` : obj_prop_name);
|
||||
// console.log(name);
|
||||
export let id: string = `${obj_type}__${obj_prop_name}--${id_random}`; // Same as the value for "for"
|
||||
// console.log(id);
|
||||
|
||||
export let value: null|boolean|number|string = null; // The current value of the property
|
||||
console.log(`Input name=${name} value=${value}`);
|
||||
export let default_value: null|boolean|number|string = null; // The default value for when value is ''
|
||||
// console.log('Default Value:', default_value);
|
||||
export let original_value: null|boolean|number|string = value; // The original value
|
||||
console.log('Original Value', original_value);
|
||||
export let data_type: string = typeof value; // boolean, number, string, json
|
||||
console.log('Data Type:', data_type);
|
||||
|
||||
// hidden, text, email, date, number, select, checkbox, radio, textarea, etc
|
||||
export let type: string = 'text'; // Input type
|
||||
|
||||
let set_input_type = (node) => {
|
||||
node.type = 'text';
|
||||
};
|
||||
let input_element_type_list = ['checkbox', 'date', 'email', 'hidden', 'number', 'text'];
|
||||
if (input_element_type_list.includes(type)) {
|
||||
set_input_type = (node) => {
|
||||
node.type = type;
|
||||
};
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
console.log(`Input name=${name} value=${value} type=${type}`);
|
||||
|
||||
export let disabled: boolean = false; // attribute format: disabled
|
||||
export let readonly: boolean = false; // attribute format: readonly="readonly"
|
||||
export let required: boolean = false; // attribute format: required="required"
|
||||
export let pattern: null|string = null;
|
||||
export let focus: boolean = false;
|
||||
|
||||
/* *** END *** Core input settings */
|
||||
|
||||
|
||||
/* *** BEGIN *** Container content, layout, and behavior */
|
||||
|
||||
// Input element specific label and placeholder
|
||||
export let label: string = '';
|
||||
export let label_date: string = 'Date';
|
||||
export let label_time: string = 'Time';
|
||||
|
||||
export let placeholder: string = label;
|
||||
|
||||
// Input description for container
|
||||
export let description: string = ''; // Description
|
||||
|
||||
// Layout and style
|
||||
export let content_layout: string = ''; // Default empty is label wrap first, label_start, label_end, floating_input
|
||||
|
||||
export let class_li: string[] = []; // Classes for the input element
|
||||
export let style: string = ''; // Style for the input element
|
||||
export let display: string = ''; // Default empty is inline. inline, block, inline-block, break (break after)
|
||||
|
||||
export let label_class_li: string[] = [];
|
||||
export let label_style: string = '';
|
||||
export let label_display: string = ''; // Default empty is inline. inline, block, inline-block, break (break after)
|
||||
export let label_location: string = 'start'; // start, end
|
||||
|
||||
export let description_location: string = 'end'; // start, end
|
||||
|
||||
export let container_class_li: string[] = []; // Classes for the container element
|
||||
export let container_style: string = '';
|
||||
export let container_display: string = ''; // Default empty is div block. inline, block, inline-block, break (break after)
|
||||
|
||||
export let multipart_class_li: string[] = [];
|
||||
|
||||
export let input_mode: null|string = null; // This is for special/custom modes like a rich text editor or search.
|
||||
console.log(`Input input_mode=${input_mode}`);
|
||||
|
||||
/* *** END *** Container content, layout, and behavior */
|
||||
|
||||
|
||||
/* *** BEGIN *** Input type specific */
|
||||
|
||||
// For checkbox, radio, and select option
|
||||
export let option_li: any[] = []; // For checkbox, radio, and select inputs. Not specific to select options. List of key value pairs.
|
||||
export let option_none: boolean = false; // If set to true then it will add an option using the select_option_none_text value
|
||||
export let option_none_text: string = '-- Not Selected --'; // If set to true then it will add an option using the select_option_none_text value
|
||||
|
||||
// For textarea
|
||||
export let size: number = null;
|
||||
export let rows: number = 3;
|
||||
export let cols: number = 80;
|
||||
if (type == 'textarea') {
|
||||
console.log(`Input textarea size=${size} rows=${rows} cols=${cols}`);
|
||||
}
|
||||
|
||||
// For custom date_time. Not "date" or "time" input type only.
|
||||
export let date_time_tz: null|string = null;
|
||||
let value_datetime = null;
|
||||
let value_date = null;
|
||||
let value_time = null;
|
||||
|
||||
if (type == 'date_time' && value) {
|
||||
console.log(`date_time value: ${value}`);
|
||||
value_datetime = new Date(value+'Z'); // Append the Z so it knows it is UTC (YYY-MM-DDTHH:mm:ssZ)
|
||||
console.log(value_datetime);
|
||||
|
||||
value_datetime = util.iso_datetime_formatter(value_datetime,'datetime_iso');
|
||||
value_date = util.iso_datetime_formatter(value_datetime,'date_iso');
|
||||
value_time = util.iso_datetime_formatter(value_datetime,'time_iso');
|
||||
// value_date = value_datetime.toLocaleDateString();
|
||||
// value_date = value_datetime.toISOString();
|
||||
// value_time = value_datetime.toLocaleTimeString();
|
||||
// value_time = value_datetime.toISOString();
|
||||
} else if (type == 'date_time') {
|
||||
console.log('No datetime value passed');
|
||||
}
|
||||
if (type == 'date_time') {
|
||||
console.log(`Input date_time value_datetime=${value_datetime} value_date=${value_date} value_time=${value_time} date_time_tz=${date_time_tz}`);
|
||||
}
|
||||
|
||||
/* *** END *** Input type specific */
|
||||
|
||||
|
||||
/* THIS NEEDS TO BE CLEANED UP */
|
||||
let multipart_style;
|
||||
let label_begin;
|
||||
let checkbox_none;
|
||||
let checked;
|
||||
let checkbox_none_text;
|
||||
let checkbox_li;
|
||||
let radio_option_class_li;
|
||||
let radio_none;
|
||||
let radio_none_text;
|
||||
let radio_li;
|
||||
let value_new_line;
|
||||
let select_option_none;
|
||||
let select_option_li_lu_name;
|
||||
let select_option_li;
|
||||
let select_option_none_text;
|
||||
/* ^^^ THIS NEEDS TO BE CLEANED UP ^^^ */
|
||||
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
// type Input = {
|
||||
// name: string;
|
||||
// value: number;
|
||||
// };
|
||||
// let input: Input;
|
||||
|
||||
/*
|
||||
* container_element: span, div, section
|
||||
* container_class_li: []
|
||||
* content_layout: inline, break, bs_floating
|
||||
* content_order: label_value_description, value_label_description, label_description_value
|
||||
* label_class_li: []
|
||||
* class_li: []
|
||||
* start_desc: string
|
||||
* end_desc: string
|
||||
*
|
||||
*/
|
||||
|
||||
onMount(() => {
|
||||
console.log(`** Element Mounted: ** Element Input v2: ${obj_type} ${obj_prop_name}; type=${data_type}; value=${value}`);
|
||||
|
||||
if (input_mode == 'editor_basic_200') {
|
||||
rich_editor();
|
||||
}
|
||||
});
|
||||
|
||||
// $: if (value) {
|
||||
// console.log(`input name=${name} value=${value}`);
|
||||
// console.log(typeof value);
|
||||
// } else if (typeof value === 'undefined') {
|
||||
// console.log(`input name=${name} value=undefined; resetting to ''`);
|
||||
// value = '';
|
||||
// }
|
||||
|
||||
$: if (disabled) { disabled=true; } // else { disabled=false; }
|
||||
$: if (readonly) { readonly=true; } // else { readonly=false; }
|
||||
$: if (required) { required=true; } // else { required=true; }
|
||||
|
||||
$: if (data_type) {
|
||||
console.log(`Input value data_type=${data_type}`);
|
||||
} else if (data_type == 'json') {
|
||||
console.log(`Need to convert JSON object to string.`);
|
||||
if (typeof value === 'object') {
|
||||
value = JSON.stringify(value);
|
||||
console.log(`Value as data type (${data_type}): ${value}`);
|
||||
} else {
|
||||
console.log('Value is not an object type')
|
||||
console.log(typeof value);
|
||||
}
|
||||
}
|
||||
|
||||
// // input_mode options: null or '' assume 'text', 'none', 'text', 'tel', 'url', 'email', 'numeric', 'decimal', and 'search'
|
||||
// $: if (input_mode === null || input_mode == '') {
|
||||
// input_mode = 'text';
|
||||
// } else if (input_mode == 'json') {
|
||||
// } else if (input_mode == 'editor_basic_200') {
|
||||
// }
|
||||
// console.log(`input_mode=${input_mode}`);
|
||||
|
||||
$: if (container_display == '' || container_display == 'block') {
|
||||
console.log(`input name=${name} container_display=${container_display}`);
|
||||
// NOTE: Using a div will be displayed as block by default
|
||||
} else if (container_display == 'inline') {
|
||||
console.log(`input name=${name} container_display=${container_display}`);
|
||||
container_class_li.push('container_inline');
|
||||
container_style = 'display: inline;'
|
||||
} else if (container_display == 'inline-block') {
|
||||
console.log(`input name=${name} container_display=${container_display}`);
|
||||
container_class_li.push('container_inline_block');
|
||||
container_style = 'display: inline-block;'
|
||||
} else if (typeof container_display === 'undefined') {
|
||||
console.log(`input name=${name} container_display=${container_display}`);
|
||||
}
|
||||
|
||||
$: if (content_layout == '' || content_layout == 'label_begin') {
|
||||
console.log(`input name=${name} content_layout=${content_layout}`);
|
||||
// label_begin = true;
|
||||
} else if (content_layout == 'label_end') {
|
||||
console.log(`input name=${name} content_layout=${content_layout}`);
|
||||
// label_begin = false;
|
||||
} else if (content_layout == 'floating_input') {
|
||||
console.log(`input name=${name} content_layout=${content_layout}`);
|
||||
// label_begin = false;
|
||||
|
||||
if (type != 'date_time') {
|
||||
container_class_li.push('form-floating'); // set the container class list
|
||||
} else {
|
||||
multipart_class_li.push('form-floating'); // set the container with multi input parts class list
|
||||
}
|
||||
|
||||
if (type === 'select') {
|
||||
class_li.push('form-select'); // set the select class list
|
||||
} else {
|
||||
console.log('Make form-control???');
|
||||
class_li.push('form-control'); // set the input, select, textarea class list
|
||||
}
|
||||
|
||||
if (type === 'textarea') {
|
||||
let estimate_row_rem = rows + 5.5;
|
||||
style = `height: calc(2px + ${estimate_row_rem}rem);`;
|
||||
}
|
||||
} else if (typeof content_layout === 'undefined') {
|
||||
console.log(`input name=${name} content_layout=${content_layout}`);
|
||||
}
|
||||
|
||||
// function handle_oninput_dispatch() {
|
||||
// console.log(input);
|
||||
// dispatch('oninput', {
|
||||
// name: input.name,
|
||||
// value: input.value,
|
||||
// });
|
||||
// }
|
||||
|
||||
function handle_oninput_dispatch(event) {
|
||||
console.log(event.target);
|
||||
console.log(value);
|
||||
dispatch('oninput', {
|
||||
name: event.target.name,
|
||||
value: event.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function rich_editor() {
|
||||
console.log('*** rich_editor() ***');
|
||||
|
||||
|
||||
// let test_element = $('.editor_basic_200');
|
||||
// console.log(test_element);
|
||||
|
||||
// let textarea_element = document.querySelector('.editor_basic_200');
|
||||
|
||||
// let toolbar_element = document.querySelector('.quilljs_toolbar');
|
||||
// console.log(toolbar_element);
|
||||
|
||||
let editor_element = document.querySelector('.quilljs_editor');
|
||||
console.log(editor_element);
|
||||
|
||||
// var editor = new Quill(editor_element, {
|
||||
// modules: {
|
||||
// 'multi-cursor': true,
|
||||
// 'toolbar': { container: '#quilljs_toolbar' },
|
||||
// // 'link-tooltip': true,
|
||||
// },
|
||||
// placeholder: 'Text goes here',
|
||||
// theme: 'snow'
|
||||
// });
|
||||
|
||||
var editor = new Quill(editor_element, {
|
||||
modules: {
|
||||
'multi-cursor': true,
|
||||
toolbar: [
|
||||
[{
|
||||
header: [1, 2, false]
|
||||
}],
|
||||
['bold', 'italic', 'underline'],
|
||||
['image', 'code-block']
|
||||
]
|
||||
// 'link-tooltip': true,
|
||||
},
|
||||
placeholder: 'Text goes here',
|
||||
theme: 'snow'
|
||||
});
|
||||
|
||||
|
||||
|
||||
/*
|
||||
textarea_element.summernote(
|
||||
{
|
||||
tabsize: 4,
|
||||
height: 200,
|
||||
toolbar: [
|
||||
['cleaner',['cleaner']], // The Button
|
||||
['edit', ['undo', 'redo']],
|
||||
['font', ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'clear']],
|
||||
['color', ['forecolor']],
|
||||
['para', ['ul', 'ol', 'paragraph']],
|
||||
['insert', ['link']],
|
||||
['view', ['codeview', 'help']]
|
||||
],
|
||||
cleaner:{
|
||||
action: 'both', // both|button|paste 'button' only cleans via toolbar button, 'paste' only clean when pasting content, both does both options.
|
||||
newline: '<br>', // Summernote's default is to use '<p><br></p>'
|
||||
notStyle: '', // Position of Notification
|
||||
icon: '<i class="fas fa-paste"></i>',
|
||||
keepHtml: true, // Remove all Html formats
|
||||
keepOnlyTags: ['<p>', '<br>', '<ul>', '<li>', '<b>', '<strong>','<i>', '<a>'], // If keepHtml is true, remove all tags except these
|
||||
keepClasses: false, // Remove Classes
|
||||
badTags: ['style', 'script', 'applet', 'embed', 'noframes', 'noscript', 'html'], // Remove full tags with contents
|
||||
badAttributes: ['style', 'start'], // Remove attributes from remaining tags
|
||||
limitChars: false, // 0/false|# 0/false disables option
|
||||
limitDisplay: 'none', // none|text|html|both
|
||||
limitStop: false // true/false
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="element element_input {container_class_li.join(' ')}" style={container_style}>
|
||||
|
||||
{#if type === 'email' || type === 'date' || type === 'number' || type === 'tel' || type === 'text' || type === 'time' || type === 'url' }
|
||||
{#if (content_layout == 'label_start' && label)}
|
||||
<label for={id} class={label_class_li.join(' ')} style={label_style}>{label}</label>
|
||||
{/if}
|
||||
|
||||
<input {id} {name} class={class_li.join(' ')} {style} use:set_input_type {placeholder} {size} {pattern} {readonly} {disabled} {required} inputmode={input_mode} data-id_random={id_random} data-obj_type={obj_type} data-obj_prop_name={obj_prop_name} use:util.set_focus={focus} on:input={handle_oninput_dispatch} bind:value={value} />
|
||||
|
||||
{#if (content_layout != 'label_start' && label)}
|
||||
<label for={id} class={label_class_li.join(' ')} style={label_style}>{label}</label>
|
||||
{/if}
|
||||
|
||||
<span>{description}</span>
|
||||
|
||||
{:else if type === 'date_time' }
|
||||
{#if (content_layout == 'label_start' && label)}
|
||||
<span class={label_class_li.join(' ')} style={label_style}>{label}</span>
|
||||
<!-- <label for={id} class={label_class_li.join(' ')} style={label_style}>{label}</label> -->
|
||||
{/if}
|
||||
|
||||
<div class="element_input multipart {multipart_class_li.join(' ')}" style={multipart_style}>
|
||||
{#if label_begin}
|
||||
<label for={`${id}_date`} class={label_class_li.join(' ')} style={label_style}>{label_date}</label>
|
||||
{/if}
|
||||
|
||||
<input id={id+'_date'} name={name+'_date'} {style} class={class_li.join(' ')} type="date" value={value_date} {placeholder} {readonly} {disabled} {required} data-id_random={id_random} data-obj_type={obj_type} data-obj_prop_name={obj_prop_name} on:input={handle_oninput_dispatch} />
|
||||
|
||||
{#if !label_begin}
|
||||
<label for={`${id}_date`} class={label_class_li.join(' ')} style={label_style}>{label_date}</label>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="element_input multipart {multipart_class_li.join(' ')}" style={multipart_style}>
|
||||
{#if label_begin}
|
||||
<label for={`${id}_time`} class={label_class_li.join(' ')} style={label_style}>{label_time}</label>
|
||||
{/if}
|
||||
|
||||
<input id={id+'_time'} name={name+'_time'} {style} class={class_li.join(' ')} type="time" value={value_time} {placeholder} {readonly} {disabled} {required} data-id_random={id_random} data-obj_type={obj_type} data-obj_prop_name={obj_prop_name} on:input={handle_oninput_dispatch} />
|
||||
|
||||
{#if !label_begin}
|
||||
<label for={`${id}_time`} class={label_class_li.join(' ')} style={label_style}>{label_time}</label>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
{#if (content_layout != 'label_start' && label)}
|
||||
<span class={label_class_li.join(' ')} style={label_style}>{label}</span>
|
||||
<!-- <label for={id} class={label_class_li.join(' ')} style={label_style}>{label}</label> -->
|
||||
{/if}
|
||||
|
||||
<span>{description}</span>
|
||||
|
||||
{:else if type === 'checkbox' }
|
||||
{#if checkbox_none}
|
||||
{#if !value}
|
||||
{checked='checked'}
|
||||
{/if}
|
||||
<label>{checkbox_none_text}
|
||||
<input {name} type="checkbox" value="" {readonly} {disabled} {required} {checked} on:input={handle_oninput_dispatch} />
|
||||
</label>
|
||||
{/if}
|
||||
{#if checkbox_li.length}
|
||||
{#if (content_layout == 'label_start' && label)}
|
||||
<span class="input_container_label label_begin">{label}</span>
|
||||
{/if}
|
||||
|
||||
{#each Object.entries(checkbox_li) as [li_key, li_value]}
|
||||
{#if li_key.toString() === value.toString() }
|
||||
<label>{li_value}
|
||||
<input {name} type="checkbox" value="{li_key}" checked on:input={handle_oninput_dispatch}>
|
||||
</label>
|
||||
{:else}
|
||||
<label>{li_value}
|
||||
<input {name} type="checkbox" value="{li_key}" on:input={handle_oninput_dispatch}>
|
||||
</label>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
{#if (content_layout != 'label_start' && label)}
|
||||
<span class="input_container_label label_end">{label}</span>
|
||||
{/if}
|
||||
{:else}
|
||||
{#if label_begin}
|
||||
<label for={id} class={label_class_li.join(' ')} style={label_style}>{label}</label>
|
||||
{/if}
|
||||
|
||||
<input {id} {name} class={class_li.join(' ')} {style} type="checkbox" {value} {placeholder} {readonly} {disabled} {required} data-id_random={id_random} data-obj_type={obj_type} data-obj_prop_name={obj_prop_name} {checked} on:input={handle_oninput_dispatch} />
|
||||
{/if}
|
||||
|
||||
<span>{description}</span>
|
||||
|
||||
{:else if type === 'radio' }
|
||||
{#if radio_none}
|
||||
{#if !value}
|
||||
{checked='checked'}
|
||||
{/if}
|
||||
<label class={radio_option_class_li.join(' ')}>{@html radio_none_text}
|
||||
<input {name} type="radio" value="" {readonly} {disabled} {required} {checked} on:input={handle_oninput_dispatch} />
|
||||
</label>
|
||||
{/if}
|
||||
{#if radio_li}
|
||||
{#if (content_layout == 'label_start' && label)}
|
||||
<span class="input_container_label label_begin">{label}:</span>
|
||||
{/if}
|
||||
|
||||
<span class="input_container_radio_options">
|
||||
{#each Object.entries(radio_li) as [li_key, li_value]}
|
||||
{#if value === null} <!-- If null then no option should be selected. -->
|
||||
<label>{@html li_value}
|
||||
<input {name} type="radio" bind:group={value} value="{li_key}" on:input={handle_oninput_dispatch}>
|
||||
</label>
|
||||
{:else if ( li_key.toString() === value.toString() || (li_key == 'true' && value == true) || (li_key == 'false' && value == false) ) }
|
||||
<label>{@html li_value}
|
||||
<input {name} type="radio" bind:group={value} value="{li_key}" checked on:input={handle_oninput_dispatch}> Z
|
||||
</label>
|
||||
{:else}
|
||||
<label>{@html li_value}
|
||||
<input {name} type="radio" bind:group={value} value="{li_key}" on:input={handle_oninput_dispatch}>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
<!-- <label>{@html li_value}
|
||||
<input {name} type="radio" bind:group={value} value="{li_key}" on:input={handle_oninput_dispatch}>
|
||||
</label> -->
|
||||
{/each}
|
||||
</span>
|
||||
|
||||
{#if !label_begin}
|
||||
<span class="input_container_label label_end">{label}:</span>
|
||||
{/if}
|
||||
{:else}
|
||||
{#if label_begin}
|
||||
<label for={id} class={label_class_li.join(' ')} style={label_style}>{@html label}</label>
|
||||
{/if}
|
||||
|
||||
<input {id} {name} class={class_li.join(' ')} {style} type="radio" {value} {placeholder} {readonly} {disabled} {required} data-id_random={id_random} data-obj_type={obj_type} data-obj_prop_name={obj_prop_name} {checked} on:input={handle_oninput_dispatch} />
|
||||
|
||||
{#if !label_begin}
|
||||
<label for={id} class={label_class_li.join(' ')} style={label_style}>{@html label}</label>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<span>{description}</span>
|
||||
|
||||
{:else if type === 'textarea'}
|
||||
{#if label_begin}
|
||||
<label for={id} class={label_class_li.join(' ')} style={label_style}>{label}</label>
|
||||
{/if}
|
||||
|
||||
{#if value_new_line}<br>{/if}
|
||||
<textarea {id} {name} class={class_li.join(' ')} {style} bind:value={value} {placeholder} {readonly} {disabled} {required} {rows} {cols} data-id_random={id_random} data-obj_type={obj_type} data-obj_prop_name={obj_prop_name} on:input={handle_oninput_dispatch}></textarea>
|
||||
<!-- rows="" cols="" maxlength="" -->
|
||||
|
||||
{#if !label_begin}
|
||||
<label for={id} class={label_class_li.join(' ')} style={label_style}>{label}</label>
|
||||
{/if}
|
||||
|
||||
<span>{description}</span>
|
||||
|
||||
{:else if type === 'hidden'}
|
||||
<input {id} {name} class={class_li.join(' ')} type="hidden" bind:value={value} {placeholder} {readonly} {disabled} {required} data-id_random={id_random} data-obj_type={obj_type} data-obj_prop_name={obj_prop_name} />
|
||||
|
||||
{:else if type === 'select'}
|
||||
{#if label_begin}
|
||||
<label for={id} class={label_class_li.join(' ')} style={label_style}>
|
||||
{label}
|
||||
<!-- {#if value} <span class="label_select_value">{value}</span>{/if} -->
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
<select {id} {name} class={class_li.join(' ')} {style} {readonly} {disabled} {required} data-id_random={id_random} data-obj_type={obj_type} data-obj_prop_name={obj_prop_name} on:input={handle_oninput_dispatch}>
|
||||
{#if select_option_none}
|
||||
<option value="">{select_option_none_text}</option>
|
||||
{/if}
|
||||
{#if select_option_li_lu_name}
|
||||
<!-- <Select_element_lu lu_list_name={select_option_li_lu_name} params={select_option_li_lu_params} value_field_name={select_option_lu_value_field_name} text_field_name={select_option_lu_text_field_name} slct_value={value} /> -->
|
||||
{:else}
|
||||
{#each Object.entries(select_option_li) as [li_key, li_value]}
|
||||
{#if value === null} <!-- If null then no option should be selected. -->
|
||||
<option value="{li_key}">
|
||||
{li_value}
|
||||
</option>
|
||||
{:else if li_key.toString() === value.toString() }
|
||||
<option value="{li_key}" selected>
|
||||
{li_value}
|
||||
</option>
|
||||
{:else}
|
||||
<option value="{li_key}">
|
||||
{li_value}
|
||||
</option>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
</select>
|
||||
|
||||
{#if !label_begin}
|
||||
<label for={id} class={label_class_li.join(' ')} style={label_style}>
|
||||
{label}
|
||||
{#if value} <span class="label_select_value">{value}</span>{/if}
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
<span>{description}</span>
|
||||
{/if}
|
||||
|
||||
<slot name="more_html">
|
||||
</slot>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.container_inline {
|
||||
display: inline;
|
||||
}
|
||||
</style>
|
||||
686
src/lib/elements/element_manage_event_file_li.svelte
Normal file
686
src/lib/elements/element_manage_event_file_li.svelte
Normal file
@@ -0,0 +1,686 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
// import { liveQuery } from "dexie";
|
||||
import MyClipboard from '$lib/e_app_clipboard.svelte';
|
||||
|
||||
import type { key_val } from '$lib/ae_stores';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
import { api } from '$lib/api';
|
||||
// import Element_ae_crud from '$lib/element_ae_crud.svelte';
|
||||
// import Element_data_store from '$lib/element_data_store_v2.svelte';
|
||||
|
||||
// import { core_func } from '$lib/ae_core_functions';
|
||||
import { ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/ae_stores';
|
||||
import { db_events } from "$lib/ae_events/db_events";
|
||||
import { events_loc, events_sess, events_slct, events_trigger } from '$lib/ae_events_stores';
|
||||
import { events_func } from '$lib/ae_events_functions';
|
||||
|
||||
interface Props {
|
||||
log_lvl?: number;
|
||||
container_class_li?: string|Array<string>;
|
||||
lq__event_file_obj_li: any;
|
||||
link_to_type: string;
|
||||
link_to_id: string;
|
||||
allow_basic?: boolean;
|
||||
allow_moderator?: boolean;
|
||||
display_mode?: string; // 'default', 'compact', 'minimal', 'launcher'
|
||||
}
|
||||
|
||||
let {
|
||||
log_lvl = 0,
|
||||
container_class_li = [],
|
||||
lq__event_file_obj_li,
|
||||
link_to_type,
|
||||
link_to_id,
|
||||
allow_basic = false,
|
||||
allow_moderator = false,
|
||||
display_mode = 'default'
|
||||
}: Props = $props();
|
||||
|
||||
// export let show_convert_btn: null|boolean = null;
|
||||
|
||||
// let ae_placeholder_li: key_val = {};
|
||||
let ae_promises: key_val = $state({});
|
||||
let ae_tmp: key_val = $state({});
|
||||
ae_tmp.show__file_li = true;
|
||||
ae_tmp.show__direct_download = $events_loc.pres_mgmt.show__direct_download;
|
||||
// let ae_triggers: key_val = {};
|
||||
|
||||
onMount(() => {
|
||||
if (log_lvl) {
|
||||
console.log(`Element - Manage Event File List: link_to_type: ${link_to_type}; link_to_id: ${link_to_id}`);
|
||||
console.log(`allow_basic: ${allow_basic}; allow_moderator: ${allow_moderator}`);
|
||||
}
|
||||
});
|
||||
|
||||
let clipboard_success = $state(false);
|
||||
</script>
|
||||
|
||||
|
||||
<div
|
||||
class="float-right flex flex-row items-center"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
console.log('*** Refresh button clicked ***');
|
||||
|
||||
db_events.file.clear();
|
||||
|
||||
// let params = {
|
||||
// qry__enabled: 'all',
|
||||
// qry__hidden: 'all',
|
||||
// }
|
||||
|
||||
events_func.load_ae_obj_li__event_file({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_type: link_to_type,
|
||||
for_obj_id: link_to_id,
|
||||
enabled: 'all',
|
||||
hidden: 'all',
|
||||
// params: params,
|
||||
try_cache: true
|
||||
});
|
||||
|
||||
// ae_tmp.show__file_li = false;
|
||||
// console.log(`$lq__event_file_obj_li:`, $lq__event_file_obj_li);
|
||||
// $slct_trigger = 'load__event_file_obj_li';
|
||||
// ae_tmp.show__file_li = true;
|
||||
}}
|
||||
class="btn btn-sm p-1 m-1 preset-tonal-tertiary hover:preset-tonal-warning border border-warning-500 transition hover:transition-all *:hover:inline"
|
||||
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>
|
||||
<div class="hidden">
|
||||
Files
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
console.log('*** Show Alt Download button clicked ***');
|
||||
ae_tmp.show__direct_download = !ae_tmp.show__direct_download;
|
||||
}}
|
||||
class="btn btn-sm p-1 m-1 preset-tonal-tertiary hover:preset-tonal-warning border border-warning-500 transition hover:transition-all *:hover:inline"
|
||||
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>
|
||||
<div class="hidden">
|
||||
{ae_tmp.show__direct_download ? 'Alt On' : 'Alt Download Off'}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<section class="svelte_component event_file_uploaded_manage {container_class_li}"
|
||||
class:text-sm={display_mode != 'default'}
|
||||
>
|
||||
|
||||
|
||||
<h3
|
||||
class="h6"
|
||||
class:hidden={!$lq__event_file_obj_li?.length || display_mode != 'default'}
|
||||
>
|
||||
Manage Files:
|
||||
<span class="font-bold bg-success-100 px-4 border rounded-lg border-success-200"
|
||||
title="Files for {link_to_type ?? '-- not set --'}: {link_to_id ?? '-- not set --'} (files: {$lq__event_file_obj_li?.length ?? 'None'})"
|
||||
>
|
||||
<span class="fas fa-folder-open mx-1"></span>
|
||||
{@html $lq__event_file_obj_li ? `${$lq__event_file_obj_li.length}×` : '-- none --'}
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
{#if $lq__event_file_obj_li && $lq__event_file_obj_li.length}
|
||||
<div class="overflow-auto w-full">
|
||||
<table class="table-auto w-full">
|
||||
|
||||
{#if display_mode === 'default'}
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">Download File</th>
|
||||
{#if display_mode === 'default'}
|
||||
<th
|
||||
class="text-center"
|
||||
class:hidden={!allow_basic && !$ae_loc.trusted_access}
|
||||
>Options</th>
|
||||
{/if}
|
||||
{#if display_mode === 'default'}
|
||||
<th
|
||||
class="text-center"
|
||||
class:hidden={!allow_basic && !$ae_loc.trusted_access}
|
||||
>Status</th>
|
||||
{/if}
|
||||
<th class="text-center">Meta</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{/if}
|
||||
|
||||
<tbody>
|
||||
|
||||
{#each $lq__event_file_obj_li as event_file_obj}
|
||||
<tr
|
||||
class="ae_obj obj_event_file border-t border-b border-gray-200 hover:bg-gray-50 hover:border-gray-300"
|
||||
class:dim={event_file_obj?.hide}
|
||||
>
|
||||
<td class="event_file__file align-middle">
|
||||
{#if $events_sess.pres_mgmt?.show_field_edit__filename != event_file_obj.event_file_id_random}
|
||||
<button
|
||||
type="button"
|
||||
disabled={!allow_basic && !allow_moderator && !$ae_loc.trusted_access}
|
||||
onclick={() => {
|
||||
// ae_promises[event_file_obj.event_file_id_random]
|
||||
ae_promises[event_file_obj.event_file_id_random] = api.download_hosted_file({
|
||||
api_cfg: $ae_api,
|
||||
hosted_file_id: event_file_obj.hosted_file_id_random,
|
||||
return_file: true,
|
||||
filename: event_file_obj.filename,
|
||||
auto_download: true,
|
||||
log_lvl: 0
|
||||
});
|
||||
|
||||
// window.postMessage({ type: 'download_event_file', event_file_id: event_file_obj.event_file_id_random, filename: event_file_obj.filename, auto_download: true }, '*');
|
||||
}}
|
||||
class="btn btn-sm lg:btn-md preset-tonal-primary hover:preset-filled-primary-500 min-w-72 lg:min-w-96"
|
||||
title={`Download this file:\n${event_file_obj.filename}\n[API] SHA256: ${event_file_obj.hash_sha256.slice(0, 10)}... Hosted ID: ${event_file_obj.hosted_file_id_random} Event File ID: ${event_file_obj.event_file_id_random}`}
|
||||
>
|
||||
{#await ae_promises[event_file_obj.event_file_id_random]}
|
||||
<span class="fas fa-spinner fa-spin mx-1"></span>
|
||||
<span class="">
|
||||
Downloading
|
||||
{#if $ae_sess.api_download_kv[event_file_obj.hosted_file_id_random]}
|
||||
{$ae_sess.api_download_kv[event_file_obj.hosted_file_id_random].percent_completed}%
|
||||
{/if}
|
||||
:
|
||||
</span>
|
||||
{:then}
|
||||
<!-- <span class="fas fa-download mx-1"></span> -->
|
||||
<span class="fas fa-{ae_util.file_extension_icon(event_file_obj.extension)}"></span>
|
||||
<!-- <span class="text-sm">
|
||||
Download:
|
||||
</span> -->
|
||||
{/await}
|
||||
|
||||
<span class="grow">
|
||||
{ae_util.shorten_filename({filename: event_file_obj.filename, max_length: 30})}
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="badge preset-tonal-success hover:preset-filled-success-500 text-sm"
|
||||
class:hidden={!event_file_obj.file_purpose}
|
||||
>
|
||||
{event_file_obj.file_purpose}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<span
|
||||
class="px-4 py-2 flex flex-col gap-0.5"
|
||||
class:hidden={!ae_tmp.show__direct_download}
|
||||
>
|
||||
<div class="flex flex-row gap-0.5">
|
||||
<span class="text-xs text-gray-500 w-32">
|
||||
Original:
|
||||
</span>
|
||||
<a
|
||||
href="{$ae_api.base_url}/event/file/{event_file_obj?.event_file_id_random}/download?filename={ae_util.clean_filename(event_file_obj?.filename)}&x_no_account_id_token=direct-download"
|
||||
class="btn btn-sm p-1 preset-tonal-secondary *:hover:inline lg:text-xs underline"
|
||||
title={`Download this file:\n${ae_util.clean_filename(event_file_obj?.filename)}\n[API] SHA256: ${event_file_obj?.hash_sha256.slice(0, 10)}...\nHosted ID: ${event_file_obj?.hosted_file_id_random} Event File ID: ${event_file_obj?.event_file_id_random}`}
|
||||
>
|
||||
<span class="fas fa-download mx-1"></span>
|
||||
<span class="hidden">
|
||||
Download
|
||||
</span>
|
||||
</a>
|
||||
<!-- {#if clipboard_success}Copied!{:else}Copy Access Link{/if} -->
|
||||
<!-- <span class="fas fa-copy mx-1"></span> -->
|
||||
<!-- <Clipboard
|
||||
value={encodeURI(`${$ae_api.base_url}/event/file/${event_file_obj?.event_file_id_random}/download?filename=${ae_util.clean_filename(event_file_obj?.filename)}&x_no_account_id_token=direct-download`)}
|
||||
bind:success={clipboard_success}
|
||||
class="w-24"
|
||||
>
|
||||
{#if clipboard_success}Copied!{:else}Copy Link{/if}
|
||||
</Clipboard> -->
|
||||
<MyClipboard
|
||||
value={encodeURI(`${$ae_api.base_url}/event/file/${event_file_obj?.event_file_id_random}/download?filename=${ae_util.clean_filename(event_file_obj?.filename)}&x_no_account_id_token=direct-download`)}
|
||||
btn_text="Copy Link"
|
||||
btn_title="Copy the direct download link to the clipboard."
|
||||
btn_class="btn btn-sm preset-tonal-warning hover:preset-filled-warning-500 float-right m-1"
|
||||
></MyClipboard>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-0.5">
|
||||
<span class="text-xs text-gray-500 w-32">
|
||||
Session Name:
|
||||
</span>
|
||||
<a
|
||||
href="{$ae_api.base_url}/event/file/{event_file_obj?.event_file_id_random}/download?filename={event_file_obj?.event_session_code}-{ae_util.clean_filename(event_file_obj?.event_presentation_name).substring(0, 20)}-{ae_util.clean_filename(event_file_obj?.event_presenter_full_name)}.{event_file_obj?.extension}&x_no_account_id_token=direct-download"
|
||||
class="btn btn-sm p-1 preset-tonal-secondary *:hover:inline lg:text-xs underline"
|
||||
title={`Download renamed with session name to: ${event_file_obj?.event_session_code}-${ae_util.clean_filename(event_file_obj?.event_session_name).substring(0, 20)}-${ae_util.clean_filename(event_file_obj?.event_presenter_full_name)}.${event_file_obj?.extension}`}
|
||||
>
|
||||
<span class="fas fa-download mx-1"></span>
|
||||
<span class="hidden">
|
||||
Renamed
|
||||
</span>
|
||||
</a>
|
||||
<!-- <Clipboard
|
||||
value={encodeURI(`${$ae_api.base_url}/event/file/${event_file_obj?.event_file_id_random}/download?filename=${event_file_obj?.event_session_code}-${ae_util.clean_filename(event_file_obj?.event_session_name).substring(0, 20)}-${ae_util.clean_filename(event_file_obj?.event_presenter_full_name)}.${event_file_obj?.extension}&x_no_account_id_token=direct-download`)}
|
||||
bind:success={clipboard_success}
|
||||
class="w-24"
|
||||
>
|
||||
{#if clipboard_success}Copied!{:else}Copy Renamed{/if}
|
||||
</Clipboard> -->
|
||||
<MyClipboard
|
||||
value={encodeURI(`${$ae_api.base_url}/event/file/${event_file_obj?.event_file_id_random}/download?filename=${event_file_obj?.event_session_code}-${ae_util.clean_filename(event_file_obj?.event_session_name).substring(0, 20)}-${ae_util.clean_filename(event_file_obj?.event_presenter_full_name)}.${event_file_obj?.extension}&x_no_account_id_token=direct-download`)}
|
||||
btn_text="Copy Renamed"
|
||||
btn_title="Copy the renamed download link to the clipboard."
|
||||
btn_class="btn btn-sm preset-tonal-warning hover:preset-filled-warning-500 float-right m-1"
|
||||
></MyClipboard>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-0.5">
|
||||
<span class="text-xs text-gray-500 w-32">
|
||||
Presentation Name:
|
||||
</span>
|
||||
<a
|
||||
href="{$ae_api.base_url}/event/file/{event_file_obj?.event_file_id_random}/download?filename={event_file_obj?.event_session_code}-{ae_util.clean_filename(event_file_obj?.event_presentation_name).substring(0, 20)}-{ae_util.clean_filename(event_file_obj?.event_presenter_full_name)}.{event_file_obj?.extension}&x_no_account_id_token=direct-download"
|
||||
class="btn btn-sm p-1 preset-tonal-secondary *:hover:inline lg:text-xs underline"
|
||||
title={`Download renamed with presentation name to: ${event_file_obj?.event_session_code}-${ae_util.clean_filename(event_file_obj?.event_presentation_name).substring(0, 20)}-${ae_util.clean_filename(event_file_obj?.event_presenter_full_name)}.${event_file_obj?.extension}`}
|
||||
>
|
||||
<span class="fas fa-download mx-1"></span>
|
||||
<span class="hidden">
|
||||
Renamed
|
||||
</span>
|
||||
</a>
|
||||
<!-- {#if clipboard_success}Copied!{:else}Copy Access Link{/if} -->
|
||||
<!-- <span class="fas fa-copy mx-1"></span> -->
|
||||
<!-- <Clipboard
|
||||
value={encodeURI(`${$ae_api.base_url}/event/file/${event_file_obj?.event_file_id_random}/download?filename=${event_file_obj?.event_session_code}-${ae_util.clean_filename(event_file_obj?.event_presentation_name).substring(0, 20)}-${ae_util.clean_filename(event_file_obj?.event_presenter_full_name)}.${event_file_obj?.extension}&x_no_account_id_token=direct-download`)}
|
||||
bind:success={clipboard_success}
|
||||
class="w-24"
|
||||
>
|
||||
{#if clipboard_success}Copied!{:else}Copy Renamed{/if}
|
||||
</Clipboard> -->
|
||||
<MyClipboard
|
||||
value={encodeURI(`${$ae_api.base_url}/event/file/${event_file_obj?.event_file_id_random}/download?filename=${event_file_obj?.event_session_code}-${ae_util.clean_filename(event_file_obj?.event_presentation_name).substring(0, 20)}-${ae_util.clean_filename(event_file_obj?.event_presenter_full_name)}.${event_file_obj?.extension}&x_no_account_id_token=direct-download`)}
|
||||
btn_text="Copy Renamed"
|
||||
btn_title="Copy the renamed download link to the clipboard."
|
||||
btn_class="btn btn-sm preset-tonal-warning hover:preset-filled-warning-500 float-right m-1"
|
||||
></MyClipboard>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
{:else}
|
||||
|
||||
<!-- Show change filename input field here -->
|
||||
<span
|
||||
class="flex flex-col gap-1 text-sm"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
size="12"
|
||||
placeholder="Filename"
|
||||
bind:value="{$events_sess.pres_mgmt.tmp_val__filename_no_ext}"
|
||||
data-original_value="{event_file_obj.filename}"
|
||||
class="input min-w-72 lg:min-w-96 text-sm bg-warning-100"
|
||||
title="Rename this file. No extension."
|
||||
>
|
||||
{#if $events_sess.pres_mgmt.tmp_val__filename_no_ext.trim() != event_file_obj.filename_no_ext}
|
||||
<button
|
||||
type="button"
|
||||
onclick={async () => {
|
||||
let new_filename = $events_sess.pres_mgmt.tmp_val__filename_no_ext.trim()
|
||||
// Remove possible double extension
|
||||
new_filename = new_filename.replace('.'+event_file_obj.extension, '');
|
||||
|
||||
// Add the extension back
|
||||
new_filename = new_filename + '.' + event_file_obj.extension;
|
||||
|
||||
// Remove any double dots
|
||||
new_filename = new_filename.replace(/\.\./g, '.');
|
||||
|
||||
let event_file_data = {
|
||||
filename: new_filename,
|
||||
};
|
||||
|
||||
ae_promises.update__event_file_obj = events_func.update_ae_obj__event_file({
|
||||
api_cfg: $ae_api,
|
||||
event_file_id: event_file_obj.event_file_id_random,
|
||||
data_kv: event_file_data,
|
||||
log_lvl: 0
|
||||
})
|
||||
.then (function (update_results) {
|
||||
console.log(`Update results:`, update_results);
|
||||
$events_sess.pres_mgmt.show_field_edit__filename = false;
|
||||
});
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-tertiary hover:preset-tonal-success"
|
||||
title="Save changes"
|
||||
>
|
||||
{#await ae_promises.update__event_file_obj}
|
||||
<span class="fas fa-spinner fa-spin mx-1"></span>
|
||||
<span class="">Saving {event_file_obj.extension}</span>
|
||||
{:then}
|
||||
<span class="fas fa-save mx-1"></span>
|
||||
Save {event_file_obj.extension} filename?
|
||||
{/await}
|
||||
</button>
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
</td>
|
||||
|
||||
{#if display_mode === 'default'}
|
||||
<td
|
||||
class="event_file__options"
|
||||
class:hidden={!allow_basic && !$ae_loc.trusted_access}
|
||||
>
|
||||
<div class="flex flex-col gap-1 text-sm">
|
||||
<button
|
||||
type="button"
|
||||
disabled={!allow_basic && !$ae_loc.trusted_access}
|
||||
onclick={() => {
|
||||
if ($events_sess.pres_mgmt.show_field_edit__filename == event_file_obj.event_file_id_random) {
|
||||
$events_sess.pres_mgmt.tmp_val__filename_no_ext = '';
|
||||
$events_sess.pres_mgmt.show_field_edit__filename = false;
|
||||
} else {
|
||||
$events_sess.pres_mgmt.tmp_val__filename_no_ext = event_file_obj.filename_no_ext;
|
||||
$events_sess.pres_mgmt.show_field_edit__filename = event_file_obj.event_file_id_random;
|
||||
}
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-tertiary hover:preset-tonal-success"
|
||||
class:preset-tonal-warning={$events_sess.pres_mgmt.show_field_edit__filename == event_file_obj.event_file_id_random}
|
||||
title={`Rename this file? "${event_file_obj.filename}"`}
|
||||
>
|
||||
<span class="fas fa-edit mx-1"></span>
|
||||
{#if $events_sess.pres_mgmt?.show_field_edit__filename == event_file_obj.event_file_id_random}
|
||||
Cancel?
|
||||
{:else}
|
||||
Rename
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
disabled={!allow_basic && !$ae_loc.trusted_access}
|
||||
onclick={async () => {
|
||||
let event_file_data = {
|
||||
hide: !event_file_obj.hide,
|
||||
};
|
||||
|
||||
ae_promises.update__event_file_obj = events_func.update_ae_obj__event_file({
|
||||
api_cfg: $ae_api,
|
||||
event_file_id: event_file_obj.event_file_id_random,
|
||||
data_kv: event_file_data,
|
||||
log_lvl: 0
|
||||
})
|
||||
.then (function (update_results) {
|
||||
console.log(`Update results:`, update_results);
|
||||
|
||||
// let params = {
|
||||
// qry__enabled: 'all',
|
||||
// qry__hidden: 'all',
|
||||
// }
|
||||
|
||||
events_func.load_ae_obj_li__event_file({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_type: link_to_type,
|
||||
for_obj_id: link_to_id,
|
||||
enabled: 'all',
|
||||
hidden: 'all',
|
||||
// params: params,
|
||||
try_cache: true
|
||||
});
|
||||
});
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-tertiary hover:preset-tonal-success"
|
||||
title="Hide this file from the presentation launcher"
|
||||
>
|
||||
|
||||
<!-- Users see this as the "Archive" option button -->
|
||||
<!-- {@html (event_file_obj?.hide ? '<span class="fas fa-archive m-1"></span> Unarchive' : '<span class="fas fa-archive m-1"></span> Archive')} -->
|
||||
|
||||
{#await ae_promises.update__event_file_obj}
|
||||
<span class="fas fa-spinner fa-spin mx-1"></span>
|
||||
<span class="">Saving {event_file_obj.extension}</span>
|
||||
{:then}
|
||||
{#if event_file_obj.hide}
|
||||
<span class="fas fa-eye m-1"></span> Unhide File
|
||||
{:else}
|
||||
<span class="fas fa-eye-slash m-1"></span> Hide
|
||||
{/if}
|
||||
|
||||
<!-- {@html (event_file_obj?.hide ? '<span class="fas fa-eye m-1"></span> Unhide?' : '<span class="fas fa-eye-slash m-1"></span> Hide?')} -->
|
||||
<!-- <span class="fas fa-save mx-1"></span>
|
||||
Save {event_file_obj.extension} filename? -->
|
||||
{/await}
|
||||
</button>
|
||||
|
||||
|
||||
|
||||
<button
|
||||
type="button"
|
||||
disabled={!allow_basic && !$ae_loc.trusted_access}
|
||||
onclick={async () => {
|
||||
if (!confirm(`Are you sure you want to delete this file?\n${event_file_obj.filename} [${event_file_obj.event_file_id_random}]`)) {return false;}
|
||||
|
||||
// ae_promises[event_file_obj.event_file_id_random] = handle_delete__event_file({event_file_id: event_file_obj.event_file_id_random});
|
||||
|
||||
ae_promises.delete__event_file_obj = await events_func.delete_ae_obj_id__event_file({
|
||||
api_cfg: $ae_api,
|
||||
event_file_id: event_file_obj.event_file_id_random,
|
||||
log_lvl: 1
|
||||
})
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-tertiary hover:preset-tonal-success"
|
||||
title="Delete this file"
|
||||
>
|
||||
<span class="fas fa-trash-alt mx-1"></span>
|
||||
<!-- <span class="fas fa-minus mx-1"></span> -->
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
{/if}
|
||||
|
||||
{#if display_mode === 'default'}
|
||||
<td
|
||||
class="event_file__status"
|
||||
class:hidden={!allow_basic && !$ae_loc.trusted_access}
|
||||
>
|
||||
<div class="flex flex-col gap-1 items-center justify-center text-sm">
|
||||
|
||||
<div class="">
|
||||
{#if event_file_obj.open_in_os == 'win'}
|
||||
|
||||
MS Windows <span class="fab fa-windows"></span>
|
||||
|
||||
{:else if event_file_obj.open_in_os == 'mac'}
|
||||
|
||||
Apple macOS <span class="fab fa-apple"></span>
|
||||
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Select from options for the purpose of this file -->
|
||||
<div>
|
||||
<label
|
||||
for="file_purpose"
|
||||
class="text-sm mx-1 hidden"
|
||||
>
|
||||
Purpose:
|
||||
</label>
|
||||
<select
|
||||
id="file_purpose"
|
||||
name="file_purpose"
|
||||
disabled={!allow_basic && !allow_moderator && !$ae_loc.trusted_access}
|
||||
value={event_file_obj.file_purpose}
|
||||
onchange={e => {
|
||||
// ae_tmp[event_file_obj.event_file_id_random].file_purpose = e.target.value;
|
||||
console.log(`Selected file_purpose: ${e.target.value}`);
|
||||
|
||||
let event_file_data = {
|
||||
event_file_id_random: event_file_obj.event_file_id_random,
|
||||
file_purpose: e.target.value,
|
||||
};
|
||||
|
||||
events_func.update_ae_obj__event_file({
|
||||
api_cfg: $ae_api,
|
||||
event_file_id: event_file_obj.event_file_id_random,
|
||||
data_kv: event_file_data,
|
||||
log_lvl: 1
|
||||
})
|
||||
.then (function (update_results) {
|
||||
console.log(`Update results:`, update_results);
|
||||
$slct_trigger = 'load__event_file_obj_li';
|
||||
});
|
||||
|
||||
|
||||
|
||||
// ae_triggers.update_event_file_purpose = true;
|
||||
}}
|
||||
class="select min-w-fit max-w-fit text-xs mx-1 border border-gray-300 rounded-md p-1 hover:border-gray-400"
|
||||
>
|
||||
<option value={null} selected={!event_file_obj.file_purpose}>-- purpose not set --</option>
|
||||
{#if $events_loc.pres_mgmt?.file_purpose_option_kv}
|
||||
{#each Object.entries($events_loc.pres_mgmt.file_purpose_option_kv) as [key, file_purpose_option]}
|
||||
<option
|
||||
value={key}
|
||||
selected={event_file_obj.file_purpose === key}
|
||||
disabled={file_purpose_option?.disabled && !$ae_loc.edit_mode}
|
||||
class:hidden={file_purpose_option?.hidden && !$ae_loc.edit_mode}
|
||||
>
|
||||
{file_purpose_option?.name}
|
||||
</option>
|
||||
{/each}
|
||||
{/if}
|
||||
<!-- <option value="outline" selected={event_file_obj.file_purpose === 'outline'}>1. Outline</option> -->
|
||||
<!-- <option value="draft" selected={event_file_obj.file_purpose === 'draft'}>2. Draft</option> -->
|
||||
<!-- <option value="final" selected={event_file_obj.file_purpose === 'final'}>3. Final</option> -->
|
||||
<!-- <option value="supporting">X. Supporting File (audio, video, data, etc)</option> -->
|
||||
<!-- <option value="handout">X. Handout</option> -->
|
||||
<!-- <option value="other">X. Other</option> -->
|
||||
<!-- <option value="poster">Final - Poster</option> -->
|
||||
<!-- <option value="presentation">Final - Presentation</option> -->
|
||||
<!-- Shows in session room -->
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</td>
|
||||
{/if}
|
||||
|
||||
<td class="event_file_info file_meta text-gray-500">
|
||||
<div
|
||||
class="flex flex-col gap-0.5 text-xs"
|
||||
|
||||
>
|
||||
<!-- {event_file_obj.hosted_file_content_type} -->
|
||||
|
||||
<span class="w-full flex flex-col lg:flex-row justify-between">
|
||||
<span
|
||||
class:hidden={display_mode != 'default'}
|
||||
>
|
||||
Type:
|
||||
<strong>{event_file_obj.extension} <span class="fas fa-{ae_util.file_extension_icon(event_file_obj.extension)}"></span>
|
||||
</strong>
|
||||
<!-- {#if event_file_obj.open_in_os == 'win'}
|
||||
<strong>
|
||||
MS Windows <span class="fab fa-windows"></span>
|
||||
</strong>
|
||||
{:else if event_file_obj.open_in_os == 'mac'}
|
||||
<strong>
|
||||
Apple macOS <span class="fab fa-apple"></span>
|
||||
</strong>
|
||||
{/if} -->
|
||||
|
||||
</span>
|
||||
<span>
|
||||
<span
|
||||
class:hidden={display_mode != 'default'}
|
||||
>
|
||||
Size:
|
||||
</span>
|
||||
<strong>{ae_util.format_bytes(event_file_obj.file_size)}</strong>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="w-full flex flex-col lg:flex-row justify-between">
|
||||
<span title="SHA 256: {event_file_obj.hash_sha256}">
|
||||
<span
|
||||
class:hidden={display_mode != 'default'}
|
||||
>
|
||||
Hash:
|
||||
</span>
|
||||
<strong
|
||||
class:font-normal={display_mode != 'default'}
|
||||
>{event_file_obj.hash_sha256.slice(0, 10)}…</strong>
|
||||
</span>
|
||||
<span
|
||||
class:hidden={!$ae_loc.administrator_access || display_mode != 'default'}>
|
||||
<span
|
||||
class:hidden={display_mode != 'default'}
|
||||
>
|
||||
ID:
|
||||
</span>
|
||||
<strong>{event_file_obj.hosted_file_id_random}</strong>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<!-- If this file was uploaded (created) within the last 15 minutes we want to highlight it in blue. -->
|
||||
<span
|
||||
class:bg-yellow-200={ae_util.is_datetime_recent({datetime: event_file_obj?.created_on, minutes: 30})}
|
||||
class:bg-green-200={ae_util.is_datetime_recent({datetime: event_file_obj?.created_on, minutes: 240})}
|
||||
class:bg-blue-200={ae_util.is_datetime_recent({datetime: event_file_obj?.created_on, minutes: 2880})}
|
||||
>
|
||||
{#if display_mode == 'default'}
|
||||
<!-- <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>
|
||||
<strong>
|
||||
{ae_util.iso_datetime_formatter(event_file_obj.created_on, 'dddd')}
|
||||
</strong>
|
||||
{ae_util.iso_datetime_formatter(event_file_obj.created_on, 'date_iso')}
|
||||
at
|
||||
<strong>{ae_util.iso_datetime_formatter(event_file_obj.created_on, 'time_12_short_no_leading')}</strong>
|
||||
<!-- {event_file_obj.updated_on} -->
|
||||
{:else}
|
||||
<!-- <span class="fas fa-calendar-day mx-1"></span> -->
|
||||
<strong>
|
||||
{ae_util.iso_datetime_formatter(event_file_obj.created_on, 'date_short')}
|
||||
<!-- at -->
|
||||
<strong>{ae_util.iso_datetime_formatter(event_file_obj.created_on, 'time_12_short_no_leading')}</strong>
|
||||
</strong>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{:else}
|
||||
|
||||
<p
|
||||
class="w-96 text-center text-gray-500"
|
||||
class:hidden={display_mode != 'default'}
|
||||
>
|
||||
No files uploaded to display
|
||||
</p>
|
||||
|
||||
{/if}
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
71
src/lib/elements/element_manage_event_file_li_all.svelte
Normal file
71
src/lib/elements/element_manage_event_file_li_all.svelte
Normal file
@@ -0,0 +1,71 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
// import { events_func } from '$lib/ae_events_functions';
|
||||
container_class_li?: string|Array<string>;
|
||||
link_to_type: string;
|
||||
link_to_id: string;
|
||||
allow_basic?: boolean;
|
||||
allow_moderator?: boolean;
|
||||
display_mode?: string; // 'default', 'compact', 'minimal', 'launcher'
|
||||
}
|
||||
|
||||
let {
|
||||
container_class_li = [],
|
||||
link_to_type,
|
||||
link_to_id,
|
||||
allow_basic = false,
|
||||
allow_moderator = false,
|
||||
display_mode = 'default'
|
||||
}: Props = $props();
|
||||
|
||||
import { liveQuery } from "dexie";
|
||||
|
||||
import type { key_val } from '$lib/ae_stores';
|
||||
// import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
// import { api } from '$lib/api';
|
||||
// import Element_ae_crud from '$lib/element_ae_crud.svelte';
|
||||
// import Element_data_store from '$lib/element_data_store_v2.svelte';
|
||||
import Element_manage_event_file_li from '$lib/element_manage_event_file_li.svelte';
|
||||
|
||||
// import { core_func } from '$lib/ae_core_functions';
|
||||
// import { ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/ae_stores';
|
||||
import { db_events } from "$lib/ae_events/db_events";
|
||||
// import { events_loc, events_sess, events_slct, events_trigger } from '$lib/ae_events_stores';
|
||||
|
||||
// export let show_convert_btn: null|boolean = null;
|
||||
|
||||
// let ae_placeholder_li: key_val = {};
|
||||
// let ae_promises: key_val = {};
|
||||
let ae_tmp: key_val = {};
|
||||
ae_tmp.show__file_li = true;
|
||||
ae_tmp.show__direct_download = false;
|
||||
// let ae_triggers: key_val = {};
|
||||
|
||||
let dq__where_val: string = `${link_to_type}_id_random`;
|
||||
let dq__where_eq_val: string = link_to_id;
|
||||
|
||||
// This should include all files that are associated with an object (event, location, session, presenter, etc.)
|
||||
// I am not sure why, but doing reverse() and then sortBy() seems to sort in descending order.
|
||||
let lq__event_file_obj_li = $derived(liveQuery(async () => {
|
||||
let results = await db_events.file
|
||||
.where(dq__where_val)
|
||||
.equals(dq__where_eq_val)
|
||||
.reverse()
|
||||
.sortBy('created_on')
|
||||
// .toArray()
|
||||
;
|
||||
|
||||
return results;
|
||||
}));
|
||||
</script>
|
||||
|
||||
|
||||
<Element_manage_event_file_li
|
||||
link_to_type={link_to_type}
|
||||
link_to_id={link_to_id}
|
||||
lq__event_file_obj_li={lq__event_file_obj_li}
|
||||
allow_basic={allow_basic}
|
||||
allow_moderator={allow_moderator}
|
||||
container_class_li={container_class_li}
|
||||
display_mode={display_mode}
|
||||
/>
|
||||
82
src/lib/elements/element_manage_event_file_li_direct.svelte
Normal file
82
src/lib/elements/element_manage_event_file_li_direct.svelte
Normal file
@@ -0,0 +1,82 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
log_lvl?: number;
|
||||
// import { events_func } from '$lib/ae_events_functions';
|
||||
container_class_li?: string|Array<string>;
|
||||
link_to_type: string;
|
||||
link_to_id: string;
|
||||
allow_basic?: boolean;
|
||||
allow_moderator?: boolean;
|
||||
display_mode?: string; // 'default', 'compact', 'minimal', 'launcher'
|
||||
}
|
||||
|
||||
let {
|
||||
log_lvl = 0,
|
||||
container_class_li = [],
|
||||
link_to_type,
|
||||
link_to_id,
|
||||
allow_basic = false,
|
||||
allow_moderator = false,
|
||||
display_mode = 'default'
|
||||
}: Props = $props();
|
||||
|
||||
import { liveQuery } from "dexie";
|
||||
|
||||
import type { key_val } from '$lib/ae_stores';
|
||||
// import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
// import { api } from '$lib/api';
|
||||
// import Element_ae_crud from '$lib/element_ae_crud.svelte';
|
||||
// import Element_data_store from '$lib/element_data_store_v2.svelte';
|
||||
import Element_manage_event_file_li from '$lib/element_manage_event_file_li.svelte';
|
||||
|
||||
// import { core_func } from '$lib/ae_core_functions';
|
||||
// import { ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/ae_stores';
|
||||
import { db_events } from "$lib/ae_events/db_events";
|
||||
// import { events_loc, events_sess, events_slct, events_trigger } from '$lib/ae_events_stores';
|
||||
|
||||
// export let show_convert_btn: null|boolean = null;
|
||||
|
||||
// let ae_promises: key_val = {};
|
||||
let ae_tmp: key_val = {};
|
||||
ae_tmp.show__file_li = true;
|
||||
ae_tmp.show__direct_download = false;
|
||||
// let ae_triggers: key_val = {};
|
||||
|
||||
let dq__where_val: string = `for_type`;
|
||||
let dq__where_eq_val: string = link_to_type;
|
||||
let dq__where_for_id_eq_val: string = link_to_id;
|
||||
|
||||
// This should only include files that are directly linked to an object (event, location, session, presenter, etc.).
|
||||
// I am not sure why, but doing reverse() and then sortBy() seems to sort in descending order.
|
||||
let lq__event_file_obj_li = $derived(liveQuery(async () => {
|
||||
let results = await db_events.file
|
||||
.where(dq__where_val)
|
||||
.equals(dq__where_eq_val)
|
||||
.and(file => file.for_id_random == dq__where_for_id_eq_val)
|
||||
.reverse()
|
||||
.sortBy('created_on')
|
||||
// .toArray()
|
||||
;
|
||||
return results;
|
||||
}));
|
||||
</script>
|
||||
|
||||
|
||||
{#await lq__event_file_obj_li}
|
||||
|
||||
<p>Loading...</p>
|
||||
|
||||
{:then lq__event_file_obj_li}
|
||||
<Element_manage_event_file_li
|
||||
link_to_type={link_to_type}
|
||||
link_to_id={link_to_id}
|
||||
lq__event_file_obj_li={lq__event_file_obj_li}
|
||||
allow_basic={allow_basic}
|
||||
allow_moderator={allow_moderator}
|
||||
container_class_li={container_class_li}
|
||||
display_mode={display_mode}
|
||||
log_lvl={log_lvl}
|
||||
/>
|
||||
{:catch error}
|
||||
<p style="color: red;">{error.message}</p>
|
||||
{/await}
|
||||
269
src/lib/elements/element_manage_hosted_file_li.svelte
Normal file
269
src/lib/elements/element_manage_hosted_file_li.svelte
Normal file
@@ -0,0 +1,269 @@
|
||||
<script lang="ts">
|
||||
import type { key_val } from '$lib/ae_stores';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
// import { api } from '$lib/api';
|
||||
// import Element_ae_crud from '$lib/element_ae_crud.svelte';
|
||||
// import Element_data_store from '$lib/element_data_store_v2.svelte';
|
||||
|
||||
// import { core_func } from '$lib/ae_core_functions';
|
||||
import { ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/ae_stores';
|
||||
import { db_core } from "$lib/ae_core/db_core";
|
||||
import { core_func } from '$lib/ae_core/ae_core_functions';
|
||||
|
||||
// export let allow_basic: boolean = false;
|
||||
|
||||
interface Props {
|
||||
log_lvl?: number;
|
||||
class_li_default?: string;
|
||||
class_li?: string;
|
||||
lq__hosted_file_obj_li: any;
|
||||
link_to_type: string;
|
||||
link_to_id: string;
|
||||
// export let allow_moderator: boolean = false;
|
||||
display_mode?: string; // 'default', 'compact', 'minimal', 'launcher'
|
||||
max_file_count?: number;
|
||||
file_type?: string; // 'image', 'video', 'audio', 'document', 'other'
|
||||
slct_hosted_file_kv?: key_val;
|
||||
slct_hosted_file_id?: any;
|
||||
slct_hosted_file_obj?: any;
|
||||
}
|
||||
|
||||
let {
|
||||
log_lvl = 0,
|
||||
class_li_default = 'flex flex-col gap-1 items-center justify-center w-fit max-w-4xl mx-auto my-1 max-h-96 overflow-auto',
|
||||
class_li = '',
|
||||
lq__hosted_file_obj_li = $bindable([]),
|
||||
link_to_type,
|
||||
link_to_id,
|
||||
display_mode = 'default',
|
||||
max_file_count = 49,
|
||||
file_type = 'all',
|
||||
slct_hosted_file_kv = $bindable({}),
|
||||
slct_hosted_file_id = $bindable(null),
|
||||
slct_hosted_file_obj = $bindable(null)
|
||||
}: Props = $props();
|
||||
|
||||
// export let show_convert_btn: null|boolean = null;
|
||||
|
||||
// let ae_placeholder_li: key_val = {};
|
||||
let ae_promises: key_val = {};
|
||||
let ae_tmp: key_val = $state({});
|
||||
ae_tmp.show__file_li = true;
|
||||
ae_tmp.show__direct_download = $ae_loc.core?.show__direct_download ?? false;
|
||||
// let ae_triggers: key_val = {};
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<section
|
||||
class="{class_li_default} {class_li}"
|
||||
>
|
||||
|
||||
<h3
|
||||
class="h3"
|
||||
class:hidden={!$lq__hosted_file_obj_li?.length || display_mode != 'default'}
|
||||
>
|
||||
Manage Files:
|
||||
<span class="font-bold bg-success-100 px-4 border rounded-lg border-success-200"
|
||||
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>
|
||||
{@html $lq__hosted_file_obj_li ? `${$lq__hosted_file_obj_li.length}×` : '-- none --'}
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<div
|
||||
class="flex flex-row flex-wrap items-center justify-center gap-1"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
console.log('*** Refresh button clicked ***');
|
||||
|
||||
db_core.file.clear();
|
||||
|
||||
// let params = {
|
||||
// qry__enabled: 'all',
|
||||
// qry__hidden: 'all',
|
||||
// }
|
||||
|
||||
core_func.load_ae_obj_li__hosted_file({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_type: link_to_type,
|
||||
for_obj_id: link_to_id,
|
||||
enabled: 'enabled',
|
||||
hidden: 'not_hidden',
|
||||
limit: 250,
|
||||
// params: params,
|
||||
try_cache: true,
|
||||
log_lvl: 2
|
||||
});
|
||||
|
||||
// ae_tmp.show__file_li = false;
|
||||
// console.log(`$lq__hosted_file_obj_li:`, $lq__hosted_file_obj_li);
|
||||
// $slct_trigger = 'load__hosted_file_obj_li';
|
||||
// ae_tmp.show__file_li = true;
|
||||
}}
|
||||
class="btn btn-sm p-1 m-1 preset-tonal-tertiary hover:preset-tonal-warning border border-warning-500 transition hover:transition-all *:hover:inline"
|
||||
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>
|
||||
<div class="hidden">
|
||||
Files
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
console.log('*** Show Alt Download button clicked ***');
|
||||
ae_tmp.show__direct_download = !ae_tmp.show__direct_download;
|
||||
}}
|
||||
class="btn btn-sm p-1 m-1 preset-tonal-tertiary hover:preset-tonal-warning border border-warning-500 transition hover:transition-all *:hover:inline"
|
||||
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>
|
||||
<div class="hidden">
|
||||
{ae_tmp.show__direct_download ? 'Alt On' : 'Alt Download Off'}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
{#if $lq__hosted_file_obj_li && $lq__hosted_file_obj_li.length}
|
||||
<div class="overflow-auto w-full">
|
||||
|
||||
|
||||
<ol class="list-decimal list-inside">
|
||||
{#each [...$lq__hosted_file_obj_li].reverse().slice(0, max_file_count) as hosted_file_obj}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!$ae_loc.trusted_access}
|
||||
onclick={() => {
|
||||
// This (uploaded_file_kv) is referenced by other AE components. Currently it is only used for the video clipper. This should be a toggle of Add/Remove.
|
||||
if ($ae_loc.files.uploaded_file_kv[hosted_file_obj.hosted_file_id]) {
|
||||
delete $ae_loc.files.uploaded_file_kv[hosted_file_obj.hosted_file_id];
|
||||
$ae_loc.files.uploaded_file_kv = {...$ae_loc.files.uploaded_file_kv};
|
||||
|
||||
delete slct_hosted_file_kv[hosted_file_obj.hosted_file_id];
|
||||
slct_hosted_file_id = null;
|
||||
slct_hosted_file_obj = null;
|
||||
} else {
|
||||
$ae_loc.files.uploaded_file_kv[hosted_file_obj.hosted_file_id] = hosted_file_obj;
|
||||
lq__hosted_file_obj_li[hosted_file_obj.hosted_file_id] = hosted_file_obj;
|
||||
|
||||
slct_hosted_file_kv[hosted_file_obj.hosted_file_id] = hosted_file_obj;
|
||||
slct_hosted_file_id = hosted_file_obj.hosted_file_id;
|
||||
slct_hosted_file_obj = hosted_file_obj;
|
||||
}
|
||||
log_lvl = 1;
|
||||
if (log_lvl) {
|
||||
console.log(`slct_hosted_file_kv:`, slct_hosted_file_kv);
|
||||
}
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-secondary hover:preset-filled-secondary-500"
|
||||
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>
|
||||
<span class="hidden">Remove</span>
|
||||
{:else}
|
||||
<span class="fas fa-plus-circle m-1"></span>
|
||||
<span class="hidden">Add</span>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
disabled={!$ae_loc.administrator_access}
|
||||
onclick={() => {
|
||||
if (!confirm(`Are you sure you want to delete this hosted file?\n${hosted_file_obj.filename} [${hosted_file_obj.hosted_file_id_random}]`)) {return false;}
|
||||
|
||||
ae_promises.delete__hosted_file_obj = core_func.delete_ae_obj_id__hosted_file({
|
||||
api_cfg: $ae_api,
|
||||
hosted_file_id: hosted_file_obj.hosted_file_id_random,
|
||||
link_to_type: link_to_type,
|
||||
link_to_id: link_to_id,
|
||||
rm_orphan: true,
|
||||
fake_delete: false,
|
||||
log_lvl: 1
|
||||
})
|
||||
}}
|
||||
class:hidden={!$ae_loc.administrator_access}
|
||||
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>
|
||||
<span class="hidden">Delete</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
disabled={!$ae_loc.administrator_access}
|
||||
class:hidden={!$ae_loc.administrator_access}
|
||||
onclick={() => {
|
||||
// This should show/hide the editable fields for the file. This might need to be in a little modal.
|
||||
}}
|
||||
|
||||
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500"
|
||||
title="Edit file details"
|
||||
>
|
||||
<span class="fas fa-edit m-1"></span>
|
||||
<span class="hidden">Edit</span>
|
||||
</button>
|
||||
|
||||
<span class="text-xs text-gray-500">
|
||||
<span class="fas fa-calendar-day mx-1"></span>
|
||||
<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>
|
||||
Updated on
|
||||
{ae_util.iso_datetime_formatter(hosted_file_obj.updated_on, 'datetime_medium_sec')}
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<span class="text-sm font-semibold">
|
||||
<!-- <a href="" class="underline text-blue-500"> -->
|
||||
{hosted_file_obj.filename}
|
||||
<!-- </a> -->
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
{ae_util.format_bytes(hosted_file_obj.size)}
|
||||
</span>
|
||||
|
||||
<span
|
||||
class:hidden={!$ae_loc.edit_mode}
|
||||
class="text-xs text-gray-500"
|
||||
>
|
||||
{hosted_file_obj.hash_sha256?.slice(0, 5)}...
|
||||
</span>
|
||||
|
||||
</li>
|
||||
{/each}
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
|
||||
{:else}
|
||||
|
||||
<p
|
||||
class="w-96 text-center text-gray-500"
|
||||
class:hidden={display_mode != 'default'}
|
||||
>
|
||||
No files available to display
|
||||
</p>
|
||||
|
||||
{/if}
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
160
src/lib/elements/element_manage_hosted_file_li_all.svelte
Normal file
160
src/lib/elements/element_manage_hosted_file_li_all.svelte
Normal file
@@ -0,0 +1,160 @@
|
||||
<script lang="ts">
|
||||
// NEW NEW NEW: 2025-01-07
|
||||
import { liveQuery } from "dexie";
|
||||
|
||||
import type { key_val } from '$lib/ae_stores';
|
||||
// import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
// import { api } from '$lib/api';
|
||||
// import Element_ae_crud from '$lib/element_ae_crud.svelte';
|
||||
// import Element_data_store from '$lib/element_data_store_v2.svelte';
|
||||
import Element_manage_hosted_file_li from '$lib/element_manage_hosted_file_li.svelte';
|
||||
|
||||
// import { core_func } from '$lib/ae_core_functions';
|
||||
// import { ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/ae_stores';
|
||||
import { db_core } from "$lib/ae_core/db_core";
|
||||
// import { events_loc, events_sess, events_slct, events_trigger } from '$lib/ae_events_stores';
|
||||
|
||||
interface Props {
|
||||
// import { events_func } from '$lib/ae_events_functions';
|
||||
class_li_default?: string; // |Array<string>;
|
||||
class_li?: string; // |Array<string>;
|
||||
link_to_type: string;
|
||||
link_to_id: string;
|
||||
allow_basic?: boolean; // Not used yet
|
||||
allow_moderator?: boolean; // Not used yet
|
||||
display_mode?: string; // 'default', 'compact', 'minimal', 'launcher'
|
||||
max_file_count?: number;
|
||||
file_type?: string; // 'image', 'video', 'audio', 'document', 'other'
|
||||
slct_hosted_file_kv?: key_val;
|
||||
slct_hosted_file_id?: any;
|
||||
slct_hosted_file_obj?: any;
|
||||
}
|
||||
|
||||
let {
|
||||
class_li_default,
|
||||
class_li,
|
||||
link_to_type,
|
||||
link_to_id,
|
||||
allow_basic = false,
|
||||
allow_moderator = false,
|
||||
display_mode = 'default',
|
||||
max_file_count = 49,
|
||||
file_type = 'all',
|
||||
slct_hosted_file_kv = $bindable({}),
|
||||
slct_hosted_file_id = $bindable(null),
|
||||
slct_hosted_file_obj = $bindable(null)
|
||||
}: Props = $props();
|
||||
|
||||
console.log(`HERE HERE HERE HERE: link_to_type: ${link_to_type} link_to_id: ${link_to_id}`);
|
||||
|
||||
// export let show_convert_btn: null|boolean = null;
|
||||
|
||||
// let ae_placeholder_li: key_val = {};
|
||||
// let ae_promises: key_val = {};
|
||||
let ae_tmp: key_val = {};
|
||||
ae_tmp.show__file_li = true;
|
||||
ae_tmp.show__direct_download = false;
|
||||
// let ae_triggers: key_val = {};
|
||||
|
||||
let dq__where_val: string = `${link_to_type}_id`; // no more _random ???
|
||||
let dq__where_eq_val: string = link_to_id;
|
||||
|
||||
// This should include all files that are associated with an object (event, location, session, presenter, etc.)
|
||||
// I am not sure why, but doing reverse() and then sortBy() seems to sort in descending order.
|
||||
let lq__hosted_file_obj_li = $derived(liveQuery(async () => {
|
||||
// console.log(`dq__where_val: ${dq__where_val}`);
|
||||
// console.log(`dq__where_eq_val: ${dq__where_eq_val}`);
|
||||
let results = null;
|
||||
if (file_type == 'all' || !file_type) {
|
||||
results = await db_core.file
|
||||
.where(dq__where_val)
|
||||
.equals(dq__where_eq_val)
|
||||
.sortBy('created_on')
|
||||
;
|
||||
} else if (file_type == 'video') {
|
||||
// Handle video/mp4, video/mov, video/webm. If the content type is prefixed with "video/", then it is a video file.
|
||||
let extension = 'mp4';
|
||||
results = await db_core.file
|
||||
.where(dq__where_val)
|
||||
.equals(dq__where_eq_val)
|
||||
// .and((x) => (x.extension == extension))
|
||||
// .and((x) => (x.content_type == `video/${extension}`))
|
||||
.and((x) => (x.content_type.startsWith('video/')))
|
||||
// .reverse()
|
||||
.sortBy('created_on')
|
||||
// .toArray()
|
||||
;
|
||||
} else if (file_type == 'audio') {
|
||||
// Handle audio/mp3, audio/wav, audio/ogg. If the content type is prefixed with "audio/", then it is an audio file.
|
||||
results = await db_core.file
|
||||
.where(dq__where_val)
|
||||
.equals(dq__where_eq_val)
|
||||
.and((x) => (x.content_type.startsWith('audio/')))
|
||||
.sortBy('created_on')
|
||||
;
|
||||
} else if (file_type == 'image') {
|
||||
// Handle image/jpeg, image/png, image/gif. If the content type is prefixed with "image/", then it is an image file.
|
||||
results = await db_core.file
|
||||
.where(dq__where_val)
|
||||
.equals(dq__where_eq_val)
|
||||
.and((x) => (x.content_type.startsWith('image/')))
|
||||
.sortBy('created_on')
|
||||
;
|
||||
} else if (file_type == 'document') {
|
||||
// Handle application/pdf, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.presentation. If the content type is prefixed with "application/", then it is a document file.
|
||||
results = await db_core.file
|
||||
.where(dq__where_val)
|
||||
.equals(dq__where_eq_val)
|
||||
.and((x) => (x.content_type.startsWith('application/')))
|
||||
.sortBy('created_on')
|
||||
;
|
||||
} else {
|
||||
results = await db_core.file
|
||||
.where(dq__where_val)
|
||||
.equals(dq__where_eq_val)
|
||||
// .reverse()
|
||||
.sortBy('created_on')
|
||||
;
|
||||
}
|
||||
|
||||
return results;
|
||||
}));
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<!-- {#if lq__hosted_file_obj_li}
|
||||
<h3 class="h3">{lq__hosted_file_obj_li.length}× files clipped</h3>
|
||||
<div class="container">
|
||||
<ol>
|
||||
{#each lq__hosted_file_obj_li as hosted_file_obj}
|
||||
<li>
|
||||
{hosted_file_obj.filename}
|
||||
</li>
|
||||
{/each}
|
||||
</ol>
|
||||
</div>
|
||||
{:else}
|
||||
<p>No files found</p>
|
||||
{/if} -->
|
||||
|
||||
|
||||
{#if lq__hosted_file_obj_li}
|
||||
<Element_manage_hosted_file_li
|
||||
link_to_type={link_to_type}
|
||||
link_to_id={link_to_id}
|
||||
lq__hosted_file_obj_li={lq__hosted_file_obj_li}
|
||||
class_li_default={class_li_default}
|
||||
class_li={class_li}
|
||||
display_mode={display_mode}
|
||||
bind:max_file_count={max_file_count}
|
||||
bind:file_type={file_type}
|
||||
bind:slct_hosted_file_kv={slct_hosted_file_kv}
|
||||
bind:slct_hosted_file_id={slct_hosted_file_id}
|
||||
bind:slct_hosted_file_obj={slct_hosted_file_obj}
|
||||
/>
|
||||
<!-- allow_basic={allow_basic} -->
|
||||
<!-- allow_moderator={allow_moderator} -->
|
||||
{:else}
|
||||
<p>No files found</p>
|
||||
{/if}
|
||||
150
src/lib/elements/element_obj_tbl_row.svelte
Normal file
150
src/lib/elements/element_obj_tbl_row.svelte
Normal file
@@ -0,0 +1,150 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
// This (ae) is only used for utilities
|
||||
// import { ae } from 'aether_npm_lib';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
|
||||
// These (slct_*) are only used internally for this component. Not needed???
|
||||
// import { slct_obj_id, slct_obj_li_type, slct_obj_type } from '../admin/stores_admin.js';
|
||||
|
||||
// Should these slct_* be exported???
|
||||
let slct_obj_id = null;
|
||||
let slct_obj_li_type = null;
|
||||
let slct_obj_type = null;
|
||||
|
||||
export let row_header: boolean = false;
|
||||
export let primary_obj_li_type: string = slct_obj_li_type; // account, person, user, event, event_session, membership_person
|
||||
export let obj = null;
|
||||
console.log(obj);
|
||||
console.log(typeof obj);
|
||||
|
||||
|
||||
onMount(() => {
|
||||
console.log('** Element Mounted: ** Element Object Table Row');
|
||||
|
||||
if (obj) {
|
||||
console.log('Table Row Object:', obj);
|
||||
// console.log(typeof obj);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
/* BEGIN: Handle requests (archive, create, hide, remove, select, update, POST, PATCH, GET, DELETE) */
|
||||
/* END: Handle requests (archive, create, hide, remove, select, update, POST, PATCH, GET, DELETE) */
|
||||
|
||||
/* BEGIN: Handle other local actions (show/hide form, process data) */
|
||||
/* END: Handle other local actions (show/hide form, process data) */
|
||||
|
||||
/* BEGIN: Handle children events (archived, canceled, closed, created, deleted, hidden, updated) */
|
||||
/* END: Handle children events (archived, canceled, closed, created, deleted, hidden, updated) */
|
||||
</script>
|
||||
|
||||
|
||||
<tr>
|
||||
{#if obj != null && typeof obj == 'object'}
|
||||
{#each Object.entries(obj) as [obj_prop_name, obj_prop_value]}
|
||||
|
||||
<!-- NEED TO ADD A CHECK IF:
|
||||
NOTE:
|
||||
NOTE:
|
||||
|
||||
obj.id_random and obj.name then replace the ID field column with a link using obj.name
|
||||
|
||||
This should probably go outside/before this tr loop.
|
||||
|
||||
NOTE:
|
||||
NOTE:
|
||||
-->
|
||||
|
||||
{#if obj_prop_name.endsWith('_id_random') || obj_prop_name == 'for_type' || obj_prop_name == 'for_id'}
|
||||
{#if row_header}
|
||||
<th data-obj_type={primary_obj_li_type} data-obj_prop_name={obj_prop_name} on:click={() => primary_obj_li_type=obj_prop_name.replace('_id_random', '')}>
|
||||
{ae_util.set_obj_prop_display_name({prop_name: obj_prop_name, obj_type: primary_obj_li_type})}
|
||||
</th>
|
||||
{:else}
|
||||
<td
|
||||
data-obj_type={primary_obj_li_type}
|
||||
data-obj_prop_name={obj_prop_name}
|
||||
on:click={() => {
|
||||
slct_obj_type=obj_prop_name.replace('_id_random', '');
|
||||
slct_obj_id=obj_prop_value;
|
||||
}}
|
||||
on:keypress={() => {
|
||||
slct_obj_type=obj_prop_name.replace('_id_random', '');
|
||||
slct_obj_id=obj_prop_value;
|
||||
}}
|
||||
>
|
||||
<!-- {obj_prop_value} -->
|
||||
|
||||
<!-- {#if (obj_prop_value && obj_prop_value.length > 25)}
|
||||
{obj_prop_value.substring(0,25)}
|
||||
{:else} -->
|
||||
{#if obj_prop_value}
|
||||
<a href="/{ae_util.return_obj_type_path({obj_type_prop_name: obj_prop_name})}/{obj_prop_value}">
|
||||
{obj_prop_value.substring(0,25)}
|
||||
</a>
|
||||
{:else}
|
||||
<!-- {obj_prop_value} -->
|
||||
<span class="fs_smaller">-- None --</span>
|
||||
{/if}
|
||||
|
||||
</td>
|
||||
{/if}
|
||||
{:else if obj_prop_name.endsWith('[URL]')}
|
||||
{#if row_header}
|
||||
<th data-obj_type={primary_obj_li_type} data-obj_prop_name={obj_prop_name} on:click={() => primary_obj_li_type=obj_prop_name.replaceAll('[URL]', '')}>
|
||||
{ae_util.set_obj_prop_display_name({prop_name: obj_prop_name.replaceAll('[URL]', ''), obj_type: primary_obj_li_type})}
|
||||
</th>
|
||||
{:else}
|
||||
<td
|
||||
data-obj_type={primary_obj_li_type}
|
||||
data-obj_prop_name={obj_prop_name}
|
||||
on:click={() => {
|
||||
slct_obj_type=obj_prop_name.replaceAll('[URL]', '');
|
||||
slct_obj_id=obj_prop_value;
|
||||
}}
|
||||
on:keypress={() => {
|
||||
slct_obj_type=obj_prop_name.replaceAll('[URL]', '');
|
||||
slct_obj_id=obj_prop_value;
|
||||
}}
|
||||
>
|
||||
<a href="{obj_prop_value}">{obj_prop_value}</a>
|
||||
</td>
|
||||
{/if}
|
||||
{:else}
|
||||
{#if row_header}
|
||||
<th data-obj_type={primary_obj_li_type} data-obj_prop_name={obj_prop_name}>
|
||||
{ae_util.set_obj_prop_display_name({prop_name: obj_prop_name, obj_type: primary_obj_li_type})}
|
||||
</th>
|
||||
{:else}
|
||||
<td data-obj_type={primary_obj_li_type} data-obj_prop_name={obj_prop_name}>
|
||||
{#if (obj_prop_value)}
|
||||
{#if (obj_prop_value && obj_prop_value.length > 25)}
|
||||
{obj_prop_value.substring(0,25)} ...
|
||||
{:else}
|
||||
{obj_prop_value}
|
||||
{/if}
|
||||
{:else}
|
||||
<span class="fs_smaller">-- None --</span>
|
||||
{/if}
|
||||
</td>
|
||||
{/if}
|
||||
{/if}
|
||||
<!-- <td data-obj_type={primary_obj_li_type} data-obj_prop_name={obj_prop_name}>{obj_prop_value}</td> -->
|
||||
{/each}
|
||||
{:else}
|
||||
<!-- This should never happen -->
|
||||
<td class="fs_smaller" colspan="100"
|
||||
>
|
||||
-- Not Set --
|
||||
</td>
|
||||
{/if}
|
||||
</tr>
|
||||
|
||||
|
||||
<style lang="postcss">
|
||||
</style>
|
||||
146
src/lib/elements/element_sql_qry.svelte
Normal file
146
src/lib/elements/element_sql_qry.svelte
Normal file
@@ -0,0 +1,146 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
// *** Import Aether core variables and functions
|
||||
import type { key_val } from '$lib/ae_stores';
|
||||
// import { api, Element_obj_tbl_row } from 'aether_npm_lib';
|
||||
import Element_obj_tbl_row from '$lib/element_obj_tbl_row.svelte';
|
||||
import { post_object } from '$lib/ae_api/api_post_object';
|
||||
|
||||
// *** Import Aether core components
|
||||
// import Element_obj_tbl_row from './element_obj_tbl_row.svelte';
|
||||
|
||||
// *** Import Aether module variables and functions
|
||||
|
||||
// *** Import Aether module components
|
||||
|
||||
// *** Export/Exposed variables and functions for component
|
||||
export let api_cfg: any;
|
||||
export let show_textarea = true;
|
||||
export let button_label = 'Run SQL!';
|
||||
export let show_record_count = true;
|
||||
export let remove_breaks = false;
|
||||
export let run_on_load = false;
|
||||
|
||||
export let sql_statement: string;
|
||||
export let sql_data = null;
|
||||
export let as_list = false;
|
||||
export let log_lvl: number = 0;
|
||||
|
||||
// *** Set initial variables
|
||||
let ae_promises: key_val = {};
|
||||
let sql_qry_result: any = null;
|
||||
|
||||
|
||||
onMount(() => {
|
||||
console.log('** Element Mounted: ** Element SQL Query');
|
||||
|
||||
if (run_on_load) {
|
||||
console.log('Run On Load');
|
||||
let result = handle_run_sql(sql_statement, sql_data, as_list, log_lvl)
|
||||
.then((qry_result) => {
|
||||
console.log('SQL Query Result:', qry_result);
|
||||
sql_qry_result = qry_result;
|
||||
return qry_result;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// const dispatch = createEventDispatcher();
|
||||
|
||||
async function handle_run_sql(qry, data, as_list=false, log_lvl=0) {
|
||||
console.log('*** handle_run_sql() ***');
|
||||
|
||||
let sql_qry_data: key_val = {};
|
||||
|
||||
let endpoint = '/sql/select';
|
||||
let params: key_val = {};
|
||||
if (as_list) {
|
||||
params['as_list'] = true;
|
||||
}
|
||||
if (log_lvl) {
|
||||
console.log('Params:', params);
|
||||
}
|
||||
|
||||
if (qry) {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (log_lvl>1) {
|
||||
console.log('Qry:', qry);
|
||||
}
|
||||
|
||||
if (remove_breaks) {
|
||||
sql_qry_data['sql_qry'] = qry.replace(/(\r\n|\n|\r)/gm, "");
|
||||
} else {
|
||||
sql_qry_data['sql_qry'] = qry;
|
||||
}
|
||||
|
||||
sql_qry_data['sql_data'] = data;
|
||||
|
||||
if (log_lvl) {
|
||||
console.log('SQL Qry Data:', sql_qry_data);
|
||||
}
|
||||
|
||||
ae_promises.sql_qry_promise = await post_object({api_cfg: api_cfg, endpoint: endpoint, params: params, data: sql_qry_data, log_lvl: log_lvl});
|
||||
|
||||
if (log_lvl) {
|
||||
console.log('SQL Query Results', ae_promises.sql_qry_promise);
|
||||
}
|
||||
|
||||
return ae_promises.sql_qry_promise;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<section id="sql_qry" class="sql_qry">
|
||||
{#if show_textarea}
|
||||
<textarea
|
||||
class="textarea"
|
||||
bind:value={sql_statement}></textarea>
|
||||
{/if}
|
||||
|
||||
<div class="text-center">
|
||||
<button
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
sql_qry_result = await handle_run_sql(sql_statement, sql_data, as_list, log_lvl);
|
||||
}}
|
||||
class="btn btn-md preset-tonal-primary hover:preset-tonal-primary border border-primary-500"
|
||||
>
|
||||
{button_label}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if show_record_count && sql_qry_result && sql_qry_result.length}
|
||||
<div>
|
||||
Record count: <strong>{sql_qry_result.length}</strong>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="sql_statement_result">
|
||||
{#await ae_promises.sql_qry_promise}
|
||||
Getting results...
|
||||
{:then}
|
||||
{#if sql_qry_result && sql_qry_result.length}
|
||||
<table class="table table-compact table-bordered table-striped min-w-min sql_qry_result text-xs">
|
||||
<Element_obj_tbl_row row_header={true} obj={sql_qry_result[0]} primary_obj_li_type='' />
|
||||
{#each sql_qry_result as record}
|
||||
<Element_obj_tbl_row obj={record} primary_obj_li_type='' />
|
||||
{/each}
|
||||
</table>
|
||||
{:else}
|
||||
<div>Nothing to show yet...</div>
|
||||
{/if}
|
||||
|
||||
{/await}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<style lang="postcss">
|
||||
/* .sql_qry textarea {
|
||||
width: 100%;
|
||||
height: 8em;
|
||||
} */
|
||||
</style>
|
||||
113
src/lib/elements/element_tiptap_editor.scss
Normal file
113
src/lib/elements/element_tiptap_editor.scss
Normal file
@@ -0,0 +1,113 @@
|
||||
/* Basic editor styles */
|
||||
.tiptap {
|
||||
:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* Link styles */
|
||||
a {
|
||||
color: var(--purple);
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
color: var(--purple-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
/* List styles */
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
margin-left: 1.5rem;
|
||||
// border: solid thin red;
|
||||
}
|
||||
ol {
|
||||
list-style-type: decimal;
|
||||
margin-left: 1.5rem;
|
||||
// border: solid thin red;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
margin: 1.25rem 1rem 1.25rem 0.4rem;
|
||||
|
||||
li p {
|
||||
margin-top: 0.25em;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
/* Heading styles */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
margin-top: 2.5rem;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
margin-top: 3.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Code and preformatted text styles */
|
||||
code {
|
||||
background-color: var(--purple-light);
|
||||
border-radius: 0.4rem;
|
||||
color: var(--black);
|
||||
font-size: 0.85rem;
|
||||
padding: 0.25em 0.3em;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: var(--black);
|
||||
border-radius: 0.5rem;
|
||||
color: var(--white);
|
||||
font-family: 'JetBrainsMono', monospace;
|
||||
margin: 1.5rem 0;
|
||||
padding: 0.75rem 1rem;
|
||||
|
||||
code {
|
||||
background: none;
|
||||
color: inherit;
|
||||
font-size: 0.8rem;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 3px solid var(--gray-3);
|
||||
margin: 1.5rem 0;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--gray-2);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
}
|
||||
160
src/lib/elements/element_tiptap_editor.svelte
Normal file
160
src/lib/elements/element_tiptap_editor.svelte
Normal file
@@ -0,0 +1,160 @@
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
// import { fade } from 'svelte/transition'
|
||||
// import { cubicOut } from 'svelte/easing';
|
||||
|
||||
import ShadEditor from '$lib/components/shad-editor/shad-editor.svelte';
|
||||
|
||||
|
||||
// Import Tiptap related modules
|
||||
// import { Editor } from "@tiptap/core";
|
||||
// import StarterKit from "@tiptap/starter-kit";
|
||||
// import Bold from '@tiptap/extension-bold';
|
||||
// import BulletList from '@tiptap/extension-bullet-list';
|
||||
// import CodeBlock from '@tiptap/extension-code-block';
|
||||
// import Code from '@tiptap/extension-code';
|
||||
// import Color from '@tiptap/extension-color';
|
||||
// import Document from '@tiptap/extension-document';
|
||||
// import Heading from '@tiptap/extension-heading';
|
||||
// import Highlight from '@tiptap/extension-highlight';
|
||||
// import History from '@tiptap/extension-history';
|
||||
// import Italic from '@tiptap/extension-italic';
|
||||
// import Link from '@tiptap/extension-link';
|
||||
// import ListItem from '@tiptap/extension-list-item';
|
||||
// import OrderedList from '@tiptap/extension-ordered-list';
|
||||
// import Paragraph from '@tiptap/extension-paragraph';
|
||||
// import Strike from '@tiptap/extension-strike';
|
||||
// import Text from '@tiptap/extension-text';
|
||||
// import TextStyle from '@tiptap/extension-text-style';
|
||||
// import Typography from '@tiptap/extension-typography';
|
||||
// import Underline from '@tiptap/extension-underline';
|
||||
|
||||
import './element_tiptap_editor.scss';
|
||||
|
||||
// https://tiptap.dev/docs/examples/basics/default-text-editor
|
||||
// https://tiptap.dev/docs/examples/basics/formatting
|
||||
// <code class="language-css">
|
||||
|
||||
export let html_text: string = '';
|
||||
export let default_minimal: boolean = false;
|
||||
export let show_toolbar: boolean = true;
|
||||
export let placeholder: string = 'Type your text here...';
|
||||
export let changed: boolean = false;
|
||||
|
||||
export let classes: string = '';
|
||||
|
||||
if (default_minimal) {
|
||||
show_toolbar = false;
|
||||
}
|
||||
|
||||
// export let html_text: string = `
|
||||
// <h2>
|
||||
// Hi there,
|
||||
// </h2>
|
||||
// <p>
|
||||
// this is a <em>basic</em> example of <strong>Tiptap</strong>. Sure, there are all kind of basic text styles you’d probably expect from a text editor. But wait until you see the lists:
|
||||
// </p>
|
||||
// <ul>
|
||||
// <li>
|
||||
// That’s a bullet list with one …
|
||||
// </li>
|
||||
// <li>
|
||||
// … or two list items.
|
||||
// </li>
|
||||
// </ul>
|
||||
// <p>
|
||||
// Isn’t that great? And all of that is editable. But wait, there’s more. Let’s try a code block:
|
||||
// </p>
|
||||
// <pre><code class="language-css">body {
|
||||
// display: none;
|
||||
// }</code></pre>
|
||||
// <p>
|
||||
// I know, I know, this is impressive. It’s only the tip of the iceberg though. Give it a try and click a little bit around. Don’t forget to check the other examples too.
|
||||
// </p>
|
||||
// <blockquote>
|
||||
// Wow, that’s amazing. Good work, boy! 👏
|
||||
// <br />
|
||||
// — Mom
|
||||
// </blockquote>
|
||||
// `;
|
||||
|
||||
let element: HTMLDivElement;
|
||||
let editor: any;
|
||||
// More default options should be defined later.
|
||||
// minimal, basic, full
|
||||
|
||||
export let show_button_kv: any;
|
||||
|
||||
// export let new_json = editor?.getJSON();
|
||||
export let new_html: string = '';
|
||||
let orig_html: string = html_text;
|
||||
|
||||
onMount(() => {
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
});
|
||||
|
||||
$: if (html_text !== orig_html && html_text !== '<p></p>') {
|
||||
console.log('html_text changed:', html_text);
|
||||
console.log('orig_html:', orig_html);
|
||||
changed = true;
|
||||
} else {
|
||||
changed = false;
|
||||
}
|
||||
|
||||
let mouse_entered_timer: any;
|
||||
let mouse_enter_wait: number = 500;
|
||||
let mouse_leave_wait: number = 2000;
|
||||
</script>
|
||||
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
on:click={() => {
|
||||
if (default_minimal) {
|
||||
show_toolbar = true;
|
||||
}
|
||||
}}
|
||||
on:mouseleave={() => {
|
||||
clearTimeout(mouse_entered_timer);
|
||||
|
||||
mouse_entered_timer = setTimeout(() => {
|
||||
if (default_minimal) {
|
||||
show_toolbar = false;
|
||||
}
|
||||
}, mouse_leave_wait);
|
||||
}}
|
||||
on:mouseenter={() => {
|
||||
clearTimeout(mouse_entered_timer);
|
||||
|
||||
mouse_entered_timer = setTimeout(() => {
|
||||
if (default_minimal) {
|
||||
show_toolbar = true;
|
||||
}
|
||||
}, mouse_enter_wait);
|
||||
}}
|
||||
class="block w-full h-full {classes}"
|
||||
>
|
||||
<ShadEditor
|
||||
class="p-1 transition-all duration-1000"
|
||||
bind:content={html_text}
|
||||
bind:new_html={new_html}
|
||||
placeholder={placeholder}
|
||||
show_toolbar={show_toolbar}
|
||||
show_button_kv={show_button_kv}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style lang="css">
|
||||
/*
|
||||
// :global(.ProseMirror) {
|
||||
// padding: .25em;
|
||||
// }
|
||||
*/
|
||||
:global(.ProseMirror-focused) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
579
src/lib/elements/element_websocket_v2.svelte
Normal file
579
src/lib/elements/element_websocket_v2.svelte
Normal file
@@ -0,0 +1,579 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
log_lvl?: number;
|
||||
ws_connect?: boolean; // If true then we should be trying to connect to the WS server.
|
||||
ws_connect_status?: null|string;
|
||||
ws_server?: string;
|
||||
ws_retry_delay?: number;
|
||||
ws_retry_count?: number;
|
||||
base_url?: any;
|
||||
group_id?: string;
|
||||
client_id?: any;
|
||||
cmd?: null|string;
|
||||
msg?: null|string;
|
||||
type?: null|string; // msg, cmd, json, hello, bye
|
||||
trigger_send?: any;
|
||||
trigger_connect?: boolean;
|
||||
trigger_disconnect?: boolean;
|
||||
classes?: string;
|
||||
hide__ws_element?: boolean;
|
||||
hide__ws_form?: boolean;
|
||||
hide__ws_messages?: boolean;
|
||||
hide__ws_commands?: boolean;
|
||||
|
||||
ws_conn_status?: any;
|
||||
ws_recv_status?: any;
|
||||
ws_sent_status?: any;
|
||||
}
|
||||
|
||||
let {
|
||||
log_lvl = 0,
|
||||
ws_connect = $bindable(false),
|
||||
ws_connect_status = $bindable(null),
|
||||
ws_server = 'dev-api.oneskyit.com',
|
||||
ws_retry_delay = 3500,
|
||||
ws_retry_count = 0,
|
||||
base_url = `wss://${ws_server}/ws`,
|
||||
group_id = $bindable('ae-grp-99'),
|
||||
client_id = $bindable(Date.now()),
|
||||
cmd = $bindable(null),
|
||||
msg = $bindable(null),
|
||||
type = null,
|
||||
trigger_send = $bindable(null),
|
||||
trigger_connect = $bindable(false),
|
||||
trigger_disconnect = $bindable(false),
|
||||
classes = 'container p-1 bg-pink-100 text-xs mx-auto pb-16 mb-20 sm:mb-12 md:mb-8',
|
||||
hide__ws_element = $bindable(false),
|
||||
hide__ws_form = $bindable(true),
|
||||
hide__ws_messages = $bindable(false),
|
||||
hide__ws_commands = $bindable(false),
|
||||
|
||||
ws_conn_status = $bindable(null),
|
||||
ws_recv_status = $bindable(null),
|
||||
ws_sent_status = $bindable(null)
|
||||
}: Props = $props();
|
||||
|
||||
|
||||
// import { run, preventDefault } from 'svelte/legacy';
|
||||
// import { createEventDispatcher, onMount } from 'svelte';
|
||||
|
||||
|
||||
// *** Set initial variables
|
||||
// const dispatch = createEventDispatcher();
|
||||
|
||||
// JSON formatted data
|
||||
let ws_data = $state({
|
||||
'client_id': null, // The device or browser ID if available.
|
||||
// 'src': null, // Sending client
|
||||
// 'account_id': null, // Essentially the person ID or user ID if available.
|
||||
// 'dest': null, // Destination client
|
||||
'target': 'echo', // echo, dm (direct), grp (group), all (broadcast)
|
||||
'type': 'cmd', // msg, cmd, json, hello, bye
|
||||
// 'grp': null, // Destination group
|
||||
'msg': null, // Message string
|
||||
'cmd': null, // Command string
|
||||
// 'data': null,
|
||||
// 'b64': null,
|
||||
})
|
||||
|
||||
let ws_received_list_cmd: string[] = $state([]);
|
||||
let ws_received_list_other: any[] = $state([]);
|
||||
let ws_received_list_msg: string[] = [];
|
||||
|
||||
|
||||
// onMount(async () => {
|
||||
// console.log('** Component Mounted: ** Element Websocket v2');
|
||||
// });
|
||||
|
||||
// *** Functions and Logic
|
||||
|
||||
function ws_connect_group_id({group_id, client_id}) {
|
||||
if (!group_id) {
|
||||
group_id = 'ae-grp-99';
|
||||
console.log(`WS: No group_id specified! Setting to default: ${group_id}`);
|
||||
return false;
|
||||
}
|
||||
if (!client_id) {
|
||||
client_id = Date.now();
|
||||
console.log(`WS: No client_id specified! Setting to current timestamp: ${client_id}`);
|
||||
// return false;
|
||||
}
|
||||
console.log(`WS Connect URL: ${base_url}/group/${group_id}/client/${client_id}`);
|
||||
let ws_connection = new WebSocket(`${base_url}/group/${group_id}/client/${client_id}`);
|
||||
|
||||
ws_connection.onopen = function() {
|
||||
console.log('WS: connected');
|
||||
|
||||
ws_connect_status = 'connected';
|
||||
|
||||
// dispatch('ws_conn', {
|
||||
// 'status': 'connected'
|
||||
// });
|
||||
ws_conn_status = 'connected';
|
||||
|
||||
ws_retry_count = 0;
|
||||
|
||||
// ws_connection.send(JSON.stringify({
|
||||
// client_id: client_id,
|
||||
// target: 'echo',
|
||||
// type: 'hello',
|
||||
// group_id: group_id,
|
||||
// msg: 'You are connected!'
|
||||
// }));
|
||||
|
||||
ws_connection.send(JSON.stringify({
|
||||
client_id: client_id,
|
||||
target: 'all',
|
||||
type: 'hello',
|
||||
group_id: group_id,
|
||||
msg: `Client ${client_id.toString().slice(-5)} connected!`
|
||||
}));
|
||||
};
|
||||
|
||||
ws_connection.onmessage = function(event) {
|
||||
if (log_lvl) {
|
||||
console.log('WS: message received', event);
|
||||
}
|
||||
|
||||
let ws_recv_data = JSON.parse(event.data);
|
||||
if (log_lvl) {
|
||||
console.log('WS: Received data:', ws_recv_data);
|
||||
}
|
||||
|
||||
if (client_id == ws_recv_data.client_id) {
|
||||
if (log_lvl) {
|
||||
console.log('WS: Message received was sent by self and is an echo that can be ignored.');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ws_recv_data.type == 'cmd') {
|
||||
console.log(`WS: Type CMD: ${ws_recv_data.cmd}`);
|
||||
ws_received_list_cmd.unshift(ws_recv_data); // Add to the beginning of the list
|
||||
// ws_received_list_cmd.push(ws_recv_data); // Add to the end of the list
|
||||
ws_received_list_cmd = ws_received_list_cmd; // trigger Svelte update -2024-10-04
|
||||
} else {
|
||||
console.log('WS: Type other');
|
||||
ws_received_list_other.unshift(ws_recv_data); // Add to the beginning of the list
|
||||
// ws_received_list_other.push(ws_recv_data); // Add to the end of the list
|
||||
ws_received_list_other = ws_received_list_other; // trigger Svelte update -2024-10-04
|
||||
}
|
||||
|
||||
// dispatch('ws_recv', {
|
||||
// // 'client_id': ws_data.client_id, // The device or browser ID if available.
|
||||
// 'src': ws_recv_data.client_id, // The device or browser ID if available.
|
||||
// // 'account_id': ws_recv_data.account_id, // Essentially the person ID or user ID if available.
|
||||
// 'dest': group_id, // Destination client
|
||||
// 'target': ws_recv_data.target, // echo, dm (direct), grp (group), all (broadcast)
|
||||
// 'type': ws_recv_data.type, // Message type (msg, cmd, json, hello, bye)
|
||||
// // 'grp': ws_recv_data.grp, // Destination group
|
||||
// 'msg': ws_recv_data.msg, // Message string
|
||||
// 'cmd': ws_recv_data.cmd, // Command string
|
||||
// });
|
||||
ws_recv_status = {
|
||||
// 'client_id': ws_data.client_id, // The device or browser ID if available.
|
||||
'src': ws_recv_data.client_id, // The device or browser ID if available.
|
||||
// 'account_id': ws_recv_data.account_id, // Essentially the person ID or user ID if available.
|
||||
'dest': group_id, // Destination client
|
||||
'target': ws_recv_data.target, // echo, dm (direct), grp (group), all (broadcast)
|
||||
'type': ws_recv_data.type, // Message type (msg, cmd, json, hello, bye)
|
||||
// 'grp': ws_recv_data.grp, // Destination group
|
||||
'msg': ws_recv_data.msg, // Message string
|
||||
'cmd': ws_recv_data.cmd, // Command string
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
ws_connection.onclose = function(event) {
|
||||
console.log('WS: connection closed');
|
||||
|
||||
ws_connection.send(JSON.stringify({
|
||||
client_id: client_id,
|
||||
target: 'all',
|
||||
type: 'hello',
|
||||
group_id: group_id,
|
||||
msg: `Client ${client_id} is disconnecting!`
|
||||
}));
|
||||
|
||||
ws_connect_status = 'disconnected';
|
||||
|
||||
let fake_ws_recv_data = {
|
||||
'client_id': client_id,
|
||||
'target': 'local',
|
||||
'type': 'bye',
|
||||
'group_id': group_id,
|
||||
'msg': `LOCAL Client ${client_id} has disconnected!`
|
||||
};
|
||||
ws_received_list_other.unshift(fake_ws_recv_data);
|
||||
ws_received_list_other = ws_received_list_other; // trigger Svelte update -2024-10-04
|
||||
|
||||
// dispatch('ws_conn', {
|
||||
// 'status': 'disconnected'
|
||||
// });
|
||||
ws_conn_status = 'disconnected';
|
||||
|
||||
if (ws_connect) {
|
||||
if (ws_retry_count >= 10) {
|
||||
ws_retry_delay = ws_retry_delay += 4999; // Set to a very long time
|
||||
ws_retry_delay = Math.min(ws_retry_delay, 120000); // Max of 2 minutes
|
||||
console.log(`WS: Retry count exceeded. Increasing the delay. ws_retry_count=${ws_retry_count} ws_retry_delay=${ws_retry_delay}`);
|
||||
}
|
||||
setTimeout(function() {
|
||||
console.log('WS: Disconnected... Try again!');
|
||||
ws_retry_count += 1;
|
||||
ws_connect_group_id({group_id: group_id, client_id: client_id});
|
||||
console.log('WS: Again done?');
|
||||
}, ws_retry_delay);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
ws_connection.onerror = function(event) {
|
||||
console.log('WS: connection error???');
|
||||
|
||||
ws_connection.send(JSON.stringify({
|
||||
client_id: client_id,
|
||||
target: 'all',
|
||||
type: 'hello',
|
||||
group_id: group_id,
|
||||
msg: `Client ${client_id} is having trouble?!`
|
||||
}));
|
||||
};
|
||||
|
||||
// NOTE WARNING: Uncommenting this seems to break FastAPI somehow???
|
||||
// NOTE: from FastAPI log: RuntimeError: Unexpected ASGI message 'websocket.send', after sending 'websocket.close'.
|
||||
// ws_connection.onerror = function(err) {
|
||||
// console.error('WS socket error: ', err.message, 'Closing socket');
|
||||
// // ws_connection.close();
|
||||
// };
|
||||
|
||||
return ws_connection;
|
||||
}
|
||||
|
||||
// Start the WS function
|
||||
let ws_group: any = $state(null);
|
||||
|
||||
|
||||
function handle_send_ws_data() {
|
||||
console.log(ws_data);
|
||||
if (!ws_data) {
|
||||
return false;
|
||||
}
|
||||
if (!ws_group) {
|
||||
console.log('WS: No connection!');
|
||||
return false;
|
||||
}
|
||||
let ws_data_json_str = JSON.stringify(ws_data);
|
||||
let resp = ws_group.send(ws_data_json_str);
|
||||
console.log(`WS: Send data response:`, resp);
|
||||
|
||||
// dispatch('ws_sent', {
|
||||
// // 'client_id': ws_data.client_id, // The device or browser ID if available.
|
||||
// 'src': ws_data.client_id, // The device or browser ID if available.
|
||||
// // 'account_id': ws_data.account_id, // Essentially the person ID or user ID if available.
|
||||
// 'dest': group_id, // Destination client
|
||||
// 'group_id': group_id,
|
||||
// 'target': ws_data.target, // echo, dm (direct), grp (group), all (broadcast)
|
||||
// 'type': ws_data.type, // Message type (msg, cmd, json, hello, bye)
|
||||
// // 'grp': ws_data.grp, // Destination group
|
||||
// 'msg': ws_data.msg, // Message string
|
||||
// 'cmd': ws_data.cmd, // Command string
|
||||
// });
|
||||
ws_sent_status = {
|
||||
// 'client_id': ws_data.client_id, // The device or browser ID if available.
|
||||
'src': ws_data.client_id, // The device or browser ID if available.
|
||||
// 'account_id': ws_data.account_id, // Essentially the person ID or user ID if available.
|
||||
'dest': group_id, // Destination client
|
||||
'group_id': group_id,
|
||||
'target': ws_data.target, // echo, dm (direct), grp (group), all (broadcast)
|
||||
'type': ws_data.type, // Message type (msg, cmd, json, hello, bye)
|
||||
// 'grp': ws_data.grp, // Destination group
|
||||
'msg': ws_data.msg, // Message string
|
||||
'cmd': ws_data.cmd, // Command string
|
||||
};
|
||||
|
||||
cmd = '';
|
||||
msg = '';
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (ws_connect && group_id) {
|
||||
console.log('HERE!!!!!');
|
||||
ws_group = ws_connect_group_id({group_id: group_id, client_id: client_id});
|
||||
// } else if (!ws_connect) {
|
||||
// console.log('HERE!!!!!');
|
||||
// log_lvl = 1;
|
||||
// if (log_lvl) {
|
||||
// console.log(`WS: WS not set to connect. Need to close WS Group connection.`);
|
||||
// }
|
||||
// ws_group?.close();
|
||||
// ws_connect_status = 'disconnected';
|
||||
} else {
|
||||
console.log('HERE!!!!!');
|
||||
log_lvl = 1;
|
||||
if (log_lvl) {
|
||||
console.log(`WS: Not connecting. ws_connect=${ws_connect} group_id=${group_id}`);
|
||||
}
|
||||
ws_group?.close();
|
||||
ws_connect_status = 'disconnected';
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (trigger_send && cmd) {
|
||||
trigger_send = null;
|
||||
console.log('WS: Send triggered!');
|
||||
console.log(cmd);
|
||||
ws_data.target = 'group';
|
||||
ws_data.type = 'cmd';
|
||||
ws_data.cmd = cmd;
|
||||
handle_send_ws_data();
|
||||
cmd = '';
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (trigger_connect) {
|
||||
trigger_connect = false;
|
||||
if (!ws_connect) {
|
||||
ws_connect = true;
|
||||
}
|
||||
console.log('WS: Connect triggered!');
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (trigger_disconnect) {
|
||||
console.log('WS: Disconnect triggered!');
|
||||
trigger_disconnect = false;
|
||||
if (ws_connect) {
|
||||
ws_connect = false;
|
||||
}
|
||||
if (ws_group) {
|
||||
ws_group.close();
|
||||
ws_group = null;
|
||||
ws_connect_status = 'disconnected';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function preventDefault(fn) {
|
||||
return function (event) {
|
||||
event.preventDefault();
|
||||
fn.call(this, event);
|
||||
};
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<section
|
||||
class:hidden={!ws_connect || hide__ws_element}
|
||||
class="ae_element__websocket container p-1 bg-pink-100 text-xs mx-auto pb-16 mt-32 mb-32 relative"
|
||||
>
|
||||
|
||||
<span class="absolute top-0 right-0 flex flex-col gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
hide__ws_form = !hide__ws_form;
|
||||
}}
|
||||
class="btn btn-sm text-xs hover:preset-filled-tertiary-500"
|
||||
class:preset-tonal-tertiary={hide__ws_form}
|
||||
class:preset-filled-tertiary-500={!hide__ws_form}
|
||||
>
|
||||
{#if hide__ws_form}
|
||||
Show Form
|
||||
{:else}
|
||||
Hide Form?
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
hide__ws_messages = !hide__ws_messages;
|
||||
}}
|
||||
class="btn btn-sm text-xs hover:preset-filled-tertiary-500"
|
||||
class:preset-tonal-tertiary={hide__ws_messages}
|
||||
class:preset-filled-tertiary-500={!hide__ws_messages}
|
||||
>
|
||||
{#if hide__ws_messages}
|
||||
Show Messages
|
||||
{:else}
|
||||
Hide Messages?
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
hide__ws_commands = !hide__ws_commands;
|
||||
}}
|
||||
class="btn btn-sm text-xs hover:preset-filled-tertiary-500"
|
||||
class:preset-tonal-tertiary={hide__ws_commands}
|
||||
class:preset-filled-tertiary-500={!hide__ws_commands}
|
||||
>
|
||||
{#if hide__ws_commands}
|
||||
Show Commands
|
||||
{:else}
|
||||
Hide Commands?
|
||||
{/if}
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<header>
|
||||
|
||||
<h1 class="h6 text-center">Websocket Messages & Commands</h1>
|
||||
|
||||
</header>
|
||||
|
||||
|
||||
<!-- <form on:submit|preventDefault={handle_send_message}>
|
||||
<select bind:value={type}>
|
||||
<option value="">None</option>
|
||||
<option value="echo">Echo</option>
|
||||
<option value="dm">Direct Message</option>
|
||||
<option value="group">Group Message</option>
|
||||
<option value="all">Broadcast to All</option>
|
||||
<option value="cmd">Command</option>
|
||||
</select>
|
||||
|
||||
<select bind:value={group_id}>
|
||||
<option value="">None</option>
|
||||
<option value="test_grp_123">123</option>
|
||||
<option value="test_grp_999">999</option>
|
||||
<option value="test_grp_poster">A Poster Group</option>
|
||||
</select>
|
||||
|
||||
<input type="text" bind:value={message_text} placeholder="Your message"/>
|
||||
|
||||
<button>Send</button>
|
||||
</form> -->
|
||||
|
||||
{#if !hide__ws_form}
|
||||
<form
|
||||
onsubmit={preventDefault(handle_send_ws_data)}
|
||||
>
|
||||
<select
|
||||
bind:value={ws_data.type}
|
||||
class="input text-sm w-24"
|
||||
>
|
||||
<option value="">None</option>
|
||||
<option value="echo">Echo</option>
|
||||
<option value="dm">Direct Message</option>
|
||||
<option value="group">Group Message</option>
|
||||
<option value="all">Broadcast to All</option>
|
||||
<option value="cmd">Command</option>
|
||||
</select>
|
||||
<input type="text" bind:value={group_id} placeholder="Group ID" class="input text-sm w-36" />
|
||||
<!-- <select bind:value={group_id}>
|
||||
<option value="">None</option>
|
||||
<option value="test_grp_123">123</option>
|
||||
<option value="test_grp_999">999</option>
|
||||
<option value="test_grp_poster">A Poster Group</option>
|
||||
</select> -->
|
||||
|
||||
<!-- <input type="text" bind:value={dm_client_id} placeholder="Direct message client ID"/> -->
|
||||
|
||||
<input type="text" bind:value={ws_data.cmd} placeholder="Your command" class="input text-sm w-36" />
|
||||
<input type="text" bind:value={ws_data.msg} placeholder="Your message" class="input text-xs w-96" />
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-sm preset-tonal-warning"
|
||||
>Send Group</button>
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
<!-- <pre>
|
||||
ae_load:event_session=jEG9APQRUs8 (Poster Session #3: Work Never Ends - Pythagoras)
|
||||
ae_open:event_file=CHqU5sW7xbc (jpg)
|
||||
ae_open:event_file=Kljq0uiTlXt (video)
|
||||
</pre> -->
|
||||
|
||||
<!-- <hr> -->
|
||||
|
||||
<section
|
||||
class:hidden={hide__ws_messages}
|
||||
>
|
||||
<h2 class="text-center underline">Messages [grp, client, target, type]</h2>
|
||||
|
||||
<ol class="list-decimal list-outside max-h-24 overflow-y-auto messages">
|
||||
{#each ws_received_list_other as msg_entry, index}
|
||||
<li
|
||||
class="ml-4"
|
||||
>
|
||||
<div
|
||||
class="flex flex-row justify-between gap-1 w-full"
|
||||
>
|
||||
<span>
|
||||
[{(msg_entry.group_id||'No Group ID')}]
|
||||
{(msg_entry.client_id.toString().slice(-5)||'No Client ID')}
|
||||
–
|
||||
{(msg_entry.target||'No Target')}
|
||||
|
|
||||
<!-- – -->
|
||||
{(msg_entry.type||'No Type')}:
|
||||
</span>
|
||||
<span class="justify-self-end">
|
||||
"{msg_entry.msg}"
|
||||
</span>
|
||||
<!-- <br>{JSON.stringify(msg_entry)} -->
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ol>
|
||||
</section> <!-- End Messages -->
|
||||
|
||||
<hr>
|
||||
|
||||
<section
|
||||
class:hidden={hide__ws_commands}
|
||||
>
|
||||
<h2 class="text-center underline">Commands</h2>
|
||||
|
||||
<ol class="list-decimal list-outside max-h-24 overflow-y-auto commands">
|
||||
{#each ws_received_list_cmd as cmd_entry}
|
||||
<li
|
||||
class="ml-4"
|
||||
>
|
||||
<div
|
||||
class="flex flex-row justify-between gap-1 w-full"
|
||||
>
|
||||
<span>
|
||||
[{(cmd_entry.group_id||'No Group ID')}]
|
||||
{(cmd_entry.client_id.toString().slice(-5)||'No Client ID')}
|
||||
—
|
||||
{(cmd_entry.target||'No Target')}
|
||||
|
|
||||
<!-- — -->
|
||||
{(cmd_entry.type||'No Type')}:
|
||||
</span>
|
||||
<span class="justify-self-end">
|
||||
"{cmd_entry.cmd}"
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ol>
|
||||
</section> <!-- End Commands -->
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<style>
|
||||
/* .websocket_element {
|
||||
background-color: white;
|
||||
border-top: dashed medium gray;
|
||||
color: gray;
|
||||
font-size: .7em;
|
||||
}
|
||||
.websocket_element header {
|
||||
font-size: .7em;
|
||||
}
|
||||
.websocket_element h2 {
|
||||
font-size: .9em;
|
||||
} */
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user