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",
"license": "ISC",
"dependencies": {
"@djledda/ladder": "^1.1.0",
"@vitejs/plugin-vue": "^4.0.0",
"@vitejs/plugin-vue": "^5.0.4",
"@vue/tsconfig": "^0.1.3",
"pinia": "^2.0.32",
"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": {
"@types/node": "^17.0.40",
"@types/node": "^20.12.2",
"@typescript-eslint/eslint-plugin": "^5.26.0",
"@typescript-eslint/parser": "^5.26.0",
"cross-env": "^7.0.3",
"eslint": "^8.40.0",
"eslint-plugin-vue": "^9.11.0",
"typescript": "^5.0.4",
"vite": "^4.3.0"
"typescript": "^5.4.3",
"vite": "^5.2.7"
}
}

View File

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

View File

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

View File

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

View File

@@ -2,14 +2,17 @@
<div 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-titles-container">
<div class="beat-track-title" v-for="title in titles">{{ title }}</div>
</div>
<div class="beat-track-container">
<track-view
v-for="(_, i) in beat!.tracks.value"
:beat-index="beatIndex"
:track-index="i" />
<draggable handle=".handle" :list="tracks" @change="onMove" item-key="index">
<template #item="{ element }">
<div class="beat-line">
<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>
@@ -21,6 +24,9 @@
import { useAppStateStore } from '@/AppState';
import { useBeatStore } from '@/BeatStore';
import { computed } from "vue";
import Draggable from "vuedraggable";
import type { Track } from "@/Track";
import Icon from "../Widgets/Icon/Icon.vue";
const props = defineProps<{
beatIndex: number,
@@ -31,13 +37,30 @@
const { beats } = useBeatStore();
const beat = computed(() => beats.value[props.beatIndex] ?? null);
const titles = computed(() => {
const titles = beats.value[props.beatIndex]?.tracks.value.map(track => track.name.value) ?? [];
if (props.orientation === 'horizontal') {
titles.reverse();
}
return titles;
const tracks = computed(() => {
const trackList = beat.value?.tracks.value ?? [];
const length = trackList.length;
const list: { index: number, track: Track }[] = [];
for (let i = 0; i < length; i++) {
list.push({
index: i,
track: trackList[i]!,
});
}
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>
<style scoped lang="scss">
@@ -72,6 +95,7 @@
}
.beat-track-container {
position: relative;
display: inline-block;
}
@@ -115,5 +139,25 @@
writing-mode: vertical-rl;
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>

View File

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

View File

@@ -35,12 +35,20 @@
activeStickingType,
activeTrackUnitType,
selectingUnits,
deselectingUnits,
deselectingUnts,
} = useAppStateStore();
const { beats } = useBeatStore();
const beat = computed(() => beats.value[props.beatIndex] ?? 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 units = [];

View File

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