gpx.studio/website/src/lib/components/FileList.svelte

156 lines
3.7 KiB
Svelte

<script lang="ts">
import { fileOrder, selectedFiles, selectFiles } from '$lib/stores';
import { ScrollArea } from '$lib/components/ui/scroll-area/index';
import Sortable from 'sortablejs/Sortable';
import { afterUpdate, onMount } from 'svelte';
import { get } from 'svelte/store';
import FileListItem from './FileListItem.svelte';
import { fileObservers } from '$lib/db';
let container: HTMLDivElement;
let buttons: { [id: string]: HTMLElement } = {};
let sortable: Sortable;
function selectFile(fileId: string) {
selectedFiles.update((selectedFiles) => {
selectedFiles.clear();
selectedFiles.add(fileId);
return selectedFiles;
});
}
function addSelectFile(fileId: string) {
selectedFiles.update((selectedFiles) => {
selectedFiles.add(fileId);
return selectedFiles;
});
}
function selectAllFiles() {
selectedFiles.update((selectedFiles) => {
get(fileObservers).forEach((_, id) => {
selectedFiles.add(id);
});
return selectedFiles;
});
}
function deselectFile(fileId: string) {
selectedFiles.update((selectedFiles) => {
selectedFiles.delete(fileId);
return selectedFiles;
});
}
function updateFileOrder() {
let newFileOrder = sortable.toArray();
if (newFileOrder.length !== get(fileOrder).length) {
fileOrder.set(newFileOrder);
return;
}
for (let i = 0; i < newFileOrder.length; i++) {
if (newFileOrder[i] !== get(fileOrder)[i]) {
fileOrder.set(newFileOrder);
return;
}
}
}
onMount(() => {
sortable = Sortable.create(container, {
forceAutoScrollFallback: true,
multiDrag: true,
multiDragKey: 'Meta',
selectedClass: 'sortable-selected',
avoidImplicitDeselect: true,
onSelect: (e) => {
let selectedId = e.item.getAttribute('data-id');
addSelectFile(selectedId);
if (
!(e.originalEvent.shiftKey || e.originalEvent.ctrlKey || e.originalEvent.metaKey) &&
$selectedFiles.size > 1
) {
$selectedFiles.forEach((fileId) => {
if (fileId !== selectedId) {
deselectFile(fileId);
Sortable.utils.deselect(buttons[fileId]);
}
});
}
},
onDeselect: (e) => {
deselectFile(e.item.getAttribute('data-id'));
},
onSort: () => {
updateFileOrder();
}
});
});
selectFiles.update(() => {
return {
select: (fileId: string) => {
Object.values(buttons).forEach((button) => {
Sortable.utils.deselect(button);
});
Sortable.utils.select(buttons[fileId]);
selectFile(fileId);
},
addSelect: (fileId: string) => {
Sortable.utils.select(buttons[fileId]);
addSelectFile(fileId);
},
selectAllFiles: () => {
Object.values(buttons).forEach((button) => {
Sortable.utils.select(button);
});
selectAllFiles();
},
removeSelect: (fileId: string) => {
Sortable.utils.deselect(buttons[fileId]);
deselectFile(fileId);
}
};
});
afterUpdate(() => {
updateFileOrder();
Object.keys(buttons).forEach((fileId) => {
if (!get(fileObservers).has(fileId)) {
delete buttons[fileId];
}
});
});
</script>
<div class="h-10 -translate-y-10 w-full pointer-events-none absolute z-30">
<ScrollArea orientation="horizontal" class="w-full h-full" scrollbarXClasses="h-2">
<div bind:this={container} class="flex flex-row gap-1">
{#if $fileObservers}
{#each $fileObservers.entries() as [fileId, file]}
<div
bind:this={buttons[fileId]}
data-id={fileId}
class="pointer-events-auto first:ml-1 last:mr-1 mb-1 bg-transparent"
>
<FileListItem {file} />
</div>
{/each}
{/if}
</div>
</ScrollArea>
</div>
<style lang="postcss">
div :global(button) {
@apply bg-accent;
@apply hover:bg-background;
}
div :global(.sortable-selected button) {
@apply bg-background;
}
</style>