commit 76

This commit is contained in:
Moritz Ruth 2025-03-13 00:48:21 +01:00
parent b90f897123
commit 18dc17d6d5
Signed by: moritzruth
GPG key ID: C9BBAB79405EE56D
46 changed files with 2032 additions and 2096 deletions

3
ui/.gitignore vendored
View file

@ -1,4 +1,5 @@
.idea/ .idea/
node_modules/ node_modules/
*.env
dist/ dist/
src/generated-types
*.env

View file

@ -1 +0,0 @@
18

View file

@ -2,10 +2,11 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Moira</title> <title>Janus</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link href="./node_modules/@fontsource-variable/manrope/index.css" rel="stylesheet"/> <link href="./node_modules/@fontsource-variable/manrope/index.css" rel="stylesheet"/>
<script type="module" src="./src/main.ts"></script> <script type="module" src="./src/main.ts"></script>
<link rel="stylesheet" href="src/global.scss"/>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View file

@ -1,31 +1,35 @@
{ {
"name": "moira", "name": "janus-ui",
"private": true,
"version": "1.0.0", "version": "1.0.0",
"author": "Moritz Ruth <dev@moritzruth.de>", "author": "Moritz Ruth <dev@moritzruth.de>",
"license": "Apache-2.0", "license": "Apache-2.0",
"type": "module",
"scripts": { "scripts": {
"dev": "vite --port 3000 --host", "dev": "vite --port 3000 --host",
"build": "vite build --emptyOutDir --outDir ../src/main/resources/ui" "build": "vite build --emptyOutDir --outDir ../src/main/resources/ui"
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/ph": "^1.1.5", "@iconify-json/ph": "^1.2.2",
"@types/lodash-es": "^4.17.7", "@types/lodash-es": "^4.17.12",
"@vitejs/plugin-vue": "^4.2.3", "@types/node": "^22.13.10",
"typescript": "^5.0.4", "@unocss/preset-wind3": "^66.1.0-beta.3",
"unplugin-icons": "^0.16.1", "unocss": "^66.1.0-beta.3",
"vite": "^4.3.9", "@vitejs/plugin-vue": "^5.2.1",
"vite-plugin-pages": "^0.30.1", "typescript": "^5.8.2",
"vite-plugin-windicss": "^1.9.0", "unplugin-icons": "^22.1.0",
"windicss": "^3.5.6" "unplugin-vue-router": "^0.12.0",
"vite": "^6.2.1"
}, },
"dependencies": { "dependencies": {
"@fontsource-variable/manrope": "^5.0.0", "@fontsource-variable/manrope": "^5.2.5",
"@vueuse/core": "^10.1.2", "@vueuse/core": "^13.0.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"modern-normalize": "^3.0.1",
"reconnecting-websocket": "^4.4.0", "reconnecting-websocket": "^4.4.0",
"sass": "^1.62.1", "sass": "^1.85.1",
"vue": "^3.3.4", "vue": "^3.5.13",
"vue-router": "^4.2.1" "vue-router": "^4.5.0"
}, },
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [

2016
ui/pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,32 +1,39 @@
<template> <template>
<div id="app" class="bg-black text-white"> <div v-if="isConnecting" class="flex flex-col justify-center items-center h-100dvh gap-4">
<div class="flex flex-col justify-center items-center h-full space-y-4" v-if="isConnecting"> <div class="font-bold text-10">Connecting</div>
<div class="font-bold text-10">Connecting...</div> <div class="text-s1">Janus, created by Moritz Ruth</div>
<div class="text-s1">Created by Moritz Ruth</div> </div>
<div v-else class="h-100dvh flex flex-col">
<div class="font-black text-2xl md:text-4xl p-4 md:p-8 pb-0 md:pb-0 flex-shrink-0">
{{ current.act === null ? "" : `${current.act.name}` }}{{ current.scene.name }}
</div>
<div class="flex-grow p-4 md:p-8 overflow-hidden">
<router-view/>
</div>
<MusicProgressBar class="h-10 flex-shrink-0"/>
</div> </div>
<router-view v-else/>
<TimeDisplay/> <TimeDisplay/>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
html, body, #app { .list-move,
width: 100vw; .list-enter-active,
height: 100vh; .list-leave-active {
overflow: hidden; transition: all 0.5s ease;
font-family: "Manrope Variable", sans-serif;
} }
::-webkit-scrollbar { .list-leave-to {
width: 10px; opacity: 0;
transform: translateX(10px);
} }
::-webkit-scrollbar-thumb { .list-enter-from {
background: rgb(255 255 255 / 10%); opacity: 0;
transform: translateX(-10px);
&:hover {
background: rgb(255 255 255 / 20%);
} }
.list-leave-active {
position: absolute;
} }
</style> </style>
@ -34,10 +41,17 @@
import { connect } from "./syncing" import { connect } from "./syncing"
import { ref } from "vue" import { ref } from "vue"
import TimeDisplay from "./components/TimeDisplay.vue" import TimeDisplay from "./components/TimeDisplay.vue"
import { current } from "@/state.js"
import MusicProgressBar from "@/components/MusicProgressBar.vue"
export default { export default {
name: "App", name: "App",
components: { TimeDisplay }, computed: {
current() {
return current
}
},
components: { MusicProgressBar, TimeDisplay },
setup() { setup() {
const isConnecting = ref(true) const isConnecting = ref(true)
connect().then(() => { connect().then(() => {

View file

@ -1,13 +1,15 @@
<template> <template>
<div class="p-2 space-y-8"> <div class="overflow-y-auto">
<div> <transition-group name="list" tag="div">
<div class="pb-2 font-bold text-gray-400 text-2 tracking-wider uppercase"> <div v-if="current.step.actorsOnStage.length === 0" key="" class="text-gray-400">Niemand</div>
auf der Bühne <div
</div> v-for="actor in current.step.actorsOnStage"
<div> :key="parseStringWithDetails(actor).main"
<EntrancesList :entrances="current.step.actorsOnStage"/> class="truncate text-4.5"
</div> >
{{ actor }}
</div> </div>
</transition-group>
</div> </div>
</template> </template>
@ -16,8 +18,5 @@
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import EntrancesList from "./EntrancesList.vue" import { current, parseStringWithDetails } from "@/state"
import { current, getNextValidPosition, getStep, parseStringWithDetails, ShowPosition, state, Step, show } from "../state"
import { computed } from "vue"
import { intersection } from "lodash-es"
</script> </script>

View file

@ -1,7 +1,7 @@
<template> <template>
<button <button
class="px-5 py-2 active:bg-green-800 transition duration-200 font-bold text-5"
:class="[$style.root, isActive ? 'bg-green-800' : 'bg-green-600']" :class="[$style.root, isActive ? 'bg-green-800' : 'bg-green-600']"
class="px-5 py-2 active:bg-green-800 transition duration-200 font-bold text-4"
@click="e => emit('click', e)" @click="e => emit('click', e)"
> >
<slot/> <slot/>
@ -14,10 +14,14 @@
} }
</style> </style>
<script setup lang="ts"> <script lang="ts" setup>
const props = defineProps<{ const props = defineProps<{
isActive?: boolean isActive?: boolean
}>() }>()
const emit = defineEmits(["click"]) const emit = defineEmits<{
click: [MouseEvent]
pointerdown: [PointerEvent]
pointerup: [PointerEvent]
}>()
</script> </script>

View file

@ -1,9 +1,5 @@
<template> <template>
<div <div :class="$style.root" :data-blinking="isBlinking">
class="flex flex-col justify-center py-4"
:class="$style.root"
:data-blinking="isBlinking"
>
<slot/> <slot/>
</div> </div>
</template> </template>
@ -24,10 +20,9 @@
} }
</style> </style>
<script setup lang="ts"> <script lang="ts" setup>
import { toRef, watch } from "vue" import { computed, toRef, watch } from "vue"
import { autoResetRef } from "@vueuse/core" import { autoResetRef } from "@vueuse/core"
import { computed } from "vue"
const props = defineProps<{ const props = defineProps<{
value: unknown value: unknown

View file

@ -1,24 +1,23 @@
<template> <template>
<div class="flex items-start space-x-2" :class="textClass ?? 'text-4'"> <div :class="textClass ?? 'text-4'" class="flex items-start gap-2 py-0.5">
<component :is="icon" class="mt-0.5 flex-shrink-0"/> <component :is="icon" class="flex-shrink-0"/>
<div :class="singleLine && 'truncate'"> <div :class="singleLine && 'truncate'">
{{ text }} {{ text }}
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script lang="ts" setup>
import MusicNoteIcon from "virtual:icons/ph/music-note" import MusicNoteIcon from "virtual:icons/ph/music-note"
import ChatCircleTextIcon from "virtual:icons/ph/chat-circle-TEXT" import ChatCircleTextIcon from "virtual:icons/ph/chat-circle-TEXT"
import StopIcon from "virtual:icons/ph/stop" import StopIcon from "virtual:icons/ph/stop"
import HeadlightsIcon from "virtual:icons/ph/headlights"
import WarningIcon from "virtual:icons/ph/warning" import WarningIcon from "virtual:icons/ph/warning"
import ArrowsOutLineHorizontalIcon from "virtual:icons/ph/arrows-out-line-horizontal" import ArrowsOutLineHorizontalIcon from "virtual:icons/ph/arrows-out-line-horizontal"
import ArrowsInLineHorizontalIcon from "virtual:icons/ph/arrows-in-line-horizontal" import ArrowsInLineHorizontalIcon from "virtual:icons/ph/arrows-in-line-horizontal"
import DotFillIcon from "virtual:icons/ph/dot-fill" import DotFillIcon from "virtual:icons/ph/dot-fill"
import { computed } from "vue" import { computed } from "vue"
import { START_STEP, Step } from "../state" import { START_STEP, Step } from "@/state"
import { formatSeconds } from "../helpers" import { formatSeconds } from "@/helpers"
import { isEqual } from "lodash-es" import { isEqual } from "lodash-es"
const props = defineProps<{ const props = defineProps<{
@ -40,11 +39,17 @@
? ArrowsInLineHorizontalIcon ? ArrowsInLineHorizontalIcon
: ArrowsOutLineHorizontalIcon : ArrowsOutLineHorizontalIcon
case "LIGHT": return HeadlightsIcon case "TEXT":
case "TEXT": return ChatCircleTextIcon return ChatCircleTextIcon
case "MUSIC_START": return MusicNoteIcon
case "MUSIC_END": return StopIcon case "MUSIC_START":
case "CUSTOM": return WarningIcon return MusicNoteIcon
case "MUSIC_END":
return StopIcon
case "CUSTOM":
return WarningIcon
} }
}) })
@ -59,24 +64,20 @@
return cue.whileMoving ? "Der Vorhang schließt sich" : "Der Vorhang ist geschlossen" return cue.whileMoving ? "Der Vorhang schließt sich" : "Der Vorhang ist geschlossen"
} }
case "LIGHT":
if (cue.state === "on") {
return cue.whileFading ? "Das Licht geht an" : "Das Licht ist an"
} else {
return cue.whileFading ? "Das Licht geht aus" : "Das Licht ist aus"
}
case "TEXT": case "TEXT":
let suffix = "" let suffix = ""
if (cue.clarification !== undefined) { if (cue.clarification !== undefined) suffix = ` (${cue.clarification})`
suffix = ` (${cue.clarification})`
}
return `${cue.speaker}: »${cue.text}«${suffix}` return `${cue.speaker}: »${cue.text}«${suffix}`
case "MUSIC_START": return `${cue.title} [${formatSeconds(cue.duration / 1000)}]` case "MUSIC_START":
case "MUSIC_END": return "Ende der Musik" return `${cue.title} [${formatSeconds(cue.duration / 1000)}]`
case "CUSTOM": return cue.text
case "MUSIC_END":
return "Ende der Musik"
case "CUSTOM":
return cue.text
} }
}) })
</script> </script>

View file

@ -1,7 +1,8 @@
<template> <template>
<div> <Button class="w-20" :is-active="!state.isLightBehindCurtainOn" @click="onClick()">{{
<Button :is-active="!state.isLightBehindCurtainOn" @click="onClick()">{{ state.isLightBehindCurtainOn ? "Umbaulicht ist AN" : "Umbaulicht ist AUS" }}</Button> state.isLightBehindCurtainOn ? "AN" : "AUS"
</div> }}
</Button>
</template> </template>
<style module> <style module>
@ -9,7 +10,7 @@
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { state } from "../state" import { state } from "@/state"
import Button from "./Button.vue" import Button from "./Button.vue"
function onClick() { function onClick() {

View file

@ -1,54 +0,0 @@
<template>
<transition-group tag="div" name="list">
<div
v-for="actor in entrances"
:key="parseStringWithDetails(actor).main"
class="truncate"
>
<span class="font-bold">
{{ parseStringWithDetails(actor).main }}
</span>
<span class="text-gray-400 pl-2">
{{ parseStringWithDetails(actor).details }}
</span>
</div>
</transition-group>
</template>
<style scoped>
.list-move,
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-leave-to {
opacity: 0;
transform: translateX(10px);
}
.list-enter-from {
opacity: 0;
transform: translateX(-10px);
}
.list-leave-active {
position: absolute;
}
</style>
<script lang="ts">
import { PropType } from "vue"
import { parseStringWithDetails } from "../state"
export default {
name: "EntrancesList",
methods: { parseStringWithDetails },
props: {
entrances: {
type: Array as PropType<string[]>,
required: true
}
}
}
</script>

View file

@ -1,11 +1,10 @@
<template> <template>
<div class="flex flex-wrap items-center gap-3"> <div class="flex items-center gap-1">
<span class="-sm:hidden">Nebel:</span>
<Button <Button
v-for="p in buttonPowers" v-for="p in buttonPowers"
:is-active="isActive && p[0] === power"
@pointerdown="e => onButtonActive(e, p[0])" @pointerdown="e => onButtonActive(e, p[0])"
@pointerup="e => onButtonInactive(e)" @pointerup="e => onButtonInactive(e)"
:is-active="isActive && p[0] === power"
> >
{{ p[0] * 100 }}% {{ p[0] * 100 }}%
</Button> </Button>
@ -16,7 +15,7 @@
</style> </style>
<script setup lang="ts"> <script lang="ts" setup>
import Button from "./Button.vue" import Button from "./Button.vue"
import { computed, ref, watch } from "vue" import { computed, ref, watch } from "vue"
import { onKeyDown, onKeyUp, useEventListener, useIntervalFn } from "@vueuse/core" import { onKeyDown, onKeyUp, useEventListener, useIntervalFn } from "@vueuse/core"

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="overflow-hidden text-6 box p-4" :data-blinking="isBlinking"> <div :data-blinking="isBlinking" class="overflow-hidden text-6 box p-4">
{{ message }} {{ message }}
</div> </div>
</template> </template>

View file

@ -1,23 +1,19 @@
<template> <template>
<div class="h-full max-h-60"> <div class="max-h-60">
<textarea <textarea
:value="state.message" :value="state.message"
id="message-box" class="h-full w-full border border-dark-200 focus:border-green-500 focus:outline-none transition rounded-lg bg-dark-800 p-4 text-5 resize-none"
:class="$style.area"
class="h-full w-full border border-dark-200 focus:border-green-500 focus:outline-none transition rounded-lg bg-dark-800 p-4 text-3"
placeholder="Nachricht an alle" placeholder="Nachricht an alle"
@input="e => setMessage(e.target.value)" @input="e => setMessage((e.target as HTMLInputElement).value)"
/> />
</div> </div>
</template> </template>
<style module> <style module>
.area {
resize: none;
}
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { state } from "../state" import { state } from "@/state"
import { setMessage } from "../syncing" import { setMessage } from "@/syncing"
</script> </script>

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="flex flex-col space-y-5" :class="scrollable ? 'overflow-y-auto' : 'overflow-hidden'"> <div :class="scrollable ? 'overflow-y-auto' : 'overflow-hidden'" class="flex flex-col space-y-5">
<div v-for="(act, actIndex) in show.acts"> <div v-for="(act, actIndex) in show.acts">
<MotionsListAct :act="act" :center-current="Boolean(centerCurrent)"/> <MotionsListAct :act="act" :center-current="Boolean(centerCurrent)"/>
</div> </div>
@ -7,7 +7,7 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script lang="ts" setup>
import MotionsListAct from "./MotionsListAct.vue" import MotionsListAct from "./MotionsListAct.vue"
import { show } from "../state" import { show } from "../state"

View file

@ -16,9 +16,9 @@
<div class="flex flex-col"> <div class="flex flex-col">
<template v-for="step in scene.steps" :key="step.position"> <template v-for="step in scene.steps" :key="step.position">
<MotionsListStep <MotionsListStep
:step="step"
:more-positions="step.morePositions"
:center-current="centerCurrent" :center-current="centerCurrent"
:more-positions="step.morePositions"
:step="step"
/> />
</template> </template>
</div> </div>
@ -31,7 +31,7 @@
</style> </style>
<script setup lang="ts"> <script lang="ts" setup>
import { Act, Scene, ShowPosition, Step } from "../state" import { Act, Scene, ShowPosition, Step } from "../state"
import { computed } from "vue" import { computed } from "vue"
import MotionsListStep from "./MotionsListStep.vue" import MotionsListStep from "./MotionsListStep.vue"

View file

@ -1,8 +1,8 @@
<template> <template>
<div class="transition p-3" :class="isActive && 'bg-green-800'"> <div :class="isActive && 'bg-green-800'" class="transition p-3">
<div class="flex space-x-2"> <div class="flex space-x-2">
<div class="flex-grow"> <div class="flex-grow">
<CueBox text-class="text-6" :step="step"/> <CueBox :step="step" text-class="text-6"/>
<div v-if="step.actorEntrances.length + step.actorExits.length > 0" class="py-2 pl-8 space-y-2 text-7"> <div v-if="step.actorEntrances.length + step.actorExits.length > 0" class="py-2 pl-8 space-y-2 text-7">
<div class="flex flex-col space-y-1"> <div class="flex flex-col space-y-1">
<div <div
@ -46,14 +46,14 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script lang="ts" setup>
import { parseStringWithDetails, ShowPosition, START_STEP, state, Step } from "../state" import { parseStringWithDetails, ShowPosition, START_STEP, state, Step } from "../state"
import CueBox from "./CueBox.vue" import CueBox from "./CueBox.vue"
import CaretDoubleRightIcon from "virtual:icons/ph/caret-double-right" import CaretDoubleRightIcon from "virtual:icons/ph/caret-double-right"
import CaretDoubleLeftIcon from "virtual:icons/ph/caret-double-left" import CaretDoubleLeftIcon from "virtual:icons/ph/caret-double-left"
import BarricadeIcon from "virtual:icons/ph/barricade" import BarricadeIcon from "virtual:icons/ph/barricade"
import { isEqual } from "lodash-es" import { isEqual } from "lodash-es"
import { toRef, computed, watchEffect } from "vue" import { computed, toRef, watchEffect } from "vue"
import { useCurrentElement } from "@vueuse/core" import { useCurrentElement } from "@vueuse/core"
const props = defineProps<{ const props = defineProps<{

View file

@ -1,13 +1,13 @@
<template> <template>
<div class="relative"> <div class="relative">
<div <div
class="bg-green-700 h-full transition-all"
:style="{ width: (progress * 100) + '%' }"
:class="barClass" :class="barClass"
:style="{ width: (progress * 100) + '%' }"
class="bg-green-700 h-full transition-all"
></div> ></div>
<div <div
class="absolute left-0 top-0 w-full h-full px-4 text-4 text-white font-bold flex items-center transition flex justify-between"
:class="music === null ? 'opacity-0' : 'opacity-100'" :class="music === null ? 'opacity-0' : 'opacity-100'"
class="absolute left-0 top-0 w-full h-full px-4 text-4 text-white font-bold flex items-center transition flex justify-between"
> >
<div> <div>
{{ lastMusic?.title ?? "" }} {{ lastMusic?.title ?? "" }}
@ -35,8 +35,8 @@
} }
</style> </style>
<script setup lang="ts"> <script lang="ts" setup>
import { state, current } from "../state" import { current, state } from "../state"
import { computed, useCssModule } from "vue" import { computed, useCssModule } from "vue"
import { avoidNull, formatSeconds } from "../helpers" import { avoidNull, formatSeconds } from "../helpers"
import { syncedTime } from "../syncing" import { syncedTime } from "../syncing"

View file

@ -1,10 +1,10 @@
<template> <template>
<ChangeBlinkingBox class="items-center" :blink-seconds="20" :value="prop"> <ChangeBlinkingBox :blink-seconds="20" :value="prop" class="items-center">
<div class="text-s1 tracking-wide text-gray-500"> <div class="text-s1 tracking-wide text-gray-500">
{{ positionName }} {{ positionName }}
</div> </div>
<div class="flex-grow w-full"> <div class="flex-grow w-full">
<transition name="fade" mode="out-in"> <transition mode="out-in" name="fade">
<div :key="prop" class="flex flex-col items-center justify-center"> <div :key="prop" class="flex flex-col items-center justify-center">
<template v-if="prop !== null"> <template v-if="prop !== null">
<div class="font-bold text-3 text-center"> <div class="font-bold text-3 text-center">
@ -32,7 +32,7 @@
} }
</style> </style>
<script setup lang="ts"> <script lang="ts" setup>
import { toRef, watch } from "vue" import { toRef, watch } from "vue"
import { autoResetRef } from "@vueuse/core" import { autoResetRef } from "@vueuse/core"
import { parseStringWithDetails } from "../state" import { parseStringWithDetails } from "../state"

View file

@ -6,18 +6,18 @@
</div> </div>
</div> </div>
<div :class="$style.row" class="border-t border-b border-dark-300 h-30"> <div :class="$style.row" class="border-t border-b border-dark-300 h-30">
<PropBox position-name="Rechte Vorbühne" :prop="current.step.props.PROSCENIUM_RIGHT"/> <PropBox :prop="current.step.props.PROSCENIUM_RIGHT" position-name="Rechte Vorbühne"/>
<PropBox position-name="Mitte der Vorbühne" :prop="current.step.props.PROSCENIUM_CENTER"/> <PropBox :prop="current.step.props.PROSCENIUM_CENTER" position-name="Mitte der Vorbühne"/>
<PropBox position-name="Linke der Vorbühne" :prop="current.step.props.PROSCENIUM_LEFT"/> <PropBox :prop="current.step.props.PROSCENIUM_LEFT" position-name="Linke der Vorbühne"/>
</div> </div>
<div :class="$style.row" class="flex-grow h-0"> <div :class="$style.row" class="flex-grow h-0">
<PropBox position-name="Rechts" :prop="current.step.props.RIGHT"/> <PropBox :prop="current.step.props.RIGHT" position-name="Rechts"/>
<PropBox position-name="Mitte" :prop="current.step.props.CENTER"/> <PropBox :prop="current.step.props.CENTER" position-name="Mitte"/>
<PropBox position-name="Links" :prop="current.step.props.LEFT"/> <PropBox :prop="current.step.props.LEFT" position-name="Links"/>
</div> </div>
<div :class="$style.row" class="border-t border-dark-300 py-3 h-20"> <div :class="$style.row" class="border-t border-dark-300 py-3 h-20">
<div/> <div/>
<PropBox position-name="Rückwand" :prop="current.step.props.BACKDROP"/> <PropBox :prop="current.step.props.BACKDROP" position-name="Rückwand"/>
<div/> <div/>
</div> </div>
</div> </div>
@ -33,7 +33,7 @@
} }
</style> </style>
<script setup lang="ts"> <script lang="ts" setup>
import { current } from "../state" import { current } from "../state"
import PropBox from "./PropBox.vue" import PropBox from "./PropBox.vue"
</script> </script>

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="flex-grow overflow-y-auto bg-dark-800 flex flex-col pt-2"> <div class="overflow-y-auto bg-dark-800 flex flex-col pt-2">
<StepSelectionStep :step="START_STEP"/> <StepSelectionStep :step="START_STEP"/>
<StepSelectionAct v-for="act in show.acts" :key="act.name" :act="act"/> <StepSelectionAct v-for="act in show.acts" :key="act.name" :act="act"/>
</div> </div>
@ -9,8 +9,8 @@
</style> </style>
<script setup lang="ts"> <script lang="ts" setup>
import { show, START_STEP } from "../state" import { show, START_STEP } from "@/state"
import StepSelectionAct from "./StepSelectionAct.vue" import StepSelectionAct from "./StepSelectionAct.vue"
import StepSelectionStep from "./StepSelectionStep.vue" import StepSelectionStep from "./StepSelectionStep.vue"
</script> </script>

View file

@ -6,7 +6,7 @@
<span class="flex-grow h-2px bg-white"></span> <span class="flex-grow h-2px bg-white"></span>
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<StepSelectionScene v-for="scene in act.scenes" :key="scene" :scene="scene"/> <StepSelectionScene v-for="(scene, index) in act.scenes" :key="index" :scene="scene"/>
</div> </div>
</div> </div>
</template> </template>
@ -15,9 +15,9 @@
</style> </style>
<script setup lang="ts"> <script lang="ts" setup>
import StepSelectionScene from "./StepSelectionScene.vue" import StepSelectionScene from "./StepSelectionScene.vue"
import { Act } from "../state" import { Act } from "@/state"
const props = defineProps<{ const props = defineProps<{
act: Act act: Act

View file

@ -1,7 +1,7 @@
<template> <template>
<div <div
class="not-last:border-b border-dark-300 transition"
:class="scene === current.scene ? 'bg-green-900' : ''" :class="scene === current.scene ? 'bg-green-900' : ''"
class="not-last:border-b border-dark-300 transition"
> >
<div class="pb-1 px-4 text-4 font-bold"> <div class="pb-1 px-4 text-4 font-bold">
{{ scene.name }} {{ scene.name }}
@ -16,7 +16,7 @@
</style> </style>
<script setup lang="ts"> <script lang="ts" setup>
import { current, Scene } from "../state" import { current, Scene } from "../state"
import StepSelectionStep from "./StepSelectionStep.vue" import StepSelectionStep from "./StepSelectionStep.vue"

View file

@ -1,7 +1,7 @@
<template> <template>
<div <div
class="px-4 py-1 flex items-center justify-between space-x-2 transition "
:class="isActive ? 'bg-green-700' : ''" :class="isActive ? 'bg-green-700' : ''"
class="px-4 py-1 flex items-center justify-between gap-2 transition"
> >
<CueBox :step="step"/> <CueBox :step="step"/>
<button class="flex items-center text-4" @click="goToPosition(step.position)"> <button class="flex items-center text-4" @click="goToPosition(step.position)">
@ -14,9 +14,9 @@
</style> </style>
<script setup lang="ts"> <script lang="ts" setup>
import { state, Step } from "../state" import { state, Step } from "@/state"
import { goToPosition } from "../syncing" import { goToPosition } from "@/syncing"
import KeyReturnIcon from "virtual:icons/ph/key-return" import KeyReturnIcon from "virtual:icons/ph/key-return"
import CueBox from "./CueBox.vue" import CueBox from "./CueBox.vue"
import { useCurrentElement } from "@vueuse/core" import { useCurrentElement } from "@vueuse/core"
@ -28,7 +28,7 @@
}>() }>()
const position = toRef(state, "position") const position = toRef(state, "position")
const element = useCurrentElement() const element = useCurrentElement<HTMLElement>()
const isActive = computed(() => isEqual(props.step.position, position.value)) const isActive = computed(() => isEqual(props.step.position, position.value))
watchEffect(() => { watchEffect(() => {

View file

@ -9,7 +9,7 @@
</style> </style>
<script setup lang="ts"> <script lang="ts" setup>
import { syncedTime } from "../syncing" import { syncedTime } from "../syncing"
import ClockIcon from "virtual:icons/ph/clock-bold" import ClockIcon from "virtual:icons/ph/clock-bold"

48
ui/src/global.scss Normal file
View file

@ -0,0 +1,48 @@
@import "modern-normalize/modern-normalize.css";
:root {
color-scheme: dark;
}
html, body, #app {
width: 100dvw;
height: 100dvh;
font-family: "Manrope Variable", sans-serif;
user-select: none;
background: black;
color: white;
}
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-thumb {
background: rgb(255 255 255 / 10%);
&:hover {
background: rgb(255 255 255 / 20%);
}
}
::selection {
background: rgba(255 255 255 / 20%)
}
a {
text-decoration: none;
color: currentColor;
}
button {
background: transparent;
border: none;
color: inherit;
font: inherit;
padding: 0;
&:focus-visible {
outline: solid 2px theme("colors.blue.400");
outline-offset: 1px;
}
}

View file

@ -1,4 +1,4 @@
import { Ref, ComputedRef, computed, UnwrapRef, ref, watchEffect } from "vue" import { computed, ComputedRef, Ref, ref, UnwrapRef, watchEffect } from "vue"
export const computedIfPresent = <T, V>(object: Ref<T>, access: (value: Exclude<T, undefined | null>) => V): ComputedRef<V | undefined | null> => computed(() => { export const computedIfPresent = <T, V>(object: Ref<T>, access: (value: Exclude<T, undefined | null>) => V): ComputedRef<V | undefined | null> => computed(() => {
if (object.value === null) return null if (object.value === null) return null
@ -24,5 +24,5 @@ export function avoidNull<T>(originRef: Ref<UnwrapRef<T> | null>): ComputedRef<U
export function formatSeconds(seconds: number) { export function formatSeconds(seconds: number) {
const duration = new Date(seconds * 1000) const duration = new Date(seconds * 1000)
return `${duration.getMinutes().toFixed()}:${duration.getSeconds().toFixed().padStart(2, '0')}` return `${duration.getMinutes().toFixed()}:${duration.getSeconds().toFixed().padStart(2, "0")}`
} }

View file

@ -1,16 +1,8 @@
import "virtual:windi.css" import "virtual:uno.css"
import { createApp } from "vue" import { createApp } from "vue"
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router" import { createRouter, createWebHistory } from "vue-router"
import App from "./App.vue" import App from "./App.vue"
import originalRoutes from "~pages" import { handleHotUpdate, routes } from "vue-router/auto-routes"
const routes = originalRoutes.map(route => {
if (typeof route.component !== "function") return route
return {
...route,
props: false
}
}) as RouteRecordRaw[]
const router = createRouter({ const router = createRouter({
routes, routes,
@ -20,3 +12,7 @@ const router = createRouter({
const app = createApp(App) const app = createApp(App)
app.use(router) app.use(router)
app.mount("#app") app.mount("#app")
if (import.meta.hot) {
handleHotUpdate(router)
}

View file

@ -1,42 +0,0 @@
<template>
<div class="flex flex-col overflow-hidden">
<div class="flex flex-col space-y-4 p-4 pt-8 flex-grow h-full overflow-hidden">
<div class="flex justify-end">
<button class="px-5 py-3 bg-green-600 font-bold text-5" @click="goNext()">
Next
</button>
</div>
<StepSelection class="h-110"/>
<FogControl/>
<CurtainLightControl/>
</div>
</div>
</template>
<style>
html {
user-select: none;
}
</style>
<script setup lang="ts">
import { current, getNextValidPosition, getPreviousValidPosition, state } from "../state"
import { goToPosition } from "../syncing"
import { onKeyStroke } from "@vueuse/core"
import StepSelection from "../components/StepSelection.vue"
import FogControl from "../components/FogControl.vue"
import CurtainLightControl from "../components/CurtainLightControl.vue"
function goNext() {
const position = getNextValidPosition(state.position)
if (position !== null) goToPosition(position)
}
function goPrevious() {
const position = getPreviousValidPosition(state.position)
if (position !== null) goToPosition(position)
}
onKeyStroke("ArrowLeft", goPrevious)
onKeyStroke("ArrowRight", goNext)
</script>

View file

@ -1,40 +0,0 @@
<template>
<div class="flex flex-col h-full">
<h1 class="font-800 text-9 p-4 pb-0">
{{ current.act === null ? "" : `${current.act.name}`}}{{ current.scene.name }}
</h1>
<div class="h-full flex space-x-4 p-10 pt-8 flex-grow overflow-hidden">
<StepSelection class="w-1/2"/>
<div class="w-1/2 flex flex-col space-y-4">
<MessageEdit class="h-1/2"/>
<div class="text-4">Vorhang: {{ current.step.curtainState === "open" ? "auf" : "geschlossen"}}</div>
<ActorsOnStageBox class="flex-grow"/>
<FogControl/>
<CurtainLightControl/>
</div>
</div>
<MusicProgressBar class="h-10"/>
</div>
</template>
<script setup lang="ts">
import MusicProgressBar from "../components/MusicProgressBar.vue"
import { onKeyStroke } from "@vueuse/core"
import StepSelection from "../components/StepSelection.vue"
import MessageEdit from "../components/MessageEdit.vue"
import ActorsOnStageBox from "../components/ActorsOnStageBox.vue"
import { goToPosition } from "../syncing"
import { current, getNextValidPosition, getPreviousValidPosition, state } from "../state"
import FogControl from "../components/FogControl.vue"
import CurtainLightControl from "../components/CurtainLightControl.vue"
onKeyStroke("ArrowRight", () => {
const position = getNextValidPosition(state.position)
if (position !== null) goToPosition(position)
})
onKeyStroke("ArrowLeft", () => {
const position = getPreviousValidPosition(state.position)
if (position !== null) goToPosition(position)
})
</script>

View file

@ -0,0 +1,25 @@
<template>
<div class="flex flex-col h-full">
<h1 class="font-800 text-9 p-4 pb-0">
{{ current.act === null ? "" : `${current.act.name}` }}{{ current.scene.name }}
</h1>
<div class="h-full flex space-x-4 p-4 pt-8 flex-grow overflow-hidden">
<MotionsList center-current class="w-3/7" scrollable/>
<div class="w-4/7 flex flex-col space-y-4">
<ActorsOnStageBox class="h-full text-7"/>
<MessageEdit class="h-40"/>
<CurtainLightControl/>
</div>
</div>
<MusicProgressBar class="h-10"/>
</div>
</template>
<script lang="ts" setup>
import MusicProgressBar from "../../components/MusicProgressBar.vue"
import MotionsList from "../../components/MotionsList.vue"
import { current } from "../../state"
import ActorsOnStageBox from "../../components/ActorsOnStageBox.vue"
import MessageEdit from "../../components/MessageEdit.vue"
import CurtainLightControl from "../../components/CurtainLightControl.vue"
</script>

View file

@ -0,0 +1,63 @@
<template>
<div class="grid md:grid-cols-2 gap-6 h-full" :class="$style.root">
<StepSelection/>
<div class="flex flex-col gap-5 overflow-y-auto">
<MessageEdit class="h-25 md:h-40"/>
<div class="text-5">
<div class="pb-2 font-bold text-3.5 tracking-wider uppercase">
Vorhang
</div>
<div class="text-4.5">
{{ current.step.curtainState === "open" ? "öffnen" : "schließen" }}
</div>
</div>
<div class="flex-grow overflow-hidden">
<div class="pb-2 font-bold text-3.5 tracking-wider uppercase">
Auf der Bühne
</div>
<ActorsOnStageBox class="flex-grow h-full"/>
</div>
<div>
<div class="pb-2 font-bold text-3.5 tracking-wider uppercase">
Nebel
</div>
<FogControl/>
</div>
<div>
<div class="pb-2 font-bold text-3.5 tracking-wider uppercase">
Umbaulicht
</div>
<CurtainLightControl/>
</div>
</div>
</div>
</template>
<style module lang="scss">
.root {
@screen lt-md {
grid-template-rows: 200px;
}
}
</style>
<script lang="ts" setup>
import { onKeyStroke } from "@vueuse/core"
import StepSelection from "../../components/StepSelection.vue"
import MessageEdit from "../../components/MessageEdit.vue"
import ActorsOnStageBox from "../../components/ActorsOnStageBox.vue"
import { goToPosition } from "@/syncing"
import { current, getNextValidPosition, getPreviousValidPosition, state } from "@/state"
import FogControl from "../../components/FogControl.vue"
import CurtainLightControl from "../../components/CurtainLightControl.vue"
onKeyStroke("ArrowRight", () => {
const position = getNextValidPosition(state.position)
if (position !== null) goToPosition(position)
})
onKeyStroke("ArrowLeft", () => {
const position = getPreviousValidPosition(state.position)
if (position !== null) goToPosition(position)
})
</script>

View file

@ -0,0 +1,71 @@
<template>
<div class="flex flex-col gap-50">
<div class="flex flex-col gap-2">
<div class="text-gray text-2xl">
Aktuelles Ziel
</div>
<ChangeBlinkingBox :blink-seconds="currentTarget === null ? 0 : 10" :value="currentTarget" class="text-7 md:text-10">
{{ currentTarget ?? "Niemand" }}
</ChangeBlinkingBox>
</div>
<div v-if="nextStepWithChange !== null" class="flex flex-col gap-2">
<div class="text-gray text-6">
Nächstes Ziel
<span class="whitespace-nowrap">
[{{
nextStepWithChange.delta === 0
? "in dieser Szene"
: nextStepWithChange.delta === 1
? "in der nächsten Szene"
: `in ${nextStepWithChange.delta} Szenen`
}}]
</span>
</div>
<div class="text-7 md:text-10">{{ nextStepWithChange.target }}</div>
</div>
</div>
</template>
<style module>
</style>
<script lang="ts" setup>
import { useRoute } from "vue-router"
import { computed } from "vue"
import { current, getNextValidPosition, getSceneIndex, getStep, ShowPosition } from "@/state"
import ChangeBlinkingBox from "../../components/ChangeBlinkingBox.vue"
const route = useRoute()
const isLeft = computed(() => route.query.side === "left")
const targetProperty = computed<"leftSpotTarget" | "rightSpotTarget">(() => isLeft.value ? "leftSpotTarget" : "rightSpotTarget")
const currentTarget = computed(() => current.step[targetProperty.value])
interface StepWithChange {
delta: number
position: ShowPosition
target: string
}
const nextStepWithChange = computed<StepWithChange | null>(() => {
let position: ShowPosition | null = getNextValidPosition(current.step.position)
let lastTarget: string | null = currentTarget.value
while (position !== null) {
const step = getStep(position)
if (step[targetProperty.value] !== null && step[targetProperty.value] !== lastTarget) {
return {
position: step.position,
delta: getSceneIndex(step.position) - current.sceneIndex,
target: step[targetProperty.value]!
}
}
lastTarget = step[targetProperty.value]
position = getNextValidPosition(position)
}
return null
})
</script>

13
ui/src/pages/index.vue Normal file
View file

@ -0,0 +1,13 @@
<template>
<div class="flex flex-col gap-4 text-xl">
<router-link to="/mixer">audio operator</router-link>
</div>
</template>
<style module>
</style>
<script lang="ts" setup>
</script>

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="flex flex-col h-full"> <div class="flex flex-col h-full">
<h1 class="font-800 text-9 p-4 pb-0"> <h1 class="font-800 text-9 p-4 pb-0">
{{ current.act === null ? "" : `${current.act.name}`}}{{ current.scene.name }} {{ current.act === null ? "" : `${current.act.name}` }}{{ current.scene.name }}
</h1> </h1>
<div class="h-full flex space-x-4 p-4 pt-8 flex-grow overflow-hidden"> <div class="h-full flex space-x-4 p-4 pt-8 flex-grow overflow-hidden">
<MotionsList center-current class="w-3/7"/> <MotionsList center-current class="w-3/7"/>
@ -14,7 +14,7 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script lang="ts" setup>
import MusicProgressBar from "../../components/MusicProgressBar.vue" import MusicProgressBar from "../../components/MusicProgressBar.vue"
import { current } from "../../state" import { current } from "../../state"
import MotionsList from "../../components/MotionsList.vue" import MotionsList from "../../components/MotionsList.vue"

View file

@ -1,25 +0,0 @@
<template>
<div class="flex flex-col h-full">
<h1 class="font-800 text-9 p-4 pb-0">
{{ current.act === null ? "" : `${current.act.name}`}}{{ current.scene.name }}
</h1>
<div class="h-full flex space-x-4 p-4 pt-8 flex-grow overflow-hidden">
<MotionsList scrollable center-current class="w-3/7"/>
<div class="w-4/7 flex flex-col space-y-4">
<ActorsOnStageBox class="h-full text-7"/>
<MessageEdit class="h-40"/>
<CurtainLightControl/>
</div>
</div>
<MusicProgressBar class="h-10"/>
</div>
</template>
<script setup lang="ts">
import MusicProgressBar from "../components/MusicProgressBar.vue"
import MotionsList from "../components/MotionsList.vue"
import { current } from "../state"
import ActorsOnStageBox from "../components/ActorsOnStageBox.vue"
import MessageEdit from "../components/MessageEdit.vue"
import CurtainLightControl from "../components/CurtainLightControl.vue"
</script>

View file

@ -1,77 +0,0 @@
<template>
<div class="flex flex-col h-full">
<h1 class="font-800 text-9 p-4 pb-0">
{{ current.act === null ? "" : `${current.act.name}`}}{{ current.scene.name }}
</h1>
<div class="h-full flex flex-col space-y-10 p-7 text-6">
<div class="flex flex-col gap-1">
<div>
Aktuelles Ziel:
</div>
<ChangeBlinkingBox class="h-full min-w-50" :value="currentTarget" :blink-seconds="10">
{{ currentTarget ?? "Niemand" }}
</ChangeBlinkingBox>
</div>
<div v-if="nextStepWithChange !== null" class="h-full flex flex-col space-y-2 text-5">
<div class="flex gap-5 items-center">
<div>Nächstes Ziel:</div>
<div>{{ nextStepWithChange.target }}</div>
</div>
<div>
{{ nextStepWithChange.delta === 0
? "in dieser Szene (rechtzeitig positionieren!)"
: nextStepWithChange.delta === 1
? "in der nächsten Szene"
: `in ${nextStepWithChange.delta} Szenen`
}}
</div>
</div>
</div>
<MusicProgressBar class="h-10"/>
</div>
</template>
<style module>
</style>
<script setup lang="ts">
import { useRoute } from "vue-router"
import { computed } from "vue"
import { current, getNextValidPosition, getSceneIndex, getStep, ShowPosition, START_STEP } from "../state"
import ChangeBlinkingBox from "../components/ChangeBlinkingBox.vue"
import MusicProgressBar from "../components/MusicProgressBar.vue"
const route = useRoute()
const isLeft = computed(() => route.query.side === "left")
const targetProperty = computed<"leftSpotTarget" | "rightSpotTarget">(() => isLeft.value ? "leftSpotTarget" : "rightSpotTarget")
const currentTarget = computed(() => current.step[targetProperty.value])
interface StepWithChange {
delta: number
position: ShowPosition
target: string
}
const nextStepWithChange = computed<StepWithChange | null>(() => {
let position: ShowPosition | null = getNextValidPosition(current.step.position)
let lastTarget: string | null = currentTarget.value
while(position !== null) {
const step = getStep(position)
if (step[targetProperty.value] !== null && step[targetProperty.value] !== lastTarget) {
return {
position: step.position,
delta: getSceneIndex(step.position) - current.sceneIndex,
target: step[targetProperty.value]!
}
}
lastTarget = step[targetProperty.value]
position = getNextValidPosition(position)
}
return null
})
</script>

View file

@ -1,77 +0,0 @@
<template>
<div class="flex flex-col h-full">
<h1 class="font-800 text-9 p-4 pb-0">
{{ current.act === null ? "" : `${current.act.name}`}}{{ current.scene.name }}
</h1>
<div class="h-full flex flex-col space-y-10 p-10 text-10">
<div class="flex gap-5 items-center h-18">
<div>
Aktuelles Ziel:
</div>
<ChangeBlinkingBox class="px-8 h-full min-w-50 items-center" :value="currentTarget" :blink-seconds="10">
{{ currentTarget ?? "Niemand" }}
</ChangeBlinkingBox>
</div>
<div v-if="nextStepWithChange !== null" class="h-full flex flex-col space-y-2 text-7">
<div class="flex gap-5 items-center">
<div>Nächstes Ziel:</div>
<div>{{ nextStepWithChange.target }}</div>
</div>
<div>
{{ nextStepWithChange.delta === 0
? "in dieser Szene (rechtzeitig positionieren!)"
: nextStepWithChange.delta === 1
? "in der nächsten Szene"
: `in ${nextStepWithChange.delta} Szenen`
}}
</div>
</div>
</div>
<MusicProgressBar class="h-10"/>
</div>
</template>
<style module>
</style>
<script setup lang="ts">
import { useRoute } from "vue-router"
import { computed } from "vue"
import { current, getNextValidPosition, getSceneIndex, getStep, ShowPosition, START_STEP } from "../state"
import ChangeBlinkingBox from "../components/ChangeBlinkingBox.vue"
import MusicProgressBar from "../components/MusicProgressBar.vue"
const route = useRoute()
const isLeft = computed(() => route.query.side === "left")
const targetProperty = computed<"leftSpotTarget" | "rightSpotTarget">(() => isLeft.value ? "leftSpotTarget" : "rightSpotTarget")
const currentTarget = computed(() => current.step[targetProperty.value])
interface StepWithChange {
delta: number
position: ShowPosition
target: string
}
const nextStepWithChange = computed<StepWithChange | null>(() => {
let position: ShowPosition | null = getNextValidPosition(current.step.position)
let lastTarget: string | null = currentTarget.value
while(position !== null) {
const step = getStep(position)
if (step[targetProperty.value] !== null && step[targetProperty.value] !== lastTarget) {
return {
position: step.position,
delta: getSceneIndex(step.position) - current.sceneIndex,
target: step[targetProperty.value]!
}
}
lastTarget = step[targetProperty.value]
position = getNextValidPosition(position)
}
return null
})
</script>

View file

@ -1,3 +0,0 @@
/// <reference types="vite-plugin-pages/client"/>
/// <reference types="unplugin-icons/types/vue"/>
/// <reference types="vite/client"/>

View file

@ -1,20 +1,37 @@
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "declaration": false,
"module": "ESNext",
"target": "ES2021",
"lib": ["DOM", "ESNext"],
"strict": true,
"esModuleInterop": true, "esModuleInterop": true,
"skipLibCheck": false, "forceConsistentCasingInFileNames": true,
"moduleResolution": "node", "lib": [
"esnext",
"dom",
],
"module": "esnext",
"moduleResolution": "Bundler",
"allowJs": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"strictNullChecks": true, "isolatedModules": true,
"downlevelIteration": true, "rootDir": "src",
"forceConsistentCasingInFileNames": true "skipLibCheck": true,
"sourceMap": true,
"strict": true,
"stripInternal": true,
"noUncheckedIndexedAccess": false,
"target": "esnext",
"types": [
"vite/client",
"unplugin-vue-router/client",
"unplugin-icons/types/vue"
],
"paths": {
"@/*": [
"./src/*"
]
}
}, },
"include": [ "include": [
"node_modules/**/*", "src/**/*.ts",
"src/**/*" "src/**/*.vue",
] ]
} }

44
ui/unocss.config.ts Executable file
View file

@ -0,0 +1,44 @@
import { defineConfig, transformerDirectives } from "unocss"
import { presetWind, colors } from "@unocss/preset-wind3"
const generateValues = (max: number, fn: (step: number) => any) => {
const object: Record<number, any> = {}
for (let i = 1; i <= max; i++) {
object[i] = fn(i)
}
return object
}
export default defineConfig({
presets: [
presetWind({
arbitraryVariants: true,
preflight: true
})
],
theme: {
fontFamily: {
sans: `"Manrope Variable", sans-serif`,
system: "sans-serif"
},
colors: {
black: colors.black,
white: colors.white,
gray: colors.stone,
red: colors.red,
yellow: colors.amber,
orange: colors.orange,
green: colors.green,
blue: colors.blue,
violet: colors.fuchsia,
light: colors.light,
dark: colors.dark,
transparent: colors.transparent
}
},
transformers: [
transformerDirectives()
]
})

View file

@ -1,28 +1,39 @@
import { defineConfig, splitVendorChunkPlugin } from "vite" import { defineConfig } from "vite"
import vuePlugin from "@vitejs/plugin-vue" import vuePlugin from "@vitejs/plugin-vue"
import windicssPlugin from "vite-plugin-windicss"
import pagesPlugin from "vite-plugin-pages"
import iconsPlugin from "unplugin-icons/vite" import iconsPlugin from "unplugin-icons/vite"
import vueRouterPlugin from "unplugin-vue-router/vite"
import unocssPlugin from "unocss/vite"
import { resolve } from "node:path"
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
splitVendorChunkPlugin(), vueRouterPlugin({
vuePlugin(), dts: "./src/generated-types/router.d.ts",
pagesPlugin({ routesFolder: "./src/pages",
importMode: "sync"
}), }),
windicssPlugin(), unocssPlugin(),
iconsPlugin() iconsPlugin(),
vuePlugin()
], ],
resolve: {
alias: {
"@": resolve(__dirname, "./src")
}
},
css: {
preprocessorOptions: {
scss: {
api: "modern-compiler"
}
}
},
server: { server: {
proxy: { proxy: {
"/api": { "/api": {
target: "http://localhost:8000", target: "http://localhost:8000",
ws: true ws: true
} }
}
}, },
build: { allowedHosts: true
reportCompressedSize: false
} }
}) })

View file

@ -1,44 +0,0 @@
import { defineConfig } from "windicss/helpers"
import colors from "windicss/colors"
import lineClampPlugin from "windicss/plugin/line-clamp"
const generateValues = (max: number, fn: (step: number) => any) => {
const object: Record<number, any> = {}
for (let i = 1; i <= max; i++) {
object[i] = fn(i)
}
return object
}
export default defineConfig({
theme: {
colors: {
black: colors.black,
white: colors.white,
gray: colors.stone,
red: colors.red,
yellow: colors.amber,
orange: colors.orange,
green: colors.green,
blue: colors.blue,
violet: colors.fuchsia,
light: colors.light,
dark: colors.dark,
transparent: colors.transparent
},
fontSize: {
...generateValues(30, step => `${step * 0.25}rem`),
4: "1.2rem",
3: "1.1rem",
2: "1rem",
s1: "0.9rem",
s2: "0.8rem",
s3: "0.7rem"
}
},
plugins: [
lineClampPlugin
]
})