This commit is contained in:
2024-04-01 23:13:39 +02:00
parent 3c9065ee8c
commit 3cda9c3e96
14 changed files with 382 additions and 214 deletions

View File

@@ -4,16 +4,32 @@
:class="{ 'sidebar-visible': sidebarActive, 'vertical-mode': currentOrientation === 'vertical' }">
<div class="sidebar">
<div class="sidebar-left-strip">
<div v-for="(beat, i) in beats"
:key="beat.name.value"
class="sidebar-left-tab"
:class="{ 'active': i === activeBeatIndex }"
@click="activeBeatIndex = i">
{{ beat.name.value }}
</div>
<draggable animation="150"
@start="onStartDragBeatTab"
@end="onEndDragBeatTab"
v-model="beats"
ghost-class="ghost"
itemKey="name.value">
<template #item="{ element, index }">
<div
:key="element.name.value"
class="sidebar-left-tab"
:class="{ 'active': index === activeBeatIndex }"
@click="activeBeatIndex = index">
<span v-if="element.saveDirty.value" class="unsaved"></span>
<span class="name">{{ element.name.value }}</span>
<div
class="delete"
@click="onDeleteBeat(index)"
title="delete">
<div class="x">×</div>
</div>
</div>
</template>
</draggable>
<div
class="sidebar-add-beat"
@click="onAddNewBeat()">
class="tab-add"
@click="onAddNewBeat">
+
</div>
</div>
@@ -50,10 +66,22 @@
<div
class="quick-access-button"
:class="{ 'unclickable': !saveDirty }"
:title="saveDirty ? 'Save changes' : 'No unsaved changes'"
:title="saveDirty ? 'Save all changes' : 'No unsaved changes'"
@click="save('localStorage')">
<icon icon-name="save" color="var(--color-ui-neutral-dark)" />
</div>
<div
class="quick-access-button"
title="Save current beat to file"
@click="saveCurrentBeatToFile">
<icon icon-name="download" color="var(--color-ui-neutral-dark)" />
</div>
<div
class="quick-access-button"
title="Save current beat to file"
@click="uploadDialog?.showModal()">
<icon icon-name="upload" color="var(--color-ui-neutral-dark)" />
</div>
</div>
<toolbox class="toolbox" />
</div>
@@ -66,17 +94,24 @@
:orientation="currentOrientation" />
</div>
</div>
<dialog ref="uploadDialog" class="upload-dialog">
<h2>Upload a Drum Slayer file</h2>
<input id="upload-file" type="file" @change="onFileInputChange">
<button @click="onUpload" :disabled="!canUpload">Upload</button>
<button @click="uploadDialog?.close()">Cancel</button>
</dialog>
</div>
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted, provide, ref, watch } from "vue";
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
import BeatSettings from "@/ui/BeatSettings/BeatSettings.vue";
import BeatView from "@/ui/Beat/Beat.vue";
import Icon from "@/ui/Widgets/Icon/Icon.vue";
import Toolbox from "@/ui/Root/Toolbox.vue";
import { BeatStoreKey, createBeatStore } from "@/BeatStore";
import { AppStateStoreKey, createAppStateStore } from "@/AppState";
import { useBeatStore } from "@/BeatStore";
import { useAppStateStore } from "@/AppState";
import Draggable from "vuedraggable";
const TITLE = 'Drum Slayer';
@@ -87,26 +122,89 @@
const currentOrientation = ref<'horizontal' | 'vertical'>('horizontal');
const sidebarActive = ref(false);
const appStateStore = createAppStateStore();
provide(AppStateStoreKey, appStateStore);
const appStateStore = useAppStateStore();
const beatStore = useBeatStore();
const beatStore = createBeatStore();
provide(BeatStoreKey, beatStore);
window.drumslayer = {
appState: appStateStore,
beatStore,
};
const {
save,
resetActiveBeat,
activeBeatIndex,
activeBeat,
beats,
addNewBeat,
bakeAll,
saveDirty,
removeBeat,
} = beatStore;
function onAddNewBeat() {
activeBeatIndex.value = addNewBeat();
}
let lastActiveBeatIndex: number | null = null;
function onStartDragBeatTab() {
lastActiveBeatIndex = activeBeat.value?.id ?? null;
}
function onEndDragBeatTab() {
if (lastActiveBeatIndex !== null) {
activeBeatIndex.value = beats.value.findIndex(beat => beat.id === lastActiveBeatIndex) ?? lastActiveBeatIndex;
}
}
function onDeleteBeat(index: number) {
const beatToDelete = beats.value[index];
if (beatToDelete) {
if (confirm('ARE YOU SURE?')) {
removeBeat(index);
}
}
}
const uploadErr = ref(false);
const canUpload = ref(false);
const uploadDialog = ref<HTMLDialogElement | null>(null);
function onFileInputChange(event: Event) {
const inputEl = event.currentTarget as HTMLInputElement;
canUpload.value = !!inputEl?.files?.length;
}
async function onUpload() {
const input = document.getElementById('upload-file') as HTMLInputElement;
if (input.files) {
const file = input.files.item(0);
const text = await file?.text() ?? null;
if (text) {
try {
const parsed = JSON.parse(text);
const newBeatIndex = addNewBeat(parsed);
if (newBeatIndex === -1) {
uploadErr.value = true;
}
} catch (e) {
}
}
}
uploadDialog.value?.close();
}
function saveCurrentBeatToFile() {
if (activeBeat.value) {
const serial = activeBeat.value.serialise();
const a = document.createElement("a");
const file = new Blob([JSON.stringify(serial)], { type: 'text' });
a.href = URL.createObjectURL(file);
a.download = `${ activeBeat.value.name.value }.drms`;
a.click();
}
}
watch(saveDirty, (dirty) => {
if (dirty) {
document.title = `${ TITLE } (unsaved changes)`;
@@ -251,35 +349,65 @@
.sidebar-left-strip {
writing-mode: vertical-rl;
background-color: var(--color-bg-light);
}
.sidebar-left-strip > * {
display: inline-block;
}
.sidebar-left-tab {
transform: rotate(-180deg);
display: inline-block;
width: 100%;
padding: 8px 3px 8px 3px;
}
.sidebar-left-tab.active {
background-color: var(--color-bg-medium);
> * {
display: inline-block;
}
}
.sidebar-left-tab, .tab-add {
display: inline-block;
}
.sidebar-add-beat {
width: 100%;
padding: 8px 3px 8px 3px;
padding: 8px 3px 24px 3px;
position: relative;
.unsaved {
margin-bottom: 5px;
opacity: 50%;
}
.delete {
position: absolute;
bottom: 5px;
left: 6px;
width: 15px;
height: 15px;
display: inline-block;
color: var(--color-ui-neutral-dark);
border-radius: 2px;
cursor: pointer;
&:hover {
filter: brightness(1.5);
}
.x {
position: absolute;
right: -3px;
bottom: -3px;
}
}
&.active {
background-color: var(--color-bg-light);
display: inline-block;
}
&:hover:not(.active) {
cursor: pointer;
background-color: var(--color-ui-neutral-dark);
transition: background-color 200ms;
}
&:hover {
.delete {
filter: brightness(1.2);
}
}
}
.sidebar-add-beat:hover,
.sidebar-left-tab:hover:not(.active) {
cursor: pointer;
background-color: var(--color-ui-neutral-dark);
transition: background-color 200ms;
.tab-name {
display: inline-block;
}
@media screen and (max-width: 900px) {
@@ -310,5 +438,9 @@
user-select: none;
}
}
.upload-dialog {
z-index: 20;
}
</style>