This commit is contained in:
Daniel Ledda
2024-05-31 17:27:04 +02:00
parent f76205d0db
commit ff6ffa57ce
14 changed files with 1071 additions and 562 deletions

1309
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
import { inject, type InjectionKey, ref, provide, getCurrentInstance } from "vue"; import { inject, type InjectionKey, ref, getCurrentInstance } from "vue";
import { Bound } from "./utils"; import { Bound } from "./utils";
export type UITool = export type UITool =

View File

@@ -1,5 +1,5 @@
import { Beat, deserialise as deserialiseBeat, type BeatManager } from "@/Beat"; import { Beat, deserialise as deserialiseBeat } from "@/Beat";
import { inject, computed, type InjectionKey, onScopeDispose, ref, shallowRef, triggerRef, watch, nextTick, readonly, provide, getCurrentInstance } from "vue"; import { inject, computed, type InjectionKey, onScopeDispose, ref, shallowRef, triggerRef, watch, nextTick, getCurrentInstance } from "vue";
import { z } from "zod"; import { z } from "zod";
import { Bound } from "./utils"; import { Bound } from "./utils";

2
src/globals.d.ts vendored
View File

@@ -2,7 +2,7 @@ import RootView from "@/ui/Root/RootView";
declare global { declare global {
interface Window { interface Window {
appRoot?: RootView; drumslayer?: Record<string, any>,
} }
} }

View File

@@ -5,8 +5,4 @@ import "@/ui/global.css";
const app = createApp(Root, { title: "Drum Slayer" }); const app = createApp(Root, { title: "Drum Slayer" });
app.mount("#app"); app.mount("#app");
interface Window {
drumslayer?: Record<string, any>,
}

View File

@@ -28,7 +28,6 @@
<script setup lang="ts"> <script setup lang="ts">
import TrackView from "@/ui/Track/Track.vue"; import TrackView from "@/ui/Track/Track.vue";
import EditableTextField from "@/ui/Widgets/EditableTextField/EditableTextField.vue"; import EditableTextField from "@/ui/Widgets/EditableTextField/EditableTextField.vue";
import { useAppStateStore } from '@/AppState';
import { useBeatStore } from '@/BeatStore'; import { useBeatStore } from '@/BeatStore';
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import Draggable from "vuedraggable"; import Draggable from "vuedraggable";
@@ -40,7 +39,6 @@
orientation?: "horizontal" | "vertical", orientation?: "horizontal" | "vertical",
}>(); }>();
const appState = useAppStateStore();
const { beats } = useBeatStore(); const { beats } = useBeatStore();
const beat = computed(() => beats.value[props.beatIndex] ?? null); const beat = computed(() => beats.value[props.beatIndex] ?? null);
@@ -76,18 +74,10 @@
.beat { .beat {
padding: 1em; padding: 1em;
overflow-x: scroll; overflow-x: scroll;
overflow-y: hidden; overflow-y: visible;
display: flex; display: flex;
width: inherit;
flex-direction: column; flex-direction: column;
} width: 100%;
.vertical-mode .beat {
padding-bottom: 2em;
align-items: center;
overflow-x: hidden;
overflow-y: scroll;
height: inherit;
} }
.beat-title { .beat-title {
@@ -97,39 +87,17 @@
margin-left: 16px; margin-left: 16px;
} }
.vertical-mode .beat-title {
color: var(--color-title-light);
text-align: center;
padding-left: 0;
}
.beat-track-container { .beat-track-container {
position: relative; position: relative;
display: inline-block; display: inline-block;
} }
.beat-titles-container {
display: inline-flex;
flex-direction: column;
}
.vertical-mode .beat-main-container {
display: block;
}
.vertical-mode .beat-titles-container {
margin-bottom: 10px;
display: flex;
flex-direction: row;
overflow: visible;
}
.beat-main-container { .beat-main-container {
display: flex; display: flex;
white-space: nowrap; white-space: nowrap;
} }
.beat-track-title { .track-name {
height: 36px; height: 36px;
line-height: 36px; line-height: 36px;
margin: 0; margin: 0;
@@ -139,16 +107,6 @@
white-space: nowrap; white-space: nowrap;
} }
.vertical-mode .beat-track-title {
height: auto;
transform: rotate(330deg);
transform-origin: bottom;
width: 36px;
display: inline-block;
writing-mode: vertical-rl;
text-align: right;
}
.handle { .handle {
cursor: move; cursor: move;
color: var(--color-ui-neutral-dark); color: var(--color-ui-neutral-dark);
@@ -164,7 +122,6 @@
flex-direction: row; flex-direction: row;
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
} }
.dragging { .dragging {
@@ -182,5 +139,45 @@
.ghost { .ghost {
opacity: 0; opacity: 0;
} }
.vertical-mode {
.beat-title {
color: var(--color-title-light);
text-align: center;
padding-left: 0;
}
.beat-main-container {
display: block;
}
.track-name {
height: auto;
transform: rotate(330deg);
transform-origin: bottom;
display: inline-block;
writing-mode: vertical-rl;
text-align: right;
}
.beat {
padding-bottom: 2em;
align-items: center;
overflow-x: visible;
overflow-y: scroll;
height: inherit;
}
.beat-line {
flex-direction: column;
display: inline-block;
max-width: 36px;
}
.beat-track-container {
display: flex;
flex-direction: row;
}
}
</style> </style>

View File

@@ -42,9 +42,6 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.beat-settings {
}
.beat-settings-options { .beat-settings-options {
padding: 1em; padding: 1em;
display: flex; display: flex;

View File

@@ -1,8 +0,0 @@
<template>
</template>
<script setup lang="ts">
</script>
<style scoped lang="scss">
</style>

View File

@@ -342,9 +342,15 @@
padding-left: 3em; padding-left: 3em;
} }
&.vertical-mode .beat-stage { &.vertical-mode {
margin: auto auto; .beat-stage {
height: 100vh; margin: auto auto;
height: 100vh;
}
.beat-stage-container {
display: block;
}
} }
.sidebar-left-strip { .sidebar-left-strip {

View File

@@ -34,22 +34,11 @@
selectedTool, selectedTool,
activeStickingType, activeStickingType,
activeTrackUnitType, activeTrackUnitType,
selectingUnits,
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);
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 = [];
if (track.value) { if (track.value) {
@@ -66,7 +55,7 @@
function toggle(index: number) { function toggle(index: number) {
if (!track.value) return; if (!track.value) return;
track.value.toggleUnit(index); track.value.toggleUnit(index);
if (track.value.getUnitByIndex(index).on) { if (track.value.getUnitByIndex(index)?.on) {
applyCurrentToolToTrackUnit(index); applyCurrentToolToTrackUnit(index);
} }
} }
@@ -96,74 +85,72 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.vertical-mode .track { .track-unit {
height: 36px; &.spaced {
} margin-bottom: 0;
margin-right: 1em;
.vertical-mode .track { }
height: auto;
}
.track > * {
padding-right: 1em;
padding-left: 1em;
}
.vertical-mode .track-unit.spaced {
margin-bottom: 1em;
margin-right: 0;
}
.track-unit.spaced {
margin-bottom: 0;
margin-right: 1em;
}
.vertical-mode .track > * {
padding-right: 0;
padding-left: 0;
} }
.track-unit-block { .track-unit-block {
height: 2em; height: 2em;
} }
.vertical-mode .track-unit-block {
height: auto;
width: 2em;
}
.track-spacer { .track-spacer {
display: inline-block; display: inline-block;
width: 1em; width: 1em;
height: 2em; height: 2em;
} }
.vertical-mode .track-spacer {
display: block;
width: 2em;
height: 1em;
}
.track-main { .track-main {
height: 36px; height: 36px;
} }
.vertical-mode .track-main {
width: 2em;
margin-right: 4px;
display: block;
}
.track-settings-container { .track-settings-container {
display: flex; display: flex;
} }
.track { .track {
width: max-content; width: max-content;
& > * {
padding-right: 1em;
padding-left: 1em;
}
} }
.vertical-mode .track { .vertical-mode {
display: inline-block; .track-spacer {
display: block;
width: 2em;
height: 1em;
}
.track-unit-block {
height: auto;
width: 2em;
}
.track-main {
width: 2em;
margin-right: 4px;
display: block;
}
.track {
display: inline-block;
height: 36px;
& > * {
padding-right: 0;
padding-left: 0;
}
}
.track-unit {
&.spaced {
margin-bottom: 1em;
margin-right: 0;
}
}
} }
</style> </style>

View File

@@ -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, watch } from "vue"; import { computed } from "vue";
const props = defineProps<{ const props = defineProps<{
beatIndex: number, beatIndex: number,
@@ -35,14 +35,6 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.track-settings {
}
.track-settings-title-container {
}
.track-settings-title-container input { .track-settings-title-container input {
min-width: 100%; min-width: 100%;
height: 2em; height: 2em;

View File

@@ -16,7 +16,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { type IconName } from "@/ui/Widgets/Icon/icons"; import type { IconName } from "@/ui/Widgets/Icon/icons";
import Icon from "@/ui/Widgets/Icon/Icon.vue"; import Icon from "@/ui/Widgets/Icon/Icon.vue";
withDefaults(defineProps<{ withDefaults(defineProps<{

View File

@@ -1,80 +1,105 @@
<template> <template>
<input <component
v-if="editing" :is="nodeType"
:value="modelValue" class="static editable-text-field"
ref="inputField" ref="inputField"
class="input editable-text-field-view" @keydown.enter.prevent="onEnter"
type="text"
@input="onInput" @input="onInput"
@blur="onBlur" @blur="onBlur"
@keyup="onKeyUp" @keyup="onKeyUp"
/> @click="onClick">
<component
v-else
:is="nodeType"
class="static editable-text-field-view"
@click="editing = true">
{{ modelValue }} {{ modelValue }}
</component> </component>
</template> </template>
<script setup lang="ts">import { ref, watch } from 'vue'; <script setup lang="ts">
import { ref } from 'vue';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
nodeType?: string, nodeType?: string,
modelValue: string, modelValue: string,
noEmpty?: boolean, noEmpty?: boolean,
}>(), { }>(), {
nodeType: 'div', nodeType: 'div',
noEmpty: true,
}); });
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:modelValue', value: string): true, (e: 'update:modelValue', value: string): true,
}>(); }>();
let lastNonEmptyInput = ""; let lastNonEmptyInput = props.modelValue ?? "";
const editing = ref(false); const inputField = ref<HTMLElement | null>(null);
const inputField = ref<HTMLInputElement | null>(null);
watch(inputField, (newVal) => newVal && newVal.focus()); function onClick() {
if (inputField.value) {
inputField.value.contentEditable = "true";
inputField.value.focus();
const selection = window.getSelection();
const firstChild = inputField.value.firstChild;
if (selection && firstChild) {
const range = document.createRange();
range.setStart(firstChild, 0);
range.setEnd(firstChild, inputField.value.textContent?.length ?? 1);
selection.removeAllRanges();
selection.addRange(range);
}
}
}
function onInput(event: Event) { function onInput(event: Event) {
const input = (event.target as HTMLInputElement).value; const input = (event.target as HTMLElement).textContent ?? "";
const inputToSet = props.noEmpty && input === "" ? lastNonEmptyInput : input; const inputToSet = props.noEmpty && input === "" ? lastNonEmptyInput : input;
emit('update:modelValue', inputToSet); emit('update:modelValue', inputToSet);
} }
function onBlur(event: FocusEvent) { function onBlur(event: Event) {
if ((event.target as HTMLInputElement).value === "") { if ((event.target as HTMLElement).textContent === "") {
emit('update:modelValue', lastNonEmptyInput); emit('update:modelValue', lastNonEmptyInput);
} }
editing.value = false; if (inputField.value) {
inputField.value.contentEditable = "false";
window.getSelection()?.removeAllRanges();
inputField.value.textContent = props.modelValue;
}
} }
function onKeyUp(event: KeyboardEvent) { function onKeyUp(event: KeyboardEvent) {
if (event.key === "Enter") { if (event.key === "Enter") {
(event.target as HTMLInputElement).blur(); (event.target as HTMLElement).blur();
} }
} }
function onEnter(e: KeyboardEvent) {
onBlur(e);
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.editable-text-field-view { .editable-text-field {
min-width: 3em;
padding: 0.2em; padding: 0.2em;
} }
.input.editable-text-field-view { input.input {
height: 100px;
color: black; color: black;
width: fit-content;
} }
.static.editable-text-field-view { .vertical-mode {
input.input {
height: 100px;
color: black;
}
}
.static {
transition: background-color 200ms; transition: background-color 200ms;
width: fit-content; width: fit-content;
cursor: pointer; cursor: pointer;
&:hover {
background-color: var(--color-ui-neutral-dark-hover);
}
} }
.static.editable-text-field-view:hover {
background-color: var(--color-ui-neutral-dark-hover);
}
</style>" </style>"

View File

@@ -19,7 +19,9 @@
"jsx": "react" "jsx": "react"
}, },
"include": [ "include": [
"./src/**/*" "./src/**/*",
"./prod.env.ts",
"./dev.env.ts"
], ],
"references": [ "references": [
{ {