update
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user