This commit is contained in:
2024-03-30 22:44:15 +01:00
parent 4268cec832
commit ebca41dc8f
9 changed files with 497 additions and 761 deletions

1105
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,22 +15,21 @@
"author": "Daniel Ledda", "author": "Daniel Ledda",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@djledda/ladder": "^1.1.0", "@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/tsconfig": "^0.1.3", "@vue/tsconfig": "^0.1.3",
"pinia": "^2.0.32",
"sass": "^1.58.3", "sass": "^1.58.3",
"vue": "^3.2.47", "zod": "^3.21.4",
"zod": "^3.21.4" "vue": "^3.4.21",
"vuedraggable": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^17.0.40", "@types/node": "^20.12.2",
"@typescript-eslint/eslint-plugin": "^5.26.0", "@typescript-eslint/eslint-plugin": "^5.26.0",
"@typescript-eslint/parser": "^5.26.0", "@typescript-eslint/parser": "^5.26.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.40.0", "eslint": "^8.40.0",
"eslint-plugin-vue": "^9.11.0", "eslint-plugin-vue": "^9.11.0",
"typescript": "^5.0.4", "typescript": "^5.4.3",
"vite": "^4.3.0" "vite": "^5.2.7"
} }
} }

View File

@@ -117,17 +117,17 @@ export class Beat extends EffectScoped {
return track; return track;
} }
swapTracksByIndices(trackIndex1: number, trackIndex2: number): void { insertAt(trackIndex: number, newIndex: number): void {
const track1 = this.getTrackByIndex(trackIndex1); const track = this.getTrackByIndex(trackIndex);
const track2 = this.getTrackByIndex(trackIndex2); this.tracks.value.splice(trackIndex, 1);
this.tracks.value[trackIndex1] = track2; this.tracks.value = this.tracks.value.slice(0, newIndex).concat([track]).concat(this.tracks.value.slice(newIndex));
this.tracks.value[trackIndex2] = track1; triggerRef(this.tracks);
} }
addTrack(options?: Omit<TrackInitOptions, 'manager'>): Track | null { addTrack(options?: Omit<TrackInitOptions, 'manager'>): Track | null {
const optionsResolved = { const optionsResolved = {
manager: this.manager, manager: this.manager,
bars: this.barCount.value, barCount: this.barCount.value,
isLooping: this.globalIsLooping.value, isLooping: this.globalIsLooping.value,
loopLength: this.globalLoopLength.value, loopLength: this.globalLoopLength.value,
...options, ...options,

View File

@@ -9,7 +9,7 @@ export const DrumSlayerSaveSchema = z.object({
}); });
export type DrumSlayerSave = z.infer<typeof DrumSlayerSaveSchema>; export type DrumSlayerSave = z.infer<typeof DrumSlayerSaveSchema>;
function defaultMainBeatGroup(manager: BeatManager): Beat { function createDefaultMainBeatGroup(manager: BeatManager): Beat {
const defaultSettings = { const defaultSettings = {
manager, manager,
barCount: 2, barCount: 2,
@@ -33,14 +33,14 @@ export function createBeatStore() {
}, },
}; };
const beats = shallowRef<Beat[]>([ defaultMainBeatGroup(manager) ]); const beats = shallowRef<Beat[]>([ createDefaultMainBeatGroup(manager) ]);
const activeBeatIndex = ref(0); const activeBeatIndex = ref(0);
const activeBeat = computed<Beat | null>(() => beats.value[activeBeatIndex.value] ?? null); const activeBeat = computed<Beat | null>(() => beats.value[activeBeatIndex.value] ?? null);
const orientation = ref<"horizontal" | "vertical">("horizontal"); const orientation = ref<"horizontal" | "vertical">("horizontal");
function resetActiveBeat(): void { function resetActiveBeat(): void {
const current = activeBeat.value; const current = activeBeat.value;
beats.value[activeBeatIndex.value] = defaultMainBeatGroup(manager); beats.value[activeBeatIndex.value] = createDefaultMainBeatGroup(manager);
current?.destroy(); current?.destroy();
triggerRef(beats); triggerRef(beats);
} }
@@ -52,10 +52,11 @@ export function createBeatStore() {
triggerRef(beats); triggerRef(beats);
} }
function addNewBeat(): void { function addNewBeat(): number {
const newBeat = defaultMainBeatGroup(manager); const newBeat = createDefaultMainBeatGroup(manager);
beats.value.push(newBeat); const newIndex = beats.value.push(newBeat);
triggerRef(beats); triggerRef(beats);
return newIndex - 1;
} }
function save(destination: "localStorage"): void { function save(destination: "localStorage"): void {
@@ -111,7 +112,7 @@ export function createBeatStore() {
const savedItem = localStorage.getItem("drum-slayer-save"); const savedItem = localStorage.getItem("drum-slayer-save");
if (savedItem) { if (savedItem) {
const serial = JSON.parse(savedItem); const serial = JSON.parse(savedItem);
beats.value = [defaultMainBeatGroup(manager)]; beats.value = [createDefaultMainBeatGroup(manager)];
loadFromSave(serial); loadFromSave(serial);
} }

View File

@@ -13,7 +13,6 @@ export type TrackInitOptions = {
barCount?: number, barCount?: number,
units?: TrackUnit[], units?: TrackUnit[],
name?: string, name?: string,
bars?: number,
isLooping?: boolean, isLooping?: boolean,
loopLength?: number, loopLength?: number,
}; };
@@ -59,10 +58,10 @@ export function deserialise(serial: unknown, manager: BeatManager) {
})); }));
return Track.asScoped({ return Track.asScoped({
manager, manager,
bars: beat.barCount, barCount: serial.barCount,
isLooping: beat.looping, isLooping: serial.looping,
loopLength: beat.loopLength, loopLength: serial.loopLength,
name: beat.name, name: serial.name,
timeSig: { timeSig: {
up: beat.timeSigUp, up: beat.timeSigUp,
down: beat.timeSigDown, down: beat.timeSigDown,

View File

@@ -2,14 +2,17 @@
<div class="beat" :class="{ vertical: false }"> <div class="beat" :class="{ vertical: false }">
<editable-text-field node-type="h3" class="beat-title" v-model="beat!.name.value" /> <editable-text-field node-type="h3" class="beat-title" v-model="beat!.name.value" />
<div class="beat-main-container"> <div class="beat-main-container">
<div class="beat-titles-container">
<div class="beat-track-title" v-for="title in titles">{{ title }}</div>
</div>
<div class="beat-track-container"> <div class="beat-track-container">
<track-view <draggable handle=".handle" :list="tracks" @change="onMove" item-key="index">
v-for="(_, i) in beat!.tracks.value" <template #item="{ element }">
:beat-index="beatIndex" <div class="beat-line">
:track-index="i" /> <editable-text-field class="track-name" v-model="element.track.name.value" />
<span class="handle"><icon color="var(--color-ui-neutral-dark)" icon-name="list" /></span>
<track-view :beat-index="beatIndex"
:track-index="element.index" />
</div>
</template>
</draggable>
</div> </div>
</div> </div>
</div> </div>
@@ -21,6 +24,9 @@
import { useAppStateStore } from '@/AppState'; import { useAppStateStore } from '@/AppState';
import { useBeatStore } from '@/BeatStore'; import { useBeatStore } from '@/BeatStore';
import { computed } from "vue"; import { computed } from "vue";
import Draggable from "vuedraggable";
import type { Track } from "@/Track";
import Icon from "../Widgets/Icon/Icon.vue";
const props = defineProps<{ const props = defineProps<{
beatIndex: number, beatIndex: number,
@@ -31,13 +37,30 @@
const { beats } = useBeatStore(); const { beats } = useBeatStore();
const beat = computed(() => beats.value[props.beatIndex] ?? null); const beat = computed(() => beats.value[props.beatIndex] ?? null);
const titles = computed(() => { const tracks = computed(() => {
const titles = beats.value[props.beatIndex]?.tracks.value.map(track => track.name.value) ?? []; const trackList = beat.value?.tracks.value ?? [];
if (props.orientation === 'horizontal') { const length = trackList.length;
titles.reverse(); const list: { index: number, track: Track }[] = [];
for (let i = 0; i < length; i++) {
list.push({
index: i,
track: trackList[i]!,
});
} }
return titles; if (props.orientation === 'horizontal') {
list.reverse();
}
return list;
}); });
function onMove(evt: { moved: { oldIndex: number, newIndex: number }}) {
if (props.orientation === 'horizontal') {
beat.value?.insertAt(tracks.value.length - 1 - evt.moved.oldIndex, tracks.value.length - 1 - evt.moved.newIndex);
} else {
beat.value?.insertAt(evt.moved.oldIndex, evt.moved.newIndex);
}
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -72,6 +95,7 @@
} }
.beat-track-container { .beat-track-container {
position: relative;
display: inline-block; display: inline-block;
} }
@@ -115,5 +139,25 @@
writing-mode: vertical-rl; writing-mode: vertical-rl;
text-align: right; text-align: right;
} }
.handle {
color: var(--color-ui-neutral-dark);
opacity: 0%;
&:hover {
opacity: 100%;
}
}
.beat-line {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
.track-name {
text-align: right;
}
</style> </style>

View File

@@ -13,7 +13,7 @@
</div> </div>
<div <div
class="sidebar-add-beat" class="sidebar-add-beat"
@click="addNewBeat()"> @click="onAddNewBeat()">
+ +
</div> </div>
</div> </div>
@@ -103,6 +103,10 @@
saveDirty, saveDirty,
} = beatStore; } = beatStore;
function onAddNewBeat() {
activeBeatIndex.value = addNewBeat();
}
watch(saveDirty, (dirty) => { watch(saveDirty, (dirty) => {
if (dirty) { if (dirty) {
document.title = `${ TITLE } (unsaved changes)`; document.title = `${ TITLE } (unsaved changes)`;

View File

@@ -35,12 +35,20 @@
activeStickingType, activeStickingType,
activeTrackUnitType, activeTrackUnitType,
selectingUnits, selectingUnits,
deselectingUnits, deselectingUnts,
} = useAppStateStore(); } = useAppStateStore();
const { beats } = useBeatStore(); const { beats } = useBeatStore();
const beat = computed(() => beats.value[props.beatIndex] ?? null); const beat = computed(() => beats.value[props.beatIndex] ?? null);
const track = computed(() => beat.value?.tracks.value[props.trackIndex] ?? null); const track = computed(() => beat.value?.tracks.value[props.trackIndex] ?? null);
const title = computed(() => track.value?.name);
function swapUp() {
beat.value?.swapTracksByIndices(props.trackIndex + 1, props.trackIndex);
}
function swapDown() {
beat.value?.swapTracksByIndices(props.trackIndex, props.trackIndex - 1);
}
const trackUnits = computed(() => { const trackUnits = computed(() => {
const units = []; const units = [];

View File

@@ -1,16 +1,16 @@
<template> <template>
<div class="track-settings" v-if="track && beat"> <div class="track-settings" v-if="track && beat">
<div class="track-settings-title-container"> <div class="track-settings-title-container">
<editable-text-field v-model="track!.name.value" /> <editable-text-field v-model="track.name.value" />
</div> </div>
<div class="track-settings-lower"> <div class="track-settings-lower">
<action-button icon-name="snowflake" type="secondary" alt="Bake Loops" :disabled="!track!.looping.value" @click="track!.bakeLoops()" /> <action-button icon-name="snowflake" type="secondary" alt="Bake Loops" :disabled="!track.looping.value" @click="track.bakeLoops()" />
<action-button icon-name="trash" type="secondary" alt="Delete Track" @click="beat!.removeTrack(trackIndex)" /> <action-button icon-name="trash" type="secondary" alt="Delete Track" @click="beat.removeTrack(trackIndex)" />
<div class="loop-settings"> <div class="loop-settings">
<bool-box label="Loop:" v-model="track!.looping.value" /> <bool-box label="Loop:" v-model="track.looping.value" />
</div> </div>
<div class="loop-settings-option" :class="{ hide: !track!.looping.value }"> <div class="loop-settings-option" :class="{ hide: !track.looping.value }">
<number-input v-model="track!.loopLength.value" /> <number-input v-model="track.loopLength.value" />
</div> </div>
</div> </div>
</div> </div>
@@ -22,7 +22,7 @@
import ActionButton from "@/ui/Widgets/ActionButton/ActionButton.vue"; import ActionButton from "@/ui/Widgets/ActionButton/ActionButton.vue";
import EditableTextField from "@/ui/Widgets/EditableTextField/EditableTextField.vue"; import EditableTextField from "@/ui/Widgets/EditableTextField/EditableTextField.vue";
import { useBeatStore } from "@/BeatStore"; import { useBeatStore } from "@/BeatStore";
import { computed } from "vue"; import { computed, watch } from "vue";
const props = defineProps<{ const props = defineProps<{
beatIndex: number, beatIndex: number,
@@ -32,6 +32,8 @@
const { beats } = useBeatStore(); const { beats } = useBeatStore();
const beat = computed(() => beats.value[props.beatIndex] ?? null); const beat = computed(() => beats.value[props.beatIndex] ?? null);
const track = computed(() => beat.value?.tracks.value[props.trackIndex] ?? null); const track = computed(() => beat.value?.tracks.value[props.trackIndex] ?? null);
watch(track, () => console.log(track.value));
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">