Files
arne-drums/src/ui/Root/Root.vue
2024-03-31 14:56:26 +02:00

315 lines
9.0 KiB
Vue

<template>
<div
class="root"
: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>
<div
class="sidebar-add-beat"
@click="onAddNewBeat()">
+
</div>
</div>
<div class="settings">
<h1 class="title">{{ title }}</h1>
<beat-settings :beat-index="activeBeatIndex" />
</div>
<div class="sidebar-toggle">
<div class="buttons">
<div
class="quick-access-button"
:title="`${ sidebarActive ? 'Hide' : 'Show' } sidebar`"
@click="sidebarActive = !sidebarActive">
<icon icon-name="list" color="var(--color-ui-neutral-dark)" />
</div>
<div
class="quick-access-button"
title="Change orientation"
@click="toggleOrientation">
<icon icon-name="arrowClockwise" color="var(--color-ui-neutral-dark)" />
</div>
<div
class="quick-access-button"
title="Bake all tracks"
@click="bakeAll">
<icon icon-name="snowflake" color="var(--color-ui-neutral-dark)" />
</div>
<div
class="quick-access-button"
title="Reset all"
@click="resetActiveBeat">
<icon icon-name="trash" color="var(--color-ui-neutral-dark)" />
</div>
<div
class="quick-access-button"
:class="{ 'unclickable': !saveDirty }"
:title="saveDirty ? 'Save changes' : 'No unsaved changes'"
@click="save('localStorage')">
<icon icon-name="download" color="var(--color-ui-neutral-dark)" />
</div>
</div>
<toolbox class="toolbox" />
</div>
</div>
<div class="beat-stage-container">
<div class="beat-stage">
<beat-view
:beat-index="activeBeatIndex"
:orientation="currentOrientation" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted, provide, 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";
const TITLE = 'Drum Slayer';
defineProps<{
title: string,
}>();
const currentOrientation = ref<'horizontal' | 'vertical'>('horizontal');
const sidebarActive = ref(false);
const appStateStore = createAppStateStore();
provide(AppStateStoreKey, appStateStore);
const beatStore = createBeatStore();
provide(BeatStoreKey, beatStore);
const {
save,
resetActiveBeat,
activeBeatIndex,
beats,
addNewBeat,
bakeAll,
saveDirty,
} = beatStore;
function onAddNewBeat() {
activeBeatIndex.value = addNewBeat();
}
watch(saveDirty, (dirty) => {
if (dirty) {
document.title = `${ TITLE } (unsaved changes)`;
} else {
document.title = TITLE;
}
});
const mediaQueryList = window.matchMedia("screen and (max-width: 900px)");
function onMediaChange(event: MediaQueryListEvent | MediaQueryList) {
sidebarActive.value = event.matches;
}
mediaQueryList.addEventListener('change', onMediaChange);
onMediaChange(mediaQueryList);
function windowMouseUp() {
appStateStore.selectingUnits.value = false;
appStateStore.deselectingUnits.value = false;
appStateStore.unitMouseStart.value = null;
}
onMounted(() => {
window.addEventListener('mouseup', windowMouseUp);
});
onBeforeUnmount(() => {
window.removeEventListener('mouseup', windowMouseUp);
});
function toggleOrientation(): void {
if (currentOrientation.value === "vertical") {
currentOrientation.value = "horizontal";
} else {
currentOrientation.value = "vertical";
}
}
</script>
<style scoped lang="scss">
.root {
position: relative;
overflow: hidden;
color: var(--color-p-light);
background-color: var(--color-bg-dark);
height: 100vh;
align-content: center;
.sidebar {
position: absolute;
left: -28em;
width: 30em;
height: 100vh;
display: flex;
transition: left 400ms;
top: 0;
}
&.sidebar-visible {
.beat-stage {
max-width: calc(100vw - 30em);
}
.beat-stage-container {
left: 30em;
width: calc(100vw - 30em);
}
.sidebar {
left: 0;
}
}
.settings {
z-index: 1;
width: 28em;
background-color: var(--color-bg-light);
overflow: scroll;
display: inline-block;
.title {
color: var(--color-title-light);
text-align: center;
}
}
.sidebar-toggle {
z-index: 1;
height: 100vh;
min-width: 2em;
background-color: var(--color-bg-light);
left: 0;
display: flex;
flex-direction: column;
.buttons {
flex: 1;
.quick-access-button {
flex: 1;
right: 0;
width: 2em;
height: 2em;
cursor: pointer;
margin-bottom: 0.5em;
&.unclickable {
opacity: 50%;
cursor: auto;
}
}
}
.toolbox {
width: 2em;
}
}
.beat-stage-container {
position: absolute;
height: 100%;
left: 0;
top: 0;
width: 100vw;
display: flex;
flex-direction: column;
transition: left 400ms, width 400ms;
}
.beat-stage {
position: relative;
max-height: 100vh;
margin: auto;
max-width: 100vw;
transition: max-width 400ms;
padding-left: 3em;
}
&.vertical-mode .beat-stage {
margin: auto auto;
height: 100vh;
}
.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-add-beat {
width: 100%;
padding: 8px 3px 8px 3px;
}
.sidebar-add-beat:hover,
.sidebar-left-tab:hover:not(.active) {
cursor: pointer;
background-color: var(--color-ui-neutral-dark);
transition: background-color 200ms;
}
@media screen and (max-width: 900px) {
&.sidebar-visible .sidebar {
left: 0;
width: 100vw;
}
.sidebar {
left: calc(-100vw + 2em);
width: 100vw;
}
.settings {
width: calc(100vw - 2em);
}
.sidebar-visible .beat-stage-container {
left: 100vw;
}
.beat-stage-container {
left: 0;
}
.sidebar-visible .beat-stage {
max-width: 100vw;
}
}
* {
user-drag: none;
user-select: none;
}
}
</style>