fyp/webpage/src/routes/models/edit/ModelTable.svelte

416 lines
10 KiB
Svelte

<script lang="ts">
import Tabs from 'src/lib/Tabs.svelte';
import type { Class, Image } from './types';
import { post, postFormData, rdelete, showMessage } from 'src/lib/requests.svelte';
import type { Model } from './+page.svelte';
import FileUpload from 'src/lib/FileUpload.svelte';
import ZipStructure from './ZipStructure.svelte';
import { notificationStore } from 'src/lib/NotificationsStore.svelte';
import { preventDefault } from 'src/lib/utils.js';
import CreateNewClass from './api/CreateNewClass.svelte';
let selected_class: Class | undefined = $state();
let { classes, model, onreload }: { classes: Class[]; model: Model; onreload?: () => void } =
$props();
let createClass: { className: string } = $state({
className: ''
});
let page = $state(-1);
let showNext = $state(false);
let image_list = $state<Image[]>([]);
function setActiveClass(c: Class, tb_fn: (name: string) => () => void) {
selected_class = c;
tb_fn(c.name)();
}
$effect(() => {
selected_class = classes[0];
});
async function getList() {
if (!selected_class) return;
try {
let res = await post('models/data/list', {
id: selected_class.id,
page: page
});
showNext = res.showNext;
image_list = res.image_list;
} catch (e) {
console.error('TODO notify user', e);
}
}
$effect(() => {
getList();
});
$effect(() => {
if (selected_class) {
page = 0;
}
});
let file: File | undefined = $state();
let uploading = $state(Promise.resolve());
async function uploadZip() {
if (!file) return;
uploading = new Promise(() => {});
let form = new FormData();
form.append('id', model.id);
form.append('file', file, 'upload.zip');
try {
await postFormData('models/data/class/upload', form);
if (onreload) onreload();
} catch (e) {
showMessage(e, notificationStore, 'Failed to upload');
}
uploading = Promise.resolve();
}
async function createNewClass() {
try {
const r = await post('models/data/class/new', {
id: model.id,
name: createClass.className
});
selected_class = r;
classes.push(r);
classes = classes;
getList();
} catch (e) {
showMessage(e, notificationStore);
}
}
function deleteDataPoint(id: string) {
try {
rdelete('models/data/point', { id });
getList();
} catch (e) {
showMessage(e, notificationStore);
}
}
let addFile: File | undefined = $state();
let adding = $state(Promise.resolve());
let uploadImageDialog: HTMLDialogElement;
async function addImage() {
if (!selected_class?.id || !addFile) {
return;
}
try {
let form = new FormData();
form.append(
'json_data',
JSON.stringify({
id: selected_class?.id
})
);
form.append('file', addFile, 'upload.zip');
const r = await postFormData('models/data/class/image', form);
console.log(r);
uploadImageDialog.close();
addFile = undefined;
getList();
} catch (e) {
showMessage(e, notificationStore);
}
}
</script>
{#if classes.length == 0}
TODO CREATE TABLE
{:else}
<Tabs active={classes[0]?.name} let:isActive>
<div class="buttons" slot="buttons" let:setActive let:isActive>
<!-- TODO Auto Load 1st -->
<div style="display: flex; overflow-x: scroll;">
{#each classes as item}
<button
style="width: auto; white-space: nowrap;"
onclick={() => setActiveClass(item, setActive)}
class="tab"
class:selected={isActive(item.name)}
>
{item.name}
{#if model.model_type == 2}
{#if item.status == 1}
<span class="bi bi-book" style="color: orange;"></span>
{:else if item.status == 2}
<span class="bi bi-book" style="color: green;"></span>
{:else if item.status == 3}
<span class="bi bi-check" style="color: green;"></span>
{/if}
{/if}
</button>
{/each}
</div>
<button
onclick={() => {
setActive('-----New Class-----')();
selected_class = undefined;
}}
>
<span class="bi bi-plus"></span>
</button>
</div>
{#if selected_class == undefined && isActive('-----New Class-----')}
<div class="content selected">
<h2>Add New Class</h2>
<Tabs active="zip" let:isActive nobox>
<div slot="buttons" let:setActive let:isActive>
<button
class="tab"
type="button"
onclick={setActive('zip')}
class:selected={isActive('zip')}
>
Zip
</button>
<button
class="tab"
type="button"
onclick={setActive('empty')}
class:selected={isActive('empty')}
>
Empty Class
</button>
<button
class="tab"
type="button"
onclick={setActive('api')}
class:selected={isActive('api')}
>
API
</button>
</div>
<div class="content" class:selected={isActive('zip')}>
<form onsubmit={preventDefault(uploadZip)}>
<fieldset class="file-upload">
<label for="file">Data file</label>
<div class="form-msg">
Please provide a file that has the training and testing data<br />
The file must have 2 folders one with testing images and one with training images.
<br />
Each of the folders will contain the classes of the model. The folders must be the
same in testing and training. The class folders must have the images for the classes.
<ZipStructure />
</div>
<FileUpload replace_slot bind:file accept="application/zip" notExpand>
<img src="/imgs/upload-icon.png" alt="" />
<span> Upload Zip File </span>
<div slot="replaced" style="display: inline;">
<img src="/imgs/upload-icon.png" alt="" />
<span> File selected </span>
</div>
</FileUpload>
</fieldset>
{#if file}
{#await uploading}
<button disabled> Uploading </button>
{:then}
<button> Add </button>
{/await}
{/if}
</form>
</div>
<div class="content" class:selected={isActive('empty')}>
<form onsubmit={preventDefault(createNewClass)}>
<div class="form-msg">
This Creates an empty class that allows images to be added after
</div>
<fieldset>
<label for="className">Class Name</label>
<input required name="className" bind:value={createClass.className} />
</fieldset>
<button> Create New Class </button>
</form>
</div>
<div class="content" class:selected={isActive('api')}>
<CreateNewClass {model} />
</div>
</Tabs>
</div>
{/if}
{#if selected_class}
<div class="content selected">
<h2>
{#if model.model_type == 2}
{#if selected_class?.status == 1}
Class to train
{:else if selected_class?.status == 2}
Class training
{:else if selected_class?.status == 3}
Class trained
{/if}
{:else}
Class to train
{/if}
<button onclick={() => uploadImageDialog.showModal()}> Upload Image </button>
</h2>
<table>
<thead>
<tr>
<th> File Path </th>
<th> Mode </th>
<th>
<!-- Img -->
</th>
<th>
<!-- Status -->
</th>
<th>
<!-- Remove -->
</th>
</tr>
</thead>
<tbody>
{#each image_list as image}
<tr>
<td>
{#if image.file_path == 'id://'}
Managed
{:else}
{image.file_path}
{/if}
</td>
<td>
{#if image.mode == 2}
Testing
{:else}
Training
{/if}
</td>
<td class="text-center">
{#if image.file_path == 'id://'}
<img
alt=""
src="/api/savedData/{model.id}/data/{image.id}.{model.format}"
height="30px"
width="30px"
style="object-fit: contain;"
/>
{:else}
TODO img {image.file_path}
{/if}
</td>
<td class="text-center">
{#if image.status == 1}
<span class="bi bi-check-circle-fill" style="color: green"></span>
{:else}
<span class="bi bi-exclamation-triangle-fill" style="color: red"></span>
{/if}
</td>
<td style="width: 3ch">
<button class="danger" onclick={() => deleteDataPoint(image.id)}>
<span class="bi bi-trash"></span>
</button>
</td>
</tr>
{/each}
</tbody>
</table>
<div class="flex justify-center align-center">
<div class="grow-1 flex justify-end align-center">
{#if page > 0}
<button onclick={() => (page -= 1)}> Prev </button>
{/if}
</div>
<div style="padding: 10px;">
{page}
</div>
<div class="grow-1 flex justify-start align-center">
{#if showNext}
<button onclick={() => (page += 1)}> Next </button>
{/if}
</div>
</div>
</div>
{/if}
</Tabs>
{/if}
<dialog class="newImageDialog" bind:this={uploadImageDialog}>
<form onsubmit={preventDefault(addImage)}>
<fieldset class="file-upload">
<label for="file">Data file</label>
<div class="form-msg">
Please provide a file that has the training and testing data<br />
The file must have 2 folders one with testing images and one with training images.
<br />
Each of the folders will contain the classes of the model. The folders must be the same in testing
and training. The class folders must have the images for the classes.
<ZipStructure />
</div>
<FileUpload replace_slot bind:file={addFile} accept="application/zip,image/*" notExpand>
<img src="/imgs/upload-icon.png" alt="" />
<span> Upload Zip File or Image </span>
<div slot="replaced" style="display: inline;">
<img src="/imgs/upload-icon.png" alt="" />
<span> File selected </span>
</div>
</FileUpload>
</fieldset>
{#if addFile}
{#await adding}
<button disabled> Uploading </button>
{:then}
<button> Add </button>
{/await}
{/if}
</form>
</dialog>
<style lang="scss">
.newImageDialog {
border-radius: 20px;
min-width: 300px;
min-height: 200px;
}
.buttons {
width: 100%;
display: flex;
justify-content: space-between;
& > button {
margin: 3px 5px;
}
}
table {
width: 100%;
box-shadow: 0 2px 8px 1px #66666622;
border-radius: 10px;
border-collapse: collapse;
overflow: hidden;
}
table thead {
background: #60606022;
}
table tr td,
table tr th {
border-left: 1px solid #22222244;
padding: 15px;
}
table tr td:first-child,
table tr th:first-child {
border-left: none;
}
</style>