From e1399b44a58fabe88e2148267a6d93d2f8c77e1a Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Wed, 28 Feb 2024 18:37:02 +0800 Subject: [PATCH] feat:generate default scene image when project init --- spx-gui/src/class/backdrop.ts | 222 ++++++++++-------- spx-gui/src/class/project.ts | 89 ++++--- .../components/sprite-list/AssetAddBtn.vue | 3 +- .../stage-viewer-demo/StageViewerDemo.vue | 11 +- 4 files changed, 182 insertions(+), 143 deletions(-) diff --git a/spx-gui/src/class/backdrop.ts b/spx-gui/src/class/backdrop.ts index ce273b195..a8e5e06e3 100644 --- a/spx-gui/src/class/backdrop.ts +++ b/spx-gui/src/class/backdrop.ts @@ -2,22 +2,23 @@ * @Author: TuGitee tgb@std.uestc.edu.cn * @Date: 2024-01-19 21:53:50 * @LastEditors: Zhang Zhi Yang - * @LastEditTime: 2024-02-19 09:10:03 - * @FilePath: /spx-gui/src/class/backdrop.ts + * @LastEditTime: 2024-02-28 17:54:55 + * @FilePath: \spx-gui\src\class\backdrop.ts * @Description: The class of a backdrop. */ -import type { BackdropConfig, Scene } from "@/interface/file"; -import { AssetBase } from "@/class/asset-base"; -import { isInstance, getAllFromLocal } from "@/util/class"; -import type { RawDir } from "@/types/file"; -import { useProjectStore } from "@/store/modules/project"; +import type { BackdropConfig, Scene } from '@/interface/file' +import { AssetBase } from '@/class/asset-base' +import { isInstance, getAllFromLocal } from '@/util/class' +import type { RawDir } from '@/types/file' +import { useProjectStore } from '@/store/modules/project' +import type FileWithUrl from './file-with-url' /** * @class Backdrop - * + * * @author tgb * @createDate 2024-01-11 - * + * * @example * // create a new backdrop * const backdrop = new Backdrop() @@ -25,129 +26,146 @@ import { useProjectStore } from "@/store/modules/project"; * const backdrop = new Backdrop('bg', [file1, file2], { height: 200 }) * // create a backdrop from raw data * const backdrop = Backdrop.fromRawData({ name: 'bg', _files: [file1, file2], config: { height: 200 } }) - * + * * // change any params * backdrop.config.height = 300 - * + * * // provide addFile and removeFile method * const file = fileDom.target.files[0] // typeof file ==> File * backdrop.addFile(file) * backdrop.removeFile(file) - * + * * // check if an obj is an instance of a backdrop * Backdrop.isInstance(backdrop) // true - * + * * // conputed path * backdrop.path // assets/ - * + * * // computed dir * backdrop.dir // { "assets/index.json": { height: 300 }, "assets/[file1.name]": file1, "assets/[file2.name]": file2 } - * + * * // config * backdrop.config = backdrop.genDefualtConfig() */ export class Backdrop extends AssetBase { - /** - * The root path of the backdrop. - */ - static ROOT_PATH = "assets/" + /** + * The root path of the backdrop. + */ + static ROOT_PATH = 'assets/' - /** - * The regular expression of the backdrop. - */ - static REG_EXP = new RegExp(`^${Backdrop.ROOT_PATH}([^/]+)$`); + /** + * The regular expression of the backdrop. + */ + static REG_EXP = new RegExp(`^${Backdrop.ROOT_PATH}([^/]+)$`) - /** - * The name of the backdrop. - */ - static NAME = "backdrop"; + /** + * The name of the backdrop. + */ + static NAME = 'backdrop' - /** - * The config of the backdrop. - */ - public config: BackdropConfig; + /** + * The config of the backdrop. + */ + public config: BackdropConfig - /** - * Get the store name for the backdrop. - * @returns the name of the store - */ - protected getStoreName(): string { - return Backdrop.NAME; - } + /** + * Get the store name for the backdrop. + * @returns the name of the store + */ + protected getStoreName(): string { + return Backdrop.NAME + } - /** - * Get all items in the storage. - * @returns all items in the storage - */ - static async getAllFromLocal() { - return await getAllFromLocal(Backdrop); - } + /** + * Get all items in the storage. + * @returns all items in the storage + */ + static async getAllFromLocal() { + return await getAllFromLocal(Backdrop) + } - /** - * @constructor create a new backdrop - * @param {string} name the name of the backdrop - * @param {File[]} files the files of the backdrop - * @param {BackdropConfig} config the config of the backdrop using json to generate `index.json` - */ - constructor(name: string = Backdrop.NAME, files: File[] = [], config?: BackdropConfig) { - super(name, files) - this.config = this.genConfig(config) - } + /** + * @constructor create a new backdrop + * @param {string} name the name of the backdrop + * @param {File[]} files the files of the backdrop + * @param {BackdropConfig} config the config of the backdrop using json to generate `index.json` + */ + constructor(name: string = Backdrop.NAME, files: File[] = [], config?: BackdropConfig) { + super(name, files) + this.config = this.genConfig(config) + } - /** - * Create a new backdrop from raw data. - * @param data the data of the backdrop - * @returns the backdrop instance - */ - static fromRawData(data: any): Backdrop { - return new Backdrop(data.name, data._files, data.config) - } + /** + * Create a new backdrop from raw data. + * @param data the data of the backdrop + * @returns the backdrop instance + */ + static fromRawData(data: any): Backdrop { + return new Backdrop(data.name, data._files, data.config) + } - /** - * Generate the default backdrop config. - * @returns the default config - */ - genDefualtConfig(): BackdropConfig { - return this.defaultConfig - } + /** + * Generate the default backdrop config. + * @returns the default config + */ + genDefualtConfig(): BackdropConfig { + return this.defaultConfig + } - /** - * Generate the default backdrop config. - */ - get defaultConfig(): BackdropConfig { - return { - "scenes": this.files.map(file => ({ - "name": file.name.split(".")[0], - "path": file.name - })), - "zorder": [], - } + /** + * Generate the default backdrop config. + */ + get defaultConfig(): BackdropConfig { + return { + scenes: this.files.map((file) => ({ + name: file.name.split('.')[0], + path: file.name + })), + zorder: [] } + } - /** - * Get the directory of the backdrop. - */ - get dir() { - const dir: RawDir = {} - dir[`${this.path}index.json`] = this.config - for (const file of this.files) { - dir[`${this.path}${file.name}`] = file - } - return dir + /** + * Get the directory of the backdrop. + */ + get dir() { + const dir: RawDir = {} + dir[`${this.path}index.json`] = this.config + for (const file of this.files) { + dir[`${this.path}${file.name}`] = file } + return dir + } - /** - * Get the path of the backdrop. - */ - get path() { - return Backdrop.ROOT_PATH - } + /** + * Get the path of the backdrop. + */ + get path() { + return Backdrop.ROOT_PATH + } + + /** + * Check if an object is an instance of a backdrop. + */ + static isInstance(obj: any): boolean { + return isInstance(obj, Backdrop) + } - /** - * Check if an object is an instance of a backdrop. - */ - static isInstance(obj: any): boolean { - return isInstance(obj, Backdrop); + /** + * add scene of the backdrop + */ + addScene(sceneList: Array<{ name: string; file: FileWithUrl }>) { + if (this.config.scenes) { + this.addFile(...sceneList.map((scene) => scene.file)) + this.config.scenes.push( + ...sceneList.map((scene) => { + return { + name: scene.file.name.split('.')[0], + path: scene.file.name + } + }) + ) } + } } diff --git a/spx-gui/src/class/project.ts b/spx-gui/src/class/project.ts index 724061951..4e6eeaaaa 100644 --- a/spx-gui/src/class/project.ts +++ b/spx-gui/src/class/project.ts @@ -8,6 +8,7 @@ import { getDirPathFromZip, getPrefix } from '@/util/file' +import FileWithUrl from '@/class/file-with-url' import saveAs from 'file-saver' import { SoundList, SpriteList } from '@/class/asset-list' import { Backdrop } from '@/class/backdrop' @@ -56,15 +57,15 @@ export class Project implements ProjectDetail, ProjectSummary { entryCode: string get defaultEntryCode() { - let str = "" - str += "var (\n" + let str = '' + str += 'var (\n' for (const sprite of this.sprite.list) { str += `\t${sprite.name} ${sprite.name}\n` } for (const sound of this.sound.list) { str += `\t${sound.name} Sound\n` } - str += ")\n" + str += ')\n' str += `run "assets", {Title: "${this.title}"}\n` return str } @@ -81,7 +82,7 @@ export class Project implements ProjectDetail, ProjectSummary { const paths = await fs.readdir('summary/') const projects: ProjectSummary[] = [] for (const path of paths) { - const content = await fs.readFile(path) as ProjectSummary + const content = (await fs.readFile(path)) as ProjectSummary projects.push(content) } return projects @@ -115,13 +116,29 @@ export class Project implements ProjectDetail, ProjectSummary { this.title = '' this.sprite = new SpriteList() this.sound = new SoundList() - this.backdrop = new Backdrop() + this.backdrop = this.initDefaultBackdrop() this.entryCode = '' this.unidentifiedFile = {} this.id = nanoid() this.version = 1 this.source = ProjectSource.local } + initDefaultBackdrop() { + const defaultBackdrop = new Backdrop() + const canvas = document.createElement('canvas') + canvas.width = 480 + canvas.height = 360 + const context = canvas.getContext('2d') as CanvasRenderingContext2D + context.fillStyle = '#FFFFFF' + context.fillRect(0, 0, canvas.width, canvas.height) + canvas.toBlob(function (blob) { + const file = new File([blob!], 'blank_scene.png', { type: 'image/png' }) + const fileURL = URL.createObjectURL(file) + const fileWithUrl = new FileWithUrl(file, fileURL) + defaultBackdrop.addScene([{ name: 'default', file: fileWithUrl }]) + }, 'image/png') + return defaultBackdrop + } async load(id: string, source: ProjectSource = ProjectSource.cloud): Promise { this.id = id @@ -135,7 +152,7 @@ export class Project implements ProjectDetail, ProjectSummary { } this._load(dirPath) - const summary = await fs.readFile("summary/" + id) as ProjectSummary + const summary = (await fs.readFile('summary/' + id)) as ProjectSummary Object.assign(this, summary) } else { // TODO: load from cloud @@ -150,56 +167,54 @@ export class Project implements ProjectDetail, ProjectSummary { const handleFile = (file: FileType, filename: string, item: any) => { switch (file.type) { case 'application/json': - item.config = arrayBuffer2Content(file.content, file.type) as Config; - break; + item.config = arrayBuffer2Content(file.content, file.type) as Config + break default: - item.files.push(arrayBuffer2Content(file.content, file.type, filename) as File); - break; + item.files.push(arrayBuffer2Content(file.content, file.type, filename) as File) + break } } - const findOrCreateItem = (name: string, collection: any[], constructor: typeof Sprite | typeof Sound) => { - let item = collection.find(item => item.name === name); + const findOrCreateItem = ( + name: string, + collection: any[], + constructor: typeof Sprite | typeof Sound + ) => { + let item = collection.find((item) => item.name === name) if (!item) { - item = new constructor(name); - collection.push(item); + item = new constructor(name) + collection.push(item) } - return item; + return item } const prefix = getPrefix(dir) // eslint-disable-next-line prefer-const for (let [path, file] of Object.entries(dir)) { - const filename = file.path.split('/').pop()!; + const filename = file.path.split('/').pop()! const content = arrayBuffer2Content(file.content, file.type, filename) path = path.replace(prefix, '') if (Sprite.REG_EXP.test(path)) { - const spriteName = path.match(Sprite.REG_EXP)?.[1] || ''; - const sprite: Sprite = findOrCreateItem(spriteName, this.sprite.list, Sprite); - handleFile(file, filename, sprite); - } - else if (/^(main|index)\.(spx|gmx)$/.test(path)) { + const spriteName = path.match(Sprite.REG_EXP)?.[1] || '' + const sprite: Sprite = findOrCreateItem(spriteName, this.sprite.list, Sprite) + handleFile(file, filename, sprite) + } else if (/^(main|index)\.(spx|gmx)$/.test(path)) { this.entryCode = content as string - } - else if (/^.+\.spx$/.test(path)) { - const spriteName = path.match(/^(.+)\.spx$/)?.[1] || ''; - const sprite: Sprite = findOrCreateItem(spriteName, this.sprite.list, Sprite); - sprite.code = content as string; - } - else if (Sound.REG_EXP.test(path)) { - const soundName = path.match(Sound.REG_EXP)?.[1] || ''; - const sound: Sound = findOrCreateItem(soundName, this.sound.list, Sound); - handleFile(file, filename, sound); - } - else if (Backdrop.REG_EXP.test(path)) { - handleFile(file, filename, this.backdrop); - } - else { + } else if (/^.+\.spx$/.test(path)) { + const spriteName = path.match(/^(.+)\.spx$/)?.[1] || '' + const sprite: Sprite = findOrCreateItem(spriteName, this.sprite.list, Sprite) + sprite.code = content as string + } else if (Sound.REG_EXP.test(path)) { + const soundName = path.match(Sound.REG_EXP)?.[1] || '' + const sound: Sound = findOrCreateItem(soundName, this.sound.list, Sound) + handleFile(file, filename, sound) + } else if (Backdrop.REG_EXP.test(path)) { + handleFile(file, filename, this.backdrop) + } else { this.unidentifiedFile[path] = content } } - } async loadFromZip(file: File, title?: string) { diff --git a/spx-gui/src/components/sprite-list/AssetAddBtn.vue b/spx-gui/src/components/sprite-list/AssetAddBtn.vue index 4bdc95ba8..4630cea95 100644 --- a/spx-gui/src/components/sprite-list/AssetAddBtn.vue +++ b/spx-gui/src/components/sprite-list/AssetAddBtn.vue @@ -100,8 +100,8 @@ >

{{ $t('list.name') }}:

@@ -245,6 +245,7 @@ const beforeUpload = ( let fileNameWithoutExtension = fileName.substring(0, fileName.lastIndexOf('.')) switch (fileType) { case 'backdrop': { + // TODO: need sync to backdrop's scene config let backdrop = backdropStore.backdrop backdrop.addFile(...fileArray) break diff --git a/spx-gui/src/components/stage-viewer-demo/StageViewerDemo.vue b/spx-gui/src/components/stage-viewer-demo/StageViewerDemo.vue index 32e81b717..df7dd3d86 100644 --- a/spx-gui/src/components/stage-viewer-demo/StageViewerDemo.vue +++ b/spx-gui/src/components/stage-viewer-demo/StageViewerDemo.vue @@ -2,7 +2,7 @@ * @Author: Zhang Zhi Yang * @Date: 2024-02-05 14:18:34 * @LastEditors: Zhang Zhi Yang - * @LastEditTime: 2024-02-28 15:25:10 + * @LastEditTime: 2024-02-28 18:31:44 * @FilePath: \spx-gui\src\components\stage-viewer-demo\StageViewerDemo.vue * @Description: --> @@ -162,10 +162,11 @@ import { NInputNumber, NSwitch } from 'naive-ui' import type { Sprite } from '@/class/sprite' import StageViewer from '../stage-viewer' import type { SelectedSpritesChangeEvent } from '../stage-viewer' -import type { Project } from '@/class/project' +import { Project } from '@/class/project' import { useProjectStore } from '@/store/modules/project' import { storeToRefs } from 'pinia' import { ref, computed } from 'vue' +import FileWithUrl from '@/class/file-with-url' const projectStore = useProjectStore() const { project } = storeToRefs(projectStore) @@ -202,7 +203,9 @@ const backdropConfig = computed(() => { // get the zorder list of sprite const zorderList = computed>(() => { - return project.value.backdrop.config.zorder.filter((item) => typeof item === 'string') as Array + return project.value.backdrop.config.zorder.filter( + (item) => typeof item === 'string' + ) as Array }) const onSelectedSpritesChange = (e: SelectedSpritesChangeEvent) => { @@ -249,4 +252,6 @@ const chooseBackdropScene = (index: number) => { const chooseBackdropCostume = (index: number) => { project.value.backdrop.config.currentCostumeIndex = index } + +