update
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="beat" :class="{ vertical: false }">
|
||||
<editable-text-field node-type="h3" class="beat-title" v-model="beat!.name.value" />
|
||||
<div v-if="beat" class="beat" :class="{ vertical: false }">
|
||||
<editable-text-field node-type="h3" class="beat-title" v-model="beat.name.value" />
|
||||
<div class="beat-main-container">
|
||||
<div class="beat-track-container" :class="{ dragging }">
|
||||
<draggable @start="dragging = true"
|
||||
|
||||
@@ -2,20 +2,20 @@
|
||||
<div class="beat-settings" v-if="beat">
|
||||
<div class="beat-settings-options">
|
||||
<div class="beat-settings-boxes beat-settings-option">
|
||||
<number-input label="Bars: " v-model="beat!.barCount.value" :disabled="beat!.barSettingsLocked.value" />
|
||||
<number-input label="Bars: " v-model="beat.barCount.value" :disabled="beat.barSettingsLocked.value" />
|
||||
</div>
|
||||
<div class="beat-settings-bar-count beat-settings-option">
|
||||
<number-input label="Boxes per bar: " v-model="beat!.timeSigUp.value" />
|
||||
<number-input label="Boxes per bar: " v-model="beat.timeSigUp.value" />
|
||||
</div>
|
||||
<div class="beat-settings-bar-count beat-settings-option">
|
||||
<bool-box label="Auto beat length: " v-model="beat!.useAutoBeatLength.value" />
|
||||
<bool-box label="Auto beat length: " v-model="beat.useAutoBeatLength.value" />
|
||||
</div>
|
||||
<action-button
|
||||
label="New Track"
|
||||
@click="() => beat!.addTrack()" />
|
||||
@click="() => beat?.addTrack()" />
|
||||
<div>
|
||||
<track-settings
|
||||
v-for="(_, i) in beat!.tracks.value ?? []"
|
||||
v-for="(_, i) in beat.tracks.value ?? []"
|
||||
:key="i"
|
||||
:beat-index="beatIndex"
|
||||
:track-index="i" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import RightHand from "@/assets/svgs/RH.png";
|
||||
import LeftFoot from "@/assets/svgs/LF.png";
|
||||
import RightFoot from "@/assets/svgs/RF.png";
|
||||
import Eraser from "@/assets/svgs/eraser-fill.svg";
|
||||
import Upload from "@/assets/svgs/upload.svg";
|
||||
import Floppy from "@/assets/svgs/floppy2-fill.svg";
|
||||
|
||||
export const IconUrlMap = {
|
||||
arrowClockwise: ArrowClockwise,
|
||||
@@ -20,6 +22,8 @@ export const IconUrlMap = {
|
||||
lf: LeftFoot,
|
||||
rf: RightFoot,
|
||||
eraser: Eraser,
|
||||
upload: Upload,
|
||||
save: Floppy,
|
||||
} as const;
|
||||
|
||||
export type IconName = keyof typeof IconUrlMap;
|
||||
|
||||
Reference in New Issue
Block a user