From 093f9d7f1ab15e2561eb7cbab27a59e6bf6c1309 Mon Sep 17 00:00:00 2001 From: Haoyu Xu Date: Fri, 2 May 2025 16:57:44 +0800 Subject: [PATCH] feat: separate steam build and directory build to save space --- apps/directory/package.json | 6 +- apps/directory/runner.ts | 24 +- apps/directory/src/routes/path/Home.jsx | 4 +- apps/directory/src/routes/path/Operator.jsx | 24 +- apps/directory/vite.config.js | 6 +- apps/module/src/libs/TimeKeeper.ts | 35 +++ apps/module/src/player.ts | 249 ++++++++-------- apps/showcase/package.json | 5 +- apps/showcase/runner.ts | 19 +- apps/showcase/src/components/background.js | 2 +- apps/showcase/src/components/fallback.js | 2 +- apps/showcase/src/components/logo.js | 2 +- apps/showcase/src/components/music.js | 4 +- apps/showcase/src/components/player.js | 6 +- apps/showcase/src/components/voice.js | 6 +- package.json | 1 + packages/config/config.yaml | 43 +-- packages/config/types.ts | 41 +-- packages/vite-helpers/index.ts | 93 ++++-- turbo.json | 304 ++++++++++++-------- 20 files changed, 503 insertions(+), 373 deletions(-) create mode 100644 apps/module/src/libs/TimeKeeper.ts diff --git a/apps/directory/package.json b/apps/directory/package.json index 2a77dd9..d279a2f 100644 --- a/apps/directory/package.json +++ b/apps/directory/package.json @@ -4,9 +4,9 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev:directory": "vite --clearScreen false", - "build": "mode=build bun runner.ts", - "preview:directory": "vite preview", + "dev:directory": "bunx --bun vite --clearScreen false", + "build:directory": "mode=build:directory bun runner.ts", + "preview:directory": "bunx --bun vite preview", "lint": "eslint && stylelint \"src/**/*.css\" \"src/**/*.scss\" && prettier --check ." }, "dependencies": { diff --git a/apps/directory/runner.ts b/apps/directory/runner.ts index 56d14db..06587e9 100644 --- a/apps/directory/runner.ts +++ b/apps/directory/runner.ts @@ -1,28 +1,12 @@ import path from 'node:path' import { DIST_DIR } from '@aklive2d/showcase' import { build as viteBuild } from 'vite' -import { envParser, file } from '@aklive2d/libs' - -const build = async (namesToBuild: string[]) => { - if (!namesToBuild.length) { - // skip as directory can only build - // when all operators are built - await viteBuild() - const releaseDir = path.resolve(DIST_DIR) - file.rmdir(releaseDir) - } -} +import { file } from '@aklive2d/libs' async function main() { - const { name } = envParser.parse({ - name: { - type: 'string', - short: 'n', - multiple: true, - default: [], - }, - }) - await build(name as string[]) + await viteBuild() + const releaseDir = path.resolve(DIST_DIR) + file.rmdir(releaseDir) } main() diff --git a/apps/directory/src/routes/path/Home.jsx b/apps/directory/src/routes/path/Home.jsx index 8b351e2..c51d2d8 100644 --- a/apps/directory/src/routes/path/Home.jsx +++ b/apps/directory/src/routes/path/Home.jsx @@ -349,7 +349,7 @@ function OperatorElement({ item, hidden, handleVoicePlay }) {
handleVoicePlay( - `/${item.link}/assets/${buildConfig.voice_folders.main}/${buildConfig.app_voice_url}` + `/${item.link}/${buildConfig.default_assets_dir}${buildConfig.voice_folders.main}/${buildConfig.app_voice_url}` ) } > @@ -439,7 +439,7 @@ function ImageElement({ item }) { const { language } = useLanguage() return ( {item.codename[language]} ) diff --git a/apps/directory/src/routes/path/Operator.jsx b/apps/directory/src/routes/path/Operator.jsx index 746e905..bf629bb 100644 --- a/apps/directory/src/routes/path/Operator.jsx +++ b/apps/directory/src/routes/path/Operator.jsx @@ -108,7 +108,9 @@ export default function Operator() { if (spineRef.current?.children.length > 0) { spineRef.current?.removeChild(spineRef.current?.children[0]) } - fetch(`/${key}/assets/charword_table.json`) + fetch( + `/${key}/${buildConfig.default_assets_dir}charword_table.json` + ) .then((res) => res.json()) .then((data) => { setVoiceConfig(data) @@ -159,7 +161,7 @@ export default function Operator() { useEffect(() => { if (spineRef.current?.children.length === 0 && configRef.current) { const playerConfig = { - atlasUrl: `./${key}/assets/${configRef.current.filename.replace(/#/g, '%23')}.atlas`, + atlasUrl: `./${key}/${buildConfig.default_assets_dir}${configRef.current.filename.replace(/#/g, '%23')}.atlas`, animation: spineAnimationName, premultipliedAlpha: true, alpha: true, @@ -200,7 +202,7 @@ export default function Operator() { currentVoiceId = id setCurrentVoiceId(id) setVoiceSrc( - `/${configRef.current.link}/assets/${getVoiceFoler(voiceLangRef.current)}/${id}.ogg` + `/${configRef.current.link}/${buildConfig.default_assets_dir}${getVoiceFoler(voiceLangRef.current)}/${id}.ogg` ) lastVoiceId = currentVoiceId } @@ -208,9 +210,9 @@ export default function Operator() { } if (configRef.current.use_json) { - playerConfig.jsonUrl = `./${key}/assets/${configRef.current.filename.replace(/#/g, '%23')}.json` + playerConfig.jsonUrl = `./${key}/${buildConfig.default_assets_dir}${configRef.current.filename.replace(/#/g, '%23')}.json` } else { - playerConfig.skelUrl = `./${key}/assets/${configRef.current.filename.replace(/#/g, '%23')}.skel` + playerConfig.skelUrl = `./${key}/${buildConfig.default_assets_dir}${configRef.current.filename.replace(/#/g, '%23')}.skel` } setSpinePlayer(new SpinePlayer(spineRef.current, playerConfig)) } @@ -267,7 +269,7 @@ export default function Operator() { useEffect(() => { if (voiceLang && isVoicePlaying) { - const audioUrl = `/assets/${getVoiceFoler(voiceLang)}/${currentVoiceId}.ogg` + const audioUrl = `/${buildConfig.default_assets_dir}${getVoiceFoler(voiceLang)}/${currentVoiceId}.ogg` if ( voiceSrc !== window.location.href.replace(/\/$/g, '') + audioUrl @@ -287,7 +289,7 @@ export default function Operator() { if (id) { setCurrentVoiceId(id) setVoiceSrc( - `/${key}/assets/${getVoiceFoler(voiceLangRef.current)}/${id}.ogg` + `/${key}/${buildConfig.default_assets_dir}${getVoiceFoler(voiceLangRef.current)}/${id}.ogg` ) } } @@ -563,13 +565,13 @@ export default function Operator() { className={classes.container} style={ currentBackground && { - backgroundImage: `url(/chen/assets/${buildConfig.background_folder}/${currentBackground})`, + backgroundImage: `url(/${buildConfig.directory_folder}/${buildConfig.background_folder}/${currentBackground})`, } } > {config && ( {config?.codename[language]} { if (background && enableMusic) { const introOgg = musicMapping[background].intro - const intro = `./chen/assets/${buildConfig.music_folder}/${introOgg}` - const loop = `./chen/assets/${buildConfig.music_folder}/${musicMapping[background].loop}` + const intro = `/${buildConfig.directory_folder}/${buildConfig.music_folder}/${introOgg}` + const loop = `/${buildConfig.directory_folder}/${buildConfig.music_folder}/${musicMapping[background].loop}` musicLoopRef.current.src = loop if (introOgg) { musicIntroRef.current.src = intro || loop diff --git a/apps/directory/vite.config.js b/apps/directory/vite.config.js index 2a2b632..801f419 100644 --- a/apps/directory/vite.config.js +++ b/apps/directory/vite.config.js @@ -30,9 +30,9 @@ export default defineConfig(async () => { ), rollupOptions: { output: { - entryFileNames: `${config.directory.assets_dir}/[name]-[hash:8].js`, - chunkFileNames: `${config.directory.assets_dir}/[name]-[hash:8].js`, - assetFileNames: `${config.directory.assets_dir}/[name]-[hash:8].[ext]`, + entryFileNames: `${config.app.directory.assets}/[name].js`, + chunkFileNames: `${config.app.directory.assets}/[name].js`, + assetFileNames: `${config.app.directory.assets}/[name].[ext]`, manualChunks: (id) => { if (id.includes('node_modules')) { return 'vendor' // all other package goes here diff --git a/apps/module/src/libs/TimeKeeper.ts b/apps/module/src/libs/TimeKeeper.ts new file mode 100644 index 0000000..583a0f1 --- /dev/null +++ b/apps/module/src/libs/TimeKeeper.ts @@ -0,0 +1,35 @@ +/** + * Adapted from 'spine-ts/core/src/Utils.ts' + */ +export class TimeKeeper { + framesPerSecond = 0 + delta = 0 + totalTime = 0 + + private lastTime = performance.now() / 1000 + private frameCount = 0 + private frameTime = 0 + private fpsInterval = 1 / 60 + + update() { + const now = performance.now() / 1000 + this.delta = now - this.lastTime + if (this.delta > this.fpsInterval) { + this.frameTime += this.delta + this.totalTime += this.delta + this.lastTime = now + + this.frameCount++ + if (this.frameTime > 1) { + this.framesPerSecond = this.frameCount / this.frameTime + this.frameTime = 0 + this.frameCount = 0 + } + } else { + this.delta = -1 + } + } + setFps(v: number) { + this.fpsInterval = 1 / v + } +} diff --git a/apps/module/src/player.ts b/apps/module/src/player.ts index ab8a867..ce0a549 100644 --- a/apps/module/src/player.ts +++ b/apps/module/src/player.ts @@ -1,4 +1,5 @@ import { spine } from '../spine-ts/build/spine-webgl.js' +import { TimeKeeper } from './libs/TimeKeeper.ts' /** * Adapted from 'spine-ts/player/src/Player.ts' @@ -104,15 +105,13 @@ export class Player { private paused = false private playTime = 0 - private speed = 1 - private time = new spine.TimeKeeper() + private time = new TimeKeeper() private currentViewport!: Viewport private previousViewport!: Viewport private viewportTransitionStart = 0 private parent: HTMLElement private devicePixelRatio = window.devicePixelRatio || 1 - private lastFrameTime: number = 0 private disposed = false private eventListeners: { target: HTMLElement | Document | Window @@ -330,136 +329,134 @@ export class Player { if (this.disposed) return if (requestNextFrame) requestAnimationFrame(() => this.drawFrame()) - // Have we finished loading the asset? Then set things up - if (this.assetManager.isLoadingComplete() && this.skeleton == null) - this.loadSkeleton() + this.time.update() + const delta = this.time.delta + if (delta !== -1) { + // Have we finished loading the asset? Then set things up + if (this.assetManager.isLoadingComplete() && this.skeleton == null) + this.loadSkeleton() - // Resize the canvas - this.resize(spine.webgl.ResizeMode.Expand) + // Resize the canvas + this.resize(spine.webgl.ResizeMode.Expand) + // Update and draw the skeleton + if (this.loaded) { + // Update animation and skeleton based on user selections + if (!this.paused && this.config.animation) { + const ctx = this.context + const gl = ctx.gl - // Update and draw the skeleton + // Clear the viewport + const bg = new spine.Color().setFromString( + this.config.backgroundColor + ) + gl.clearColor(bg.r, bg.g, bg.b, bg.a) + gl.clear(gl.COLOR_BUFFER_BIT) - if (this.loaded) { - const fpsInterval = 1 / this.config.fps - const now = performance.now() / 1000 - // Update animation and skeleton based on user selections - if (!this.paused && this.config.animation) { - const ctx = this.context - const gl = ctx.gl + const animationDuration = + this.animationState.getCurrent(0).animation.duration + this.playTime += delta + while ( + this.playTime >= animationDuration && + animationDuration != 0 + ) { + this.playTime -= animationDuration + } + this.playTime = Math.max( + 0, + Math.min(this.playTime, animationDuration) + ) - // Clear the viewport - const bg = new spine.Color().setFromString( - this.config.backgroundColor - ) - gl.clearColor(bg.r, bg.g, bg.b, bg.a) - gl.clear(gl.COLOR_BUFFER_BIT) - - this.lastFrameTime = now - this.time.update() - const delta = this.time.delta * this.speed - - const animationDuration = - this.animationState.getCurrent(0).animation.duration - this.playTime += delta - while ( - this.playTime >= animationDuration && - animationDuration != 0 - ) { - this.playTime -= animationDuration + this.animationState.update(delta) + this.animationState.apply(this.skeleton) } - this.playTime = Math.max( - 0, - Math.min(this.playTime, animationDuration) + + this.skeleton.updateWorldTransform() + + let viewport = { + x: + this.currentViewport.x - + (this.currentViewport.padLeft as number), + y: + this.currentViewport.y - + (this.currentViewport.padBottom as number), + width: + this.currentViewport.width + + (this.currentViewport.padLeft as number) + + (this.currentViewport.padRight as number), + height: + this.currentViewport.height + + (this.currentViewport.padBottom as number) + + (this.currentViewport.padTop as number), + } + + const transitionAlpha = + (performance.now() - this.viewportTransitionStart) / + 1000 / + this.config.viewport.transitionTime + if (this.previousViewport && transitionAlpha < 1) { + const oldViewport = { + x: + this.previousViewport.x - + (this.previousViewport.padLeft as number), + y: + this.previousViewport.y - + (this.previousViewport.padBottom as number), + width: + this.previousViewport.width + + (this.previousViewport.padLeft as number) + + (this.previousViewport.padRight as number), + height: + this.previousViewport.height + + (this.previousViewport.padBottom as number) + + (this.previousViewport.padTop as number), + } + + viewport = { + x: + oldViewport.x + + (viewport.x - oldViewport.x) * transitionAlpha, + y: + oldViewport.y + + (viewport.y - oldViewport.y) * transitionAlpha, + width: + oldViewport.width + + (viewport.width - oldViewport.width) * + transitionAlpha, + height: + oldViewport.height + + (viewport.height - oldViewport.height) * + transitionAlpha, + } + } + + const viewportSize = this.scaleViewport( + viewport.width, + viewport.height, + this.canvas.width, + this.canvas.height ) - this.animationState.update(delta) - this.animationState.apply(this.skeleton) + this.sceneRenderer.camera.zoom = + ((viewport.width * this.devicePixelRatio) / + viewportSize.x) * + this.scale + this.sceneRenderer.camera.position.x = + viewport.x + viewport.width / 2 + this.sceneRenderer.camera.position.y = + viewport.y + viewport.height / 2 + + this.sceneRenderer.begin() + + // Draw skeleton and debug output + this.sceneRenderer.drawSkeleton( + this.skeleton, + this.config.premultipliedAlpha + ) + + this.sceneRenderer.end() + + this.sceneRenderer.camera.zoom = 0 } - - this.skeleton.updateWorldTransform() - - let viewport = { - x: - this.currentViewport.x - - (this.currentViewport.padLeft as number), - y: - this.currentViewport.y - - (this.currentViewport.padBottom as number), - width: - this.currentViewport.width + - (this.currentViewport.padLeft as number) + - (this.currentViewport.padRight as number), - height: - this.currentViewport.height + - (this.currentViewport.padBottom as number) + - (this.currentViewport.padTop as number), - } - - const transitionAlpha = - (performance.now() - this.viewportTransitionStart) / - 1000 / - this.config.viewport.transitionTime - if (this.previousViewport && transitionAlpha < 1) { - const oldViewport = { - x: - this.previousViewport.x - - (this.previousViewport.padLeft as number), - y: - this.previousViewport.y - - (this.previousViewport.padBottom as number), - width: - this.previousViewport.width + - (this.previousViewport.padLeft as number) + - (this.previousViewport.padRight as number), - height: - this.previousViewport.height + - (this.previousViewport.padBottom as number) + - (this.previousViewport.padTop as number), - } - - viewport = { - x: - oldViewport.x + - (viewport.x - oldViewport.x) * transitionAlpha, - y: - oldViewport.y + - (viewport.y - oldViewport.y) * transitionAlpha, - width: - oldViewport.width + - (viewport.width - oldViewport.width) * transitionAlpha, - height: - oldViewport.height + - (viewport.height - oldViewport.height) * - transitionAlpha, - } - } - - const viewportSize = this.scaleViewport( - viewport.width, - viewport.height, - this.canvas.width, - this.canvas.height - ) - - this.sceneRenderer.camera.zoom = - ((viewport.width * this.devicePixelRatio) / viewportSize.x) * - this.scale - this.sceneRenderer.camera.position.x = - viewport.x + viewport.width / 2 - this.sceneRenderer.camera.position.y = - viewport.y + viewport.height / 2 - - this.sceneRenderer.begin() - - // Draw skeleton and debug output - this.sceneRenderer.drawSkeleton( - this.skeleton, - this.config.premultipliedAlpha - ) - - this.sceneRenderer.end() - - this.sceneRenderer.camera.zoom = 0 } } @@ -621,7 +618,6 @@ export class Player { } this.config.success(this) - this.lastFrameTime = performance.now() / 1000 this.loaded = true } @@ -847,6 +843,7 @@ export class Player { set fps(v) { this.config.fps = v + this.time.setFps(v) } } diff --git a/apps/showcase/package.json b/apps/showcase/package.json index a1c5c94..5f06307 100644 --- a/apps/showcase/package.json +++ b/apps/showcase/package.json @@ -5,9 +5,10 @@ "type": "module", "main": "index.ts", "scripts": { - "dev:showcase": "vite --clearScreen false", + "dev:showcase": "bunx --bun vite --clearScreen false", "build": "mode=build bun runner.ts", - "preview:showcase": "vite preview", + "build:directory": "mode=build:directory bun runner.ts", + "preview:showcase": "bunx --bun vite preview", "lint": "eslint && prettier --check ." }, "peerDependencies": { diff --git a/apps/showcase/runner.ts b/apps/showcase/runner.ts index 751c425..ade27a6 100644 --- a/apps/showcase/runner.ts +++ b/apps/showcase/runner.ts @@ -5,34 +5,41 @@ import { envParser, file } from '@aklive2d/libs' import { copyShowcaseData, copyProjectJSON } from '@aklive2d/vite-helpers' import * as dirs from './index.js' -const build = async (namesToBuild: string[]) => { +const build = async (namesToBuild: string[], mode: string) => { const names = !namesToBuild.length ? Object.keys(operators) : namesToBuild console.log('Generating assets for', names.length, 'operators') for (const name of names) { copyShowcaseData(name, { dataDir: dirs.DATA_DIR, publicAssetsDir: dirs.PUBLIC_ASSETS_DIR, + mode, }) await viteBuild() const releaseDir = path.join(dirs.DIST_DIR, name) file.mv(dirs.OUT_DIR, releaseDir) file.rm(dirs.DATA_DIR) - copyProjectJSON(name, { - releaseDir, - }) + if (mode !== 'build:directory') { + copyProjectJSON(name, { + releaseDir, + }) + } } } async function main() { - const { name } = envParser.parse({ + const { name, mode } = envParser.parse({ name: { type: 'string', short: 'n', multiple: true, default: [], }, + mode: { + type: 'string', + short: 'm', + }, }) - await build(name as string[]) + await build(name as string[], mode as string) } main() diff --git a/apps/showcase/src/components/background.js b/apps/showcase/src/components/background.js index f6c422a..11c65dc 100644 --- a/apps/showcase/src/components/background.js +++ b/apps/showcase/src/components/background.js @@ -13,7 +13,7 @@ export default class Background { #parentEl #videoEl #default = { - location: `${import.meta.env.BASE_URL}assets/${buildConfig.background_folder}/`, + location: `${import.meta.env.BASE_URL}${buildConfig.build_assets_dir}${buildConfig.background_folder}/`, image: buildConfig.default_background, } #config = { diff --git a/apps/showcase/src/components/fallback.js b/apps/showcase/src/components/fallback.js index 7d288c3..b2f6f3d 100644 --- a/apps/showcase/src/components/fallback.js +++ b/apps/showcase/src/components/fallback.js @@ -30,7 +30,7 @@ export default class Fallback { this.#el.innerHTML = `
` diff --git a/apps/showcase/src/components/logo.js b/apps/showcase/src/components/logo.js index 6e1fa54..0014839 100644 --- a/apps/showcase/src/components/logo.js +++ b/apps/showcase/src/components/logo.js @@ -13,7 +13,7 @@ export default class Logo { #imageEl #parentEl #default = { - location: `${import.meta.env.BASE_URL}assets/`, + location: `${import.meta.env.BASE_URL}${buildConfig.build_assets_dir}${buildConfig.logo_dir}`, image: `${buildConfig.logo_filename}.png`, useInvertFilter: buildConfig.invert_filter, ratio: 61.8, diff --git a/apps/showcase/src/components/music.js b/apps/showcase/src/components/music.js index 68b70e3..abc9f7a 100644 --- a/apps/showcase/src/components/music.js +++ b/apps/showcase/src/components/music.js @@ -104,8 +104,8 @@ export default class Music { #playMusic() { if (!this.#config.name) { const introOgg = this.#music.mapping[this.#music.current].intro - const intro = `./assets/${this.#music.location}/${introOgg}` - const loop = `./assets/${this.#music.location}/${this.#music.mapping[this.#music.current].loop}` + const intro = `${import.meta.env.BASE_URL}${buildConfig.build_assets_dir}${this.#music.location}/${introOgg}` + const loop = `${import.meta.env.BASE_URL}${buildConfig.build_assets_dir}${this.#music.location}/${this.#music.mapping[this.#music.current].loop}` this.#audio.loop.el.src = loop this.#audio.loop.el.querySelector('source').type = 'audio/ogg' if (introOgg) { diff --git a/apps/showcase/src/components/player.js b/apps/showcase/src/components/player.js index 01db10f..e0ff089 100644 --- a/apps/showcase/src/components/player.js +++ b/apps/showcase/src/components/player.js @@ -45,7 +45,7 @@ export default class Player { async init() { const _this = this const playerConfig = { - atlasUrl: `./assets/${buildConfig.filename}.atlas`, + atlasUrl: `${import.meta.env.BASE_URL}${buildConfig.default_assets_dir}${buildConfig.filename}.atlas`, premultipliedAlpha: true, alpha: true, backgroundColor: '#00000000', @@ -115,9 +115,9 @@ export default class Player { }, } if (buildConfig.use_json) { - playerConfig.jsonUrl = `./assets/${buildConfig.filename}.json` + playerConfig.jsonUrl = `${import.meta.env.BASE_URL}${buildConfig.default_assets_dir}${buildConfig.filename}.json` } else { - playerConfig.skelUrl = `./assets/${buildConfig.filename}.skel` + playerConfig.skelUrl = `${import.meta.env.BASE_URL}${buildConfig.default_assets_dir}${buildConfig.filename}.skel` } this.#spine = new SpinePlayer(this.#el, playerConfig) } diff --git a/apps/showcase/src/components/voice.js b/apps/showcase/src/components/voice.js index d411dff..9296167 100644 --- a/apps/showcase/src/components/voice.js +++ b/apps/showcase/src/components/voice.js @@ -83,7 +83,9 @@ export default class Voice { } async init() { - const res = await fetch('./assets/charword_table.json') + const res = await fetch( + `${import.meta.env.BASE_URL}${buildConfig.default_assets_dir}charword_table.json` + ) this.#charwordTable = await res.json() this.#voice.languages = Object.keys( this.#charwordTable.voiceLangs[this.#default.region] @@ -170,7 +172,7 @@ export default class Voice { if (!this.useVoice) return this.#voice.id.last = this.#voice.id.current this.#voice.id.current = id - this.#audio.el.src = `./assets/${this.#getVoiceLocation()}/${id}.ogg` + this.#audio.el.src = `${import.meta.env.BASE_URL}${buildConfig.default_assets_dir}${this.#getVoiceLocation()}/${id}.ogg` let startPlayPromise = this.#audio.el.play() if (startPlayPromise !== undefined) { startPlayPromise diff --git a/package.json b/package.json index 7d7e849..7b98bbc 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "update": "turbo run update", "init": "turbo run init", "download:game": "turbo run download:game", + "build:directory": "turbo run build:directory", "build:cleanup": "turbo run build:cleanup" }, "devDependencies": { diff --git a/packages/config/config.yaml b/packages/config/config.yaml index c1e501a..ab12663 100644 --- a/packages/config/config.yaml +++ b/packages/config/config.yaml @@ -50,6 +50,28 @@ app: public: public assets: assets release: release + directory: + assets: _assets + title: AKLive2D + voice: jp/CN_037.ogg + portraits: portraits + error: + files: + - key: build_char_128_plosis_epoque#3 + paddings: + left: -120 + right: 150 + top: 10 + bottom: 0 + - key: build_char_128_plosis + paddings: + left: -90 + right: 100 + top: 10 + bottom: 0 + voice: + file: CN_034.ogg + target: error.ogg dir_name: data: data dist: dist @@ -73,24 +95,3 @@ dir_name: - name: custom lang: CUSTOM lookup_region: zh_CN -directory: - assets_dir: _assets - title: AKLive2D - voice: jp/CN_037.ogg - error: - files: - - key: build_char_128_plosis_epoque#3 - paddings: - left: -120 - right: 150 - top: 10 - bottom: 0 - - key: build_char_128_plosis - paddings: - left: -90 - right: 100 - top: 10 - bottom: 0 - voice: - file: CN_034.ogg - target: error.ogg diff --git a/packages/config/types.ts b/packages/config/types.ts index 24ee564..66bc93b 100644 --- a/packages/config/types.ts +++ b/packages/config/types.ts @@ -65,6 +65,27 @@ export type Config = { assets: string release: string } + directory: { + assets: string + title: string + voice: string + portraits: string + error: { + files: { + key: string + paddings: { + left: string + right: string + top: string + bottom: string + } + }[] + voice: { + file: string + target: string + } + } + } } dir_name: { data: string @@ -80,24 +101,4 @@ export type Config = { }[] } } - directory: { - assets_dir: string - title: string - voice: string - error: { - files: { - key: string - paddings: { - left: string - right: string - top: string - bottom: string - } - }[] - voice: { - file: string - target: string - } - } - } } diff --git a/packages/vite-helpers/index.ts b/packages/vite-helpers/index.ts index 99babfb..c6d3001 100644 --- a/packages/vite-helpers/index.ts +++ b/packages/vite-helpers/index.ts @@ -18,7 +18,11 @@ interface DirectoryOperatorConfig extends OperatorConfig { export const copyShowcaseData = ( name: string, - { dataDir, publicAssetsDir }: { dataDir: string; publicAssetsDir: string } + { + dataDir, + publicAssetsDir, + mode, + }: { dataDir: string; publicAssetsDir: string; mode: string } ) => { file.mkdir(publicAssetsDir) const operatorAssetsDir = path.join( @@ -40,11 +44,13 @@ export const copyShowcaseData = ( publicAssetsDir, config.module.assets.background ), + condition: mode !== 'build:directory', }, { fn: file.symlink, source: path.resolve(ASSETS_DIST_DIR, config.module.assets.music), target: path.resolve(publicAssetsDir, config.module.assets.music), + condition: mode !== 'build:directory', }, { fn: file.symlinkAll, @@ -70,6 +76,7 @@ export const copyShowcaseData = ( filename: `${operators[name].logo}.png`, source: path.resolve(ASSETS_DIST_DIR, config.module.operator.logos), target: path.resolve(publicAssetsDir), + condition: mode !== 'build:directory', }, { fn: file.symlink, @@ -90,12 +97,14 @@ export const copyShowcaseData = ( target: path.resolve(publicAssetsDir), }) }) - q.map(({ fn, filename, source, target }) => { - if (filename) { - source = path.resolve(source, filename) - target = path.resolve(target, filename) + q.map(({ fn, filename, source, target, condition = true }) => { + if (condition) { + if (filename) { + source = path.resolve(source, filename) + target = path.resolve(target, filename) + } + fn(source, target) } - fn(source, target) }) const buildConfig = { insight_id: config.insight.id, @@ -118,6 +127,15 @@ export const copyShowcaseData = ( music_folder: config.module.assets.music, music_mapping: musicMapping.musicFileMapping, use_json: operators[name].use_json, + default_assets_dir: `${config.app.showcase.assets}/`, + logo_dir: + mode === 'build:directory' + ? `${config.module.operator.logos}/` + : '', + build_assets_dir: + mode === 'build:directory' + ? `../${config.app.directory.assets}/` + : `${config.app.showcase.assets}/`, // default is assets/, on build:directory mode is ../_assets } file.writeSync( JSON.stringify(buildConfig), @@ -165,9 +183,9 @@ export const copyDirectoryData = async ({ OPERATOR_SOURCE_FOLDER, config.module.operator.directory_assets ) - const targetFolder = path.join(publicDir, config.directory.assets_dir) + const targetFolder = path.join(publicDir, config.app.directory.assets) const sourceFolder = path.join(ASSETS_DIST_DIR) - const filesToCopy = Object.keys(operators) + const operatorFilesToCopy = Object.keys(operators) const operatorConfig = Object.values( Object.values(operators).reduce( (acc, cur) => { @@ -205,7 +223,7 @@ export const copyDirectoryData = async ({ ) ).sort((a, b) => Date.parse(b[0].date) - Date.parse(a[0].date)) await Promise.all( - config.directory.error.files.map(async (key) => { + config.app.directory.error.files.map(async (key) => { await generateAssetsJson(key.key, extractedFolder, targetFolder, { useSymLink: false, }) @@ -214,17 +232,20 @@ export const copyDirectoryData = async ({ const directoryConfig = { insight_id: config.insight.id, - app_voice_url: config.directory.voice, + app_voice_url: config.app.directory.voice, voice_folders: config.dir_name.voice, - directory_folder: config.directory.assets_dir, + directory_folder: config.app.directory.assets, default_background: config.module.background.operator_bg_png, background_files: backgroundFiles, background_folder: config.module.assets.background, available_operators: Object.keys(operators), - error_files: config.directory.error, + error_files: config.app.directory.error, music_folder: config.module.assets.music, music_mapping: musicMapping.musicFileMapping, operators: operatorConfig, + default_assets_dir: `${config.app.showcase.assets}/`, + logo_dir: `${config.module.operator.logos}/`, + portraits: `${config.app.directory.portraits}/`, } file.writeSync( JSON.stringify(directoryConfig), @@ -235,7 +256,7 @@ export const copyDirectoryData = async ({ env.generate([ { key: 'app_title', - value: config.directory.title, + value: config.app.directory.title, }, { key: 'insight_url', @@ -245,23 +266,51 @@ export const copyDirectoryData = async ({ path.join(dataDir, '.env') ) - filesToCopy.map((key) => { + const filesToCopy = [ + { + src: path.join( + extractedFolder, + config.app.directory.error.voice.file + ), + dest: path.join( + targetFolder, + config.app.directory.error.voice.target + ), + }, + { + src: path.resolve(ASSETS_DIST_DIR, config.module.assets.background), + dest: path.resolve(targetFolder, config.module.assets.background), + }, + { + src: path.resolve(ASSETS_DIST_DIR, config.module.assets.music), + dest: path.resolve(targetFolder, config.module.assets.music), + }, + { + src: path.resolve(ASSETS_DIST_DIR, config.module.operator.logos), + dest: path.resolve(targetFolder, config.module.operator.logos), + }, + ] + + operatorFilesToCopy.map((key) => { const portraitName = `${operators[key].fallback_name}_portrait.png` - file.cpSync( - path.join( + filesToCopy.push({ + src: path.join( sourceFolder, config.module.operator.operator, key, portraitName ), - path.join(targetFolder, portraitName) - ) + dest: path.join( + targetFolder, + config.app.directory.portraits, + portraitName + ), + }) }) - file.cpSync( - path.join(extractedFolder, config.directory.error.voice.file), - path.join(targetFolder, config.directory.error.voice.target) - ) + filesToCopy.forEach((item) => { + file.symlink(item.src, item.dest) + }) return directoryConfig } diff --git a/turbo.json b/turbo.json index f4aff65..65cc90a 100644 --- a/turbo.json +++ b/turbo.json @@ -1,130 +1,180 @@ { - "$schema": "https://turbo.build/schema.json", - "tasks": { - "@aklive2d/directory#build": { - "env": ["name"], - "inputs": ["$TURBO_DEFAULT$", "../../packages/assets/dist/**", "src/**"], - "outputs": ["../../dist/index.html", "../../dist/_assets/**", "../../dist/_directory/**"], - "dependsOn": ["^@aklive2d/showcase#build"] - }, - "@aklive2d/showcase#build": { - "env": ["name"], - "inputs": ["$TURBO_DEFAULT$", "../../packages/assets/dist/**", "src/**"], - "outputs": ["../../dist/**"], - "dependsOn": ["@aklive2d/assets#build"] - }, - "@aklive2d/background#build": { - "inputs": ["../music/auto_update/music_table.json"], - "outputs": ["dist/bg_*.png", "dist/operator_bg.png"] - }, - "@aklive2d/charword-table#build": { - "env": ["name"], - "inputs": ["../official-info/auto_update/official_info.json", "../operator/operators.yaml"], - "outputs": ["dist/**/charword_table.json"] - }, - "@aklive2d/operator#build": { - "env": ["name"], - "dependsOn": ["@aklive2d/charword-table#build"], - "inputs": ["../official-info/auto_update/official_info.json", "operators.yaml"], - "outputs": ["dist/**"] - }, - "@aklive2d/project-json#build": { - "env": ["name"], - "dependsOn": ["@aklive2d/background#build", "@aklive2d/charword-table#build", "@aklive2d/music#build"], - "inputs": ["../operator/operators.yaml"], - "outputs": ["dist/**"] - }, - "@aklive2d/assets#build": { - "dependsOn": ["@aklive2d/background#build", "@aklive2d/charword-table#build", "@aklive2d/operator#build", "@aklive2d/project-json#build"], - "outputs": ["dist/**"] - }, - "build": { - "env": ["name"], - "dependsOn": ["^build"], - "inputs": ["$TURBO_DEFAULT$"], - "outputs": ["dist/**"] - }, - "@aklive2d/charword-table#update": { - "cache": false, - "outputs": ["auto_update/charword_table*.json"] - }, - "@aklive2d/music#update": { - "cache": false, - "outputs": ["auto_update/audio_data*.json", "auto_update/display_meta_table*.json", "auto_update/music_table.json"] - }, - "@aklive2d/official-info#update": { - "cache": false, - "outputs": ["auto_update/official_info.json"] - }, - "update": { - "cache": false - }, - "@aklive2d/operator#init": { - "env": ["name", "id"], - "cache": false, - "inputs": ["../official-info/auto_update/official_info.json"], - "outputs": ["config/*.yaml", "config.yaml"] - }, - "init": { - "env": ["name", "id"], - "cache": false - }, - "lint": { - "cache": false - }, - "dev:directory": { - "env": ["name"], - "dependsOn": ["^@aklive2d/assets#build"], - "cache": false, - "persistent": true - }, - "preview:directory": { - "env": ["name"], - "dependsOn": ["^@aklive2d/assets#build"], - "cache": false, - "persistent": true - }, - "dev:showcase": { - "env": ["name"], - "dependsOn": ["^@aklive2d/assets#build"], - "cache": false, - "persistent": true - }, - "preview:showcase": { - "env": ["name"], - "dependsOn": ["^@aklive2d/showcase#build"], - "cache": false, - "persistent": true - }, - "@aklive2d/assets#download:game": { - "cache": false, - "outputs": ["data/**"] - }, - "download:game": { - "cache": false - }, - "@aklive2d/assets#build:cleanup": { - "cache": false, - "dependsOn": ["@aklive2d/directory#build"] - }, - "@aklive2d/background#build:cleanup": { - "cache": false, - "dependsOn": ["@aklive2d/directory#build"] - }, - "@aklive2d/charword-table#build:cleanup": { - "cache": false, - "dependsOn": ["@aklive2d/directory#build"] - }, - "@aklive2d/music#build:cleanup": { - "cache": false, - "dependsOn": ["@aklive2d/directory#build"] - }, - "@aklive2d/operator#build:cleanup": { - "cache": false, - "dependsOn": ["@aklive2d/directory#build"] - }, - "build:cleanup": { - "cache": false + "$schema": "https://turbo.build/schema.json", + "tasks": { + "@aklive2d/directory#dev:directory": { + "inputs": [ + "$TURBO_DEFAULT$", + "../../release/**", + "../../packages/assets/dist/**", + "src/**" + ], + "dependsOn": ["@aklive2d/showcase#build:directory"], + "persistent": true + }, + "@aklive2d/directory#build:directory": { + "inputs": [ + "$TURBO_DEFAULT$", + "../../release/**", + "../../packages/assets/dist/**", + "src/**" + ], + "outputs": ["../../dist/index.html", "../../dist/_assets/**"], + "dependsOn": ["@aklive2d/showcase#build:directory"] + }, + "@aklive2d/showcase#build:directory": { + "env": ["name"], + "inputs": [ + "$TURBO_DEFAULT$", + "../../packages/assets/dist/**", + "src/**" + ], + "outputs": ["../../release/**"], + "dependsOn": ["@aklive2d/assets#build"] + }, + "@aklive2d/showcase#build": { + "env": ["name"], + "inputs": [ + "$TURBO_DEFAULT$", + "../../packages/assets/dist/**", + "src/**" + ], + "outputs": ["../../release/**"], + "dependsOn": ["@aklive2d/assets#build"] + }, + "@aklive2d/background#build": { + "inputs": ["../music/auto_update/music_table.json"], + "outputs": ["dist/bg_*.png", "dist/operator_bg.png"] + }, + "@aklive2d/charword-table#build": { + "env": ["name"], + "inputs": [ + "../official-info/auto_update/official_info.json", + "../operator/operators.yaml" + ], + "outputs": ["dist/**/charword_table.json"] + }, + "@aklive2d/operator#build": { + "env": ["name"], + "dependsOn": ["@aklive2d/charword-table#build"], + "inputs": [ + "../official-info/auto_update/official_info.json", + "operators.yaml" + ], + "outputs": ["dist/**"] + }, + "@aklive2d/project-json#build": { + "env": ["name"], + "dependsOn": [ + "@aklive2d/background#build", + "@aklive2d/charword-table#build", + "@aklive2d/music#build" + ], + "inputs": ["../operator/operators.yaml"], + "outputs": ["dist/**"] + }, + "@aklive2d/assets#build": { + "dependsOn": [ + "@aklive2d/background#build", + "@aklive2d/charword-table#build", + "@aklive2d/operator#build", + "@aklive2d/project-json#build" + ], + "outputs": ["dist/**"] + }, + "build": { + "env": ["name"], + "dependsOn": ["^build"], + "inputs": ["$TURBO_DEFAULT$"], + "outputs": ["release/**"] + }, + "build:directory": { + "env": ["name"], + "inputs": ["$TURBO_DEFAULT$"], + "outputs": ["dist/**"] + }, + "@aklive2d/charword-table#update": { + "cache": false, + "outputs": ["auto_update/charword_table*.json"] + }, + "@aklive2d/music#update": { + "cache": false, + "outputs": [ + "auto_update/audio_data*.json", + "auto_update/display_meta_table*.json", + "auto_update/music_table.json" + ] + }, + "@aklive2d/official-info#update": { + "cache": false, + "outputs": ["auto_update/official_info.json"] + }, + "update": { + "cache": false + }, + "@aklive2d/operator#init": { + "env": ["name", "id"], + "cache": false, + "inputs": ["../official-info/auto_update/official_info.json"], + "outputs": ["config/*.yaml", "config.yaml"] + }, + "init": { + "env": ["name", "id"], + "cache": false + }, + "lint": { + "cache": false + }, + "dev:directory": { + "env": ["name"], + "dependsOn": ["^@aklive2d/assets#build"], + "cache": false, + "persistent": true + }, + "preview:directory": { + "env": ["name"], + "dependsOn": ["^@aklive2d/assets#build"], + "cache": false, + "persistent": true + }, + "dev:showcase": { + "cache": false, + "persistent": true + }, + "preview:showcase": { + "env": ["name"], + "dependsOn": ["^@aklive2d/showcase#build"], + "cache": false, + "persistent": true + }, + "@aklive2d/assets#download:game": { + "cache": false, + "outputs": ["data/**"] + }, + "download:game": { + "cache": false + }, + "@aklive2d/assets#build:cleanup": { + "cache": false, + "dependsOn": ["@aklive2d/directory#build"] + }, + "@aklive2d/background#build:cleanup": { + "cache": false, + "dependsOn": ["@aklive2d/directory#build"] + }, + "@aklive2d/charword-table#build:cleanup": { + "cache": false, + "dependsOn": ["@aklive2d/directory#build"] + }, + "@aklive2d/music#build:cleanup": { + "cache": false, + "dependsOn": ["@aklive2d/directory#build"] + }, + "@aklive2d/operator#build:cleanup": { + "cache": false, + "dependsOn": ["@aklive2d/directory#build"] + }, + "build:cleanup": { + "cache": false + } } - } }