Working on a better file upload element and component. Slow progress...

This commit is contained in:
Scott Idem
2024-08-12 18:35:36 -04:00
parent 074cf154f2
commit df36727540
9 changed files with 910 additions and 70 deletions

View File

@@ -4,5 +4,9 @@
"path": "."
}
],
"settings": {}
"settings": {
"cSpell.words": [
"filelist"
]
}
}

View File

@@ -19,6 +19,41 @@ import {
let ae_promises: key_val = {}; // Promise<any>;
// Updated 2024-08-12
async function check_hosted_file_obj_w_hash(
{
api_cfg,
hosted_file_hash,
check_for_local=true,
params={},
return_meta=false,
log_lvl=0
} : {
api_cfg: any,
hosted_file_hash: string,
check_for_local?: boolean,
params?: key_val,
return_meta?: boolean,
log_lvl?: number
}
) {
console.log('*** stores_event_api.js: check_hosted_file_obj_w_hash() ***');
const endpoint = `/hosted_file/hash/${hosted_file_hash}`;
if (check_for_local) {
params['check_for_local'] = true;
}
let check_hosted_file_obj_w_hash_get_promise = await api.get_object({
api_cfg: api_cfg,
endpoint: endpoint,
params: params,
return_meta: return_meta,
log_lvl: log_lvl
});
return check_hosted_file_obj_w_hash_get_promise;
}
// Updated 2024-03-29
async function handle_load_ae_obj_id__site_domain(
{
@@ -364,6 +399,7 @@ async function handle_download_export__obj_type(
let export_obj = {
check_hosted_file_obj_w_hash: check_hosted_file_obj_w_hash,
handle_load_ae_obj_id__site_domain: handle_load_ae_obj_id__site_domain,
handle_load_ae_obj_code__data_store: handle_load_ae_obj_code__data_store,
handle_load_ae_obj_id__person: handle_load_ae_obj_id__person,

View File

@@ -89,6 +89,10 @@ let events_local_data_struct: key_val = {
'classes__form': 'border border-surface-200 p-4 space-y-4 rounded-container-token',
},
// Event Files - uploads for sessions, presenters, etc
'files': {
},
// Event Presentation Launcher
'launcher': {
qry_max__sessions: 75,
@@ -241,6 +245,15 @@ let events_session_data_struct: key_val = {
qr_scan_result: null,
},
// Event Files - uploads for sessions, presenters, etc
'files': {
disable_submit__event_file_obj: null,
status__submit: null,
status__file_list: null, // processing, complete
processed_file_list: [],
},
// Event Presentation Launcher
'launcher': {
},

View File

@@ -33,6 +33,63 @@ function number_w_commas(x) {
}
// Updated 2024-08-12
function guess_file_name(filename_string: string) {
// console.log('*** guess_file_name() ***');
if (!filename_string) {
return '';
}
if (filename_string.includes('.')) {
let file_name = filename_string.substring(0, filename_string.lastIndexOf('.'));
// console.log(file_name);
return file_name;
} else {
return filename_string;
}
}
// Updated 2024-08-12
function guess_file_extension(filename_string: string) {
// console.log('*** guess_file_extension() ***');
if (!filename_string) {
return '';
}
if (!filename_string.includes('.')) {
return '';
}
let file_extension = filename_string.substring(filename_string.lastIndexOf('.') + 1, filename_string.length) || filename_string;
// console.log(file_extension);
return file_extension;
}
// Updated 2024-08-12
async function get_file_hash(file) {
return new Promise((resolve, reject) => {
let file_reader = new FileReader();
file_reader.onload = async function() {
if (file_reader.result.byteLength !== file.size) {
console.log('File was not read completely');
reject("Error reading the file");
}
const hash_buffer = await crypto.subtle.digest('SHA-256', file_reader.result);
const hash_array = Array.from(new Uint8Array(hash_buffer));
const hash_hex = hash_array.map(b => b.toString(16).padStart(2, '0')).join('');
resolve(hash_hex);
};
file_reader.readAsArrayBuffer(file);
});
}
/* This utility function looks for any form data with the prefixed name passed and returns a new object.
* This function is used heavily! Be very careful making changes!!!
* If rm_empty_id then it will remove/ignore fields matching. This helps with the API and new records/objects
@@ -726,6 +783,9 @@ export let ae_util = {
iso_datetime_formatter: iso_datetime_formatter,
format_bytes: format_bytes,
number_w_commas: number_w_commas,
guess_file_name: guess_file_name,
guess_file_extension: guess_file_extension,
get_file_hash: get_file_hash,
extract_prefixed_form_data: extract_prefixed_form_data,
process_data_string: process_data_string,
handle_url_and_message: handle_url_and_message,

View File

@@ -0,0 +1,413 @@
<script lang="ts">
import { createEventDispatcher, onMount, tick } from 'svelte';
import type { key_val } from '$lib/ae_stores';
import { ae_util } from '$lib/ae_utils';
// import { api } from '$lib/api';
import { core_func } from '$lib/ae_core_functions';
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', 'table-hover' , '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 core_func.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 variant-ghost-warning 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 variant-soft-warning hover:variant-filled-secondary m-1"
title="Remove file from upload list">
<span class="fas fa-minus"></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>

View File

@@ -1,25 +1,28 @@
<script lang="ts">
import { createEventDispatcher, onMount } from 'svelte';
import { createEventDispatcher, onMount, tick } from 'svelte';
import { ae } from 'aether_npm_lib';
import type { key_val } from '$lib/ae_stores';
import { ae_util } from '$lib/ae_utils';
// import { api } from '$lib/api';
import { core_func } from '$lib/ae_core_functions';
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 = [];
export let input_class_li = ['file_drop_area'];
export let table_class_li = [];
export let container_class_li: string[] = [];
export let table_class_li: string[] = ['table', 'table-sm', 'table-striped', 'table-hover' , '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 input_file_list: any = null;
export let file_list_status: null|string = null;
export let processed_file_list: any[] = [];
const dispatch = createEventDispatcher();
export let input_file_list;
let input_file_list_processed;
let input_file_list_processed: any[] = [];
onMount(() => {
console.log('** Element Mounted: ** Element Input File');
@@ -29,9 +32,14 @@ onMount(() => {
$: if (input_file_list) {
console.log(input_file_list);
process_file_list(input_file_list).then(function (result) {
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
@@ -44,7 +52,6 @@ $: if (input_file_list) {
}
);
});
}
@@ -52,15 +59,16 @@ $: if (input_file_list) {
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;
return false;
file_list_status = 'none';
// await tick();
return processed_file_list;
}
// input_file_list_processed = [];
let file_list_processing = [];
// const forLoop = async _ => {
// console.log('*** Start ***');
@@ -74,25 +82,25 @@ async function process_file_list(file_list) {
// file_item.name = file_item.name.replace('.odpwin', '.odp');
}
let file_data = {};
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);
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');
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);
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.
@@ -164,14 +172,18 @@ async function process_file_list(file_list) {
}
} else if (file_size_bytes > 52428800) {
console.log('This is large file size. Going to warn.');
// 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 > 209715200) { // > 200 MB
warning_message = `This file size (${file_size_string}) is very large and may take a while 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.`;
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 or more 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.`;
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 or more 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.`;
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 {
@@ -192,27 +204,49 @@ async function process_file_list(file_list) {
// console.log(get_file_hash(file_item));
// console.log(await get_file_hash(file_item));
const file_hash = await ae.util.get_file_hash(file_item);
// const file_hash = get_file_hash(file_item);
// console.log(file_hash);
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;
file_data['hash_sha256_match'] = null;
let check_hosted_file_obj_w_hash_result = await core_func.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;
}
file_list_processing.push(file_data);
processed_file_list.push(file_data);
// input_file_list_processed.push(file_data);
}
console.log(file_list_processing);
file_list_status = 'ready';
console.log(processed_file_list);
// return JSON.parse(JSON.stringify(file_list_processing));
return file_list_processing
// return JSON.parse(JSON.stringify(processed_file_list));
return processed_file_list
}
@@ -225,8 +259,19 @@ function remove_file_from_filelist(index) {
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.
@@ -244,14 +289,20 @@ function remove_file_from_filelist(index) {
</script>
<div class="svelte_element element_input_file {container_class_li.join(' ')}">
<input type="file" bind:files={input_file_list} {multiple} {required} {accept} name={input_name} class="svelte_input_file_element {input_class_li.join(' ')}" id={element_id} />
<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 variant-ghost-warning 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 {table_class_li.join(' ')}">
<table class="slct_file_list text-sm {table_class_li.join(' ')}">
<thead>
<tr>
<th>Remove</th>
@@ -267,17 +318,37 @@ function remove_file_from_filelist(index) {
<tbody>
<tr>
<td class="file_remove">
<button on:click|preventDefault={() => { (remove_file_from_filelist(file_index)); }} class="ae_btn btn_md btn_default" title="Remove file from upload list">
<button
on:click|preventDefault={() => { (remove_file_from_filelist(file_index)); }}
class="btn btn-md variant-soft-warning hover:variant-filled-secondary m-1"
title="Remove file from upload list">
<span class="fas fa-minus"></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:warning_file_size={file_list_item.warning_size}>{file_list_item.file_size_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:warning_file_untrusted_extension={file_list_item.warning_untrusted_extension} class:warning_file_legacy_extension={file_list_item.warning_legacy_extension}>{file_list_item.guessed_extension}</td>
<td class="file_hash file_hash256">
{ae.util.shorten_string({string: file_list_item.hash_sha256, begin_length: 5, end_length: 4, wildcard_length: 2})}
<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>
@@ -285,42 +356,26 @@ function remove_file_from_filelist(index) {
</table>
{/if}
<!-- {/await} -->
<button on:click|preventDefault={async () => {
// const file = document.getElementById('event_file_upload_file_list').files[0];
// let test_result = await ae.util.get_file_hash(file);
// get_file_hash(file)
// .then(hash => {
// console.log(hash); // This will print the hash value
// })
// .catch(error => {
// console.error(error);
// });
// let test_result = await get_file_hash(input_file_list[0]);
// console.log(test_result);;
}}
class="ae_btn btn_md btn_default d_none" title="Get file hash?"
>
<span class="fas fa-hash"></span>
</button>
</div>
<style>
th {
text-align: center;
font-size: smaller;
/* font-size: smaller; */
}
td {
text-align: center;
}
.file_last_modified, .file_size, .file_type {
.file_last_modified {
font-size: smaller;
}
.file_size, .file_type {
/* font-size: smaller; */
}
.file_hash {
font-family: 'Courier New', Courier, monospace;
}

View File

@@ -0,0 +1,253 @@
<script lang="ts">
export let log_lvl: number = 0;
// Imports
// Import components and elements
import Element_input_files_tbl from '$lib/element_input_files_tbl.svelte';
// Import storage, functions, and libraries
import type { key_val } from '$lib/ae_stores';
import { api } from '$lib/api';
import { ae_snip, ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/ae_stores';
import { events_loc, events_sess, events_slct, events_trigger } from '$lib/ae_events_stores';
import { events_func } from '$lib/ae_events_functions';
// Exports
// Expecting these for link_to_type: 'event', 'event_location', 'event_presentation', 'event_presenter', 'event_session'
export let link_to_type: string;
export let link_to_id: string;
export let input_name = 'file_list';
export let multiple: boolean = false;
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 input_class_li: string[] = ['file_drop_area'];
export let table_class_li: string[] = ['table', 'table-sm', 'table-striped', 'table-hover' , 'text-sm'];
// Local Variables
let task_id = link_to_id;
let input_file_list: any = null;
let ae_promises: key_val = {}; // Promise<any>;
let ae_triggers: key_val = {};
let input_element_id = 'ae_comp__event_files_upload__input';
// Functions and Logic
async function handle_submit_form_files(event) {
console.log('*** handle_submit_form() ***');
$events_sess.files.disable_submit__event_file_obj = true;
$events_sess.files.submit_status = 'saving';
let hosted_file_results;
if (event.target[input_element_id].files.length > 0) {
task_id = link_to_id; // Ideally this should be the file hash, but we may be uploading multiple files at once. This should be done with a loop instead?
hosted_file_results = await handle_input_upload_files(event.target[input_element_id].files, task_id);
if (hosted_file_results) {
console.log(`hosted_file_results:`, hosted_file_results);
} else {
console.log(`hosted_file_results:`, hosted_file_results);
}
}
$events_sess.files.disable_submit__event_file_obj = false;
}
async function handle_input_upload_files(input_upload_files, task_id) {
console.log('*** handle_input_upload_files() ***');
const form_data = new FormData();
form_data.append('account_id', $ae_loc.account_id);
form_data.append('link_to_type', link_to_type);
form_data.append('link_to_id', link_to_id);
for (let i = 0; i < input_upload_files.length; i++) {
form_data.append(`file_list`, input_upload_files[i]);
}
// hash_sha256, uploaded, uploaded_bytes
// $events_sess.files.processed_file_list[i] = {
// ...$events_sess.files.processed_file_list[i],
// uploaded: $ae_sess.api_upload_kv[link_to_id].percent_completed,
// uploaded_bytes: $ae_sess.api_upload_kv[link_to_id].uploaded_bytes,
// };
let params = null;
let endpoint = '/hosted_file/upload_files';
console.log(form_data);
params = null;
// Uncomment and the post_promise is not seen by the "await" below
// post_promise = await api.post_object({api_cfg: $cfg.api, endpoint: endpoint, params: params, data:form_data});
// Uncomment so that the post_promise is not seen by the "await" below
ae_promises.upload__hosted_file_obj = api.post_object({
api_cfg: $ae_api,
endpoint: endpoint,
params: params,
form_data: form_data,
task_id: task_id,
log_lvl: log_lvl
})
.then(async function (result) {
// WARNING!!!! ONLY ONE FILE IS EXPECTED TO BE UPLOADED AT A TIME!!!
// NOTE: The /hosted_file/upload_files endpoint will always return a list of successful files uploaded. In this case we are only uploading one file and expecting a list of one item.
let x = 0;
console.log(result[x]);
let hosted_file_obj = result[x];
let hosted_file_id = hosted_file_obj.hosted_file_id_random;
let event_file_data: key_val = {};
event_file_data['hosted_file_id_random'] = hosted_file_id;
event_file_data['for_type'] = link_to_type;
event_file_data['for_id_random'] = link_to_id;
event_file_data['filename'] = hosted_file_obj.filename;
event_file_data['extension'] = hosted_file_obj.extension;
event_file_data['enable'] = true;
console.log(event_file_data);
// $events_sess.files.new_upload_list[i].uploaded_bytes = 10; // fake 10 bytes at least...
let event_file_id = await events_func.create_event_file_obj_from_hosted_file_async({
api_cfg: $ae_api,
hosted_file_id: hosted_file_id,
data: event_file_data,
log_lvl: log_lvl
})
.then(function (create_result) {
console.log(create_result); // NOTE: This should be the event_file_id string
// let event_file_id = create_result;
return create_result;
});
return event_file_id;
})
.then(function (event_file_id) {
// NOTE: Need to make sure the event file records are created first. The update won't see the changes if too fast.
// dispatch(
// 'event_file_obj_li_updated',
// {
// link_to_type: link_to_type,
// link_to_id: link_to_id,
// }
// );
// $ae_events.pres_mgmt.new_upload_list[i].uploaded = true;
// $ae_events.pres_mgmt.new_upload_list[i].uploaded_bytes = event.target.event_file_upload_file_list.files[i].size;
return event_file_id;
})
.catch(function (error) {
console.log('Something went wrong.');
console.log(error);
return false;
})
.finally(function (event_file_id) {
// $events_sess.files.files_uploading_count--;
$slct_trigger = 'load__event_file_obj_li';
return event_file_id;
});
console.log(ae_promises.upload__hosted_file_obj);
let hosted_file_result = ae_promises.upload__hosted_file_obj;
return hosted_file_result;
}
</script>
<form
on:submit|preventDefault={handle_submit_form_files}
class:hidden={!$ae_loc.trusted_access}
class="modal-form {$ae_loc.hub.classes__form} flex flex-col gap-1 items-center justify-center w-full"
>
<label
for="ae_comp__event_files_upload__input"
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={input_element_id}
type="file"
bind:files={input_file_list}
{multiple}
{required}
{accept}
name={input_name}
class="svelte_input_file_element {input_class_li.join(' ')}"
/>
<Element_input_files_tbl
bind:input_file_list={input_file_list}
bind:file_list_status={$events_sess.files.status__file_list}
bind:processed_file_list={$events_sess.files.processed_file_list}
table_class_li={table_class_li}
/>
<button
type="submit"
class="btn btn-lg btn-primary variant-filled-primary hover:variant-ghost-success w-54"
disabled={$events_sess.files.disable_submit__event_file_obj || $events_sess.files.status__file_list != 'ready'}
>
{#await ae_promises.upload__hosted_file_obj}
<span class="fas fa-spinner fa-spin m-1"></span>
<span class="">
Uploading
{#if $ae_sess.api_upload_kv[link_to_id]}
{$ae_sess.api_upload_kv[link_to_id].percent_completed}%
{/if}
</span>
{:then}
<span class="fas fa-upload m-1"></span>
<span class="text-sm">
Upload?
</span>
<!-- <span class="fas fa-save m-1"></span> -->
<span class="grow font-bold">
{#if $events_sess.files.processed_file_list.length > 0}
<!-- {#each $events_sess.files.processed_file_list as file_obj, index}
<span class="text-xs">
{file_obj.filename}
</span>
{/each} -->
{$events_sess.files.processed_file_list.length == 1 ? `${$events_sess.files.processed_file_list.length} file` : `${$events_sess.files.processed_file_list.length} files`}
{:else}
<span class="text-xs">
No files selected
</span>
{/if}
<!-- Files -->
</span>
{/await}
</button>
</form>

View File

@@ -24,9 +24,10 @@ import { ae_snip, ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$
import { events_loc, events_sess, events_slct, events_trigger, events_trig_kv } from '$lib/ae_events_stores';
import { events_func } from '$lib/ae_events_functions';
import Comp_event_files_upload from './../../ae_comp__event_files_upload.svelte';
import Session_view from './../../session_view.svelte';
import Session_page_menu from './../../session_page_menu.svelte';
import Sign_in_out from './../../sign_in_out.svelte';
// import Sign_in_out from './../../sign_in_out.svelte';
import { browser } from '$app/environment';
if (browser) {
@@ -250,6 +251,11 @@ onMount(() => {
lq__auth__event_presenter_obj={lq__auth__event_presenter_obj}
lq__event_presentation_obj_li={lq__event_presentation_obj_li}
/>
<Comp_event_files_upload
link_to_type="event_session"
link_to_id={$lq__event_session_obj.event_session_id}
/>
{:else}
<div class="bg-red-100 p-4 border border-red-200 rounded-md">
<h2 class="h3">

View File

@@ -25,7 +25,7 @@ import { events_func } from '$lib/ae_events_functions';
import Form_agree from './form_agree.svelte';
import Comp_event_presenter_obj_li from './ae_comp__event_presenter_obj_li.svelte';
import Element_manage_event_file_li from '$lib/element_manage_event_file_li.svelte';
import AeCompEventFileObjTbl from './ae_comp__event_file_obj_tbl.svelte';
// Exports
export let event_session_id: string;