Files
OSIT-AE-App-Svelte/src/lib/elements/element_input_files_tbl.svelte
2025-11-13 20:15:09 -05:00

390 lines
17 KiB
Svelte

<script lang="ts">
import { createEventDispatcher, onMount, tick } from 'svelte';
import type { key_val } from '$lib/stores/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/stores/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: any) {
// 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: any) {
// // 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>