diff --git a/.envrc b/.envrc
new file mode 100644
index 000000000..8f390c35a
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+use_flake
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index df6cc8aa3..3c22a3472 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,6 @@
/compilerTypes
node_modules/*
-!node_modules/vue-template-compiler
# local env files
.env
@@ -23,4 +22,8 @@ pnpm-debug.log*
*.njsproj
*.sln
*.sw?
-todos.md
\ No newline at end of file
+todos.md
+
+public/changelog.html
+
+.direnv
\ No newline at end of file
diff --git a/.prettierrc.json b/.prettierrc.json
index 3ffcd03fd..38f534037 100644
--- a/.prettierrc.json
+++ b/.prettierrc.json
@@ -4,5 +4,5 @@
"tabWidth": 4,
"semi": false,
"singleQuote": true,
- "printWidth": 80
-}
+ "printWidth": 140
+}
\ No newline at end of file
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index d506ab55b..863e341bc 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,3 +1,3 @@
{
- "recommendations": ["esbenp.prettier-vscode", "octref.vetur"]
+ "recommendations": ["esbenp.prettier-vscode", "Vue.volar"]
}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 20b1188f8..490848d0d 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -7,6 +7,13 @@
"problemMatcher": [],
"label": "npm: dev",
"detail": "vue-cli-service serve"
+ },
+ {
+ "type": "npm",
+ "script": "dev:native",
+ "problemMatcher": [],
+ "label": "npm: dev:native",
+ "detail": "cargo tauri dev"
}
]
-}
\ No newline at end of file
+}
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 000000000..eb9d74da4
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,25 @@
+{
+ "nodes": {
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1759733170,
+ "narHash": "sha256-TXnlsVb5Z8HXZ6mZoeOAIwxmvGHp1g4Dw89eLvIwKVI=",
+ "rev": "8913c168d1c56dc49a7718685968f38752171c3b",
+ "revCount": 873256,
+ "type": "tarball",
+ "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.873256%2Brev-8913c168d1c56dc49a7718685968f38752171c3b/0199bd36-8ae7-7817-b019-8688eb4f61ff/source.tar.gz"
+ },
+ "original": {
+ "type": "tarball",
+ "url": "https://flakehub.com/f/NixOS/nixpkgs/0.1"
+ }
+ },
+ "root": {
+ "inputs": {
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 000000000..b8ecc24dc
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,47 @@
+{
+ description = "A Nix-flake-based Node.js development environment";
+
+ inputs.nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.1";
+
+ outputs =
+ inputs:
+ let
+ supportedSystems = [
+ "x86_64-linux"
+ "aarch64-linux"
+ "x86_64-darwin"
+ "aarch64-darwin"
+ ];
+ forEachSupportedSystem =
+ f:
+ inputs.nixpkgs.lib.genAttrs supportedSystems (
+ system:
+ f {
+ pkgs = import inputs.nixpkgs {
+ inherit system;
+ overlays = [ inputs.self.overlays.default ];
+ };
+ }
+ );
+ in
+ {
+ overlays.default = final: prev: rec {
+ nodejs = prev.nodejs;
+ yarn = (prev.yarn.override { inherit nodejs; });
+ };
+
+ devShells = forEachSupportedSystem (
+ { pkgs }:
+ {
+ default = pkgs.mkShell {
+ packages = with pkgs; [
+ node2nix
+ nodejs
+ nodePackages.pnpm
+ yarn
+ ];
+ };
+ }
+ );
+ };
+}
\ No newline at end of file
diff --git a/index.html b/index.html
index 35275fbc3..457fc39ad 100644
--- a/index.html
+++ b/index.html
@@ -2,19 +2,10 @@
-
-
+
+
-
+
@@ -23,103 +14,45 @@
-
+
-
-
- <% if(isNightly) { %>
-
-
-
-
-
-
- <% } else { %>
-
-
+
+
+
+
+
+
+
+
-
-
-
- <% } %>
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Actions/Action.ts b/src/components/Actions/Action.ts
deleted file mode 100644
index 6d0083a0a..000000000
--- a/src/components/Actions/Action.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { ActionManager } from './ActionManager'
-import { KeyBinding } from './KeyBinding'
-import { v4 as uuid } from 'uuid'
-import { IActionConfig, SimpleAction } from './SimpleAction'
-
-export class Action extends SimpleAction {
- public readonly type = 'action'
- public readonly id: string
- protected _keyBinding: KeyBinding | undefined
-
- constructor(
- protected actionManager: ActionManager,
- protected config: IActionConfig
- ) {
- super(config)
- this.id = config.id ?? uuid()
-
- if (config.keyBinding) {
- const keyBindings = Array.isArray(config.keyBinding)
- ? config.keyBinding
- : [config.keyBinding]
-
- keyBindings.forEach((keyBinding) =>
- this.addKeyBinding(
- KeyBinding.fromStrKeyCode(
- actionManager.keyBindingManager,
- keyBinding,
- true,
- config.prevent
- )
- )
- )
- }
- }
-
- get keyBinding() {
- return this._keyBinding
- }
-
- addKeyBinding(keyBinding: KeyBinding) {
- this._keyBinding = keyBinding
- keyBinding.on(() => this.trigger())
- return this
- }
- disposeKeyBinding() {
- if (this._keyBinding) this._keyBinding.dispose()
- }
-
- dispose() {
- this.actionManager.disposeAction(this.id)
- this.disposeKeyBinding()
- }
-}
diff --git a/src/components/Actions/ActionManager.ts b/src/components/Actions/ActionManager.ts
deleted file mode 100644
index 62e4e9b27..000000000
--- a/src/components/Actions/ActionManager.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import { Action } from './Action'
-import { IActionConfig } from './SimpleAction'
-import { del, set, shallowReactive } from 'vue'
-import type { KeyBindingManager } from './KeyBindingManager'
-import { v4 as uuid } from 'uuid'
-import { ISubmenuConfig } from '../ContextMenu/showContextMenu'
-
-export class ActionManager {
- type = 'submenu'
- public state: Record<
- string,
- | Action
- | { type: 'divider' }
- | {
- type: 'submenu'
- icon: string
- name: string
- submenu: Submenu
- }
- > = shallowReactive({})
-
- constructor(public readonly _keyBindingManager?: KeyBindingManager) {}
-
- get keyBindingManager() {
- if (!this._keyBindingManager)
- throw new Error(
- `No keyBindingManager was defined for this actionManager`
- )
-
- return this._keyBindingManager
- }
-
- create(actionConfig: IActionConfig) {
- const action = new Action(this, actionConfig)
- set(this.state, action.id, action)
- return action
- }
- getAction(actionId: string) {
- return this.state[actionId]
- }
- getAllActions() {
- return Object.values(this.state).filter(
- (action) => action.type === 'action'
- )
- }
- getAllElements() {
- return Object.values(this.state)
- }
-
- /**
- * This is used by some classes that use an action manager as an abstraction to render action lists.
- * e.g. showContextMenu(...)
- */
- addDivider() {
- this.state[uuid()] = { type: 'divider' }
- }
- addSubMenu(submenuConfig: ISubmenuConfig) {
- const submenu = new Submenu()
-
- submenuConfig.actions.forEach((action) => {
- if (action === null) return
-
- if (action.type === 'divider') {
- submenu.addDivider()
- } else {
- submenu.create(action)
- }
- })
- this.state[uuid()] = {
- type: 'submenu',
- icon: submenuConfig.icon,
- name: submenuConfig.name,
- submenu,
- }
- }
- disposeAction(actionId: string) {
- del(this.state, actionId)
- }
- dispose() {
- Object.values(this.state).forEach((action) =>
- action.type === 'action' ? action.dispose() : null
- )
- }
-
- async trigger(actionId: string) {
- if (!this.state[actionId] || this.state[actionId].type !== 'action')
- throw new Error(
- `Failed to trigger "${actionId}": Action does not exist.`
- )
-
- // This must be an action because of the check above
- await (this.state[actionId]).trigger()
- }
-}
-
-export class Submenu extends ActionManager {
- type = 'submenu'
-}
diff --git a/src/components/Actions/ActionViewer.vue b/src/components/Actions/ActionViewer.vue
deleted file mode 100644
index 92432529c..000000000
--- a/src/components/Actions/ActionViewer.vue
+++ /dev/null
@@ -1,75 +0,0 @@
-
-
-
-
- {{ action.icon }}
-
-
{{ t(action.name) }}
-
-
-
- {{ action.keyBinding.toStrKeyCode() }}
-
-
- mdi-play
-
-
- {{
- selected
- ? 'mdi-check-circle-outline'
- : 'mdi-checkbox-blank-circle-outline'
- }}
-
-
-
-
- {{ t(action.description) }}
-
-
-
-
-
-
-
diff --git a/src/components/Actions/Actions.ts b/src/components/Actions/Actions.ts
deleted file mode 100644
index 6c0a45a68..000000000
--- a/src/components/Actions/Actions.ts
+++ /dev/null
@@ -1,256 +0,0 @@
-import { TextTab } from '../Editors/Text/TextTab'
-import { TreeTab } from '../Editors/TreeEditor/Tab'
-import { clearAllNotifications } from '../Notifications/create'
-import { FileTab } from '../TabSystem/FileTab'
-import { fullScreenAction } from '../TabSystem/TabContextMenu/Fullscreen'
-import { ViewCompilerOutput } from '../UIElements/DirectoryViewer/ContextMenu/Actions/ViewCompilerOutput'
-import { App } from '/@/App'
-import { platformRedoBinding } from '/@/utils/constants'
-import { platform } from '/@/utils/os'
-
-export function setupActions(app: App) {
- addViewActions(app)
- addToolActions(app)
- addEditActions(app)
-}
-
-function addEditActions(app: App) {
- app.actionManager.create({
- icon: 'mdi-undo',
- name: 'actions.undo.name',
- description: 'actions.undo.description',
- keyBinding: 'Ctrl + Z',
- prevent: (el) => el.tagName === 'INPUT' || el.tagName === 'TEXTAREA',
- onTrigger: () => {
- const currentTab = app.tabSystem?.selectedTab
- if (currentTab instanceof TreeTab) currentTab.treeEditor.undo()
- else if (currentTab instanceof TextTab)
- currentTab.editorInstance.trigger('toolbar', 'undo', null)
- else document.execCommand('undo')
- },
- })
-
- app.actionManager.create({
- icon: 'mdi-redo',
- name: 'actions.redo.name',
- description: 'actions.redo.description',
- keyBinding: platformRedoBinding,
- prevent: (el) => el.tagName === 'INPUT' || el.tagName === 'TEXTAREA',
- onTrigger: () => {
- const currentTab = app.tabSystem?.selectedTab
- if (currentTab instanceof TreeTab) currentTab.treeEditor.redo()
- else if (currentTab instanceof TextTab)
- currentTab.editorInstance.trigger('toolbar', 'redo', null)
- else document.execCommand('redo')
- },
- })
-
- const blockActions = new Set(['INPUT', 'TEXTAREA'])
-
- app.actionManager.create({
- icon: 'mdi-content-copy',
- name: 'actions.copy.name',
- description: 'actions.copy.description',
- keyBinding: 'Ctrl + C',
- prevent: (element) => {
- return blockActions.has(element.tagName)
- },
- onTrigger: () => app.tabSystem?.selectedTab?.copy(),
- })
-
- app.actionManager.create({
- icon: 'mdi-content-cut',
- name: 'actions.cut.name',
- description: 'actions.cut.description',
- keyBinding: 'Ctrl + X',
- prevent: (element) => {
- return blockActions.has(element.tagName)
- },
- onTrigger: () => app.tabSystem?.selectedTab?.cut(),
- })
-
- app.actionManager.create({
- icon: 'mdi-content-paste',
- name: 'actions.paste.name',
- description: 'actions.paste.description',
- keyBinding: 'Ctrl + V',
- prevent: (element) => {
- return blockActions.has(element.tagName)
- },
- onTrigger: () => app.tabSystem?.selectedTab?.paste(),
- })
-}
-
-function addToolActions(app: App) {
- app.actionManager.create({
- icon: 'mdi-folder-refresh-outline',
- name: 'general.reloadBridge.name',
- description: 'general.reloadBridge.description',
- keyBinding: 'Ctrl + R',
- onTrigger: () => {
- location.reload()
- },
- })
-
- app.actionManager.create({
- id: 'bridge.action.refreshProject',
- icon: 'mdi-folder-refresh-outline',
- name: 'packExplorer.refresh.name',
- description: 'packExplorer.refresh.description',
- keyBinding:
- platform() === 'win32' ? 'Ctrl + Alt + R' : 'Ctrl + Meta + R',
- onTrigger: async () => {
- if (app.isNoProjectSelected) return
- await app.projectManager.projectReady.fired
- await app.project.refresh()
- },
- })
-
- app.actionManager.create({
- icon: 'mdi-reload',
- name: 'actions.reloadAutoCompletions.name',
- description: 'actions.reloadAutoCompletions.description',
- keyBinding: 'Ctrl + Shift + R',
- onTrigger: async () => {
- if (app.isNoProjectSelected) return
- await app.projectManager.projectReady.fired
- app.project.jsonDefaults.reload()
- },
- })
-
- app.actionManager.create({
- icon: 'mdi-puzzle-outline',
- name: 'actions.reloadExtensions.name',
- description: 'actions.reloadExtensions.description',
- onTrigger: async () => {
- // Global extensions
- app.extensionLoader.disposeAll()
- app.extensionLoader.loadExtensions()
- if (app.isNoProjectSelected) return
- await app.projectManager.projectReady.fired
- // Local extensions
- app.project.extensionLoader.disposeAll()
- app.project.extensionLoader.loadExtensions()
- },
- })
-
- app.actionManager.create({
- icon: 'mdi-cancel',
- name: 'actions.clearAllNotifications.name',
- description: 'actions.clearAllNotifications.description',
- onTrigger: () => clearAllNotifications(),
- })
-}
-
-function addViewActions(app: App) {
- app.actionManager.create({
- icon: 'mdi-folder-outline',
- name: 'toolbar.view.togglePackExplorer.name',
- description: 'toolbar.view.togglePackExplorer.description',
- keyBinding: 'Ctrl + Shift + E',
- onTrigger: () => {
- App.sidebar.elements.packExplorer.click()
- },
- })
-
- app.actionManager.create({
- icon: 'mdi-file-search-outline',
- name: 'toolbar.view.openFileSearch.name',
- description: 'toolbar.view.openFileSearch.description',
- keyBinding: 'Ctrl + Shift + F',
- onTrigger: () => {
- App.sidebar.elements.fileSearch.click()
- },
- })
-
- const fullscreenAction = fullScreenAction()
- if (fullscreenAction) app.actionManager.create(fullscreenAction)
-
- app.actionManager.create({
- icon: 'mdi-chevron-right',
- name: 'toolbar.view.nextTab.name',
- description: 'toolbar.view.nextTab.description',
- keyBinding: platform() === 'darwin' ? 'Meta + Tab' : 'Ctrl + Tab',
- onTrigger: () => {
- if (app.tabSystem?.hasRecentTab()) {
- app.tabSystem?.selectRecentTab()
- } else {
- app.tabSystem?.selectNextTab()
- }
- },
- })
-
- app.actionManager.create({
- icon: 'mdi-chevron-left',
- name: 'toolbar.view.previousTab.name',
- description: 'toolbar.view.previousTab.description',
- keyBinding:
- platform() === 'darwin'
- ? 'Meta + Shift + Tab'
- : 'Ctrl + Shift + Tab',
- onTrigger: () => {
- app.tabSystem?.selectPreviousTab()
- },
- })
-
- app.actionManager.create({
- icon: 'mdi-arrow-u-left-bottom',
- name: 'toolbar.view.cursorUndo.name',
- description: 'toolbar.view.cursorUndo.description',
- keyBinding: 'ctrl + mouseBack',
- onTrigger: async () => {
- const tabSystem = app.project.tabSystem
- if (!tabSystem) return
- // Await monacoEditor being created
- await tabSystem.fired
- tabSystem?.monacoEditor?.trigger('keybinding', 'cursorUndo', null)
- },
- })
-
- app.actionManager.create({
- icon: 'mdi-arrow-u-right-top',
- name: 'toolbar.view.cursorRedo.name',
- description: 'toolbar.view.cursorRedo.description',
- keyBinding: 'mouseForward',
- onTrigger: async () => {
- const tabSystem = app.project.tabSystem
- if (!tabSystem) return
- // Await monacoEditor being created
- await tabSystem.fired
- tabSystem?.monacoEditor?.trigger('keybinding', 'cursorRedo', null)
- },
- })
-
- app.actionManager.create(ViewCompilerOutput(undefined, true))
-
- app.actionManager.create({
- icon: 'mdi-puzzle-outline',
- name: 'actions.viewExtensionsFolder.name',
- description: 'actions.viewExtensionsFolder.description',
- onTrigger: async () => {
- const extensionsFolder = await app.fileSystem.getDirectoryHandle(
- '~local/extensions'
- )
- app.viewFolders.addDirectoryHandle({
- directoryHandle: extensionsFolder,
- })
- },
- })
-
- app.actionManager.create({
- icon: 'mdi-pencil-outline',
- name: 'actions.toggleReadOnly.name',
- description: 'actions.toggleReadOnly.description',
- onTrigger: () => {
- const currentTab = app.tabSystem?.selectedTab
- if (
- !(currentTab instanceof FileTab) ||
- currentTab.readOnlyMode === 'forced'
- )
- return
- currentTab.setReadOnly(
- currentTab.readOnlyMode === 'manual' ? 'off' : 'manual'
- )
- },
- })
-}
diff --git a/src/components/Actions/KeyBinding.ts b/src/components/Actions/KeyBinding.ts
deleted file mode 100644
index 7cfe3d993..000000000
--- a/src/components/Actions/KeyBinding.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { EventDispatcher } from '/@/components/Common/Event/EventDispatcher'
-import { KeyBindingManager } from './KeyBindingManager'
-import { fromStrKeyCode, toStrKeyCode } from './Utils'
-
-export interface IKeyBindingConfig {
- key: string
- shiftKey?: boolean
- ctrlKey?: boolean
- altKey?: boolean
- metaKey?: boolean
- prevent?: (element: HTMLElement) => boolean
-}
-
-export class KeyBinding extends EventDispatcher {
- constructor(
- protected keyBindingManager: KeyBindingManager,
- protected config: IKeyBindingConfig
- ) {
- super()
- }
-
- static fromStrKeyCode(
- keyBindingManager: KeyBindingManager,
- keyCode: string,
- forceWindowsCtrl = false,
- prevent: IKeyBindingConfig['prevent']
- ) {
- return keyBindingManager.create({
- ...fromStrKeyCode(keyCode, forceWindowsCtrl),
- prevent,
- })
- }
- toStrKeyCode() {
- return toStrKeyCode(this.config)
- }
-
- async trigger() {
- this.dispatch()
- }
- prevent(element: HTMLElement) {
- if (typeof this.config.prevent === 'function')
- return this.config.prevent(element)
- return false
- }
-
- dispose() {
- this.keyBindingManager.disposeKeyBinding(this.toStrKeyCode())
- }
-}
diff --git a/src/components/Actions/KeyBindingManager.ts b/src/components/Actions/KeyBindingManager.ts
deleted file mode 100644
index b4b6a8578..000000000
--- a/src/components/Actions/KeyBindingManager.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import { platform } from '/@/utils/os'
-import { IKeyBindingConfig, KeyBinding } from './KeyBinding'
-import { toStrKeyCode } from './Utils'
-import { del, set, shallowReactive } from 'vue'
-
-const IGNORE_KEYS = ['Control', 'Alt', 'Meta']
-
-interface IKeyEvent {
- key: string
- altKey: boolean
- ctrlKey: boolean
- shiftKey: boolean
- metaKey: boolean
- target: EventTarget | null
- preventDefault: () => void
- stopImmediatePropagation: () => void
-}
-export class KeyBindingManager {
- protected state: Record = shallowReactive({})
- protected lastTimeStamp = 0
-
- protected onKeydown = (event: IKeyEvent) => {
- const { key, ctrlKey, altKey, metaKey, shiftKey } = event
- if (IGNORE_KEYS.includes(key)) return
-
- const keyCode = toStrKeyCode({
- key,
- ctrlKey: platform() === 'darwin' ? metaKey : ctrlKey,
- altKey,
- metaKey: platform() === 'darwin' ? ctrlKey : metaKey,
- shiftKey,
- })
-
- const keyBinding = this.state[keyCode]
-
- if (keyBinding && this.lastTimeStamp + 100 < Date.now()) {
- if (keyBinding.prevent(event.target as HTMLElement)) return
-
- this.lastTimeStamp = Date.now()
- event.preventDefault()
- event.stopImmediatePropagation()
- keyBinding.trigger()
- }
- }
- protected onMouseDown = (event: MouseEvent) => {
- let buttonName = null
- switch (event.button) {
- case 0:
- buttonName = 'Left'
- break
- case 1:
- buttonName = 'Middle'
- break
- case 2:
- buttonName = 'Right'
- break
- case 3:
- buttonName = 'Back'
- break
- case 4:
- buttonName = 'Forward'
- break
- default:
- console.error(`Unknown mouse button: ${event.button}`)
- }
- if (!buttonName) return
-
- this.onKeydown({
- key: `mouse${buttonName}`,
- ctrlKey: event.ctrlKey,
- altKey: event.altKey,
- metaKey: event.metaKey,
- shiftKey: event.shiftKey,
- target: event.target,
- preventDefault: () => event.preventDefault(),
- stopImmediatePropagation: () => event.stopImmediatePropagation(),
- })
- }
-
- constructor(protected element: HTMLDivElement | Document = document) {
- // @ts-ignore TypeScript isn't smart enough to understand that the type "KeyboardEvent" is correct
- element.addEventListener('keydown', this.onKeydown)
-
- // @ts-ignore TypeScript isn't smart enough to understand that the type "MouseEvent" is correct
- element.addEventListener('mousedown', this.onMouseDown)
- }
-
- create(keyBindingConfig: IKeyBindingConfig) {
- const keyBinding = new KeyBinding(this, keyBindingConfig)
- const keyCode = keyBinding.toStrKeyCode()
-
- if (this.state[keyCode])
- throw new Error(
- `KeyBinding with keyCode "${keyCode}" already exists!`
- )
-
- set(this.state, keyCode, keyBinding)
- return keyBinding
- }
- disposeKeyBinding(keyCode: string) {
- del(this.state, keyCode)
- }
-
- dispose() {
- // @ts-ignore TypeScript isn't smart enough to understand that the type "KeyboardEvent" is correct
- this.element.removeEventListener('keydown', this.onKeydown)
- }
-}
diff --git a/src/components/Actions/SimpleAction.ts b/src/components/Actions/SimpleAction.ts
deleted file mode 100644
index af3e9e169..000000000
--- a/src/components/Actions/SimpleAction.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { IKeyBindingConfig } from './KeyBinding'
-import { EventDispatcher } from '/@/components/Common/Event/EventDispatcher'
-import { v4 as uuid } from 'uuid'
-
-export interface IActionConfig {
- type?: 'action'
- id?: string
- icon?: string
- name?: string
- color?: string
- description?: string
- isDisabled?: (() => boolean) | boolean
- keyBinding?: string | string[]
- prevent?: IKeyBindingConfig['prevent']
- onTrigger: (action: SimpleAction) => Promise | unknown
-}
-
-export class SimpleAction extends EventDispatcher {
- public readonly type = 'action'
- id: string
- protected addPadding = false
-
- constructor(protected config: IActionConfig) {
- super()
- this.id = config.id ?? uuid()
- }
-
- //#region GETTERS
- get name() {
- return this.config.name
- }
- get icon() {
- return this.config.icon
- }
- get description() {
- return this.config.description
- }
- get color() {
- return this.config.color
- }
- get isDisabled() {
- if (typeof this.config.isDisabled === 'function')
- return this.config.isDisabled?.() ?? false
- return this.config.isDisabled ?? false
- }
- //#endregion
-
- getConfig() {
- return this.config
- }
-
- async trigger() {
- if (this.isDisabled) return
- this.dispatch()
- return await this.config.onTrigger(this)
- }
-
- withPadding() {
- const action = new SimpleAction(this.config)
- action.addPadding = true
- return action
- }
-}
diff --git a/src/components/Actions/Utils.ts b/src/components/Actions/Utils.ts
deleted file mode 100644
index bff692300..000000000
--- a/src/components/Actions/Utils.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { platform } from '/@/utils/os'
-import { IKeyBindingConfig } from './KeyBinding'
-
-export function fromStrKeyCode(keyCode: string, forceWindowsCtrl = false) {
- const parts = keyCode.toLowerCase().split(' + ')
- const keyBinding: IKeyBindingConfig = { key: '' }
-
- parts.forEach((p) => {
- if (
- p === '⌘' ||
- ((platform() !== 'darwin' || forceWindowsCtrl) && p === 'ctrl')
- ) {
- keyBinding.ctrlKey = true
- } else if (p === '⌥' || p === 'alt') {
- keyBinding.altKey = true
- } else if (p === '⇧' || p === 'shift') {
- keyBinding.shiftKey = true
- } else if (p === '⌃' || p === 'meta') {
- keyBinding.metaKey = true
- } else {
- keyBinding.key = p
- }
- })
-
- return keyBinding
-}
-
-export function toStrKeyCode({
- key,
- ctrlKey,
- altKey,
- shiftKey,
- metaKey,
-}: IKeyBindingConfig) {
- const p = platform()
-
- let code = key.toUpperCase()
- if (shiftKey) {
- if (p === 'darwin') code = '⇧ + ' + code
- else code = 'Shift + ' + code
- }
- if (altKey) {
- if (p === 'darwin') code = '⌥ + ' + code
- else code = 'Alt + ' + code
- }
- if (metaKey) {
- if (p === 'darwin') code = '⌃ + ' + code
- else code = 'Meta + ' + code
- }
- if (ctrlKey) {
- if (p === 'darwin') code = '⌘ + ' + code
- else code = 'Ctrl + ' + code
- }
-
- return code
-}
diff --git a/src/components/App/Icon/Blockbench.vue b/src/components/App/Icon/Blockbench.vue
deleted file mode 100644
index a7b21eb83..000000000
--- a/src/components/App/Icon/Blockbench.vue
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-
-
-
-
diff --git a/src/components/App/Icon/IconMap.ts b/src/components/App/Icon/IconMap.ts
deleted file mode 100644
index c9cf33f05..000000000
--- a/src/components/App/Icon/IconMap.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import BlockbenchIcon from './Blockbench.vue'
-
-export const iconMap = {
- blockbench: BlockbenchIcon,
-}
diff --git a/src/components/App/Install.ts b/src/components/App/Install.ts
deleted file mode 100644
index ff4721c09..000000000
--- a/src/components/App/Install.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { Signal } from '../Common/Event/Signal'
-import { Notification } from '../Notifications/Notification'
-
-export class InstallApp extends Notification {
- public readonly isInstallable = new Signal()
- public readonly isInstalled = new Signal()
- protected installEvent!: any
-
- constructor() {
- super({
- message: 'sidebar.notifications.installApp.message',
- color: 'primary',
- icon: 'mdi-download',
- textColor: 'white',
- isVisible: false,
- })
-
- window.addEventListener('beforeinstallprompt', (event: any) =>
- this.onInstallPrompt(event)
- )
- window.addEventListener('appinstalled', () => this.dispose())
- this.addClickHandler(() => this.prompt())
- }
-
- onInstallPrompt(event: any) {
- event.preventDefault()
- this.installEvent = event
- this.show()
- this.isInstallable.dispatch()
- }
-
- prompt() {
- if (this.installEvent) {
- this.installEvent.prompt()
-
- this.installEvent.userChoice.then((choice: any) => {
- if (choice.outcome === 'accepted') {
- this.dispose()
- this.isInstalled.dispatch()
- }
- })
- } else {
- this.dispose()
- }
- }
-}
diff --git a/src/components/App/Mobile.ts b/src/components/App/Mobile.ts
deleted file mode 100644
index 92cd8a751..000000000
--- a/src/components/App/Mobile.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { ref, watch } from 'vue'
-import { Framework } from 'vuetify'
-import { EventDispatcher } from '../Common/Event/EventDispatcher'
-import { App } from '/@/App'
-
-export class Mobile {
- public readonly change = new EventDispatcher()
- public readonly is = ref(this.isCurrentDevice())
-
- constructor(protected vuetify: Framework) {
- watch(vuetify.breakpoint, () => {
- this.is.value = this.isCurrentDevice()
- this.change.dispatch(vuetify.breakpoint.mobile)
- })
-
- App.getApp().then(() => {
- setTimeout(
- () => this.change.dispatch(vuetify.breakpoint.mobile),
- 10
- )
- })
- }
-
- isCurrentDevice() {
- return this.vuetify?.breakpoint?.mobile
- }
-}
diff --git a/src/components/App/Tauri/TauriUpdater.ts b/src/components/App/Tauri/TauriUpdater.ts
deleted file mode 100644
index 50ce3b002..000000000
--- a/src/components/App/Tauri/TauriUpdater.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { createNotification } from '../../Notifications/create'
-import { checkUpdate, installUpdate } from '@tauri-apps/api/updater'
-import { relaunch } from '@tauri-apps/api/process'
-import { App } from '/@/App'
-import { openUpdateWindow } from '../../Windows/Update/UpdateWindow'
-
-async function installTauriUpdate() {
- const app = await App.getApp()
-
- // Task to indicate background progress
- const task = app.taskManager.create({
- icon: 'mdi-update',
- name: 'sidebar.notifications.installingUpdate.name',
- description: 'sidebar.notifications.installingUpdate.description',
-
- indeterminate: true,
- })
-
- // Install the update
- await installUpdate()
- // Dispose task
- task.complete()
-
- // Create a notification to indicate that the app needs to be restarted
- createNotification({
- icon: 'mdi-update',
- color: 'primary',
- message: 'sidebar.notifications.restartToApplyUpdate.message',
- textColor: 'white',
- onClick: async () => {
- // ...and finally relaunch the app
- await relaunch()
- },
- })
-}
-
-checkUpdate()
- .then(async (update) => {
- if (!update.shouldUpdate) return
-
- const notification = createNotification({
- icon: 'mdi-update',
- color: 'primary',
- message: 'sidebar.notifications.updateAvailable.message',
- textColor: 'white',
- onClick: async () => {
- // Dispose the notification
- notification.dispose()
-
- // Install the update
- await installTauriUpdate()
- },
- })
-
- openUpdateWindow({
- // Version should always be defined because we're checking update.shouldUpdate before
- version: update.manifest?.version ?? '2.x',
- onClick: () => {
- // Dispose the notification
- notification.dispose()
-
- // Install the update
- installTauriUpdate()
- },
- })
- })
- .catch((err: any) => {
- console.error(`[TauriUpdater] ${err}`)
-
- return null
- })
diff --git a/src/components/App/Vue.ts b/src/components/App/Vue.ts
deleted file mode 100644
index 6f01f5aa2..000000000
--- a/src/components/App/Vue.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import Vue from 'vue'
-import Vuetify from 'vuetify'
-import { LocaleManager } from '../Locales/Manager'
-import { vuetify } from './Vuetify'
-import AppComponent from '/@/App.vue'
-
-Vue.use(Vuetify)
-Vue.config.productionTip = false
-
-export const vue = new Vue({
- vuetify,
- render: (h) => h(AppComponent),
-})
-
-LocaleManager.setDefaultLanguage().then(() => {
- vue.$mount('#app')
-})
diff --git a/src/components/App/Vuetify.ts b/src/components/App/Vuetify.ts
deleted file mode 100644
index eb0cfbc4f..000000000
--- a/src/components/App/Vuetify.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import Vuetify from 'vuetify'
-import { iconMap } from './Icon/IconMap'
-
-export const vuetify = new Vuetify({
- breakpoint: {
- mobileBreakpoint: 'xs',
- },
- icons: {
- iconfont: 'mdi',
- values: Object.fromEntries(
- Object.entries(iconMap).map(([name, icon]) => [
- name,
- { component: icon },
- ])
- ),
- },
- theme: {
- options: {
- customProperties: true,
- variations: false,
- },
- },
-})
diff --git a/src/components/BedrockWorlds/BlockLibrary/BlockLibrary.ts b/src/components/BedrockWorlds/BlockLibrary/BlockLibrary.ts
deleted file mode 100644
index 4c7ed6cb0..000000000
--- a/src/components/BedrockWorlds/BlockLibrary/BlockLibrary.ts
+++ /dev/null
@@ -1,198 +0,0 @@
-import { AnyDirectoryHandle } from '../../FileSystem/Types'
-import { FileSystem } from '../../FileSystem/FileSystem'
-import { DataLoader } from '../../Data/DataLoader'
-import { loadImage } from './loadImage'
-import { toBlob } from '/@/utils/canvasToBlob'
-
-export type TDirection =
- | 'up'
- | 'down'
- | 'north'
- | 'west'
- | 'east'
- | 'south'
- | 'side'
- | 'all'
-
-interface IBlockLibEntry {
- faces: {
- [key in TDirection]?: {
- uvOffset?: [number, number]
- texturePath: string
- overlayColor?: [number, number, number]
- }
- }
-}
-
-export class BlockLibrary {
- protected fileSystem: FileSystem
- protected dataLoader: DataLoader
- protected _missingTexture?: ImageBitmap
- protected _tileMap?: HTMLCanvasElement
-
- get missingTexture() {
- if (!this._missingTexture)
- throw new Error(
- `Trying to access missingTexture before BlockLibrary was setup`
- )
- return this._missingTexture
- }
- get tileMap() {
- if (!this._tileMap)
- throw new Error('Trying to access tileMap before it was created')
- return this._tileMap
- }
-
- protected library = new Map()
-
- constructor(baseDirectory: AnyDirectoryHandle) {
- this.fileSystem = new FileSystem(baseDirectory)
- this.dataLoader = new DataLoader()
- }
-
- async setup() {
- if (!this.dataLoader.hasFired) await this.dataLoader.loadData()
-
- const file = await this.dataLoader.readFile(
- 'data/packages/minecraftBedrock/vanilla/missing_tile.png'
- )
-
- this._missingTexture = await createImageBitmap(
- file.isVirtual ? await file.toBlobFile() : file
- )
-
- const blocksJson = Object.assign(
- await this.dataLoader.readJSON(
- 'data/packages/minecraftBedrock/vanilla/blocks.json'
- ),
- await this.fileSystem.readJSON('RP/blocks.json')
- )
- const terrainTexture = Object.assign(
- (
- await this.dataLoader.readJSON(
- 'data/packages/minecraftBedrock/vanilla/terrain_texture.json'
- )
- ).texture_data,
- (await this.fileSystem.readJSON('RP/textures/terrain_texture.json'))
- .texture_data
- )
- console.log(terrainTexture)
-
- for (let id in blocksJson) {
- const { textures } = blocksJson[id]
-
- // Normalize identifiers to make sure they include a namespace
- if (id.indexOf(':') === -1) id = `minecraft:${id}`
-
- if (typeof textures === 'string')
- this.library.set(id, {
- faces: {
- all: {
- texturePath: `RP/${this.chooseTexture(
- terrainTexture[textures]
- )}`,
- },
- },
- })
- else if (typeof textures === 'object') {
- const entry: IBlockLibEntry = { faces: {} }
-
- for (let direction in textures) {
- const textureLookup = textures[direction]
-
- entry.faces[direction as TDirection] = {
- texturePath: `RP/${this.chooseTexture(
- terrainTexture[textureLookup]
- )}`,
- }
- }
-
- this.library.set(id, entry)
- }
- }
-
- console.log(this.library)
-
- return await this.createTileMap()
- }
-
- protected chooseTexture(textureData: {
- textures:
- | string
- | (
- | string
- | {
- path?: string
- variations?: { path: string; weight: number }[]
- }
- )[]
- | { path?: string; variations?: { path: string; weight: number }[] }
- }) {
- let { textures } = textureData
-
- if (Array.isArray(textures)) textures = textures[0]
-
- if (typeof textures === 'string') return textures
- else if (typeof textures === 'object')
- return textures.variations?.[0]?.path ?? textures.path
- }
-
- protected async createTileMap() {
- const canvas = document.createElement('canvas')
- const rowLength = Math.ceil(Math.sqrt(this.library.size * 8))
- canvas.width = rowLength * 16
- canvas.height = canvas.width
- const context = canvas.getContext('2d')
- if (!context) throw new Error(`Failed to initialize canvas 2d context`)
-
- context.imageSmoothingEnabled = false
-
- let currentUVOffset = 0
- for (const blockData of this.library.values()) {
- for (const [dir, { texturePath, overlayColor }] of Object.entries(
- blockData.faces
- )) {
- const x = currentUVOffset % rowLength
- const y = Math.floor(currentUVOffset / rowLength)
- const image =
- (await loadImage(this.fileSystem, texturePath)) ??
- this.missingTexture
- context.drawImage(image, x * 16, y * 16, 16, 16)
-
- blockData.faces[dir as TDirection] = {
- ...blockData.faces[dir as TDirection],
- uvOffset: [x, y],
- texturePath:
- blockData.faces[dir as TDirection]?.texturePath!,
- }
- currentUVOffset++
- }
- }
-
- await this.fileSystem.writeFile(
- '.bridge/bedrockWorld/uvMap.png',
- await toBlob(canvas)
- )
-
- this._tileMap = canvas
-
- return canvas
- }
-
- getTileMapSize() {
- return [this.tileMap.width, this.tileMap.height]
- }
- getVoxelUv(identifier: string, faces: TDirection[]) {
- const entry = this.library.get(identifier)
- if (!entry) return [0, 0]
-
- for (let face of faces) {
- if (entry.faces[face] !== undefined)
- return entry.faces[face]!.uvOffset!
- }
-
- if (entry.faces.all) return entry.faces.all.uvOffset!
-
- return [0, 0]
- }
-}
diff --git a/src/components/BedrockWorlds/BlockLibrary/loadImage.ts b/src/components/BedrockWorlds/BlockLibrary/loadImage.ts
deleted file mode 100644
index ccbb2aa8c..000000000
--- a/src/components/BedrockWorlds/BlockLibrary/loadImage.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { findFileExtension } from '/@/components/FileSystem/FindFile'
-import { FileSystem } from '/@/components/FileSystem/FileSystem'
-
-export async function loadImage(fileSystem: FileSystem, filePath: string) {
- // TODO: Support .tga files
- const realPath = await findFileExtension(fileSystem, filePath, [
- '.png',
- '.jpg',
- '.jpeg',
- ])
-
- if (!realPath) return null
-
- const file = await fileSystem.readFile(realPath)
-
- return await createImageBitmap(
- file.isVirtual ? await file.toBlobFile() : file
- )
-}
diff --git a/src/components/BedrockWorlds/LevelDB/Comparators/Bytewise.ts b/src/components/BedrockWorlds/LevelDB/Comparators/Bytewise.ts
deleted file mode 100644
index facb865b4..000000000
--- a/src/components/BedrockWorlds/LevelDB/Comparators/Bytewise.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-export class BytewiseComparator {
- constructor() {}
-
- public compare(a: Uint8Array, b: Uint8Array): number {
- if (a.length === b.length) {
- return this.compareFixedLength(a, b)
- } else {
- const minLength = Math.min(a.length, b.length)
- const res = this.compareFixedLength(
- a.slice(0, minLength),
- b.slice(0, minLength)
- )
-
- if (res !== 0) return res
-
- return a.length - b.length > 0 ? 1 : -1
- }
- }
-
- /**
- * Assumption: a and b are of equal length
- */
- protected compareFixedLength(a: Uint8Array, b: Uint8Array) {
- for (let i = 0; i < a.length; i++) {
- if (a[i] !== b[i]) {
- const res = a[i] - b[i]
-
- return res > 0 ? 1 : -1
- }
- }
-
- return 0
- }
-}
diff --git a/src/components/BedrockWorlds/LevelDB/FileMetaData.ts b/src/components/BedrockWorlds/LevelDB/FileMetaData.ts
deleted file mode 100644
index e62532541..000000000
--- a/src/components/BedrockWorlds/LevelDB/FileMetaData.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Table } from './Table/Table'
-
-export interface IFileMetaData {
- fileNumber: number
- fileSize: number
- smallestKey: Uint8Array
- largestKey: Uint8Array
-}
-export class FileMetaData {
- public fileNumber: number
- public fileSize: number
- public smallestKey: Uint8Array
- public largestKey: Uint8Array
- public table?: Table
-
- constructor({
- fileNumber,
- fileSize,
- smallestKey,
- largestKey,
- }: IFileMetaData) {
- this.fileNumber = fileNumber
- this.fileSize = fileSize
- this.smallestKey = smallestKey
- this.largestKey = largestKey
- }
-}
diff --git a/src/components/BedrockWorlds/LevelDB/Key/AsUsableKey.ts b/src/components/BedrockWorlds/LevelDB/Key/AsUsableKey.ts
deleted file mode 100644
index 2cc789b73..000000000
--- a/src/components/BedrockWorlds/LevelDB/Key/AsUsableKey.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export function asUsableKey(key: Uint8Array) {
- return key.slice(0, key.length - 8)
-}
diff --git a/src/components/BedrockWorlds/LevelDB/Key/GetKeyType.ts b/src/components/BedrockWorlds/LevelDB/Key/GetKeyType.ts
deleted file mode 100644
index 9a86e7993..000000000
--- a/src/components/BedrockWorlds/LevelDB/Key/GetKeyType.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export function getKeyType(key: Uint8Array) {
- return key.slice(key.length - 8, key.length - 7)[0]
-}
diff --git a/src/components/BedrockWorlds/LevelDB/LevelDB.ts b/src/components/BedrockWorlds/LevelDB/LevelDB.ts
deleted file mode 100644
index 98c0aac6f..000000000
--- a/src/components/BedrockWorlds/LevelDB/LevelDB.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { AnyDirectoryHandle } from '/@/components/FileSystem/Types'
-import { Manifest } from './Manifest'
-import { LogReader } from './LogReader'
-import { MemoryCache } from './MemoryCache'
-import { ERequestState } from './RequestStatus'
-
-export class LevelDB {
- protected _manifest?: Manifest
- protected _memoryCache?: MemoryCache
-
- constructor(protected dbDirectory: AnyDirectoryHandle) {}
-
- get manifest() {
- if (!this._manifest)
- throw new Error(`DB manifest not defined yet; did you open the DB?`)
- return this._manifest
- }
- get memoryCache() {
- if (!this._memoryCache)
- throw new Error(
- `DB memory cache not defined yet; did you open the DB?`
- )
- return this._memoryCache
- }
-
- async open() {
- this._manifest = new Manifest(this.dbDirectory)
- console.log(this.manifest)
- await this.manifest.load()
-
- const logReader = new LogReader()
- await logReader.readLogFile(
- await this.dbDirectory.getFileHandle(
- `${this.manifest.version.logNumber
- ?.toString()
- .padStart(6, '0')}.log`
- )
- )
- this._memoryCache = new MemoryCache()
- this.memoryCache.load(logReader)
- }
-
- get(key: Uint8Array) {
- let res = this.memoryCache.get(key)
- if (res.state === ERequestState.Success) return res.value!
- else if (res.state === ERequestState.Deleted) return null
-
- res = this.manifest.get(key)
- if (res.value === undefined || res.value.length === 0) return null
-
- return res.value
- }
- keys() {
- return [...this.memoryCache.keys(), ...this.manifest.keys()]
- }
-}
diff --git a/src/components/BedrockWorlds/LevelDB/LogReader.ts b/src/components/BedrockWorlds/LevelDB/LogReader.ts
deleted file mode 100644
index b2dd86fe2..000000000
--- a/src/components/BedrockWorlds/LevelDB/LogReader.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import { AnyFileHandle } from '../../FileSystem/Types'
-import { ELogRecordType, Record, UndefinedRecord } from './Record'
-
-export enum EOperationType {
- Delete = 0,
- PUT = 1,
-}
-
-export class LogReader {
- protected blockSize = 32768 // Size of a single block (32 * 1024 Bytes)
- protected headerSize = 4 + 2 + 1
- protected position: number = 0
- protected lastData: Uint8Array | null = null
- protected _logFileData?: Uint8Array
-
- constructor() {}
-
- get logFileData() {
- if (!this._logFileData)
- throw new Error(
- 'Trying to access logFileData before file was loaded'
- )
- return this._logFileData
- }
-
- async readLogFile(fileHandle: AnyFileHandle) {
- const file = await fileHandle.getFile()
- this._logFileData = new Uint8Array(await file.arrayBuffer())
-
- this.position = 0
- }
-
- readData(logFileData: Uint8Array = this.logFileData) {
- let lastRecord = new UndefinedRecord()
-
- while (this.position < logFileData.length) {
- while (true) {
- let record = this.readNextRecord(logFileData)
-
- if (record.type === ELogRecordType.InvalidRecord) {
- break
- } else if (record.type === ELogRecordType.First) {
- lastRecord = record
- continue
- } else if (record.type === ELogRecordType.Zero) {
- // Ignore other record types
- continue
- }
-
- if (
- lastRecord.type !== ELogRecordType.Undefined &&
- (record.type === ELogRecordType.Middle ||
- record.type == ELogRecordType.Last)
- ) {
- lastRecord.length! += record.length!
- var lastData = lastRecord.data!
- lastRecord.data = new Uint8Array(
- lastData.length + record.data!.length
- )
- lastRecord.data.set(lastData)
- lastRecord.data.set(record.data!, lastData.length)
-
- if (record.type == ELogRecordType.Middle) {
- continue
- }
-
- record = lastRecord
- record.type = ELogRecordType.Full
- }
-
- if (record.type !== ELogRecordType.Full) {
- console.warn(`Read unhandled record of type ${record.type}`)
- continue
- }
-
- return record.data!
- }
- }
-
- return null
- }
-
- protected readNextRecord(logFileData: Uint8Array) {
- // Blocks may be padded if size left is less than the header
- const sizeLeft = this.blockSize - (this.position % this.blockSize)
- // if (sizeLeft < 7) stream.Seek(sizeLeft, SeekOrigin.Current);
-
- // Header is checksum (4 bytes), length (2 bytes), type (1 byte).
- const header = this.consumeBytes(logFileData, this.headerSize)
- if (header.length !== this.headerSize)
- return new Record({ type: ELogRecordType.InvalidRecord })
-
- const expectedCrc =
- header[0] + (header[1] << 8) + (header[2] << 16) + (header[3] << 24)
- const length = header[4] + (header[5] << 8)
- const type = header[6]
-
- if (length > logFileData.length)
- throw new Error('Not enough data in stream to read')
-
- const data = this.consumeBytes(logFileData, length)
-
- // TODO: Calculate CRC
-
- const record = new Record({
- checksum: expectedCrc,
- length,
- type,
- data,
- })
-
- // TODO: Check CRC
-
- return record
- }
-
- protected consumeBytes(logFileData: Uint8Array, byteCount: number) {
- const consumed = logFileData.slice(
- this.position,
- this.position + byteCount
- )
- this.position += byteCount
- return consumed
- }
-}
diff --git a/src/components/BedrockWorlds/LevelDB/Manifest.ts b/src/components/BedrockWorlds/LevelDB/Manifest.ts
deleted file mode 100644
index 9a7472b5f..000000000
--- a/src/components/BedrockWorlds/LevelDB/Manifest.ts
+++ /dev/null
@@ -1,218 +0,0 @@
-import { AnyDirectoryHandle, AnyFileHandle } from '../../FileSystem/Types'
-import { BytewiseComparator } from './Comparators/Bytewise'
-import { FileMetaData } from './FileMetaData'
-import { asUsableKey } from './Key/AsUsableKey'
-import { LogReader } from './LogReader'
-import { ERequestState, RequestStatus } from './RequestStatus'
-import { Table } from './Table/Table'
-import { Uint8ArrayReader } from './Uint8ArrayUtils/Reader'
-import { Version } from './Version'
-
-enum ELogTagType {
- Comparator = 1,
- LogNumber = 2,
- NextFileNumber = 3,
- LastSequence = 4,
- CompactPointer = 5,
- DeletedFile = 6,
- NewFile = 7,
- PrevLogNumber = 9,
-}
-
-const defaultLdbOperator = 'leveldb.BytewiseComparator'
-
-export class Manifest {
- protected logReader = new LogReader()
- protected _version?: Version
- protected comparator = new BytewiseComparator()
-
- get version() {
- if (!this._version)
- throw new Error(
- 'Trying to access version before loading the manifest was done'
- )
-
- return this._version
- }
-
- constructor(protected dbDirectory: AnyDirectoryHandle) {}
-
- async load() {
- const currentManifest = await this.dbDirectory
- .getFileHandle('CURRENT')
- .then((fileHandle) => fileHandle.getFile())
- .then((file) => file.text())
- const manifestData = await this.dbDirectory
- .getFileHandle(currentManifest.trim())
- .then((fileHandle) => fileHandle.getFile())
- .then((file) => file.arrayBuffer())
- .then((data) => new Uint8Array(data))
-
- this._version = this.readVersionEdit(manifestData)
-
- // For each file in every level, create a Table that represents the backing file
- for (const files of this.version.levels.values()) {
- for (const file of files) {
- file.table = await this.createTable(file.fileNumber)
- }
- }
-
- if (defaultLdbOperator !== this.version.comparator)
- throw new Error(
- `Unsupported comparator: "${this.version.comparator}"`
- )
-
- console.log(this._version)
- }
-
- get(key: Uint8Array) {
- for (const level of this.version.levels.values()) {
- for (const file of level) {
- const smallestKey = asUsableKey(file.smallestKey)
- const largestKey = asUsableKey(file.largestKey)
-
- if (
- this.comparator.compare(smallestKey, key) < 0 &&
- this.comparator.compare(largestKey, key) > 0
- ) {
- const req = file.table!.get(key)
-
- if (
- req.state === ERequestState.Success ||
- req.state === ERequestState.Deleted
- )
- return req
- }
- }
- }
-
- return RequestStatus.createNotFound()
- }
- keys() {
- const keys: Uint8Array[] = []
-
- this.forEachFile((file) => {
- keys.push(...file.table!.keys())
- })
-
- return keys
- }
-
- protected forEachFile(cb: (file: FileMetaData) => void) {
- for (const level of this.version.levels.values()) {
- for (const file of level) {
- cb(file)
- }
- }
- }
-
- protected readVersionEdit(manifestData: Uint8Array) {
- const version = new Version()
-
- while (true) {
- const data = this.logReader.readData(manifestData)
-
- if (data === null) break
-
- const reader = new Uint8ArrayReader(data)
-
- while (reader.hasUnreadBytes) {
- const logTag = reader.readVarLong()
-
- switch (logTag) {
- case ELogTagType.Comparator:
- version.comparator = reader.readLengthPrefixedString()
- break
- case ELogTagType.LogNumber:
- version.logNumber = reader.readVarLong()
- break
- case ELogTagType.NextFileNumber:
- version.nextFileNumber = reader.readVarLong()
- break
- case ELogTagType.LastSequence:
- version.lastSequence = reader.readVarLong()
- break
- case ELogTagType.CompactPointer: {
- version.compactPointers.set(
- reader.readVarLong(),
- reader.readLengthPrefixedBytes()
- )
- break
- }
- case ELogTagType.DeletedFile: {
- const level = reader.readVarLong()
- const fileNumber = reader.readVarLong()
-
- if (!version.deletedFiles.has(level))
- version.deletedFiles.set(level, new Set())
-
- version.deletedFiles.get(level)!.add(fileNumber)
- break
- }
- case ELogTagType.NewFile: {
- const level = reader.readVarLong()
- const fileNumber = reader.readVarLong()
- const fileSize = reader.readVarLong()
- const smallestKey = reader.readLengthPrefixedBytes()
- const largestKey = reader.readLengthPrefixedBytes()
-
- const fileMetaData = new FileMetaData({
- fileNumber,
- fileSize,
- smallestKey,
- largestKey,
- })
-
- if (!version.levels.has(level))
- version.levels.set(level, [])
-
- version.levels.get(level)!.push(fileMetaData)
- break
- }
- case ELogTagType.PrevLogNumber:
- version.previousLogNumber = reader.readVarLong()
- break
- default:
- throw new Error('Unknown log tag: ' + logTag)
- }
- }
- }
-
- // Cleanup deleted files
- const deletedFiles = new Set()
- for (const deletedFile of version.deletedFiles.values()) {
- for (const fileNumber of deletedFile) {
- deletedFiles.add(fileNumber)
- }
- }
-
- for (const [levelKey, fileMetaData] of version.levels.entries()) {
- version.levels.set(
- levelKey,
- fileMetaData.filter(
- (fileMetaData) => !deletedFiles.has(fileMetaData.fileNumber)
- )
- )
- }
-
- if (!version.comparator) version.comparator = defaultLdbOperator
-
- return version
- }
-
- protected async createTable(fileNumber: number) {
- const fileName = `${fileNumber.toString().padStart(6, '0')}.ldb`
-
- let fileHandle: AnyFileHandle
- try {
- fileHandle = await this.dbDirectory.getFileHandle(fileName)
- } catch {
- throw new Error(`File ${fileName} not found`)
- }
-
- const table = new Table(fileHandle)
- await table.load()
-
- return table
- }
-}
diff --git a/src/components/BedrockWorlds/LevelDB/MemoryCache.ts b/src/components/BedrockWorlds/LevelDB/MemoryCache.ts
deleted file mode 100644
index b701c6145..000000000
--- a/src/components/BedrockWorlds/LevelDB/MemoryCache.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import { BytewiseComparator } from './Comparators/Bytewise'
-import { EOperationType, LogReader } from './LogReader'
-import { ERequestState, RequestStatus } from './RequestStatus'
-import { Uint8ArrayReader } from './Uint8ArrayUtils/Reader'
-
-interface ILogEntry {
- sequenceNumber: bigint
- state: ERequestState
- data?: Uint8Array
-}
-
-export class MemoryCache {
- protected comparator = new BytewiseComparator()
- protected cache: Map = new Map()
- protected size = 0
-
- load(logReader: LogReader) {
- this.cache = new Map()
-
- let data: Uint8Array | null
- while (true) {
- data = logReader.readData()
- if (data === null) break
-
- const logEntries = this.decodeData(new Uint8ArrayReader(data))
- for (const [key, value] of logEntries) {
- this.cache.set(key, value)
- this.size += key.length + (value.data?.length ?? 0)
- }
- }
- }
-
- protected decodeData(data: Uint8ArrayReader) {
- const sequenceNumber = data.readUint64()
- const totalOperations = data.readUint32()
-
- const res: [Uint8Array, ILogEntry][] = []
-
- for (let i = 0; i < totalOperations; i++) {
- const operation = data.readByte()
- const key = data.readLengthPrefixedBytes()
-
- if (operation === EOperationType.PUT) {
- const value = data.readLengthPrefixedBytes()
-
- res.push([
- key,
- {
- sequenceNumber,
- state: ERequestState.Success,
- data: value,
- },
- ])
- } else if (operation === EOperationType.Delete) {
- res.push([
- key,
- {
- sequenceNumber,
- state: ERequestState.Deleted,
- },
- ])
- } else {
- throw new Error('Unknown operation type')
- }
- }
- return res
- }
-
- get(key: Uint8Array) {
- const entry = this.cache.get(key)
- if (entry === undefined) {
- return RequestStatus.createNotFound()
- }
- return new RequestStatus(entry.data)
- }
- keys() {
- return this.cache.keys()
- }
-}
diff --git a/src/components/BedrockWorlds/LevelDB/Record.ts b/src/components/BedrockWorlds/LevelDB/Record.ts
deleted file mode 100644
index ae4d7ae91..000000000
--- a/src/components/BedrockWorlds/LevelDB/Record.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-export enum ELogRecordType {
- // Zero is reserved for preallocated files
- Zero = 0,
-
- Full = 1,
-
- // Data split across multiple records
- First = 2,
- Middle = 3,
- Last = 4,
-
- // Util
- InvalidRecord = Last + 1,
- Undefined = Last + 1,
-}
-
-interface IRecord {
- type: ELogRecordType
- data?: Uint8Array
- length?: number
- checksum?: number
-}
-export class Record {
- public type: ELogRecordType
- public checksum?: number
- public length?: number
- public data?: Uint8Array
-
- constructor({ type, checksum, data, length }: IRecord) {
- this.type = type
- this.checksum = checksum
- this.data = data
- this.length = length
- }
-}
-
-export class UndefinedRecord extends Record {
- constructor() {
- super({ type: ELogRecordType.Undefined })
- }
-}
diff --git a/src/components/BedrockWorlds/LevelDB/RequestStatus.ts b/src/components/BedrockWorlds/LevelDB/RequestStatus.ts
deleted file mode 100644
index 0e3971362..000000000
--- a/src/components/BedrockWorlds/LevelDB/RequestStatus.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-export enum ERequestState {
- Success,
- Deleted,
- NotFound,
- Undefined,
-}
-
-export class RequestStatus {
- constructor(public value?: T, public state = ERequestState.Success) {}
-
- static createNotFound() {
- return new RequestStatus(undefined, ERequestState.NotFound)
- }
- static createDeleted() {
- return new RequestStatus(undefined, ERequestState.Deleted)
- }
-}
diff --git a/src/components/BedrockWorlds/LevelDB/Table/BlockHandle.ts b/src/components/BedrockWorlds/LevelDB/Table/BlockHandle.ts
deleted file mode 100644
index ffe95471d..000000000
--- a/src/components/BedrockWorlds/LevelDB/Table/BlockHandle.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import { ESeekType, Uint8ArrayReader } from '../Uint8ArrayUtils/Reader'
-import { unzlibSync, inflateSync } from 'fflate'
-
-enum ECompressionTypes {
- Uncompressed = 0,
- Snappy = 1,
- Zlib = 2,
-}
-
-export class BlockHandle {
- constructor(protected offset: number, protected length: number) {}
-
- static readBlockHandle(reader: Uint8ArrayReader) {
- const offset = reader.readVarLong()
- const length = reader.readVarLong()
-
- return new BlockHandle(offset, length)
- }
-
- getOffset() {
- return this.offset
- }
- getLength() {
- return this.length
- }
-
- encode() {
- // TODO
- }
-
- readBlock(
- reader: Uint8ArrayReader,
- length = this.length,
- verifyChecksum = false
- ) {
- /**
- * A block looks like this:
- *
- * block :=
- * block_data: uint8[]
- * type: uint8
- * checksum: uint32
- */
-
- reader.seek(this.offset, ESeekType.Start)
- let data = reader.read(length)
-
- const compressionType = reader.readByte()
- const checksum = reader.read(4)
-
- // TODO: Verify checksum
-
- switch (compressionType) {
- case ECompressionTypes.Snappy:
- throw new Error('Snappy compression not implemented')
- // Do nothing
- case ECompressionTypes.Uncompressed:
- break
- default: {
- if (compressionType === ECompressionTypes.Zlib) {
- if (data[0] !== 0x78) {
- throw new Error('Invalid zlib header')
- }
-
- data = data.slice(2)
- }
-
- data = inflateSync(data)
- }
- }
-
- return data
- }
-}
diff --git a/src/components/BedrockWorlds/LevelDB/Table/BlockSeeker.ts b/src/components/BedrockWorlds/LevelDB/Table/BlockSeeker.ts
deleted file mode 100644
index 1316aa1f7..000000000
--- a/src/components/BedrockWorlds/LevelDB/Table/BlockSeeker.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-import { BytewiseComparator } from '../Comparators/Bytewise'
-import { asUsableKey } from '../Key/AsUsableKey'
-import { ESeekType, Uint8ArrayReader } from '../Uint8ArrayUtils/Reader'
-import { BlockHandle } from './BlockHandle'
-
-export class BlockSeeker {
- protected comparator = new BytewiseComparator()
- protected reader: Uint8ArrayReader
- protected restartCount = 0 // Amount of restart points
- protected restartOffset = 0 // Current restart point
- protected currentKey: Uint8Array | null = null
- protected currentValue: BlockHandle | null = null
-
- constructor(protected blockData: Uint8Array) {
- this.reader = new Uint8ArrayReader(blockData)
- this.reader.seek(-4, ESeekType.End)
- this.restartCount = this.reader.readUint32()
- this.reader.seek(-((1 + this.restartCount) * 4), ESeekType.End)
- this.restartOffset = this.reader.getPosition()
-
- this.reader.seek(0, ESeekType.Start)
- }
-
- getCurrentKey() {
- return this.currentKey
- }
- getCurrentValue() {
- this.reader.seek(this.currentValue!.getOffset(), ESeekType.Start)
- return this.reader.read(this.currentValue!.getLength())
- }
-
- binarySearchKey(key: Uint8Array) {
- if (this.restartCount === 0) return false
-
- let low = 0
- let high = this.restartCount - 1
-
- while (low < high) {
- const mid = (low + high + 1) >> 1
- this.seekToRestart(mid)
-
- if (
- this.comparator.compare(asUsableKey(this.currentKey!), key) < 0
- ) {
- low = mid
- } else {
- high = mid - 1
- }
- }
-
- this.seekToRestart(low)
-
- while (this.hasNext()) {
- const usableKey = asUsableKey(this.currentKey!)
-
- if (this.comparator.compare(usableKey, key) >= 0) {
- return true
- }
- this.next()
- }
-
- for (let i = low - 1; i >= 0; i--) {
- this.seekToRestart(i)
-
- while (this.hasNext()) {
- const usableKey = asUsableKey(this.currentKey!)
-
- if (this.comparator.compare(usableKey, key) >= 0) {
- return true
- }
- this.next()
- }
- }
-
- return false
- }
- keys() {
- let keys: Uint8Array[] = []
- this.seekToRestart(0)
-
- while (this.hasNext()) {
- keys.push(asUsableKey(this.currentKey!))
- this.next()
- }
-
- return keys
- }
-
- hasNext() {
- return this.currentKey !== null && this.currentKey.length !== 0
- }
- next() {
- if (!this.hasNext()) return false
-
- return this.parseCurrentIndex()
- }
-
- protected getRestartOffset(index: number) {
- if (index < 0) throw new Error('Index must be greater than 0')
- if (index >= this.restartCount)
- throw new Error('Index must be less than restart count')
-
- const reader = new Uint8ArrayReader(this.blockData)
- reader.seek(this.restartOffset + index * 4, ESeekType.Start)
- return reader.readUint32()
- }
- protected seekToRestart(index: number) {
- const offset = this.getRestartOffset(index)
- this.reader.unsafelySetPosition(offset)
- this.currentKey = null
- this.parseCurrentIndex()
- }
-
- protected parseCurrentIndex() {
- if (this.reader.getPosition() >= this.reader.getLength()) {
- this.currentKey = null
- return false
- }
-
- /**
- * Read key-val pair
- *
- * index :=
- * shared = varint32;
- * non_shared = varint32;
- * value_length = varint32;
- * key_delta = char[non_shared];
- */
- const shared = this.reader.readVarLong()
- if (this.currentKey === null && shared !== 0)
- throw new Error(`Shared bytes without a currentKey`)
-
- const nonShared = this.reader.readVarLong()
- const valueLength = this.reader.readVarLong()
- const keyDelta = this.reader.read(nonShared)
-
- const combinedKey = new Uint8Array(shared + nonShared)
- if (shared !== 0) combinedKey.set(this.currentKey!.slice(0, shared), 0)
- combinedKey.set(keyDelta.slice(0, nonShared), shared)
- this.currentKey = combinedKey
-
- this.currentValue = new BlockHandle(
- this.reader.getPosition(),
- valueLength
- )
- this.reader.seek(valueLength, ESeekType.Current)
- return true
- }
-}
diff --git a/src/components/BedrockWorlds/LevelDB/Table/Footer.ts b/src/components/BedrockWorlds/LevelDB/Table/Footer.ts
deleted file mode 100644
index d292f5ff3..000000000
--- a/src/components/BedrockWorlds/LevelDB/Table/Footer.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { equals } from '../Uint8ArrayUtils/Equals'
-import { ESeekType, Uint8ArrayReader } from '../Uint8ArrayUtils/Reader'
-import { BlockHandle } from './BlockHandle'
-
-export class TableFooter {
- protected static magicNumber: Uint8Array = new Uint8Array([
- 0x57,
- 0xfb,
- 0x80,
- 0x8b,
- 0x24,
- 0x75,
- 0x47,
- 0xdb,
- ])
- /**
- * Table footer contains padding to always reach 48 bytes
- *
- * 20 = max length of block handle
- * 8 = magic byte sequence length
- *
- * 48 = (20 * 2) + 8
- */
- protected static footerLength = 48
-
- constructor(
- public metaIndexBlockHandle: BlockHandle,
- public dataIndexBlockHandle: BlockHandle
- ) {}
-
- static read(reader: Uint8ArrayReader) {
- reader.seek(-this.footerLength, ESeekType.End)
- const footerReader = new Uint8ArrayReader(
- reader.read(this.footerLength)
- )
-
- const metaIndexBlockHandle = BlockHandle.readBlockHandle(footerReader)
- const dataIndexBlockHandle = BlockHandle.readBlockHandle(footerReader)
-
- footerReader.seek(-this.magicNumber.length, ESeekType.End)
- const magicNumber = footerReader.read(this.magicNumber.length)
-
- if (!equals(magicNumber, this.magicNumber)) {
- throw new Error('Invalid magic number')
- }
-
- return new TableFooter(metaIndexBlockHandle, dataIndexBlockHandle)
- }
-}
diff --git a/src/components/BedrockWorlds/LevelDB/Table/Table.ts b/src/components/BedrockWorlds/LevelDB/Table/Table.ts
deleted file mode 100644
index ccd47e1de..000000000
--- a/src/components/BedrockWorlds/LevelDB/Table/Table.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-import { AnyFileHandle } from '../../../FileSystem/Types'
-import { BytewiseComparator } from '../Comparators/Bytewise'
-import { asUsableKey } from '../Key/AsUsableKey'
-import { getKeyType } from '../Key/GetKeyType'
-import { RequestStatus } from '../RequestStatus'
-import { Uint8ArrayReader } from '../Uint8ArrayUtils/Reader'
-import { BlockHandle } from './BlockHandle'
-import { BlockSeeker } from './BlockSeeker'
-import { TableFooter } from './Footer'
-
-enum EKeyType {
- Deleted = 0,
- Exists = 1,
-}
-
-export class Table {
- protected blockIndex: Uint8Array | null = null
- protected metaIndex: Uint8Array | null = null
- protected cache = new Map()
- protected reader!: Uint8ArrayReader
- comparator = new BytewiseComparator()
-
- constructor(protected fileHandle: AnyFileHandle) {}
-
- async load() {
- const fileData = await this.fileHandle
- .getFile()
- .then((file) => file.arrayBuffer())
- .then((buffer) => new Uint8Array(buffer))
- this.reader = new Uint8ArrayReader(fileData)
-
- if (this.blockIndex === null || this.metaIndex === null) {
- const footer = TableFooter.read(this.reader)
-
- this.metaIndex = footer.metaIndexBlockHandle.readBlock(this.reader)
- this.blockIndex = footer.dataIndexBlockHandle.readBlock(this.reader)
- }
- }
-
- get(key: Uint8Array) {
- const blockHandle = this.findBlockHandleInBlockIndex(key)
- if (blockHandle === null) {
- console.warn(`Expected to find key within this table`)
- return RequestStatus.createNotFound()
- }
-
- const block = this.getBlock(blockHandle)
-
- return this.searchKeyInBlockData(key, block)
- }
- keys() {
- if (this.blockIndex === null) throw new Error('Block index not loaded')
-
- const blockHelper = new BlockSeeker(this.blockIndex)
- return blockHelper.keys()
- }
-
- protected getBlock(blockHandle: BlockHandle) {
- if (this.cache.has(blockHandle)) {
- return this.cache.get(blockHandle)!
- }
-
- const block = blockHandle.readBlock(this.reader)
- if (this.cache.size > 50) {
- const keys = this.cache.keys()
- for (let i = 0; i < 25; i++) this.cache.delete(keys.next().value)
- }
-
- this.cache.set(blockHandle, block)
-
- return block
- }
-
- protected findBlockHandleInBlockIndex(key: Uint8Array) {
- if (this.blockIndex === null) throw new Error('Block index not loaded')
-
- const searchHelper = new BlockSeeker(this.blockIndex)
- if (searchHelper.binarySearchKey(key)) {
- const foundKey = searchHelper.getCurrentKey()
- if (foundKey === null) throw new Error('Key not found')
-
- const usableKey = asUsableKey(foundKey)
- if (this.comparator.compare(usableKey, key) < 0) return null
-
- const val = searchHelper.getCurrentValue()
- if (val === null) return null
-
- return BlockHandle.readBlockHandle(new Uint8ArrayReader(val))
- }
-
- return null
- }
- protected searchKeyInBlockData(key: Uint8Array, blockData: Uint8Array) {
- const searchHelper = new BlockSeeker(blockData)
-
- if (searchHelper.binarySearchKey(key)) {
- const foundKey = searchHelper.getCurrentKey()
- if (foundKey === null) throw new Error('Key not found')
-
- const keyType = getKeyType(foundKey)
- const usableKey = asUsableKey(foundKey)
-
- if (this.comparator.compare(usableKey, key) === 0) {
- switch (keyType) {
- case EKeyType.Deleted: {
- // console.log('DELETED')
- return RequestStatus.createDeleted()
- }
- case EKeyType.Exists: {
- // console.log('EXISTS')
- return new RequestStatus(searchHelper.getCurrentValue())
- }
- default: {
- console.warn(`Unknown key type ${keyType}`)
- }
- }
- }
- }
-
- // console.log('NOT FOUND')
- return RequestStatus.createNotFound()
- }
-}
diff --git a/src/components/BedrockWorlds/LevelDB/Uint8ArrayUtils/Equals.ts b/src/components/BedrockWorlds/LevelDB/Uint8ArrayUtils/Equals.ts
deleted file mode 100644
index 158b9f3a0..000000000
--- a/src/components/BedrockWorlds/LevelDB/Uint8ArrayUtils/Equals.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * Given two Uint8Arrays, returns true if they are equal, false otherwise.
- */
-export function equals(a: Uint8Array, b: Uint8Array): boolean {
- if (a.length !== b.length) {
- return false
- }
- for (let i = 0; i < a.length; i++) {
- if (a[i] !== b[i]) {
- return false
- }
- }
- return true
-}
diff --git a/src/components/BedrockWorlds/LevelDB/Uint8ArrayUtils/Reader.ts b/src/components/BedrockWorlds/LevelDB/Uint8ArrayUtils/Reader.ts
deleted file mode 100644
index d0a1aa5c5..000000000
--- a/src/components/BedrockWorlds/LevelDB/Uint8ArrayUtils/Reader.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-const textDecoder = new TextDecoder('utf-8')
-
-export enum ESeekType {
- Start = 0,
- Current = 1,
- End = 2,
-}
-
-export class Uint8ArrayReader {
- constructor(protected data: Uint8Array, protected position = 0) {
- this.data = data
- }
-
- get hasUnreadBytes() {
- return this.position < this.data.length
- }
-
- clone() {
- return new Uint8ArrayReader(this.data, this.position)
- }
- getPosition() {
- return this.position
- }
- getLength() {
- return this.data.length
- }
-
- seek(offset: number, where: ESeekType = ESeekType.Start) {
- if (offset > this.data.length) throw new Error('Offset out of bounds')
-
- let newPosition = this.position
-
- switch (where) {
- case ESeekType.Start:
- newPosition = offset
- break
- case ESeekType.Current:
- newPosition += offset
- break
- case ESeekType.End:
- newPosition = this.data.length + offset
- break
- }
-
- if (newPosition < 0) throw new Error('Offset out of bounds')
-
- this.position = newPosition
-
- return this.position
- }
- unsafelySetPosition(position: number) {
- this.position = position
- }
-
- readByte() {
- const byte = this.data[this.position]
- this.position++
- return byte
- }
- readInt32() {
- const a = this.readByte()
- const b = this.readByte()
- const c = this.readByte()
- const d = this.readByte()
- return a | (b << 8) | (c << 16) | (d << 24)
- }
- readUint32() {
- return this.readInt32() >>> 0
- }
- readInt64() {
- const low = BigInt(this.readInt32())
- const high = BigInt(this.readInt32())
- return low | (high << 32n)
- }
- readUint64() {
- return BigInt.asUintN(64, this.readInt64())
- }
- decodeZigZagInt32() {
- const i = this.readInt32()
- return (i >>> 1) ^ -(i & 1)
- }
-
- readVarLong() {
- let result = 0
-
- for (let shift = 0; shift < 63; shift += 7) {
- const b = this.readByte()
- result |= (b & 0x7f) << shift
-
- if ((b & 0x80) === 0) {
- return result
- }
- }
-
- throw new Error('Invalid varlong')
- }
-
- read(length: number) {
- if (length > this.data.length - this.position) {
- console.log(length, this.data, this.position)
- throw new Error('Not enough data')
- }
-
- const bytes = this.data.slice(this.position, this.position + length)
- this.position += length
- return bytes
- }
-
- readWithOffset(offset: number, length: number) {
- const availableBytes = this.data.length - this.position
- if (availableBytes <= 0) return new Uint8Array(0)
-
- const bytes = new Uint8Array(offset + length)
-
- bytes.set(this.read(length), offset)
-
- return bytes
- }
-
- readLengthPrefixedBytes() {
- const length = this.readVarLong()
- return this.read(length)
- }
- readLengthPrefixedString() {
- const bytes = this.readLengthPrefixedBytes()
- return textDecoder.decode(bytes)
- }
-}
diff --git a/src/components/BedrockWorlds/LevelDB/Uint8ArrayUtils/ToUint8Array.ts b/src/components/BedrockWorlds/LevelDB/Uint8ArrayUtils/ToUint8Array.ts
deleted file mode 100644
index 6d8102e83..000000000
--- a/src/components/BedrockWorlds/LevelDB/Uint8ArrayUtils/ToUint8Array.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * Convert the signed integer n to an Uint8Array representing a little-endian, 32 bit signed integer
- * @param n
- * @returns Uint8Array[4]
- */
-export function toUint8Array(n: number) {
- const buffer = new ArrayBuffer(4)
- const view = new DataView(buffer)
- view.setInt32(0, n, true)
- return new Uint8Array(buffer)
-}
diff --git a/src/components/BedrockWorlds/LevelDB/Uint8ArrayUtils/Unpack.ts b/src/components/BedrockWorlds/LevelDB/Uint8ArrayUtils/Unpack.ts
deleted file mode 100644
index b15e35f89..000000000
--- a/src/components/BedrockWorlds/LevelDB/Uint8ArrayUtils/Unpack.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * Given an Uint8Array, return an array with only 0 and 1 values.
- */
-export function unpackBits(bytes: Uint8Array): Uint8Array {
- const result = new Uint8Array(8 * bytes.length)
-
- for (let i = 0; i < bytes.length; i++) {
- const byte = bytes[i]
- for (let j = 0; j < 8; j++) {
- result[(i + 1) * 8 - (j + 1)] = (byte >> j) & 1
- }
- }
-
- return result
-}
-
-/**
- * Given an array of 0 and 1 values, return an Uint8Array where bitsPerNumber bits are packed into each byte.
- */
-export function packBits(bits: Uint8Array, bitsPerNumber: number) {
- const bytes = new Uint8Array(Math.ceil(bits.length / bitsPerNumber))
-
- for (let i = 0; i < bytes.length; i++) {
- let byte = 0
- for (let j = 0; j < bitsPerNumber; j++) {
- byte |= bits[i * bitsPerNumber + j] << j
- }
- bytes[i] = byte
- }
-
- return bytes
-}
-
-export function unpackStruct(bytes: Uint8Array) {
- return bytes.length < 4 ? 0 : new DataView(bytes.buffer).getUint32(0, true)
-}
diff --git a/src/components/BedrockWorlds/LevelDB/Version.ts b/src/components/BedrockWorlds/LevelDB/Version.ts
deleted file mode 100644
index 5d7534dc9..000000000
--- a/src/components/BedrockWorlds/LevelDB/Version.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { FileMetaData } from './FileMetaData'
-
-export class Version {
- public comparator?: string
- public logNumber?: number
- public previousLogNumber?: number
- public nextFileNumber?: number
- public lastSequence?: number
-
- public deletedFiles = new Map>()
- public levels = new Map()
- public compactPointers = new Map()
-
- getFiles(level: number): FileMetaData[] {
- return this.levels.get(level) ?? []
- }
- addFile(level: number, file: FileMetaData) {
- if (!this.levels.has(level)) {
- this.levels.set(level, [])
- }
- this.levels.get(level)!.push(file)
- }
- removeFile(level: number, fileNumber: number) {
- const files = this.getFiles(level)
-
- const index = files.findIndex((f) => f.fileNumber === fileNumber)
- if (index === -1) {
- throw new Error(`File ${fileNumber} not found in level ${level}`)
- }
-
- files.splice(index, 1)
-
- if (!this.deletedFiles.has(level)) {
- this.deletedFiles.set(level, new Set())
- }
- this.deletedFiles.get(level)!.add(fileNumber)
- }
-
- getCompactPointer(level: number) {
- return this.compactPointers.get(level) ?? null
- }
- setCompactPointer(level: number, pointer: Uint8Array) {
- this.compactPointers.set(level, pointer)
- }
- removeCompactPointer(level: number) {
- this.compactPointers.delete(level)
- }
-}
diff --git a/src/components/BedrockWorlds/Render/Neighbours.ts b/src/components/BedrockWorlds/Render/Neighbours.ts
deleted file mode 100644
index fb8c59804..000000000
--- a/src/components/BedrockWorlds/Render/Neighbours.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * Array to store all neighbours of a voxel, useful for iterating
- */
-export const VoxelNeighbours = [
- [0, 0, 0], // Self
- [-1, 0, 0], // Left
- [1, 0, 0], // Right
- [0, -1, 0], // Down
- [0, 1, 0], // Up
- [0, 0, -1], // Back
- [0, 0, 1], // Front
-]
diff --git a/src/components/BedrockWorlds/Render/Tab.ts b/src/components/BedrockWorlds/Render/Tab.ts
deleted file mode 100644
index 11debb1e3..000000000
--- a/src/components/BedrockWorlds/Render/Tab.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { World } from '../WorldFormat/World'
-import { ThreePreviewTab } from '/@/components/Editors/ThreePreview/ThreePreviewTab'
-
-export class WorldTab extends ThreePreviewTab {
- protected world?: World
- onChange() {}
- reload() {}
-
- async onActivate() {
- const project = this.parent.project
-
- this.world = new World(
- await project.fileSystem.getDirectoryHandle(
- 'PATH TO WORLD DB FOLDER'
- ),
- project.fileSystem.baseDirectory,
- this.scene
- )
-
- await this.world.loadWorld()
- this.requestRendering()
-
- console.log(this.world.blockLibrary, this)
- }
-
- async render() {
- super.render()
-
- await this.world?.updateCurrentMeshes(
- this.camera.position.x,
- this.camera.position.y,
- this.camera.position.z
- )
- }
-
- get name() {
- return 'World'
- }
- get iconColor() {
- return '#ffb400'
- }
- get icon() {
- return 'mdi-earth'
- }
-}
diff --git a/src/components/BedrockWorlds/Render/VoxelFaces.ts b/src/components/BedrockWorlds/Render/VoxelFaces.ts
deleted file mode 100644
index 630eee4cb..000000000
--- a/src/components/BedrockWorlds/Render/VoxelFaces.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-/**
- * Stores data for the different voxel faces, needs to be updated once we support loading Minecraft's blocks
- */
-export const VoxelFaces = [
- // Left
- {
- faces: ['east', 'side', 'all'],
- dir: [-1, 0, 0],
- corners: [
- { pos: [0, 1, 0], uv: [0, 1] },
- { pos: [0, 0, 0], uv: [0, 0] },
- { pos: [0, 1, 1], uv: [1, 1] },
- { pos: [0, 0, 1], uv: [1, 0] },
- ],
- },
-
- // Right
- {
- faces: ['west', 'side', 'all'],
- dir: [1, 0, 0],
- corners: [
- { pos: [1, 1, 1], uv: [0, 1] },
- { pos: [1, 0, 1], uv: [0, 0] },
- { pos: [1, 1, 0], uv: [1, 1] },
- { pos: [1, 0, 0], uv: [1, 0] },
- ],
- },
-
- // Bottom
- {
- faces: ['down', 'all'],
- dir: [0, -1, 0],
- corners: [
- { pos: [1, 0, 1], uv: [1, 0] },
- { pos: [0, 0, 1], uv: [0, 0] },
- { pos: [1, 0, 0], uv: [1, 1] },
- { pos: [0, 0, 0], uv: [0, 1] },
- ],
- },
-
- // Top
- {
- faces: ['up', 'all'],
- dir: [0, 1, 0],
- corners: [
- { pos: [0, 1, 1], uv: [1, 1] },
- { pos: [1, 1, 1], uv: [0, 1] },
- { pos: [0, 1, 0], uv: [1, 0] },
- { pos: [1, 1, 0], uv: [0, 0] },
- ],
- },
-
- //Back
- {
- faces: ['south', 'side', 'all'],
- dir: [0, 0, -1],
- corners: [
- { pos: [1, 0, 0], uv: [0, 0] },
- { pos: [0, 0, 0], uv: [1, 0] },
- { pos: [1, 1, 0], uv: [0, 1] },
- { pos: [0, 1, 0], uv: [1, 1] },
- ],
- },
-
- //Front
- {
- faces: ['north', 'side', 'all'],
- dir: [0, 0, 1],
- corners: [
- { pos: [0, 0, 1], uv: [0, 0] },
- { pos: [1, 0, 1], uv: [1, 0] },
- { pos: [0, 1, 1], uv: [0, 1] },
- { pos: [1, 1, 1], uv: [1, 1] },
- ],
- },
-] as const
diff --git a/src/components/BedrockWorlds/Render/World/SubChunk.ts b/src/components/BedrockWorlds/Render/World/SubChunk.ts
deleted file mode 100644
index 79c9b8a6f..000000000
--- a/src/components/BedrockWorlds/Render/World/SubChunk.ts
+++ /dev/null
@@ -1,166 +0,0 @@
-import type { TDirection } from '../../BlockLibrary/BlockLibrary'
-import { SubChunk } from '../../WorldFormat/Chunk'
-import { World } from '../../WorldFormat/World'
-import { VoxelFaces } from '../VoxelFaces'
-const chunkSize = 16
-
-export class RenderSubChunk {
- constructor(protected world: World, protected subChunkInstance: SubChunk) {}
-
- /**
- * TODO:
- * This function is currently the big bottleneck of rendering worlds, needs optimizations
- */
- getGeometryData() {
- if (this.subChunkInstance.blockLayers.length === 0) return
- const tileSize = 16
- const [
- tileTextureWidth,
- tileTextureHeight,
- ] = this.world.blockLibrary.getTileMapSize()
-
- const positions: number[] = []
- const normals: number[] = []
- const uvs: number[] = []
- const indices: number[] = []
- const chunkX = this.subChunkInstance.parent.getX()
- const chunkY = this.subChunkInstance.y
- const chunkZ = this.subChunkInstance.parent.getZ()
-
- const startX = chunkX * chunkSize
- const startY = chunkY * chunkSize
- const startZ = chunkZ * chunkSize
-
- for (let y = 0; y < 16; y++) {
- const correctedY = chunkY < 0 ? chunkSize - 1 - y : y
-
- for (let z = 0; z < 16; z++) {
- const correctedZ = chunkZ < 0 ? chunkSize - 1 - z : z
-
- for (let x = 0; x < 16; x++) {
- const correctedX = chunkX < 0 ? chunkSize - 1 - x : x
-
- const block = this.subChunkInstance
- .getLayer(0)
- .getBlockAt(correctedX, correctedY, correctedZ)
-
- // The current voxel is not air, we may need to render faces for it
- if (block.name !== 'minecraft:air') {
- // console.warn(
- // chunkX,
- // chunkY,
- // chunkZ,
- // correctedX,
- // correctedY,
- // correctedZ
- // )
- // console.log(
- // 'MAIN:',
- // block.name,
- // startX + correctedX,
- // startY + correctedY,
- // startZ + correctedZ
- // )
-
- // Do we need faces for the current voxel?
- for (const { dir, corners, faces } of VoxelFaces) {
- const neighbour = this.world.getBlockAt(
- 0,
- startX + correctedX + dir[0],
- startY + correctedY + dir[1],
- startZ + correctedZ + dir[2]
- )
- // console.log(
- // faces[0] + ':',
- // neighbour.name,
- // startX + correctedX + dir[0],
- // startY + correctedY + dir[1],
- // startZ + correctedZ + dir[2]
- // )
-
- // This voxel has a transparent voxel as a neighbour in the current direction -> add face
- if (
- neighbour.name === 'minecraft:air'
- // BlockLibrary.isTransparent(
- // neighbour,
- // (faces as unknown) as TDirection[]
- // ) ||
- // BlockLibrary.isSlab(voxel) ||
- // BlockLibrary.isFence(voxel) ||
- // BlockLibrary.isStairs(voxel)
- ) {
- const ndx = positions.length / 3
- for (let {
- pos: [oX, oY, oZ],
- uv: [uvX, uvY],
- } of corners) {
- const [
- voxelUVX,
- voxelUVY,
- ] = this.world.blockLibrary.getVoxelUv(
- block.name,
- (faces as unknown) as TDirection[]
- )
- // if (BlockLibrary.isSlab(voxel)) {
- // oY /= 2
- // uvY /= 2
- // } else if (BlockLibrary.isStairs(voxel)) {
- // oY /= 2
- // oX /= 2
- // uvY /= 2
- // uvX /= 2
- // } else if (BlockLibrary.isFence(voxel)) {
- // ;(oX as number) =
- // oX === 0 ? 6 / 16 : 10 / 16
- // ;(oZ as number) =
- // oZ === 0 ? 6 / 16 : 10 / 16
- // ;(uvX as number) =
- // uvX === 0 ? 6 / 16 : 10 / 16
- // if (
- // ((faces as unknown) as TDirection[]).includes(
- // 'up'
- // ) ||
- // ((faces as unknown) as TDirection[]).includes(
- // 'down'
- // )
- // )
- // (uvY as number) =
- // uvY === 0 ? 6 / 16 : 10 / 16
- // }
- positions.push(
- oX + correctedX,
- oY + correctedY,
- oZ + correctedZ
- )
- normals.push(...dir)
- uvs.push(
- ((voxelUVX + uvX) * tileSize) /
- tileTextureWidth,
- 1 -
- ((voxelUVY + 1 - uvY) * tileSize) /
- tileTextureHeight
- )
- }
- indices.push(
- ndx,
- ndx + 1,
- ndx + 2,
- ndx + 2,
- ndx + 1,
- ndx + 3
- )
- }
- }
- }
- }
- }
- }
-
- return {
- positions,
- normals,
- uvs,
- indices,
- }
- }
-}
diff --git a/src/components/BedrockWorlds/WorldFormat/Block.ts b/src/components/BedrockWorlds/WorldFormat/Block.ts
deleted file mode 100644
index 40fa5b7b7..000000000
--- a/src/components/BedrockWorlds/WorldFormat/Block.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-export interface IBlock {
- version: 17879555
- name: string
- value?: number
- states?: IBlockStates
-}
-export interface IBlockStates {
- [key: string]: unknown
-}
-
-export class Block implements IBlock {
- public readonly version = 17879555
- public readonly name: string
- public readonly states: IBlockStates
-
- constructor(identifier: string, states?: IBlockStates) {
- this.name = identifier
- this.states = states ?? {}
- }
-}
diff --git a/src/components/BedrockWorlds/WorldFormat/Chunk.ts b/src/components/BedrockWorlds/WorldFormat/Chunk.ts
deleted file mode 100644
index 4b517732f..000000000
--- a/src/components/BedrockWorlds/WorldFormat/Chunk.ts
+++ /dev/null
@@ -1,254 +0,0 @@
-import { Uint8ArrayReader } from '../LevelDB/Uint8ArrayUtils/Reader'
-import { EDimension } from './EDimension'
-import { EKeyTypeTag } from './EKeyTypeTags'
-import { simplify } from 'prismarine-nbt'
-import { readAllNbt, readNbt } from './readNbt'
-import { unpackStruct } from '../LevelDB/Uint8ArrayUtils/Unpack'
-import { Block, IBlock } from './Block'
-import type { World } from './World'
-
-export class Chunk {
- protected subChunks: SubChunk[] = []
-
- constructor(
- protected world: World,
- public readonly x: Uint8Array,
- public readonly z: Uint8Array,
- public readonly dimension = new Uint8Array([0, 0, 0, 0])
- ) {
- this.loadSubChunks()
- // const entityData = this.loadNbtData(EKeyTypeTag.Entity)
- // if (entityData) console.log(entityData)
- // const blockEntity = this.loadNbtData(EKeyTypeTag.BlockEntity)
- // if (blockEntity) console.log(blockEntity)
- // const pendingTicks = this.loadNbtData(EKeyTypeTag.PendingTicks)
- // if (pendingTicks) console.log(pendingTicks)
- // const BlockExtraData = this.loadNbtData(EKeyTypeTag.BlockExtraData)
- // if (BlockExtraData) console.log(BlockExtraData)
- }
-
- getDimension() {
- return new Uint8ArrayReader(this.dimension).readInt32()
- }
- getX() {
- return new Uint8ArrayReader(this.x).readInt32()
- }
- getZ() {
- return new Uint8ArrayReader(this.z).readInt32()
- }
-
- getLevelDb() {
- return this.world.levelDb
- }
-
- getSubChunk(n: number) {
- return this.subChunks[n]
- }
-
- loadSubChunks() {
- for (let i = 0; i < 16; i++) {
- this.subChunks.push(new SubChunk(this, i))
- }
- }
-
- getChunkKey(tagType: EKeyTypeTag) {
- const dimension = this.getDimension()
- const key = new Uint8Array(
- 9 + // x + z coordinate + 1 byte for key type tag
- (dimension === EDimension.Overworld ? 0 : 4) // dimension
- )
- key.set(this.x, 0)
- key.set(this.z, 4)
- let offset = 0
- if (dimension !== EDimension.Overworld) {
- key.set(this.dimension, 8)
- offset = 4
- }
-
- key[8 + offset] = tagType
-
- return key
- }
-
- protected loadData(tagType: EKeyTypeTag) {
- if (tagType === EKeyTypeTag.SubChunkPrefix)
- throw new Error(
- 'SubChunkPrefix data should be loaded by sub chunks'
- )
-
- return this.getLevelDb().get(this.getChunkKey(tagType))
- }
-
- protected loadNbtData(tagType: EKeyTypeTag) {
- const data = this.loadData(tagType)
- if (!data) return null
-
- let offset = 0
- const length = data.length
-
- const loadedNbt = []
- while (offset < length) {
- const { data: d, size } = readNbt(data, offset)
- loadedNbt.push(simplify(d))
- offset += size
- }
-
- return loadedNbt
- }
-}
-
-const totalBlockSpaces = 4096 // 16 * 16 * 16
-
-export class SubChunk {
- public blockLayers: BlockLayer[] = []
-
- constructor(public readonly parent: Chunk, public readonly y: number) {
- let blockData = this.loadData()
-
- if (blockData) {
- // This var stores how many blocks can be stored in a single block space (e.g. waterlogged blocks)
- let blocksPerBlockSpace = 1
-
- // First byte in blockData is sub chunk version
- if (blockData[0] === 1) {
- blockData = blockData.slice(1)
- } else if (blockData[0] === 8) {
- blocksPerBlockSpace = blockData[1]
- blockData = blockData.slice(2)
- } else if (blockData[0] === 9) {
- blocksPerBlockSpace = blockData[1]
- // Extra byte stores sub chunk y coordinate
- blockData = blockData.slice(3)
- } else {
- throw new Error(
- `Unknown sub chunk format version: ${blockData[0]}`
- )
- }
-
- for (
- let blockSpaceIndex = 0;
- blockSpaceIndex < blocksPerBlockSpace;
- blockSpaceIndex++
- ) {
- const { blocks, palette, data } = this.loadBlockPalette(
- blockData
- )
- blockData = data
- this.blockLayers.push(new BlockLayer(blocks, palette))
- }
- } else {
- this.blockLayers.push(new EmptyBlockLayer())
- }
- }
-
- getLayer(index: number) {
- if (index < 0 || index >= this.blockLayers.length) {
- throw new Error(`Invalid layer index: ${index}`)
- }
-
- return this.blockLayers[index]
- }
-
- protected loadBlockPalette(data: Uint8Array) {
- const bitsPerBlock = data[0] >>> 1
- data = data.slice(1)
-
- if (bitsPerBlock === 0) {
- const { data: nbtData, size } = readNbt(data, 0)
-
- return {
- blocks: new Uint16Array(totalBlockSpaces),
- palette: simplify(nbtData),
- data: data.slice(0, size),
- }
- } else {
- // One word consists of 4 bytes
- const blocksPerWord = Math.floor(32 / bitsPerBlock)
- const wordCount = Math.ceil(totalBlockSpaces / blocksPerWord)
- const padding =
- bitsPerBlock === 3 || bitsPerBlock === 5 || bitsPerBlock === 6
- ? 2
- : 0
-
- let rawBlocks = new Uint8Array(wordCount * 4)
- rawBlocks.set(data.slice(0, wordCount * 4))
- data = data.slice(wordCount * 4)
-
- const processedBlocks = new Uint16Array(totalBlockSpaces)
- let position = 0
- for (let wordIndex = 0; wordIndex < wordCount; wordIndex++) {
- const rawWord = rawBlocks.slice(
- wordIndex * 4,
- (wordIndex + 1) * 4
- )
- const uint32Word = new DataView(rawWord.buffer).getUint32(
- 0,
- true
- )
-
- for (
- let blockIndex = 0;
- blockIndex < blocksPerWord;
- blockIndex++
- ) {
- const blockId =
- (uint32Word >>
- ((position % blocksPerWord) * bitsPerBlock)) &
- ((1 << bitsPerBlock) - 1)
- processedBlocks[position] = blockId
- position++
- }
- }
-
- const paletteLength = unpackStruct(data.slice(0, 4))
- data = data.slice(4)
-
- const { data: nbtData, size } = readAllNbt(data, paletteLength)
-
- // console.log(size, data.slice(0, size))
- return {
- blocks: processedBlocks,
- palette: nbtData.map((d) => simplify(d)),
- data: data.slice(size),
- }
- }
- }
-
- protected loadData() {
- return this.parent.getLevelDb().get(this.getSubChunkPrefixKey())
- }
-
- protected getSubChunkPrefixKey() {
- return new Uint8Array([
- ...this.parent.getChunkKey(EKeyTypeTag.SubChunkPrefix),
- this.y,
- ])
- }
-}
-
-export class BlockLayer {
- constructor(protected blocks: Uint16Array, protected pallete: any[]) {
- // console.log(pallete)
- }
-
- getBlockAt(x: number, y: number, z: number): IBlock {
- if (x < 0 || x >= 16 || y < 0 || y >= 16 || z < 0 || z >= 16) {
- throw new Error(`Invalid block coordinates: ${x}, ${y}, ${z}`)
- }
-
- const numericId = this.blocks[y + z * 16 + x * 256]
- const block = this.pallete[numericId]
-
- return block ?? new Block('minecraft:info_update')
- }
-}
-
-export class EmptyBlockLayer extends BlockLayer {
- constructor() {
- super(new Uint16Array(totalBlockSpaces), [])
- }
-
- getBlockAt(x: number, y: number, z: number): IBlock {
- return new Block('minecraft:air')
- }
-}
diff --git a/src/components/BedrockWorlds/WorldFormat/EDimension.ts b/src/components/BedrockWorlds/WorldFormat/EDimension.ts
deleted file mode 100644
index 495326d4e..000000000
--- a/src/components/BedrockWorlds/WorldFormat/EDimension.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export enum EDimension {
- Overworld = 0,
- Nether = 1,
- TheEnd = 2,
-}
diff --git a/src/components/BedrockWorlds/WorldFormat/EKeyTypeTags.ts b/src/components/BedrockWorlds/WorldFormat/EKeyTypeTags.ts
deleted file mode 100644
index 06949a177..000000000
--- a/src/components/BedrockWorlds/WorldFormat/EKeyTypeTags.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-export enum EKeyTypeTag {
- ChunkVersion = 44,
- Data2D = 45,
- Data2DLegacy = 46,
- SubChunkPrefix = 47,
- LegacyTerrain = 48,
- BlockEntity = 49,
- Entity = 50,
- PendingTicks = 51,
- BlockExtraData = 52,
- BiomeState = 53,
- FinalizedState = 54,
- BorderBlocks = 56,
- HardCodedSpawnAreas = 57,
- RandomTicks = 58,
- Checksums = 59,
- OldChunkVersion = 118,
-}
diff --git a/src/components/BedrockWorlds/WorldFormat/World.ts b/src/components/BedrockWorlds/WorldFormat/World.ts
deleted file mode 100644
index c3b75ff38..000000000
--- a/src/components/BedrockWorlds/WorldFormat/World.ts
+++ /dev/null
@@ -1,236 +0,0 @@
-import { LevelDB } from '../LevelDB/LevelDB'
-import { EKeyTypeTag } from './EKeyTypeTags'
-import { Chunk } from './Chunk'
-import { toUint8Array } from '../LevelDB/Uint8ArrayUtils/ToUint8Array'
-import { Block } from './Block'
-import { AnyDirectoryHandle } from '/@/components/FileSystem/Types'
-import { BlockLibrary } from '../BlockLibrary/BlockLibrary'
-import { RenderSubChunk } from '../Render/World/SubChunk'
-import {
- BufferAttribute,
- BufferGeometry,
- CanvasTexture,
- FrontSide,
- Mesh,
- MeshLambertMaterial,
- NearestFilter,
- Scene,
- Vector3,
- MathUtils,
-} from 'three'
-import { markRaw } from 'vue'
-
-export class World {
- protected chunks = new Map()
- public readonly blockLibrary: BlockLibrary
- public readonly levelDb: LevelDB
-
- constructor(
- protected worldHandle: AnyDirectoryHandle,
- protected projectHandle: AnyDirectoryHandle,
- protected scene: Scene
- ) {
- this.levelDb = markRaw(new LevelDB(this.worldHandle))
- this.blockLibrary = markRaw(new BlockLibrary(this.projectHandle))
- }
-
- async loadWorld() {
- await Promise.all([this.blockLibrary.setup(), this.levelDb.open()])
-
- const texture = new CanvasTexture(this.blockLibrary.tileMap)
- texture.magFilter = NearestFilter
- texture.minFilter = NearestFilter
- this.material = new MeshLambertMaterial({
- map: texture,
- side: FrontSide,
- alphaTest: 0.1,
- transparent: true,
- })
-
- const keys = this.levelDb.keys()
-
- for (const key of keys) {
- const decoded = this.decodeChunkKey(key)
- if (!decoded) continue
- const { x, z, dimension } = decoded
-
- const position = new Uint8Array([
- ...x,
- ...z,
- ...(dimension ? dimension : []),
- ]).join(',')
-
- if (!this.chunks.has(position))
- this.chunks.set(position, new Chunk(this, x, z, dimension))
- }
- }
-
- getSubChunkAt(x: number, y: number, z: number) {
- const chunkX = toUint8Array(Math.floor(x / 16))
- const chunkZ = toUint8Array(Math.floor(z / 16))
-
- const chunk = this.chunks.get(
- new Uint8Array([...chunkX, ...chunkZ]).join(',')
- )
-
- return chunk?.getSubChunk(Math.floor(y / 16))
- }
-
- getBlockAt(layer: number, x: number, y: number, z: number) {
- // console.log(layer, x, y, z, this.getSubChunkAt(x, y, z))
- return (
- this.getSubChunkAt(x, y, z)
- ?.getLayer(layer)
- .getBlockAt(
- Math.abs(x < 0 ? (16 + (x % 16)) % 16 : x % 16),
- Math.abs(y < 0 ? (16 + (y % 16)) % 16 : y % 16),
- Math.abs(z < 0 ? (16 + (z % 16)) % 16 : z % 16)
- ) ?? new Block('minecraft:air')
- )
- }
-
- decodeChunkKey(key: Uint8Array) {
- if (![9, 10, 13, 14].includes(key.length)) return null
-
- /**
- * Chunk key format
- * 1) little endian int32 (chunk x coordinate)
- * 2) little endian int32 (chunk z coordinate)
- * 3) optional little endian int32 (dimension)
- * 4) key type byte (see EKeyTypeTag)
- * 5) one byte for SubChunkPrefix data when type is EKeyTypeTag.SubChunkPrefix
- */
-
- const x = key.slice(0, 4)
- const z = key.slice(4, 8)
-
- // Optional dimension is defined if key is long enough
- const dimension = key.length >= 13 ? key.slice(8, 12) : undefined
-
- const keyTypeByte = key[key.length - 2]
- const subChunkPrefixByte =
- keyTypeByte === EKeyTypeTag.SubChunkPrefix &&
- (key.length === 10 || key.length === 14)
- ? key[key.length - 1]
- : null
-
- return {
- x: x,
- z: z,
- dimension: dimension,
- keyType: keyTypeByte,
- subChunkPrefix: subChunkPrefixByte,
- }
- }
-
- // TODO: Move all following methods to its own class dedicated to rendering a world
- protected loadedChunks = new Set()
- protected builtChunks = new Map()
- protected builtChunkMeshes = new Map()
- protected renderDistance = 16
- protected material!: MeshLambertMaterial
-
- async updateCurrentMeshes(currX: number, currY: number, currZ: number) {
- Array.from(this.loadedChunks).forEach((currID) => {
- let mesh = this.builtChunkMeshes.get(currID)
- if (mesh !== undefined) this.scene.remove(mesh)
- this.loadedChunks.delete(currID)
- })
-
- const max = (this.renderDistance * 16) / 2
- const start = -max
-
- for (let oX = start; oX <= max; oX += 16)
- for (let oZ = start; oZ <= max; oZ += 16)
- for (let oY = start; oY <= max; oY += 16) {
- const chunkID = this.getChunkId(
- Math.floor((currX + oX) / 16),
- Math.floor((currY + oY) / 16),
- Math.floor((currZ + oZ) / 16)
- )
-
- if (
- !this.loadedChunks.has(chunkID) &&
- new Vector3(oX, oY, oZ).distanceTo(
- new Vector3(currX, currY, currZ)
- ) <
- max * 16
- ) {
- let mesh = this.builtChunkMeshes.get(chunkID)
- if (mesh === undefined) {
- this.updateChunkGeometry(
- currX + oX,
- currY + oY,
- currZ + oZ
- )
- } else {
- this.scene.add(mesh)
- this.loadedChunks.add(chunkID)
- }
- }
- }
- }
-
- updateChunkGeometry(x: number, y: number, z: number) {
- const chunkX = Math.floor(x / 16)
- const chunkY = Math.floor(y / 16)
- const chunkZ = Math.floor(z / 16)
- const chunkId = this.getChunkId(chunkX, chunkY, chunkZ)
-
- //Building chunks is expensive, skip it whenever possible
- if (this.builtChunks.has(chunkId)) return
-
- let mesh = this.builtChunkMeshes.get(chunkId)
- let geometry = mesh?.geometry ?? new BufferGeometry()
-
- const subChunk = this.getSubChunkAt(x, y, z)
- if (!subChunk) return
-
- const renderSubChunk = new RenderSubChunk(this, subChunk)
- const data = renderSubChunk.getGeometryData()
- if (data === undefined) {
- if (mesh) {
- this.scene.remove(mesh)
- this.loadedChunks.delete(chunkId)
- this.builtChunkMeshes.delete(chunkId)
- }
- return
- }
-
- const { positions, normals, uvs, indices } = data
-
- const positionNumComponents = 3
- geometry.setAttribute(
- 'position',
- new BufferAttribute(
- new Float32Array(positions),
- positionNumComponents
- )
- )
- const normalNumComponents = 3
- geometry.setAttribute(
- 'normal',
- new BufferAttribute(new Float32Array(normals), normalNumComponents)
- )
- const uvNumComponents = 2
- geometry.setAttribute(
- 'uv',
- new BufferAttribute(new Float32Array(uvs), uvNumComponents)
- )
- geometry.setIndex(indices)
- geometry.computeBoundingSphere()
-
- if (mesh === undefined) {
- mesh = new Mesh(geometry, this.material)
- mesh.name = chunkId
- this.builtChunkMeshes.set(chunkId, mesh)
- this.loadedChunks.add(chunkId)
- mesh.position.set(chunkX * 16, chunkY * 16, chunkZ * 16)
- this.scene.add(mesh)
- }
- }
-
- getChunkId(x: number, y: number, z: number) {
- return `${x},${y},${z}`
- }
-}
diff --git a/src/components/BedrockWorlds/WorldFormat/readNbt.ts b/src/components/BedrockWorlds/WorldFormat/readNbt.ts
deleted file mode 100644
index b736bddb2..000000000
--- a/src/components/BedrockWorlds/WorldFormat/readNbt.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { protoLE } from 'prismarine-nbt'
-import { Buffer } from 'buffer'
-
-export function readNbt(nbtData: Uint8Array, offset = 0) {
- const { data, metadata } = protoLE.parsePacketBuffer(
- 'nbt',
- Buffer.from(nbtData),
- offset
- )
-
- return {
- data,
- size: metadata.size,
- }
-}
-
-export function readAllNbt(nbtData: Uint8Array, count = 1) {
- const resData = []
-
- let lastOffset = 0
- for (let i = 0; i < count; i++) {
- const { data, size } = readNbt(nbtData, lastOffset)
- resData.push(data)
- lastOffset += size
- }
-
- return {
- data: resData,
- size: lastOffset,
- }
-}
diff --git a/src/components/BottomPanel/BottomPanel.css b/src/components/BottomPanel/BottomPanel.css
deleted file mode 100644
index 23df6630a..000000000
--- a/src/components/BottomPanel/BottomPanel.css
+++ /dev/null
@@ -1,50 +0,0 @@
-.bottom-panel-content {
- height: 100%;
-}
-
-/* Tab bar */
-.bottom-panel-tab-bar {
- width: 100%;
- overflow-x: auto;
- overflow-y: visible;
- padding: 4px 0;
-}
-.bottom-panel-tab-bar::-webkit-scrollbar {
- display: none;
-}
-
-/* Tabs */
-.bottom-panel-tab {
- display: flex;
- align-items: center;
- font-size: 12px;
- font-weight: 500;
- letter-spacing: 1.25px;
-
- cursor: pointer;
- padding: 1px 8px;
- border-radius: 8px;
- background: var(--v-sidebarSelection-base);
- text-transform: uppercase;
-
- transition: transform 0.1s ease-in-out;
-}
-.bottom-panel-tab i {
- font-size: 18px !important;
-}
-.bottom-panel-tab-active {
- cursor: default;
- z-index: 1;
- background: var(--v-primary-base);
- transform: scale(1.1);
-}
-
-/* Panel content */
-.bottom-panel-content-container {
- /* Full height - tab bar height - tab bar padding - vertical padding */
- height: calc(100% - 39px - 8px);
- overflow-y: auto;
-}
-.bottom-panel-content-container-full-height {
- height: 100%;
-}
diff --git a/src/components/BottomPanel/BottomPanel.tsx b/src/components/BottomPanel/BottomPanel.tsx
deleted file mode 100644
index 264a5ee9c..000000000
--- a/src/components/BottomPanel/BottomPanel.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { computed, ref } from 'vue'
-import { JSX } from 'solid-js/types'
-import './BottomPanel.css'
-import { LogPanel } from '../Compiler/LogPanel/Panel'
-import { App } from '/@/App'
-
-interface ITab {
- name: string
- icon: string
- component: () => JSX.Element
-}
-
-export class BottomPanel {
- public readonly isVisible = ref(false)
- public readonly height = ref(400)
- public readonly tabs = ref([])
- public readonly activeTab = ref(null)
- public readonly currentHeight = computed(() =>
- this.isVisible.value ? this.height.value : 0
- )
-
- constructor() {
- this.setupTerminal()
-
- this.addTab({
- icon: 'mdi-bug',
- name: 'bottomPanel.problems.name',
- component: () => (
- <>
-
- We are still working on displaying problems with your
- project here...
-
- >
- ),
- })
-
- setTimeout(() => {
- App.getApp().then((app) => {
- this.addTab({
- icon: 'mdi-cogs',
- name: 'bottomPanel.compiler.name',
- component: () =>
- LogPanel({
- compilerWindow: app.windows.compilerWindow,
- }),
- })
- })
- })
- }
-
- async setupTerminal() {
- if (!import.meta.env.VITE_IS_TAURI_APP) return
- const { Terminal } = await import('./Terminal/Terminal')
- const { TerminalInput } = await import('./Terminal/Input')
- const { TerminalOutput } = await import('./Terminal/Output')
-
- const terminal = new Terminal()
-
- this.addTab(
- {
- icon: 'mdi-console-line',
- name: 'bottomPanel.terminal.name',
- component: () => (
- <>
-
-
- >
- ),
- },
- true
- )
- }
-
- selectTab(tab: ITab) {
- this.activeTab.value = tab
- }
- addTab(tab: ITab, asFirst = false) {
- if (asFirst) this.tabs.value.unshift(tab)
- else this.tabs.value.push(tab)
-
- if (this.activeTab.value === null || asFirst) {
- this.activeTab.value = tab
- }
- }
-}
diff --git a/src/components/BottomPanel/BottomPanel.vue b/src/components/BottomPanel/BottomPanel.vue
deleted file mode 100644
index daef53c77..000000000
--- a/src/components/BottomPanel/BottomPanel.vue
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
-
-
-
- mdi-chevron-up
- mdi-chevron-down
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/BottomPanel/PanelContent.tsx b/src/components/BottomPanel/PanelContent.tsx
deleted file mode 100644
index 5ae27e1ed..000000000
--- a/src/components/BottomPanel/PanelContent.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import { Component, Show } from 'solid-js'
-import { Dynamic } from 'solid-js/web'
-import { toSignal } from '../Solid/toSignal'
-import { toVue } from '../Solid/toVue'
-import { TabBar } from './TabBar'
-import { App } from '/@/App'
-
-export const PanelContent: Component = (props) => {
- const [activeTab] = toSignal(App.bottomPanel.activeTab)
- const [tabs] = toSignal(App.bottomPanel.tabs)
-
- return (
-
- )
-}
-
-export const VuePanelContent = toVue(PanelContent)
diff --git a/src/components/BottomPanel/TabBar.tsx b/src/components/BottomPanel/TabBar.tsx
deleted file mode 100644
index 0f914443d..000000000
--- a/src/components/BottomPanel/TabBar.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { Component, For } from 'solid-js'
-import { useTranslations } from '../Composables/useTranslations'
-import { useRipple } from '../Solid/Directives/Ripple/Ripple'
-import { SolidIcon } from '../Solid/Icon/SolidIcon'
-import { SolidIconButton } from '../Solid/Inputs/IconButton/IconButton'
-import { SolidSpacer } from '../Solid/SolidSpacer'
-import { toSignal } from '../Solid/toSignal'
-import { App } from '/@/App'
-
-export const TabBar: Component = (props) => {
- const ripple = useRipple()
- const { t } = useTranslations()
- const [tabs] = toSignal(App.bottomPanel.tabs)
- const [activeTab] = toSignal(App.bottomPanel.activeTab)
- const [_, setIsVisible] = toSignal(App.bottomPanel.isVisible)
-
- return (
-
-
-
- {(tab, i) => (
- 0,
- }}
- onClick={() => App.bottomPanel.selectTab(tab)}
- >
-
- {t(tab.name)}
-
- )}
-
-
-
-
-
-
setIsVisible(false)}
- />
-
- )
-}
diff --git a/src/components/BottomPanel/Terminal/Input.tsx b/src/components/BottomPanel/Terminal/Input.tsx
deleted file mode 100644
index 0c9b2193e..000000000
--- a/src/components/BottomPanel/Terminal/Input.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { Component, createSignal } from 'solid-js'
-import { useTranslations } from '../../Composables/useTranslations'
-import { SolidIconButton } from '../../Solid/Inputs/IconButton/IconButton'
-import { TextField } from '../../Solid/Inputs/TextField/TextField'
-import { toSignal } from '../../Solid/toSignal'
-import type { Terminal } from './Terminal'
-
-export const TerminalInput: Component<{
- terminal: Terminal
-}> = (props) => {
- const { t } = useTranslations()
- const [input, setInput] = createSignal('')
- const [hasRunningTask] = toSignal(props.terminal.hasRunningTask)
- const [output, setOutput] = toSignal(props.terminal.output)
-
- const onEnter = () => {
- props.terminal.executeCommand(input())
- setInput('')
- }
-
- return (
-
-
-
- props.terminal.killCommand()}
- />
-
- setOutput([])}
- />
-
- )
-}
diff --git a/src/components/BottomPanel/Terminal/Output.tsx b/src/components/BottomPanel/Terminal/Output.tsx
deleted file mode 100644
index f6b13eb67..000000000
--- a/src/components/BottomPanel/Terminal/Output.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { Component, For, Show } from 'solid-js'
-import { SolidIcon } from '../../Solid/Icon/SolidIcon'
-import { toSignal } from '../../Solid/toSignal'
-import type { Terminal } from './Terminal'
-
-export const TerminalOutput: Component<{
- terminal: Terminal
-}> = (props) => {
- const [output] = toSignal(props.terminal.output)
- const [cwd] = toSignal(props.terminal.cwd)
-
- const prettyCwd = () => {
- const tmp = cwd()
- .replace(props.terminal.baseCwd, '')
- .replace(/\\/g, '/')
- if (tmp === '') return 'bridge'
- return `bridge${tmp}`
- }
-
- return (
- <>
- {/* Show current cwd */}
-
-
-
-
-
- {prettyCwd()}
-
-
- {/* Render terminal output */}
-
-
- {({ kind, time, currentCwdName, msg }, i) => (
-
- [{time}]
-
-
- {currentCwdName} >
-
- {msg}
-
-
- )}
-
-
- >
- )
-}
diff --git a/src/components/BottomPanel/Terminal/Terminal.css b/src/components/BottomPanel/Terminal/Terminal.css
deleted file mode 100644
index b9e13da8d..000000000
--- a/src/components/BottomPanel/Terminal/Terminal.css
+++ /dev/null
@@ -1,11 +0,0 @@
-.terminal-line {
- line-break: normal;
-}
-
-.terminal-output-container {
- /* full height - terminal input bar height - cwd display height - a few pixels to prevent double scrollbar */
- height: calc(100% - 58px - 33px - 2px);
- overflow-y: auto;
- display: flex;
- flex-direction: column-reverse;
-}
diff --git a/src/components/BottomPanel/Terminal/Terminal.ts b/src/components/BottomPanel/Terminal/Terminal.ts
deleted file mode 100644
index b795181c2..000000000
--- a/src/components/BottomPanel/Terminal/Terminal.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import { markRaw, ref } from 'vue'
-import { invoke } from '@tauri-apps/api/tauri'
-import { App } from '/@/App'
-import { Signal } from '../../Common/Event/Signal'
-import { isAbsolute, join, sep } from '@tauri-apps/api/path'
-import { exists } from '@tauri-apps/api/fs'
-import { listen, Event } from '@tauri-apps/api/event'
-import './Terminal.css'
-import { getBridgeFolderPath } from '/@/utils/getBridgeFolderPath'
-
-type TMessageKind = 'stdout' | 'stderr' | 'stdin'
-
-interface IMessage {
- // Format HH:MM:SS
- time: string
- kind: TMessageKind
- currentCwdName: string
- msg: string
-}
-
-interface IMessagePayload {
- message: string
-}
-
-export class Terminal {
- output = ref([])
- hasRunningTask = ref(false)
- baseCwd = ''
- cwd = ref('')
- setupDone = markRaw(new Signal())
-
- constructor() {
- setTimeout(() => this.setup())
-
- listen('onStdoutMessage', ({ payload }: Event) => {
- this.addToOutput(payload.message, 'stdout')
- })
- listen('onStderrMessage', ({ payload }: Event) => {
- this.addToOutput(payload.message, 'stderr')
- })
- listen('onCommandDone', () => {
- this.hasRunningTask.value = false
- })
- }
-
- async setup() {
- const app = await App.getApp()
-
- this.baseCwd = await getBridgeFolderPath()
-
- if (this.cwd.value === '') this.cwd.value = this.baseCwd
-
- this.setupDone.dispatch()
- }
-
- addToOutput(msg: string, kind: TMessageKind) {
- this.output.value.unshift({
- time: new Date().toLocaleTimeString(),
- kind,
- currentCwdName: this.cwd.value.split(sep).pop()!,
- // Replace ANSI escape codes
- msg: msg.replace(/\x1b\[[0-9;]*m/g, ''),
- })
- }
-
- protected async handleCdCommand(command: string) {
- const path = command.substring(3)
- const prevCwd = this.cwd.value
-
- if (await isAbsolute(path)) {
- this.cwd.value = path
- } else {
- this.cwd.value = await join(this.cwd.value, path)
- }
-
- // Confirm that the path exists
- if (!(await exists(this.cwd.value))) {
- this.cwd.value = prevCwd
- this.addToOutput(`cd: no such directory`, 'stderr')
- }
-
- // Ensure that cwd doesn't leave the baseCwd
- if (!this.cwd.value.startsWith(this.baseCwd)) {
- this.cwd.value = prevCwd
- this.addToOutput(`cd: Permission denied`, 'stderr')
- }
- }
-
- async executeCommand(command: string) {
- this.addToOutput(command, 'stdin')
-
- if (command.startsWith('cd ')) {
- await this.handleCdCommand(command)
- return
- }
-
- this.hasRunningTask.value = true
-
- await this.setupDone.fired
-
- await invoke('execute_command', {
- cwd: this.cwd.value,
- command,
- })
- }
-
- async killCommand() {
- await invoke('kill_command')
- }
-}
diff --git a/src/components/CommandBar/AddFiles.ts b/src/components/CommandBar/AddFiles.ts
deleted file mode 100644
index c4be245fe..000000000
--- a/src/components/CommandBar/AddFiles.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { SimpleAction } from '../Actions/SimpleAction'
-import { AnyDirectoryHandle, AnyFileHandle } from '../FileSystem/Types'
-import { addCommandBarAction } from './State'
-import { App } from '/@/App'
-import { IDisposable } from '/@/types/disposable'
-import { loadAllFiles } from '/@/utils/file/loadAllFiles'
-
-export async function addFilesToCommandBar(
- directoryHandle: AnyDirectoryHandle,
- color?: string
-) {
- const files = await loadAllFiles(directoryHandle)
- let disposables: IDisposable[] = []
-
- for (const file of files) {
- const action = new SimpleAction({
- icon: 'mdi-file-outline',
- color,
- name: `[${file.path}]`,
- description: 'actions.openFile.name',
- onTrigger: async () => {
- const app = await App.getApp()
- app.project.openFile(file.handle)
- },
- })
-
- disposables.push(addCommandBarAction(action))
- }
-
- return {
- dispose: () => {
- disposables.forEach((disposable) => disposable.dispose())
- disposables = []
- },
- }
-}
diff --git a/src/components/CommandBar/CommandBar.vue b/src/components/CommandBar/CommandBar.vue
deleted file mode 100644
index fc43e2fbd..000000000
--- a/src/components/CommandBar/CommandBar.vue
+++ /dev/null
@@ -1,157 +0,0 @@
-
-
-
-
-
- Search for files, actions or projects.
-
-
-
-
-
-
- {{ item.icon }}
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/CommandBar/State.ts b/src/components/CommandBar/State.ts
deleted file mode 100644
index 61a504ee9..000000000
--- a/src/components/CommandBar/State.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { reactive } from 'vue'
-import { SimpleAction } from '../Actions/SimpleAction'
-
-export const CommandBarState = reactive({
- isWindowOpen: false,
- shouldRender: false, // Property is automatically updated
- closeDelay: null,
-})
-const CommandBarActions = new Set()
-export function addCommandBarAction(action: SimpleAction) {
- CommandBarActions.add(action)
-
- return {
- dispose: () => {
- CommandBarActions.delete(action)
- },
- }
-}
-export function getCommandBarActions() {
- return Array.from(CommandBarActions)
-}
diff --git a/src/components/CommandBar/Window.vue b/src/components/CommandBar/Window.vue
deleted file mode 100644
index 87bd29269..000000000
--- a/src/components/CommandBar/Window.vue
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/src/components/Common/Action.vue b/src/components/Common/Action.vue
new file mode 100644
index 000000000..b7a4edaa5
--- /dev/null
+++ b/src/components/Common/Action.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
{{ action.name ? t(action.name) : action.id }}
+
+
+
+
+
+
+
+
{{ t(action.description) }}
+
+
+
+
+ {{ action.keyBinding ?? t('Unbound') }}
+
+
+
+
diff --git a/src/components/Common/ActionContextMenuItem.vue b/src/components/Common/ActionContextMenuItem.vue
new file mode 100644
index 000000000..af2c96cf1
--- /dev/null
+++ b/src/components/Common/ActionContextMenuItem.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
+ {{ t(ActionManager.actions[action]?.name ?? 'actions.unknown.name') }}
+
+
+ {{ ActionManager.actions[action].keyBinding }}
+
+
+
diff --git a/src/components/Common/Button.vue b/src/components/Common/Button.vue
new file mode 100644
index 000000000..5e1f24293
--- /dev/null
+++ b/src/components/Common/Button.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
diff --git a/src/components/Common/ContextMenu.vue b/src/components/Common/ContextMenu.vue
new file mode 100644
index 000000000..6a7c2b35e
--- /dev/null
+++ b/src/components/Common/ContextMenu.vue
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
diff --git a/src/components/Common/ContextMenuDivider.vue b/src/components/Common/ContextMenuDivider.vue
new file mode 100644
index 000000000..7c2306b31
--- /dev/null
+++ b/src/components/Common/ContextMenuDivider.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/components/Common/ContextMenuItem.vue b/src/components/Common/ContextMenuItem.vue
new file mode 100644
index 000000000..c7ccb4a8f
--- /dev/null
+++ b/src/components/Common/ContextMenuItem.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
+ {{ t(text) }}
+
+
diff --git a/src/components/Common/Dropdown.vue b/src/components/Common/Dropdown.vue
new file mode 100644
index 000000000..177429feb
--- /dev/null
+++ b/src/components/Common/Dropdown.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Common/Error.vue b/src/components/Common/Error.vue
new file mode 100644
index 000000000..2d127e1cf
--- /dev/null
+++ b/src/components/Common/Error.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+ {{ t(text) }}
+
+
+
diff --git a/src/components/Common/Event/EventDispatcher.ts b/src/components/Common/Event/EventDispatcher.ts
deleted file mode 100644
index 6809e598f..000000000
--- a/src/components/Common/Event/EventDispatcher.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { IDisposable } from '/@/types/disposable'
-
-export class EventDispatcher {
- protected listeners = new Set<(data: T) => void>()
-
- constructor() {}
-
- get hasListeners() {
- return this.listeners.size > 0
- }
- dispatch(data: T) {
- this.listeners.forEach((listener) => listener(data))
- }
-
- on(listener: (data: T) => void, getDisposable?: true): IDisposable
- on(listener: (data: T) => void, getDisposable: false): undefined
- on(
- listener: (data: T) => void,
- getDisposable?: boolean
- ): IDisposable | undefined
- on(listener: (data: T) => void, getDisposable: boolean = true) {
- this.listeners.add(listener)
-
- if (getDisposable)
- return {
- dispose: () => {
- this.off(listener)
- },
- }
- }
-
- off(listener: (data: T) => void) {
- this.listeners.delete(listener)
- }
-
- once(listener: (data: T) => void, getDisposable = false) {
- const callback = (data: T) => {
- listener(data)
- this.off(callback)
- }
- return this.on(callback, getDisposable)
- }
-
- disposeListeners() {
- this.listeners = new Set()
- }
-}
diff --git a/src/components/Common/Event/EventSystem.ts b/src/components/Common/Event/EventSystem.ts
deleted file mode 100644
index b1e849e05..000000000
--- a/src/components/Common/Event/EventSystem.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * Trigger and react to events
- */
-
-import { EventDispatcher } from './EventDispatcher'
-
-export class EventSystem {
- protected events = new Map>()
- public readonly any = new EventDispatcher<[string, T]>()
-
- constructor(
- events: string[] | readonly string[] = [],
- protected autoCleanEmptyDispatchers = false
- ) {
- events.forEach((event) => this.create(event))
- }
-
- create(name: string) {
- const dispatcher = this.events.get(name)
- if (dispatcher !== undefined)
- throw new Error(
- `Dispatcher for event "${name}" is already defined.`
- )
-
- this.events.set(name, new EventDispatcher())
- return {
- dispose: () => {
- this.events.delete(name)
- },
- }
- }
- hasEvent(name: string) {
- return this.events.has(name)
- }
-
- protected getDispatcher(name: string) {
- const dispatcher = this.events.get(name)
- if (dispatcher === undefined)
- throw new Error(`No dispatcher defined for event "${name}".`)
-
- return dispatcher
- }
-
- dispatch(name: string, data: T) {
- this.any.dispatch([name, data])
- return this.getDispatcher(name).dispatch(data)
- }
- on(name: string, listener: (data: T) => void) {
- const dispatcher = this.getDispatcher(name)
- const disposable = dispatcher.on(listener)
-
- return {
- dispose: () => {
- disposable.dispose()
- if (this.autoCleanEmptyDispatchers && !dispatcher.hasListeners)
- this.events.delete(name)
- },
- }
- }
- off(name: string, listener: (data: T) => void) {
- const dispatcher = this.getDispatcher(name)
- dispatcher.off(listener)
- if (this.autoCleanEmptyDispatchers && !dispatcher.hasListeners)
- this.events.delete(name)
- }
- once(name: string, listener: (data: T) => void) {
- const dispatcher = this.getDispatcher(name)
-
- dispatcher.once((data: T) => {
- listener(data)
- if (this.autoCleanEmptyDispatchers && !dispatcher.hasListeners)
- this.events.delete(name)
- })
- }
-}
diff --git a/src/components/Common/Event/Signal.ts b/src/components/Common/Event/Signal.ts
deleted file mode 100644
index c7bf0a33f..000000000
--- a/src/components/Common/Event/Signal.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { EventDispatcher } from './EventDispatcher'
-import { IDisposable } from '/@/types/disposable'
-
-export class Signal extends EventDispatcher {
- protected firedTimes = 0
- protected data: T | undefined
-
- constructor(protected needsToFireAmount = 1) {
- super()
- }
-
- get fired() {
- return new Promise((resolve) => this.once(resolve, false))
- }
- get hasFired() {
- return this.firedTimes >= this.needsToFireAmount
- }
-
- setFiredTimes(firedTimes: number) {
- this.firedTimes = firedTimes
- }
-
- resetSignal() {
- this.data = undefined
- this.firedTimes = 0
- }
-
- dispatch(data: T) {
- if (this.firedTimes < this.needsToFireAmount) this.firedTimes++
- this.data = data
-
- if (this.hasFired) return super.dispatch(data)
- }
-
- on(listener: (data: T) => void, getDisposable?: true): IDisposable
- on(listener: (data: T) => void, getDisposable: false): undefined
- on(
- listener: (data: T) => void,
- getDisposable?: boolean
- ): IDisposable | undefined
- on(listener: (data: T) => void, getDisposable = true) {
- if (this.hasFired) listener(this.data!)
-
- return super.on(listener, getDisposable)
- }
-}
diff --git a/src/components/Common/Expandable.vue b/src/components/Common/Expandable.vue
new file mode 100644
index 000000000..1a4f7531c
--- /dev/null
+++ b/src/components/Common/Expandable.vue
@@ -0,0 +1,83 @@
+
+
+
+
(expanded = !expanded)"
+ >
+ {{ name }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Common/FileSystemDrop.vue b/src/components/Common/FileSystemDrop.vue
new file mode 100644
index 000000000..84dbf9251
--- /dev/null
+++ b/src/components/Common/FileSystemDrop.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+ {{ text }}
+
+
+
diff --git a/src/components/Common/FreeContextMenu.ts b/src/components/Common/FreeContextMenu.ts
new file mode 100644
index 000000000..ca9ba378c
--- /dev/null
+++ b/src/components/Common/FreeContextMenu.ts
@@ -0,0 +1,3 @@
+import { ref, Ref } from 'vue'
+
+export const openContextMenuId: Ref = ref('none')
diff --git a/src/components/Common/FreeContextMenu.vue b/src/components/Common/FreeContextMenu.vue
new file mode 100644
index 000000000..0cadcb450
--- /dev/null
+++ b/src/components/Common/FreeContextMenu.vue
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Common/GlobalMutex.ts b/src/components/Common/GlobalMutex.ts
deleted file mode 100644
index 208529610..000000000
--- a/src/components/Common/GlobalMutex.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { Mutex } from './Mutex'
-
-/**
- * A global mutex manages multiple different, keyed mutexes.
- */
-export class GlobalMutex {
- protected mutexMap = new Map()
-
- /**
- * Lock the mutex with the given key. Creates the mutex if it does not exist.
- *
- * @param key Mutex to lock
- */
- async lock(key: string) {
- let mutex = this.mutexMap.get(key)
- if (!mutex) {
- mutex = new Mutex()
- this.mutexMap.set(key, mutex)
- }
-
- await mutex.lock()
- }
-
- /**
- * Unlock the mutex with the given key.
- *
- * @throws If the mutex does not exist.
- * @param key
- */
- unlock(key: string) {
- const mutex = this.mutexMap.get(key)
- if (!mutex) {
- throw new Error('Trying to unlock a mutex that does not exist')
- }
-
- // Store whether mutex still has listeners
- const hasListeners = mutex.hasListeners()
-
- mutex.unlock()
-
- // Clean up map if no more listeners
- if (!hasListeners) {
- this.mutexMap.delete(key)
- }
- }
-}
diff --git a/src/components/Common/Icon.vue b/src/components/Common/Icon.vue
new file mode 100644
index 000000000..805c85043
--- /dev/null
+++ b/src/components/Common/Icon.vue
@@ -0,0 +1,132 @@
+
+
+ {{ resolveLegacyIcons(icon) }}
+
+
+
+
+
+
+
+
diff --git a/src/components/Common/IconButton.vue b/src/components/Common/IconButton.vue
new file mode 100644
index 000000000..47480b081
--- /dev/null
+++ b/src/components/Common/IconButton.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/src/components/Common/Info.vue b/src/components/Common/Info.vue
new file mode 100644
index 000000000..11163fafe
--- /dev/null
+++ b/src/components/Common/Info.vue
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+ {{ text }}
+
+
+
diff --git a/src/components/Common/InformativeToggle.vue b/src/components/Common/InformativeToggle.vue
new file mode 100644
index 000000000..fe6105042
--- /dev/null
+++ b/src/components/Common/InformativeToggle.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+ Active
+
+
+
+ {{ description }}
+
+
+
+
+
+
+
diff --git a/src/components/Common/LabeledAutocompleteInput.vue b/src/components/Common/LabeledAutocompleteInput.vue
new file mode 100644
index 000000000..b4b877ffb
--- /dev/null
+++ b/src/components/Common/LabeledAutocompleteInput.vue
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
diff --git a/src/components/Common/LabeledDropdown.vue b/src/components/Common/LabeledDropdown.vue
new file mode 100644
index 000000000..47a0aa964
--- /dev/null
+++ b/src/components/Common/LabeledDropdown.vue
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
diff --git a/src/components/Common/LabeledInput.vue b/src/components/Common/LabeledInput.vue
new file mode 100644
index 000000000..3efb79f5f
--- /dev/null
+++ b/src/components/Common/LabeledInput.vue
@@ -0,0 +1,50 @@
+
+
+ {{ label }}
+
+
+
+
+
diff --git a/src/components/Common/LabeledTextInput.vue b/src/components/Common/LabeledTextInput.vue
new file mode 100644
index 000000000..8227be73e
--- /dev/null
+++ b/src/components/Common/LabeledTextInput.vue
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ error ? t(error) : '' }}
+
+
diff --git a/src/components/Common/Legacy/LegacyDropdown.vue b/src/components/Common/Legacy/LegacyDropdown.vue
new file mode 100644
index 000000000..fa86b5ae6
--- /dev/null
+++ b/src/components/Common/Legacy/LegacyDropdown.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
diff --git a/src/components/Common/Logo.vue b/src/components/Common/Logo.vue
new file mode 100644
index 000000000..e904f5d92
--- /dev/null
+++ b/src/components/Common/Logo.vue
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/src/components/Common/Mutex.ts b/src/components/Common/Mutex.ts
deleted file mode 100644
index b457a7d8d..000000000
--- a/src/components/Common/Mutex.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * An instance of the Mutex class ensures that calls to specific APIs happen sequentially instead of in parallel.
- */
-export class Mutex {
- protected listeners: (() => void)[] = []
- protected isLocked = false
-
- constructor() {}
-
- /**
- * Lock the mutex. If it is already locked, the function will wait until it is unlocked.
- *
- * @returns When the mutex is unlocked
- */
- lock() {
- return new Promise(async (resolve, reject) => {
- if (this.isLocked) {
- this.listeners.push(() => {
- this.isLocked = true
- resolve()
- })
- } else {
- this.isLocked = true
- resolve()
- }
- })
- }
-
- /**
- * Unlock the mutex.
- *
- * @throws If the mutex is not locked.
- */
- unlock() {
- if (!this.isLocked) {
- throw new Error('Trying to unlock a mutex that is not locked')
- }
-
- this.isLocked = false
-
- if (this.listeners.length > 0) {
- const listener = this.listeners.shift()!
-
- listener()
- }
- }
-
- hasListeners() {
- return this.listeners.length > 0
- }
-}
diff --git a/src/components/Common/PersistentQueue.ts b/src/components/Common/PersistentQueue.ts
deleted file mode 100644
index a6d7409ec..000000000
--- a/src/components/Common/PersistentQueue.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import { App } from '/@/App'
-import { Signal } from './Event/Signal'
-import { Queue } from './Queue'
-import { set, markRaw } from 'vue'
-import { dirname } from '/@/utils/path'
-
-export class PersistentQueue extends Signal> {
- protected queue!: Queue
- protected app: App
-
- constructor(
- app: App,
- protected maxSize: number,
- protected savePath: string,
- callSetup = true
- ) {
- super()
- set(this, 'queue', new Queue(maxSize))
- this.app = markRaw(app)
-
- if (callSetup) this.setup()
- }
-
- async setup() {
- await this.app.fileSystem.fired
-
- let data = []
- try {
- data = await this.app.fileSystem.readJSON(this.savePath)
- } catch {}
-
- this.queue.fromArray(data)
- this.dispatch(this.queue)
- }
-
- protected isEquals(e1: T, e2: T) {
- return e1 === e2
- }
-
- keep(cb: (e: T) => boolean) {
- const queue = new Queue(this.maxSize)
-
- this.elements
- .filter((e) => cb(e))
- .forEach((e) => queue.add(e, this.isEquals.bind(this)))
-
- this.queue = queue
- }
-
- async add(e: T) {
- await this.fired
-
- this.queue.add(e, this.isEquals.bind(this))
-
- await this.saveQueue()
- }
- async remove(e: T) {
- await this.fired
-
- this.queue.remove(e, this.isEquals.bind(this))
-
- await this.saveQueue()
- }
- clear() {
- this.queue.clear()
- return this.saveQueue()
- }
- protected async saveQueue() {
- await this.app.fileSystem.mkdir(dirname(this.savePath), {
- recursive: true,
- })
- await this.app.fileSystem.writeFile(this.savePath, this.queue.toJSON())
- }
-
- get elements() {
- return this.queue.elements
- }
-}
diff --git a/src/components/Common/Progress.ts b/src/components/Common/Progress.ts
deleted file mode 100644
index 6110246ec..000000000
--- a/src/components/Common/Progress.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { EventDispatcher } from '/@/components/Common/Event/EventDispatcher'
-
-export class Progress extends EventDispatcher<[number, number]> {
- constructor(
- protected current: number,
- protected total: number,
- protected prevTotal: number
- ) {
- super()
- }
-
- addToCurrent(value?: number) {
- this.current += value ?? 1
- this.dispatch([this.getCurrent(), this.getTotal()])
- }
- addToTotal(value?: number) {
- this.total += value ?? 1
- this.dispatch([this.getCurrent(), this.getTotal()])
- }
-
- getTotal() {
- return this.total > this.prevTotal ? this.total : this.prevTotal
- }
- getCurrent() {
- return this.current
- }
-
- get isDone() {
- return this.getCurrent() === this.getTotal()
- }
-
- setTotal(val: number) {
- this.total = val
- }
-}
diff --git a/src/components/Common/Progress.vue b/src/components/Common/Progress.vue
new file mode 100644
index 000000000..1207b5733
--- /dev/null
+++ b/src/components/Common/Progress.vue
@@ -0,0 +1,63 @@
+
+
+
+
+
diff --git a/src/components/Common/Queue.ts b/src/components/Common/Queue.ts
deleted file mode 100644
index 9dd2d7583..000000000
--- a/src/components/Common/Queue.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-export class Queue {
- protected array: T[] = []
- constructor(
- protected maxSize: number = Infinity,
- iterable?: Iterable | null | undefined
- ) {
- for (const e of iterable ?? []) this.add(e)
- }
-
- add(
- element: T,
- isEquals: (e1: T, e2: T) => boolean = this.isEquals.bind(this)
- ) {
- const index = this.array.findIndex((e) => isEquals(e, element))
- if (index > -1) {
- this.array.splice(index, 1)
- this.array.unshift(element)
- return this
- }
-
- if (this.array.length >= this.maxSize) this.array.pop()
- this.array.unshift(element)
-
- return this
- }
- remove(
- element: T,
- isEquals: (e1: T, e2: T) => boolean = this.isEquals.bind(this)
- ) {
- const index = this.array.findIndex((e) => isEquals(e, element))
- if (index > -1) this.array.splice(index, 1)
-
- return this
- }
- clear() {
- this.array = []
- }
- protected isEquals(e1: T, e2: T) {
- return e1 === e2
- }
-
- toJSON() {
- return JSON.stringify(this.array)
- }
- fromArray(arr: any) {
- this.array = arr
- }
- [Symbol.iterator]() {
- return this.array.values()
- }
-
- get size() {
- return this.maxSize
- }
- get elementCount() {
- return this.array.length
- }
- get elements() {
- return [...this.array]
- }
-}
diff --git a/src/components/Common/SubMenu.vue b/src/components/Common/SubMenu.vue
new file mode 100644
index 000000000..8f25e3ba6
--- /dev/null
+++ b/src/components/Common/SubMenu.vue
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Common/Switch.vue b/src/components/Common/Switch.vue
new file mode 100644
index 000000000..65dd575d6
--- /dev/null
+++ b/src/components/Common/Switch.vue
@@ -0,0 +1,37 @@
+
+
+
+
+
diff --git a/src/components/Common/TextButton.vue b/src/components/Common/TextButton.vue
new file mode 100644
index 000000000..5cdada355
--- /dev/null
+++ b/src/components/Common/TextButton.vue
@@ -0,0 +1,47 @@
+
+
+
+
+
diff --git a/src/components/Common/Warning.vue b/src/components/Common/Warning.vue
new file mode 100644
index 000000000..4f5a7c5cc
--- /dev/null
+++ b/src/components/Common/Warning.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+ {{ t(text) }}
+
+
+
diff --git a/src/components/Common/WindowResize.ts b/src/components/Common/WindowResize.ts
deleted file mode 100644
index de20893ff..000000000
--- a/src/components/Common/WindowResize.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { EventDispatcher } from '/@/components/Common/Event/EventDispatcher'
-import { debounce } from 'lodash-es'
-import { reactive } from 'vue'
-import { App } from '/@/App'
-
-export class WindowResize extends EventDispatcher<[number, number]> {
- public readonly state = reactive({
- currentHeight: window.innerHeight,
- currentWidth: window.innerWidth,
- })
-
- constructor() {
- super()
-
- window.addEventListener(
- 'resize',
- debounce(() => this.dispatch(), 50, { trailing: true })
- )
-
- this.on(([newWidth, newHeight]) => {
- this.state.currentWidth = newWidth
- this.state.currentHeight = newHeight
- })
-
- App.getApp().then((app) =>
- app.projectManager.projectReady.fired.then(() => this.dispatch())
- )
- }
-
- dispatch() {
- super.dispatch([window.innerWidth, window.innerHeight])
- }
-}
diff --git a/src/components/Compiler/Actions/RecompileChanges.ts b/src/components/Compiler/Actions/RecompileChanges.ts
deleted file mode 100644
index 44676e1f3..000000000
--- a/src/components/Compiler/Actions/RecompileChanges.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { App } from '/@/App'
-import { SimpleAction } from '../../Actions/SimpleAction'
-
-export const recompileChangesConfig = {
- icon: 'mdi-cog-outline',
- name: 'actions.recompileChanges.name',
- description: 'actions.recompileChanges.description',
- onTrigger: async () => {
- const app = await App.getApp()
- const project = app.project
-
- project.packIndexer.deactivate()
- await project.packIndexer.activate(true)
-
- const [changedFiles, deletedFiles] = await project.packIndexer.fired
-
- await project.compilerService.start(changedFiles, deletedFiles)
- },
-}
-
-export const recompileChangesAction = new SimpleAction(recompileChangesConfig)
diff --git a/src/components/Compiler/Actions/RestartWatchMode.ts b/src/components/Compiler/Actions/RestartWatchMode.ts
deleted file mode 100644
index 02c5f54ae..000000000
--- a/src/components/Compiler/Actions/RestartWatchMode.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { ConfirmationWindow } from '/@/components/Windows/Common/Confirm/ConfirmWindow'
-import { App } from '/@/App'
-import { SimpleAction } from '../../Actions/SimpleAction'
-
-export const restartWatchModeConfig = (includeDescription = true) => ({
- icon: 'mdi-restart-alert',
- name: 'packExplorer.restartWatchMode.name',
- description: includeDescription
- ? 'packExplorer.restartWatchMode.description'
- : undefined,
- onTrigger: () => {
- new ConfirmationWindow({
- description: 'packExplorer.restartWatchMode.confirmDescription',
- height: 168,
- onConfirm: async () => {
- const app = await App.getApp()
-
- await Promise.all([
- app.project.fileSystem.unlink('.bridge/.lightningCache'),
- app.project.fileSystem.unlink('.bridge/.compilerFiles'),
- ])
- await app.project.fileSystem.writeFile(
- '.bridge/.restartWatchMode',
- ''
- )
-
- app.actionManager.trigger('bridge.action.refreshProject')
- },
- })
- },
-})
-
-export const restartWatchModeAction = new SimpleAction(restartWatchModeConfig())
diff --git a/src/components/Compiler/Compiler.ts b/src/components/Compiler/Compiler.ts
deleted file mode 100644
index 57e5eb799..000000000
--- a/src/components/Compiler/Compiler.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import type { DashService } from './Worker/Service'
-import CompilerWorker from './Worker/Service?worker'
-import { wrap } from 'comlink'
-import { setupWorker } from '/@/utils/worker/setup'
-
-const worker = new CompilerWorker()
-export const DashCompiler = wrap(worker)
-
-setupWorker(worker)
diff --git a/src/components/Compiler/LogPanel/Panel.tsx b/src/components/Compiler/LogPanel/Panel.tsx
deleted file mode 100644
index 6f7624899..000000000
--- a/src/components/Compiler/LogPanel/Panel.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { Component, For, Show } from 'solid-js'
-import { useTranslations } from '../../Composables/useTranslations'
-import { SolidIcon } from '../../Solid/Icon/SolidIcon'
-import { toSignal } from '../../Solid/toSignal'
-import { CompilerWindow } from '../Window/Window'
-import { ILogData } from '../Worker/Console'
-
-export const LogPanel: Component<{
- compilerWindow: CompilerWindow
-}> = (props) => {
- const { t } = useTranslations()
- const [log] = toSignal<[string, ILogData][]>(
- props.compilerWindow.getCategories().logs.data
- )
-
- const icon = (type: string | undefined) => {
- if (type === 'info') return 'mdi-information-outline'
- if (type === 'warning') return 'mdi-alert-outline'
- if (type === 'error') return 'mdi-alert-circle-outline'
-
- return null
- }
-
- return (
- <>
-
-
- {t('bottomPanel.compiler.noLogs')}
-
-
-
-
- {([logEntry, { time, type }]) => (
-
-
- [{time}]
-
-
-
-
-
-
-
- {logEntry}
-
-
- )}
-
- >
- )
-}
diff --git a/src/components/Compiler/Sidebar/create.ts b/src/components/Compiler/Sidebar/create.ts
deleted file mode 100644
index b3fcce498..000000000
--- a/src/components/Compiler/Sidebar/create.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import { proxy } from 'comlink'
-import { Project } from '/@/components/Projects/Project/Project'
-import { createSidebar } from '../../Sidebar/SidebarElement'
-import { App } from '/@/App'
-
-const saveState = new Map()
-interface ISidebarState {
- projectName: string
- lastReadCount: number
- currentCount: number
-}
-
-export function createCompilerSidebar() {
- let state: ISidebarState = {
- projectName: '',
- lastReadCount: 0,
- currentCount: 0,
- }
- let selectedCategory: string | undefined = undefined
- let isWindowOpen = false
-
- const removeListeners = async (project: Project) => {
- await project.compilerReady.fired
- project.compilerService.removeConsoleListeners()
- }
- const listenForLogChanges = async (
- project: Project,
- resetListeners = false
- ) => {
- if (resetListeners) await removeListeners(project)
-
- await project.compilerReady.fired
- project.compilerService.onConsoleUpdate(
- proxy(async () => {
- const allLogs = await project.compilerService.getCompilerLogs()
- const logs = allLogs.filter(
- ([_, { type }]) => type === 'error' || type === 'warning'
- )
-
- const app = await App.getApp()
- app.windows.compilerWindow.getCategories().logs.data.value =
- allLogs
-
- state.currentCount = logs.length
-
- // User currently still has logs tab selected and therefore sees the new logs
- if (isWindowOpen && selectedCategory === 'logs')
- state.lastReadCount = state.currentCount
- updateBadge()
- })
- )
- }
- const updateBadge = () => {
- sidebar.attachBadge({
- count: state.currentCount - state.lastReadCount,
- color: 'error',
- })
- }
-
- const sidebar = createSidebar({
- id: 'compiler',
- displayName: 'sidebar.compiler.name',
- icon: 'mdi-cogs',
- disabled: () => App.instance.isNoProjectSelected,
- /**
- * The compiler window is doing more harm than good on mobile (confusion with app settings) so
- * we are now disabling it by default.
- * Additionally, manual production builds are also pretty much useless as they are internal to bridge. and can only be
- * accessed over the "Open Project Folder" button within the project explorer context menu
- */
- defaultVisibility: !App.instance.mobile.isCurrentDevice(),
- onClick: async () => {
- const app = await App.getApp()
- const compilerWindow = app.windows.compilerWindow
-
- const disposable = compilerWindow.activeCategoryChanged.on(
- (selected) => {
- selectedCategory = selected
-
- // User switched to logs tab and therefore saw all previously unread logs
- if (selected === 'logs') {
- state.lastReadCount = state.currentCount
- updateBadge()
- }
- }
- )
-
- /**
- * We manually clear the signal here because that normally happens inside of the window.open() method
- * which is triggered after the listener registration in this case
- */
- compilerWindow.resetSignal()
- compilerWindow.once(async () => {
- disposable.dispose()
- isWindowOpen = false
- listenForLogChanges(
- await App.getApp().then((app) => app.project)
- )
- })
-
- isWindowOpen = true
- await compilerWindow.open()
-
- // User opened window and logs tab is still selected
- if (selectedCategory === 'logs') {
- state.lastReadCount = state.currentCount
- updateBadge()
- }
- },
- })
-
- App.eventSystem.on('projectChanged', async (project: Project) => {
- saveState.set(state.projectName, state)
-
- state = saveState.get(project.name) ?? {
- projectName: project.name,
- lastReadCount: 0,
- currentCount: 0,
- }
- updateBadge()
-
- await listenForLogChanges(project, true)
- })
-}
diff --git a/src/components/Compiler/Window/BuildProfiles.vue b/src/components/Compiler/Window/BuildProfiles.vue
deleted file mode 100644
index c88a2b2cf..000000000
--- a/src/components/Compiler/Window/BuildProfiles.vue
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
diff --git a/src/components/Compiler/Window/Content.vue b/src/components/Compiler/Window/Content.vue
deleted file mode 100644
index a2d3bb0e7..000000000
--- a/src/components/Compiler/Window/Content.vue
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/src/components/Compiler/Window/Logs.vue b/src/components/Compiler/Window/Logs.vue
deleted file mode 100644
index dcd22a2f7..000000000
--- a/src/components/Compiler/Window/Logs.vue
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
- {{ getIconData(type).icon }}
-
-
-
-
- {{ line }}
-
-
-
-
-
-
- {{ t('sidebar.compiler.categories.logs.noLogs') }}
-
-
-
-
diff --git a/src/components/Compiler/Window/OutputFolders.vue b/src/components/Compiler/Window/OutputFolders.vue
deleted file mode 100644
index a1d088936..000000000
--- a/src/components/Compiler/Window/OutputFolders.vue
+++ /dev/null
@@ -1,68 +0,0 @@
-
-
-
-
-
- mdi-folder-sync-outline
- {{ t('comMojang.linkFolder') }}
-
-
-
- {{ t('comMojang.linkedFolder') }}:
-
-
- {{ path }}
-
-
-
-
-
diff --git a/src/components/Compiler/Window/WatchMode.vue b/src/components/Compiler/Window/WatchMode.vue
deleted file mode 100644
index 329396438..000000000
--- a/src/components/Compiler/Window/WatchMode.vue
+++ /dev/null
@@ -1,93 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ icon }}
-
-
- {{
- t(
- `sidebar.compiler.categories.watchMode.settings.${id}.name`
- )
- }}
-
-
-
-
-
- {{
- t(
- `sidebar.compiler.categories.watchMode.settings.${id}.description`
- )
- }}
-
-
-
-
-
-
-
diff --git a/src/components/Compiler/Window/WatchMode/SettingSheet.vue b/src/components/Compiler/Window/WatchMode/SettingSheet.vue
deleted file mode 100644
index f2a841948..000000000
--- a/src/components/Compiler/Window/WatchMode/SettingSheet.vue
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/Compiler/Window/Window.ts b/src/components/Compiler/Window/Window.ts
deleted file mode 100644
index 1f907cc30..000000000
--- a/src/components/Compiler/Window/Window.ts
+++ /dev/null
@@ -1,296 +0,0 @@
-import { Sidebar, SidebarItem } from '/@/components/Windows/Layout/Sidebar'
-import Content from './Content.vue'
-import BuildProfiles from './BuildProfiles.vue'
-import Logs from './Logs.vue'
-import OutputFolders from './OutputFolders.vue'
-import WatchMode from './WatchMode.vue'
-import { IActionConfig, SimpleAction } from '/@/components/Actions/SimpleAction'
-import { App } from '/@/App'
-import { markRaw, ref } from 'vue'
-import json5 from 'json5'
-import { proxy } from 'comlink'
-import { InfoPanel, IPanelOptions } from '/@/components/InfoPanel/InfoPanel'
-import { isUsingFileSystemPolyfill } from '/@/components/FileSystem/Polyfill'
-import { EventDispatcher } from '/@/components/Common/Event/EventDispatcher'
-import { restartWatchModeAction } from '../Actions/RestartWatchMode'
-import { SettingsWindow } from '../../Windows/Settings/SettingsWindow'
-import { LocaleManager } from '../../Locales/Manager'
-import { NewBaseWindow } from '../../Windows/NewBaseWindow'
-
-export class CompilerWindow extends NewBaseWindow {
- protected sidebar = new Sidebar([], false)
- protected categories = markRaw<
- Record
- >({
- watchMode: {
- component: WatchMode,
- data: ref({
- shouldSaveSettings: false,
- }),
- },
- buildProfiles: {
- component: BuildProfiles,
- data: ref(null),
- },
- outputFolders: {
- component: OutputFolders,
- data: ref(null),
- },
- logs: {
- component: Logs,
- data: ref(null),
- },
- })
- public readonly activeCategoryChanged = markRaw(
- new EventDispatcher()
- )
- protected lastUsedBuildProfile: SimpleAction | null = null
- protected runLastProfileAction = new SimpleAction({
- name: 'sidebar.compiler.actions.runLastProfile',
- icon: 'mdi-play',
- color: 'accent',
- onTrigger: () => {
- if (!this.lastUsedBuildProfile)
- throw new Error(
- `Invalid state: Triggered runLastProfileAction without a last used build profile`
- )
- this.lastUsedBuildProfile.trigger()
- },
- })
-
- constructor() {
- super(Content, false, true)
- this.defineWindow()
-
- const reloadAction = new SimpleAction({
- icon: 'mdi-refresh',
- name: 'general.reload',
- color: 'accent',
- onTrigger: () => {
- this.reload()
- },
- })
- this.state.actions.push(reloadAction)
-
- const clearConsoleAction = new SimpleAction({
- icon: 'mdi-close-circle-outline',
- name: 'general.clear',
- color: 'accent',
- onTrigger: async () => {
- const app = await App.getApp()
- app.project.compilerService.clearCompilerLogs()
- this.categories.logs.data.value = []
- },
- })
- this.sidebar.on((selected) => {
- this.activeCategoryChanged.dispatch(selected)
-
- if (selected === 'logs')
- this.state.actions.splice(
- this.state.actions.indexOf(reloadAction),
- 0,
- clearConsoleAction
- )
- else
- this.state.actions = this.state.actions.filter(
- (a) => a !== clearConsoleAction
- )
- })
- // Close this window whenever the watch mode is restarted
- restartWatchModeAction.on(() => this.close())
-
- App.getApp().then(() => {
- this.sidebar.addElement(
- new SidebarItem({
- id: 'watchMode',
- text: LocaleManager.translate(
- 'sidebar.compiler.categories.watchMode.name'
- ),
- color: 'primary',
- icon: 'mdi-eye-outline',
- })
- )
- this.sidebar.addElement(
- new SidebarItem({
- id: 'buildProfiles',
- text: LocaleManager.translate(
- 'sidebar.compiler.categories.profiles'
- ),
- color: 'primary',
- icon: 'mdi-motion-play-outline',
- })
- )
- this.sidebar.addElement(
- new SidebarItem({
- id: 'outputFolders',
- text: LocaleManager.translate(
- 'sidebar.compiler.categories.outputFolders'
- ),
- color: 'primary',
- icon: 'mdi-folder-open-outline',
- })
- )
- this.sidebar.addElement(
- new SidebarItem({
- id: 'logs',
- text: LocaleManager.translate(
- 'sidebar.compiler.categories.logs.name'
- ),
- color: 'primary',
- icon: 'mdi-format-list-text',
- })
- )
- this.sidebar.setDefaultSelected()
- })
- }
-
- getCategories() {
- return this.categories
- }
-
- async reload() {
- const app = await App.getApp()
-
- this.categories.buildProfiles.data.value = await this.loadProfiles()
- this.categories.logs.data.value =
- await app.project.compilerService.getCompilerLogs()
- this.categories.outputFolders.data.value =
- await this.loadOutputFolders()
- }
- async open() {
- const app = await App.getApp()
-
- await this.reload()
- await app.project.compilerService.onConsoleUpdate(
- proxy(async () => {
- this.categories.logs.data.value =
- await app.project.compilerService.getCompilerLogs()
- })
- )
-
- if (this.lastUsedBuildProfile)
- this.state.actions.unshift(this.runLastProfileAction)
-
- super.open()
- }
- async close() {
- const app = await App.getApp()
- await app.project.compilerService.removeConsoleListeners()
-
- this.state.actions = this.state.actions.filter(
- (a) => a !== this.runLastProfileAction
- )
-
- super.close()
-
- if (this.categories.watchMode.data.value.shouldSaveSettings) {
- this.categories.watchMode.data.value.shouldSaveSettings = false
- await SettingsWindow.saveSettings()
- }
- }
-
- async loadProfiles() {
- const app = await App.getApp()
- const project = app.project
-
- const configDir = await project.fileSystem.getDirectoryHandle(
- `.bridge/compiler`,
- { create: true }
- )
-
- const actions: IActionConfig[] = [
- {
- icon: 'mdi-cog',
- name: 'sidebar.compiler.default.name',
- description: 'sidebar.compiler.default.description',
- onTrigger: async (action) => {
- this.close()
- this.lastUsedBuildProfile = action
-
- const service = await project.createDashService(
- 'production'
- )
- await service.build()
- },
- },
- ]
-
- for await (const entry of configDir.values()) {
- if (
- entry.kind !== 'file' ||
- entry.name === '.DS_Store' ||
- entry.name === 'default.json' // Default compiler config already gets triggerd with the default action above (outside of the loop)
- )
- continue
- const file = await entry.getFile()
-
- let config
- try {
- config = json5.parse(await file.text())
- } catch {
- continue
- }
-
- actions.push({
- icon: config.icon,
- name: config.name,
- description: config.description,
- onTrigger: async (action) => {
- this.close()
- this.lastUsedBuildProfile = action
-
- const service = await project.createDashService(
- 'production',
- `${project.projectPath}/.bridge/compiler/${entry.name}`
- )
- await service.build()
- },
- })
- }
-
- return actions.map((action) => new SimpleAction(action))
- }
-
- async loadOutputFolders() {
- const app = await App.getApp()
-
- const comMojang = app.comMojang
- const { hasComMojang, didDenyPermission } = comMojang.status
- let panelConfig: IPanelOptions
-
- if (
- !import.meta.env.VITE_IS_TAURI_APP &&
- isUsingFileSystemPolyfill.value
- ) {
- panelConfig = {
- text: 'comMojang.status.notAvailable',
- type: 'error',
- isDismissible: false,
- }
- } else if (!hasComMojang && didDenyPermission) {
- panelConfig = {
- text: 'comMojang.status.deniedPermission',
- type: 'warning',
- isDismissible: false,
- }
- } else if (hasComMojang && !didDenyPermission) {
- panelConfig = {
- text: 'comMojang.status.sucess',
- type: 'success',
- isDismissible: false,
- }
- } else if (!hasComMojang) {
- panelConfig = {
- text: import.meta.env.VITE_IS_TAURI_APP
- ? 'comMojang.status.notSetupTauri'
- : 'comMojang.status.notSetup',
- type: 'error',
- isDismissible: false,
- }
- } else {
- throw new Error(`Invalid com.mojang status`)
- }
-
- return new InfoPanel(panelConfig)
- }
-}
diff --git a/src/components/Compiler/Worker/Console.ts b/src/components/Compiler/Worker/Console.ts
deleted file mode 100644
index e5792ddc4..000000000
--- a/src/components/Compiler/Worker/Console.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { Console } from 'dash-compiler'
-
-export interface ILogData {
- // Format: HH:MM:SS
- time: string
- type?: 'info' | 'error' | 'warning'
-}
-
-export class ForeignConsole extends Console {
- protected logs: [string, ILogData][] = []
- protected changeListeners: (() => void)[] = []
-
- getLogs() {
- return this.logs
- }
-
- protected basicLog(
- message: any,
- { type }: { type?: 'info' | 'error' | 'warning' } = {}
- ) {
- switch (type) {
- case 'warning':
- console.warn(message)
- break
- case 'error':
- console.error(message)
- break
- case 'info':
- console.info(message)
- break
- default:
- console.log(message)
- break
- }
- if (message instanceof Error) message = message.message
-
- this.logs.unshift([
- typeof message === 'string' ? message : JSON.stringify(message),
- { time: new Date().toLocaleTimeString(), type },
- ])
- this.logsChanged()
- }
-
- addChangeListener(cb: () => void) {
- this.changeListeners.push(cb)
- }
- removeChangeListeners() {
- this.changeListeners = []
- }
- logsChanged() {
- this.changeListeners.forEach((cb) => cb())
- }
-
- clear() {
- this.logs = []
- this.logsChanged()
- }
- log(...args: any[]) {
- args.forEach((arg) => this.basicLog(arg))
- }
- info(...args: any[]) {
- args.forEach((arg) => this.basicLog(arg, { type: 'info' }))
- }
- warn(...args: any[]) {
- args.forEach((arg) => this.basicLog(arg, { type: 'warning' }))
- }
- error(...args: any[]) {
- args.forEach((arg) => this.basicLog(arg, { type: 'error' }))
- }
-}
diff --git a/src/components/Compiler/Worker/FileSystem.ts b/src/components/Compiler/Worker/FileSystem.ts
deleted file mode 100644
index 0d5b8b1d8..000000000
--- a/src/components/Compiler/Worker/FileSystem.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { FileSystem } from 'dash-compiler'
-import { IDirEntry } from 'dash-compiler/dist/FileSystem/FileSystem'
-import { AnyDirectoryHandle } from '../../FileSystem/Types'
-import { FileSystem as BridgeFileSystem } from '/@/components/FileSystem/FileSystem'
-
-export class DashFileSystem extends FileSystem {
- protected internalFs: BridgeFileSystem
-
- constructor(baseDirectory: AnyDirectoryHandle) {
- super()
- this.internalFs = new BridgeFileSystem(baseDirectory)
- }
-
- get internal() {
- return this.internalFs
- }
-
- readJson(path: string) {
- return this.internalFs.readJSON(path)
- }
- async writeJson(path: string, content: any, beautify?: boolean) {
- await this.internalFs.writeJSON(path, content, beautify)
- }
- async readFile(path: string): Promise {
- const file = await this.internalFs.readFile(path)
- return file.isVirtual ? await file.toBlobFile() : file
- }
- async writeFile(path: string, content: string | Uint8Array) {
- await this.internalFs.writeFile(path, content)
- }
- // async copyFile(from: string, to: string, outputFs = this) {
- // const [fromHandle, toHandle] = await Promise.all([
- // this.internalFs.getFileHandle(from),
- // outputFs.internalFs.getFileHandle(to, true),
- // ])
-
- // const [writable, fromFile] = await Promise.all([
- // toHandle.createWritable({ keepExistingData: true }),
- // fromHandle.getFile(),
- // ])
-
- // await writable.write(fromFile)
- // await writable.close()
- // }
-
- mkdir(path: string) {
- return this.internalFs.mkdir(path)
- }
- unlink(path: string) {
- return this.internalFs.unlink(path)
- }
- async allFiles(path: string) {
- return (await this.internalFs.readFilesFromDir(path)).map(
- (file) => file.path
- )
- }
- readdir(path: string): Promise {
- return this.internalFs.readdir(path, { withFileTypes: true })
- }
- async lastModified(filePath: string) {
- return 0
- }
-}
diff --git a/src/components/Compiler/Worker/Plugins/CustomCommands/generateSchemas.ts b/src/components/Compiler/Worker/Plugins/CustomCommands/generateSchemas.ts
deleted file mode 100644
index c5d989b77..000000000
--- a/src/components/Compiler/Worker/Plugins/CustomCommands/generateSchemas.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { Command, DefaultConsole } from 'dash-compiler'
-import { App } from '/@/App'
-import { JsRuntime } from '/@/components/Extensions/Scripts/JsRuntime'
-import { AnyDirectoryHandle } from '/@/components/FileSystem/Types'
-import { iterateDir, iterateDirParallel } from '/@/utils/iterateDir'
-
-// TODO: Rewrite this to properly cache evaluated scripts until they are changed by the user
-// See how it's done for custom components already!
-export async function generateCommandSchemas() {
- const app = await App.getApp()
- const project = app.project
- const jsRuntime = new JsRuntime()
- await (
- await project.compilerService.completedStartUp
- ).fired
-
- const v1CompatMode = project.config.get().bridge?.v1CompatMode ?? false
- const fromFilePath = project.config.resolvePackPath(
- 'behaviorPack',
- 'commands'
- )
-
- let baseDir: AnyDirectoryHandle
- try {
- baseDir = await app.fileSystem.getDirectoryHandle(fromFilePath)
- } catch {
- return []
- }
-
- const schemas: any[] = []
-
- await iterateDirParallel(
- baseDir,
- async (fileHandle, filePath) => {
- let fileContent = await fileHandle
- .getFile()
- .then(async (file) => new Uint8Array(await file.arrayBuffer()))
-
- // Only transform file if it's a TypeScript file
- // TODO: Change back to always go through compiler pipeline once we cache the evaluated schemas
- // This is too slow at the moment
- if (filePath.endsWith('.ts')) {
- fileContent = (
- await project.compilerService.compileFile(
- filePath,
- fileContent
- )
- )[1]
- }
-
- const file = new File([fileContent], fileHandle.name)
- const command = new Command(
- new DefaultConsole(),
- await file.text(),
- 'development',
- v1CompatMode
- )
-
- await command.load(jsRuntime, filePath, 'client').catch((err) => {
- console.error(`Failed to load command "${filePath}": ${err}`)
- })
-
- schemas.push(...command.getSchema())
- },
- undefined,
- fromFilePath
- )
-
- return schemas
-}
diff --git a/src/components/Compiler/Worker/Plugins/CustomComponent/ComponentSchemas.ts b/src/components/Compiler/Worker/Plugins/CustomComponent/ComponentSchemas.ts
deleted file mode 100644
index fd49f394e..000000000
--- a/src/components/Compiler/Worker/Plugins/CustomComponent/ComponentSchemas.ts
+++ /dev/null
@@ -1,184 +0,0 @@
-import { Remote } from 'comlink'
-import { Component, DefaultConsole } from 'dash-compiler'
-import { DashService } from '../../Service'
-import { App } from '/@/App'
-import { JsRuntime } from '/@/components/Extensions/Scripts/JsRuntime'
-import { AnyDirectoryHandle } from '/@/components/FileSystem/Types'
-import { VirtualFile } from '/@/components/FileSystem/Virtual/File'
-import { Project } from '/@/components/Projects/Project/Project'
-import { IDisposable } from '/@/types/disposable'
-import { iterateDir, iterateDirParallel } from '/@/utils/iterateDir'
-
-export const supportsCustomComponents = ['block', 'item', 'entity']
-export type TComponentFileType = typeof supportsCustomComponents[number]
-export class ComponentSchemas {
- protected schemas: Record> = {
- block: {},
- item: {},
- entity: {},
- }
- protected schemaLookup = new Map()
- protected disposables?: IDisposable[]
- protected dash?: Remote
-
- constructor(protected project: Project) {}
-
- get(fileType: TComponentFileType) {
- return this.schemas[fileType]
- }
-
- async activate() {
- this.dash = await this.project.createDashService('development')
-
- this.disposables = [
- App.eventSystem.on(
- 'fileSave',
- async ([filePath, file]: [string, File]) => {
- const app = await App.getApp()
- const project = app.project
-
- const componentPath = project.config.resolvePackPath(
- 'behaviorPack',
- 'components'
- )
-
- const v1CompatMode =
- project.config.get().bridge?.v1CompatMode ?? false
-
- if (!filePath.startsWith(componentPath)) return
- const jsRuntime = new JsRuntime()
-
- const fileType = filePath
- .replace(componentPath + '/', '')
- .split('/')[0] as TComponentFileType
- const isSupportedFileType =
- supportsCustomComponents.includes(fileType)
-
- if (!isSupportedFileType && v1CompatMode) {
- // This is not a supported file type but v1CompatMode is enabled,
- // meaning that we emulate v1's behavior where components could be placed anywhere
- await Promise.all(
- supportsCustomComponents.map((fileType) =>
- this.evalComponentSchema(
- jsRuntime,
- fileType,
- filePath,
- file,
- true
- )
- )
- )
- } else if (isSupportedFileType) {
- // No v1CompatMode but a valid file type which supports custom components to work with
- await this.evalComponentSchema(
- jsRuntime,
- fileType,
- filePath,
- file,
- v1CompatMode
- )
- }
- }
- ),
- App.eventSystem.on('fileUnlinked', (filePath: string) => {
- const [fileType, componentName] =
- this.schemaLookup.get(filePath) ?? []
- if (!fileType || !componentName) return
-
- this.schemas[fileType][componentName] = undefined
- this.schemaLookup.delete(filePath)
- }),
- ]
-
- const jsRuntime = new JsRuntime()
-
- await Promise.all(
- supportsCustomComponents.map((fileType) =>
- this.generateComponentSchemas(jsRuntime, fileType)
- )
- )
- }
-
- dispose() {
- this.disposables?.forEach((disposable) => disposable.dispose())
- this.disposables = undefined
- }
-
- protected async generateComponentSchemas(
- jsRuntime: JsRuntime,
- fileType: TComponentFileType
- ) {
- const v1CompatMode =
- this.project.config.get().bridge?.v1CompatMode ?? false
-
- const fromFilePath = v1CompatMode
- ? this.project.config.resolvePackPath('behaviorPack', 'components')
- : this.project.config.resolvePackPath(
- 'behaviorPack',
- `components/${fileType}`
- )
-
- let baseDir: AnyDirectoryHandle
- try {
- baseDir = await this.project.app.fileSystem.getDirectoryHandle(
- fromFilePath
- )
- } catch {
- return {}
- }
-
- // Reset schemas
- this.schemas[fileType] = {}
-
- await iterateDirParallel(
- baseDir,
- async (fileHandle, filePath) => {
- await this.evalComponentSchema(
- jsRuntime,
- fileType,
- filePath,
- await fileHandle.getFile(),
- v1CompatMode
- )
- },
- undefined,
- fromFilePath
- )
- }
-
- protected async evalComponentSchema(
- jsRuntime: JsRuntime,
- fileType: TComponentFileType,
- filePath: string,
- file: File | VirtualFile,
- v1CompatMode: boolean
- ) {
- let fileContent = new Uint8Array(await file.arrayBuffer())
-
- if (filePath.endsWith('.ts')) {
- fileContent = (
- await this.dash!.compileFile(filePath, fileContent)
- )[1]
- }
-
- const transformedFile = new File([fileContent], file.name)
- const component = new Component(
- new DefaultConsole(),
- fileType,
- await transformedFile.text(),
- 'development',
- v1CompatMode
- )
-
- const loadedCorrectly = await component.load(
- jsRuntime,
- filePath,
- 'client'
- )
-
- if (loadedCorrectly && component.name) {
- this.schemas[fileType][component.name] = component.getSchema()
- this.schemaLookup.set(filePath, [fileType, component.name])
- }
- }
-}
diff --git a/src/components/Compiler/Worker/Service.ts b/src/components/Compiler/Worker/Service.ts
deleted file mode 100644
index 94cdd04a8..000000000
--- a/src/components/Compiler/Worker/Service.ts
+++ /dev/null
@@ -1,258 +0,0 @@
-import '/@/utils/worker/inject'
-
-import '/@/components/FileSystem/Virtual/Comlink'
-import { expose } from 'comlink'
-import { FileTypeLibrary, IFileType } from '/@/components/Data/FileType'
-import { DataLoader } from '/@/components/Data/DataLoader'
-import type { AnyDirectoryHandle } from '/@/components/FileSystem/Types'
-import { Dash, initRuntimes, FileSystem } from 'dash-compiler'
-import { PackTypeLibrary } from '/@/components/Data/PackType'
-import { DashFileSystem } from './FileSystem'
-import { Signal } from '/@/components/Common/Event/Signal'
-import { dirname } from '/@/utils/path'
-import { EventDispatcher } from '/@/components/Common/Event/EventDispatcher'
-import { ForeignConsole } from './Console'
-import { Mutex } from '../../Common/Mutex'
-import wasmUrl from '@swc/wasm-web/wasm-web_bg.wasm?url'
-import { VirtualDirectoryHandle } from '../../FileSystem/Virtual/DirectoryHandle'
-import { TauriFsStore } from '../../FileSystem/Virtual/Stores/TauriFs'
-
-initRuntimes(wasmUrl)
-
-export interface ICompilerOptions {
- config: string
- compilerConfig?: string
- mode: 'development' | 'production'
- projectName: string
- pluginFileTypes: IFileType[]
-}
-const dataLoader = new DataLoader()
-const consoles = new Map()
-
-/**
- * Dispatches an event whenever a task starts with progress steps
- */
-export class DashService extends EventDispatcher {
- protected _fileSystem?: FileSystem
- public _fileType?: FileTypeLibrary
- protected _dash?: Dash
- public isDashFree = new Mutex()
- protected _projectDir?: string
- public isSetup = false
- public completedStartUp = new Signal()
- protected _console?: ForeignConsole
-
- constructor() {
- super()
- }
-
- async setup(
- baseDirectory: AnyDirectoryHandle,
- comMojangDirectory: AnyDirectoryHandle | undefined,
- options: ICompilerOptions
- ) {
- await this.isDashFree.lock()
-
- if (!dataLoader.hasFired) await dataLoader.loadData()
-
- this._fileSystem = await this.createFileSystem(baseDirectory)
- const outputFileSystem =
- comMojangDirectory && options.mode === 'development'
- ? await this.createFileSystem(comMojangDirectory)
- : undefined
- this._fileType = new FileTypeLibrary()
- this._fileType.setPluginFileTypes(options.pluginFileTypes)
-
- let console = consoles.get(options.projectName)
- if (!console) {
- console = new ForeignConsole()
- consoles.set(options.projectName, console)
- }
- this._console = console
-
- this._projectDir = dirname(options.config)
-
- this._dash = new Dash(this._fileSystem, outputFileSystem, {
- config: options.config,
- compilerConfig: options.compilerConfig,
- console,
- mode: options.mode,
- fileType: this._fileType,
- packType: new PackTypeLibrary(),
- verbose: true,
- requestJsonData: (path) => dataLoader.readJSON(path),
- })
- await this.dash.setup(dataLoader)
-
- this.isDashFree.unlock()
- this.isSetup = true
- }
- protected async createFileSystem(directoryHandle: AnyDirectoryHandle) {
- // Default file system on PWA builds
- if (!import.meta.env.VITE_IS_TAURI_APP)
- return new DashFileSystem(directoryHandle)
-
- if (!(directoryHandle instanceof VirtualDirectoryHandle))
- throw new Error(
- `Expected directoryHandle to be a virtual directory handle`
- )
- const baseStore = directoryHandle.getBaseStore()
- if (!(baseStore instanceof TauriFsStore))
- throw new Error(
- `Expected virtual directory to be backed by TauriFsStore`
- )
-
- const { TauriBasedDashFileSystem } = await import('./TauriFs')
- return new TauriBasedDashFileSystem(baseStore.getBaseDirectory())
- }
-
- get dash() {
- if (!this._dash) throw new Error('Dash not initialized')
- return this._dash
- }
- get fileSystem() {
- if (!this._fileSystem) throw new Error('File system not initialized')
- return this._fileSystem
- }
- get console() {
- if (!this._console) throw new Error('Console not initialized')
- return this._console
- }
- get projectDir() {
- if (!this._projectDir)
- throw new Error('Project directory not initialized')
- return this._projectDir
- }
-
- getCompilerLogs() {
- return this.console.getLogs()
- }
- clearCompilerLogs() {
- this.console.clear()
- }
- onConsoleUpdate(cb: () => void) {
- this.console.addChangeListener(cb)
- }
- removeConsoleListeners() {
- this.console.removeChangeListeners()
- }
-
- async compileFile(filePath: string, fileContent: Uint8Array) {
- await this.isDashFree.lock()
-
- const [deps, data] = await this.dash.compileFile(filePath, fileContent)
- this.isDashFree.unlock()
- return [deps, data ?? fileContent]
- }
-
- async start(changedFiles: string[], deletedFiles: string[]) {
- await this.isDashFree.lock()
-
- const fileExists = (path: string) =>
- this.fileSystem
- .readFile(path)
- .then(() => true)
- .catch(() => false)
-
- if (
- (await fileExists(
- `${this.projectDir}/.bridge/.restartWatchMode`
- )) ||
- !(await fileExists(
- // TODO(Dash): Replace with call to "this.dash.dashFilePath" once the accessor is no longer protected
- `${this.projectDir}/.bridge/.dash.${this.dash.getMode()}.json`
- ))
- ) {
- await Promise.all([
- this.build(false).catch((err) => console.error(err)),
- this.fileSystem
- .unlink(`${this.projectDir}/.bridge/.restartWatchMode`)
- .catch((err) => console.error(err)),
- ])
- } else {
- if (deletedFiles.length > 0)
- await this.unlinkMultiple(deletedFiles, false)
-
- if (changedFiles.length > 0)
- await this.updateFiles(changedFiles, false)
- }
-
- this.completedStartUp.dispatch()
- this.isDashFree.unlock()
- }
-
- async build(acquireLock = true) {
- if (acquireLock) await this.isDashFree.lock()
- this.dispatch()
-
- // Reload plugins so we can be sure that e.g. custom commands/components get discovered correctly
- await this.dash.reload()
- await this.dash.build()
-
- if (acquireLock) this.isDashFree.unlock()
- }
- async updateFiles(filePaths: string[], acquireLock = true) {
- if (acquireLock) await this.isDashFree.lock()
- this.dispatch()
-
- // Reload plugins so we can be sure that e.g. custom commands/components get discovered correctly
- await this.dash.reload()
- await this.dash.updateFiles(filePaths)
-
- if (acquireLock) this.isDashFree.unlock()
- }
- async unlink(path: string, updateDashFile = true) {
- await this.isDashFree.lock()
-
- await this.dash.unlink(path, updateDashFile)
-
- this.isDashFree.unlock()
- }
- async unlinkMultiple(paths: string[], acquireLock = true) {
- if (acquireLock) await this.isDashFree.lock()
-
- await this.dash.unlinkMultiple(paths)
-
- if (acquireLock) this.isDashFree.unlock()
- }
- async rename(oldPath: string, newPath: string) {
- await this.isDashFree.lock()
- this.dispatch()
-
- await this.dash.rename(oldPath, newPath)
-
- this.isDashFree.unlock()
- }
- async renameMultiple(renamePaths: [string, string][]) {
- for (const [oldPath, newPath] of renamePaths) {
- await this.dash.rename(oldPath, newPath)
- }
- }
- getCompilerOutputPath(filePath: string) {
- return this.dash.getCompilerOutputPath(filePath)
- }
- getFileDependencies(filePath: string) {
- return this.dash.getFileDependencies(filePath)
- }
-
- async reloadPlugins() {
- await this.isDashFree.lock()
-
- await this.dash.reload()
-
- this.isDashFree.unlock()
- }
-
- onProgress(cb: (progress: number) => void) {
- const disposable = this.dash.progress.onChange((progress) => {
- if (progress.percentage === 1) {
- disposable.dispose()
- cb(1)
- } else {
- cb(progress.percentage)
- }
- })
- }
-}
-
-expose(DashService, self)
diff --git a/src/components/Compiler/Worker/TauriFs.ts b/src/components/Compiler/Worker/TauriFs.ts
deleted file mode 100644
index 5d5db3b98..000000000
--- a/src/components/Compiler/Worker/TauriFs.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-import { FileSystem } from 'dash-compiler'
-import { IDirEntry } from 'dash-compiler/dist/FileSystem/FileSystem'
-import {
- writeFile,
- writeBinaryFile,
- createDir,
- removeDir,
- removeFile,
- readDir,
- FileEntry,
- copyFile,
-} from '@tauri-apps/api/fs'
-import { join, basename, dirname, isAbsolute, sep } from '@tauri-apps/api/path'
-import json5 from 'json5'
-import { invoke } from '@tauri-apps/api'
-
-export class TauriBasedDashFileSystem extends FileSystem {
- constructor(protected baseDirectory?: string) {
- super()
- }
-
- async resolvePath(path: string) {
- path = path.replaceAll(/\\|\//g, sep)
- if (!this.baseDirectory || (await isAbsolute(path))) return path
-
- return join(this.baseDirectory, path)
- }
-
- async readJson(path: string) {
- const file = await this.readFile(path)
-
- return json5.parse(await file.text())
- }
- async writeJson(path: string, content: any, beautify?: boolean) {
- await this.writeFile(
- path,
- JSON.stringify(content, null, beautify ? '\t' : undefined)
- )
- }
- async readFile(path: string): Promise {
- const resolvedPath = await this.resolvePath(path)
- const binaryData = new Uint8Array(
- await invoke>('read_file', {
- path: resolvedPath,
- })
- )
-
- return new File([binaryData], await basename(path))
- }
- async writeFile(path: string, content: string | Uint8Array) {
- const resolvedPath = await this.resolvePath(path)
- await createDir(await dirname(resolvedPath), { recursive: true })
-
- if (typeof content === 'string') await writeFile(resolvedPath, content)
- else await writeBinaryFile(resolvedPath, content)
- }
- async copyFile(from: string, to: string, outputFs = this) {
- const outputPath = await outputFs.resolvePath(to)
- await createDir(await dirname(outputPath), { recursive: true }).catch(
- () => {}
- )
-
- await copyFile(await this.resolvePath(from), outputPath)
- }
-
- async mkdir(path: string) {
- await createDir(await this.resolvePath(path), { recursive: true })
- }
- async unlink(path: string) {
- const resolvedPath = await this.resolvePath(path)
-
- await Promise.all([
- removeDir(resolvedPath, { recursive: true }).catch(() => {}),
- removeFile(resolvedPath).catch(() => {}),
- ])
- }
- async allFiles(path: string) {
- const entries = await readDir(await this.resolvePath(path), {
- recursive: true,
- })
-
- return this.flattenEntries(entries)
- }
- protected relative(path: string) {
- if (!this.baseDirectory) return path
-
- return path
- .replace(`${this.baseDirectory}${sep}`, '')
- .replaceAll('\\', '/') // Dash expects forward slashes
- }
- protected flattenEntries(entries: FileEntry[]) {
- const files: string[] = []
-
- for (const { path, children } of entries) {
- if (children) {
- files.push(...this.flattenEntries(children))
- continue
- }
-
- files.push(this.relative(path))
- }
-
- return files
- }
- async readdir(path: string): Promise {
- const entries = await readDir(await this.resolvePath(path))
-
- return entries.map((entry) => ({
- name: entry.name!,
- kind: entry.children ? 'directory' : 'file',
- }))
- }
- async lastModified(filePath: string) {
- return 0
- }
-}
diff --git a/src/components/Composables/Display/useDisplay.ts b/src/components/Composables/Display/useDisplay.ts
deleted file mode 100644
index a85151c5d..000000000
--- a/src/components/Composables/Display/useDisplay.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { computed } from 'vue'
-import { vuetify } from '../../App/Vuetify'
-
-export function useDisplay() {
- return {
- isMobile: computed(() => vuetify.framework.breakpoint.mobile),
- isMinimalDisplay: computed(() => {
- return !vuetify.framework.breakpoint.mdAndUp
- }),
- }
-}
diff --git a/src/components/Composables/DoubleClick.ts b/src/components/Composables/DoubleClick.ts
deleted file mode 100644
index ecb042fb9..000000000
--- a/src/components/Composables/DoubleClick.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { pointerDevice } from '/@/utils/pointerDevice'
-
-export function useDoubleClick(
- onClick: (isDoubleClick: boolean, ...args: T[]) => void,
- alwaysTriggerSingleClick: boolean = false
-) {
- let timer: number | null = null
- let clickedAmount = 0
-
- return (...args: T[]) => {
- if (pointerDevice.value === 'touch') return onClick(false, ...args)
-
- if (clickedAmount === 0) {
- clickedAmount++
- if (alwaysTriggerSingleClick) onClick(false, ...args)
-
- timer = window.setTimeout(() => {
- clickedAmount = 0
- timer = null
- if (!alwaysTriggerSingleClick) onClick(false, ...args)
- }, 500)
- } else {
- if (timer) window.clearTimeout(timer)
- clickedAmount = 0
- onClick(true, ...args)
- }
- }
-}
diff --git a/src/components/Composables/LongPress.ts b/src/components/Composables/LongPress.ts
deleted file mode 100644
index 77ea7541b..000000000
--- a/src/components/Composables/LongPress.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * A function that returns two functions (onTouchStart & onTouchEnd) given a callback that should be run when a user
- * long presses on an element on touch devices
- */
-export function useLongPress(
- longPressCallback: (...args: any[]) => unknown,
- shortPressCallback?: (...args: any[]) => unknown,
- touchEndCallback?: (...args: any[]) => unknown,
- longPressDuration: number = 500
-) {
- let timeoutId: number | null = null
-
- const onTouchStart = (...args: any[]) => {
- // Set a timeout to fire the callback
- timeoutId = window.setTimeout(() => {
- longPressCallback(...args)
- timeoutId = null
- }, longPressDuration)
- }
-
- const onTouchEnd = () => {
- // Clear the timeout
- if (timeoutId) {
- window.clearTimeout(timeoutId)
- timeoutId = null
- shortPressCallback?.()
- }
- touchEndCallback?.()
- }
-
- return { onTouchStart, onTouchEnd }
-}
diff --git a/src/components/Composables/Sidebar/useSidebarState.ts b/src/components/Composables/Sidebar/useSidebarState.ts
deleted file mode 100644
index 185539835..000000000
--- a/src/components/Composables/Sidebar/useSidebarState.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { computed, watch } from 'vue'
-import { settingsState } from '../../Windows/Settings/SettingsState'
-import { App } from '/@/App'
-
-export function useSidebarState() {
- const isNavVisible = computed(() => App.sidebar.isNavigationVisible.value)
- const isContentVisible = computed(
- () => isNavVisible.value && App.sidebar.isContentVisible.value
- )
- const isAttachedRight = computed(
- () => settingsState.sidebar && settingsState.sidebar.isSidebarRight
- )
-
- return {
- isNavVisible,
- isContentVisible,
- isAttachedRight,
- }
-}
diff --git a/src/components/Composables/UseProject.ts b/src/components/Composables/UseProject.ts
deleted file mode 100644
index 1fcd8a0f6..000000000
--- a/src/components/Composables/UseProject.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Ref, ref, watch, onUnmounted, nextTick, watchEffect } from 'vue'
-import { Project } from '../Projects/Project/Project'
-import { App } from '/@/App'
-import { IDisposable } from '/@/types/disposable'
-
-export function useProject() {
- const project = [>ref(null)
- let disposable: IDisposable | null = null
-
- App.getApp().then(async (app) => {
- await app.projectManager.projectReady.fired
- project.value = app.project
-
- disposable = App.eventSystem.on('projectChanged', (newProject) => {
- project.value = newProject
- })
- })
-
- onUnmounted(() => {
- disposable?.dispose()
- disposable = null
- })
-
- return {
- project,
- }
-}
diff --git a/src/components/Composables/UseTabSystem.ts b/src/components/Composables/UseTabSystem.ts
deleted file mode 100644
index 688e59a32..000000000
--- a/src/components/Composables/UseTabSystem.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { computed, ref } from 'vue'
-import { useProject } from './UseProject'
-
-export function useTabSystem(tabSystemId = ref(0)) {
- const { project } = useProject()
- const tabSystem = computed(
- () => project.value?.tabSystems[tabSystemId.value]
- )
- const activeTabSystem = computed(() => project.value?.tabSystem)
- const tabSystems = computed(() => project.value?.tabSystems)
- const shouldRenderWelcomeScreen = computed(() => {
- return (
- tabSystems.value &&
- (tabSystems.value?.[0].shouldRender.value ||
- tabSystems.value?.[1].shouldRender.value)
- )
- })
-
- return {
- tabSystem,
- activeTabSystem,
- tabSystems,
- shouldRenderWelcomeScreen,
- }
-}
diff --git a/src/components/Composables/useDarkMode.ts b/src/components/Composables/useDarkMode.ts
deleted file mode 100644
index 4b05e97c0..000000000
--- a/src/components/Composables/useDarkMode.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { computed } from 'vue'
-import { vuetify } from '../App/Vuetify'
-
-export function useDarkMode() {
- return {
- isDarkMode: computed(() => vuetify.framework.theme.dark),
- }
-}
diff --git a/src/components/Composables/useHighContrast.ts b/src/components/Composables/useHighContrast.ts
deleted file mode 100644
index 8a8b4ee70..000000000
--- a/src/components/Composables/useHighContrast.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { computed } from 'vue'
-import { settingsState } from '../Windows/Settings/SettingsState'
-
-export function useHighContrast() {
- return {
- highContrast: computed(
- () => settingsState.appearance?.highContrast ?? false
- ),
- }
-}
diff --git a/src/components/Composables/useTranslations.ts b/src/components/Composables/useTranslations.ts
deleted file mode 100644
index 9308f5c6c..000000000
--- a/src/components/Composables/useTranslations.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { LocaleManager } from '../Locales/Manager'
-
-export function useTranslations() {
- return {
- t: (translationKey?: string) => LocaleManager.translate(translationKey),
- }
-}
diff --git a/src/components/ContextMenu/ContextMenu.ts b/src/components/ContextMenu/ContextMenu.ts
deleted file mode 100644
index d8a476ae1..000000000
--- a/src/components/ContextMenu/ContextMenu.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import { markRaw, reactive, ref } from 'vue'
-import { ActionManager } from '../Actions/ActionManager'
-
-export interface IPosition {
- clientX: number
- clientY: number
-}
-
-interface ICard {
- title: string
- text: string
-}
-
-export interface IContextMenuOptions {
- /**
- * Context menu cards appear above the regular action list
- */
- card?: ICard
- mayCloseOnClickOutside?: boolean
-}
-
-export class ContextMenu {
- protected mayCloseOnClickOutside = true
- protected isVisible = ref(false)
- protected actionManager = ref()
- protected position = reactive({
- x: 0,
- y: 0,
- })
- protected menuHeight = 0
- protected card: ICard = {
- title: '',
- text: '',
- }
-
- show(
- event: IPosition,
- actionManager: ActionManager,
- {
- card = { title: '', text: '' },
- mayCloseOnClickOutside = true,
- }: IContextMenuOptions
- ) {
- this.position.x = event.clientX
- this.position.y = event.clientY
- this.actionManager.value = markRaw(actionManager)
- this.mayCloseOnClickOutside = mayCloseOnClickOutside
- this.card = Object.assign(this.card, card)
-
- // Add up size of each context menu element + top/bottom padding
- this.menuHeight =
- this.actionManager.value
- .getAllElements()
- .reduce((result, action) => {
- switch (action.type) {
- case 'submenu':
- return result + 40
- case 'divider':
- return result + 1
- case 'action':
- default:
- return result + 40
- }
- }, 0) + 16
- this.isVisible.value = true
- }
-
- setMayCloseOnClickOutside(mayCloseOnClickOutside: boolean) {
- setTimeout(() => {
- this.mayCloseOnClickOutside = mayCloseOnClickOutside
- }, 100)
- }
-}
diff --git a/src/components/ContextMenu/ContextMenu.vue b/src/components/ContextMenu/ContextMenu.vue
deleted file mode 100644
index 22ae33e7c..000000000
--- a/src/components/ContextMenu/ContextMenu.vue
+++ /dev/null
@@ -1,110 +0,0 @@
-
-
-
-
- {{ contextMenu.card.title }}
-
-
- {{ contextMenu.card.text }}
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/ContextMenu/List.vue b/src/components/ContextMenu/List.vue
deleted file mode 100644
index 54bc9c557..000000000
--- a/src/components/ContextMenu/List.vue
+++ /dev/null
@@ -1,168 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- {{ action.icon }}
-
-
-
-
-
-
-
- mdi-chevron-right
-
-
-
- ]
-
-
-
-
-
- {{ action.icon }}
-
-
-
-
-
- {{
- id === openSection
- ? 'mdi-chevron-down'
- : 'mdi-chevron-right'
- }}
-
-
-
-
-
-
-
- {{ action.icon }}
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/ContextMenu/showContextMenu.ts b/src/components/ContextMenu/showContextMenu.ts
deleted file mode 100644
index 68e44aee1..000000000
--- a/src/components/ContextMenu/showContextMenu.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { App } from '/@/App'
-import { IActionConfig } from '../Actions/SimpleAction'
-import { ActionManager } from '../Actions/ActionManager'
-import { IContextMenuOptions, IPosition } from './ContextMenu'
-
-export interface ISubmenuConfig {
- type: 'submenu'
- name: string
- icon: string
- actions: (IActionConfig | null | { type: 'divider' })[]
-}
-
-export type TActionConfig =
- | IActionConfig
- | ISubmenuConfig
- | { type: 'divider' }
- | null
-
-export async function showContextMenu(
- event: MouseEvent | IPosition,
- actions: TActionConfig[],
- options: IContextMenuOptions = {}
-) {
- let filteredActions = <
- (IActionConfig | ISubmenuConfig | { type: 'divider' })[]
- >actions.filter((action) => action !== null)
-
- if (event instanceof MouseEvent) {
- event.preventDefault()
- event.stopImmediatePropagation()
- }
- if (filteredActions.length === 0) return
-
- const app = await App.getApp()
- const actionManager = new ActionManager()
-
- filteredActions.forEach((action) => {
- switch (action.type) {
- case 'submenu':
- actionManager.addSubMenu(action)
- break
- case 'divider':
- actionManager.addDivider()
- break
- default:
- actionManager.create(action)
- break
- }
- })
-
- app.contextMenu.show(event, actionManager, options)
-}
diff --git a/src/components/Data/DataLoader.ts b/src/components/Data/DataLoader.ts
deleted file mode 100644
index b44fda63b..000000000
--- a/src/components/Data/DataLoader.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-import { baseUrl } from '/@/utils/baseUrl'
-import { unzip, Unzipped } from 'fflate'
-import { VirtualDirectoryHandle } from '../FileSystem/Virtual/DirectoryHandle'
-import { basename, dirname } from '/@/utils/path'
-import { FileSystem } from '../FileSystem/FileSystem'
-import { zipSize } from '/@/utils/app/dataPackage'
-import { whenIdle } from '/@/utils/whenIdle'
-import { get, set } from 'idb-keyval'
-import { compareVersions } from 'bridge-common-utils'
-import { version as appVersion } from '/@/utils/app/version'
-import { IndexedDbStore } from '../FileSystem/Virtual/Stores/IndexedDb'
-import { MemoryStore } from '../FileSystem/Virtual/Stores/Memory'
-
-export class DataLoader extends FileSystem {
- _virtualFileSystem?: VirtualDirectoryHandle
-
- get virtualFileSystem() {
- if (!this._virtualFileSystem) {
- throw new Error('DataLoader: virtualFileSystem is not initialized')
- }
- return this._virtualFileSystem
- }
- constructor(protected isMainLoader = false) {
- super()
- }
-
- async loadData(forceDataDownload = false) {
- if (this.hasFired) {
- console.warn(
- `This dataLoader instance already loaded data. You called loadData() twice.`
- )
- return
- }
-
- let savedDataForVersion = await get(
- 'savedDataForVersion'
- )
- if (forceDataDownload) {
- savedDataForVersion = undefined
- await set('savedDataForVersion', undefined)
- }
- const savedAllDataInIdb = savedDataForVersion
- ? compareVersions(appVersion, savedDataForVersion, '=')
- : false
-
- if (this.isMainLoader)
- console.log(
- savedAllDataInIdb
- ? '[APP] Data saved; restoring from cache...'
- : `[APP] Latest data not saved; fetching now...`
- )
-
- console.time('[App] Data')
-
- const indexedDbStore = new IndexedDbStore(
- 'data-fs',
- // Do not allow writes to data-fs
- true
- )
- // Clear data-fs if the version has changed
- const mayClearDb = this.isMainLoader && !savedAllDataInIdb
- if (mayClearDb) await indexedDbStore.clear()
-
- // Create virtual filesystem
- this._virtualFileSystem = new VirtualDirectoryHandle(
- savedAllDataInIdb ? indexedDbStore : new MemoryStore('data-fs'),
- ''
- )
- await this._virtualFileSystem.setupDone.fired
-
- // All current data is already downloaded & saved in IDB, no need to do it again
- if (savedAllDataInIdb) {
- this.setup(this._virtualFileSystem)
- console.timeEnd('[App] Data')
- return
- }
-
- // Read packages.zip file
- const rawData = await fetch(baseUrl + 'packages.zip').then((response) =>
- response.arrayBuffer()
- )
- if (rawData.byteLength !== zipSize) {
- throw new Error(
- `Error: Data package was larger than the expected size of ${zipSize} bytes; got ${rawData.byteLength} bytes`
- )
- }
-
- // Unzip data
- const unzipped = await new Promise((resolve, reject) =>
- unzip(new Uint8Array(rawData), async (error, zip) => {
- if (error) return reject(error)
- resolve(zip)
- })
- )
-
- const defaultHandle = await this._virtualFileSystem.getDirectoryHandle(
- 'data',
- { create: true }
- )
- const folders: Record = {
- '.': defaultHandle,
- }
-
- for (const path in unzipped) {
- const name = basename(path)
- const parentDir = dirname(path)
-
- if (path.endsWith('/')) {
- // Current entry is a folder
- const handle = await folders[parentDir].getDirectoryHandle(
- name,
- { create: true }
- )
- folders[path.slice(0, -1)] = handle
- } else {
- // Current entry is a file
- await folders[parentDir].getFileHandle(name, {
- create: true,
- initialData: unzipped[path],
- })
- }
- }
-
- if (this.isMainLoader && !forceDataDownload) {
- await this._virtualFileSystem!.moveToIdb()
- await set('savedDataForVersion', appVersion)
- }
-
- this.setup(this._virtualFileSystem)
- console.timeEnd('[App] Data')
- }
-}
diff --git a/src/components/Data/FileType.ts b/src/components/Data/FileType.ts
deleted file mode 100644
index dee5257fd..000000000
--- a/src/components/Data/FileType.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-import type { ILightningInstruction } from '/@/components/PackIndexer/Worker/Main'
-import type { IPackSpiderInstruction } from '/@/components/PackIndexer/Worker/PackSpider/PackSpider'
-import { Signal } from '/@/components/Common/Event/Signal'
-import { DataLoader } from './DataLoader'
-import { isMatch } from 'bridge-common-utils'
-import type { ProjectConfig } from '/@/components/Projects/Project/Config'
-import { FileType } from 'mc-project-core'
-
-export type { IFileType, IDefinition } from 'mc-project-core'
-
-/**
- * Used for return type of FileType.getMonacoSchemaArray() function
- */
-export interface IMonacoSchemaArrayEntry {
- fileMatch?: string[]
- uri: string
- schema?: any
-}
-
-/**
- * Utilities around bridge.'s file definitions
- */
-export class FileTypeLibrary extends FileType {
- public ready = new Signal()
-
- constructor(projectConfig?: ProjectConfig) {
- super(projectConfig, isMatch)
- }
-
- setProjectConfig(projectConfig: ProjectConfig) {
- this.projectConfig = projectConfig
- }
-
- async setup(dataLoader: DataLoader) {
- if (this.fileTypes.length > 0) return
- await dataLoader.fired
-
- this.fileTypes = await dataLoader.readJSON(
- 'data/packages/minecraftBedrock/fileDefinitions.json'
- )
-
- this.loadLightningCache(dataLoader)
- this.loadPackSpider(dataLoader)
-
- this.ready.dispatch()
- }
-
- /**
- * Get a JSON schema array that can be used to set Monaco's JSON defaults
- */
- getMonacoSchemaEntries() {
- return this.fileTypes
- .map(({ detect = {}, schema }) => {
- if (!detect.matcher) return null
-
- const packTypes =
- detect?.packType === undefined
- ? []
- : Array.isArray(detect?.packType)
- ? detect?.packType
- : [detect?.packType]
-
- return {
- fileMatch: this.prefixMatchers(
- packTypes,
- Array.isArray(detect.matcher)
- ? [...detect.matcher]
- : [detect.matcher]
- ).map((fileMatch) =>
- encodeURI(fileMatch)
- // Monaco doesn't like these characters in fileMatch
- .replaceAll(/;|,|@|&|=|\+|\$|\.|!|'|\(|\)|#/g, '*')
- ),
- uri: schema,
- }
- })
- .filter((schemaEntry) => schemaEntry !== null)
- .flat()
- }
-
- protected lCacheFiles: Record = {}
- protected lCacheFilesLoaded = new Signal()
- async loadLightningCache(dataLoader: DataLoader) {
- const lightningCache = await dataLoader.readJSON(
- `data/packages/minecraftBedrock/lightningCaches.json`
- )
-
- const findCacheFile = (fileName: string) =>
- Object.entries(lightningCache).find(([filePath]) =>
- filePath.endsWith(fileName)
- )
-
- for (const fileType of this.fileTypes) {
- if (!fileType.lightningCache) continue
-
- const [filePath, cacheFile] =
- findCacheFile(fileType.lightningCache) ?? []
- if (!filePath) {
- throw new Error(
- `Lightning cache file "${fileType.lightningCache}" for file type "${fileType.id}" not found`
- )
- }
-
- this.lCacheFiles[fileType.lightningCache] = <
- string | ILightningInstruction[]
- >cacheFile
- }
- this.lCacheFilesLoaded.dispatch()
- }
-
- async getLightningCache(filePath: string) {
- const { lightningCache } = this.get(filePath) ?? {}
- if (!lightningCache) return []
-
- await this.lCacheFilesLoaded.fired
-
- return this.lCacheFiles[lightningCache] ?? []
- }
-
- protected packSpiderFiles: Record = {}
- protected packSpiderFilesLoaded = new Signal()
- async loadPackSpider(dataLoader: DataLoader) {
- const packSpiderFiles = await dataLoader.readJSON(
- `data/packages/minecraftBedrock/packSpiders.json`
- )
-
- this.packSpiderFiles = Object.fromEntries(
- <[string, IPackSpiderInstruction][]>this.fileTypes
- .map(({ id, packSpider }) => {
- if (!packSpider) return
-
- return [
- id,
- packSpiderFiles[
- `file:///data/packages/minecraftBedrock/packSpider/${packSpider}`
- ],
- ]
- })
- .filter((data) => data !== undefined)
- )
-
- this.packSpiderFilesLoaded.dispatch()
- }
- async getPackSpiderData() {
- await this.packSpiderFilesLoaded.fired
-
- return this.packSpiderFiles
- }
-}
diff --git a/src/components/Data/FormatVersions.ts b/src/components/Data/FormatVersions.ts
deleted file mode 100644
index 66b67e93a..000000000
--- a/src/components/Data/FormatVersions.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { App } from '/@/App'
-import { compareVersions } from 'bridge-common-utils'
-import type { DataLoader } from './DataLoader'
-
-interface IFormatVersionDefs {
- currentStable: string
- formatVersions: string[]
-}
-
-export async function getFilteredFormatVersions(targetVersion?: string) {
- const app = await App.getApp()
- await app.dataLoader.fired
-
- await app.projectManager.projectReady.fired
- if (!targetVersion) targetVersion = app.projectConfig.get().targetVersion
-
- return getFormatVersions().then((formatVersions) =>
- formatVersions.filter(
- (formatVersion) =>
- !targetVersion ||
- compareVersions(formatVersion, targetVersion, '<=')
- )
- )
-}
-export async function getFormatVersions() {
- const app = await App.getApp()
- await app.dataLoader.fired
-
- const def: IFormatVersionDefs = await app.dataLoader.readJSON(
- 'data/packages/minecraftBedrock/formatVersions.json'
- )
-
- return def.formatVersions.reverse()
-}
-
-export function getLatestFormatVersion() {
- return getFormatVersions().then((formatVersions) => formatVersions[0])
-}
-
-export async function getStableFormatVersion(dataLoader: DataLoader) {
- await dataLoader.fired
-
- const def: IFormatVersionDefs = await dataLoader.readJSON(
- 'data/packages/minecraftBedrock/formatVersions.json'
- )
-
- return def.currentStable
-}
diff --git a/src/components/Data/JSONDefaults.ts b/src/components/Data/JSONDefaults.ts
deleted file mode 100644
index 52f226c38..000000000
--- a/src/components/Data/JSONDefaults.ts
+++ /dev/null
@@ -1,272 +0,0 @@
-import { App } from '/@/App'
-import { IMonacoSchemaArrayEntry } from '/@/components/Data/FileType'
-import { Project } from '../Projects/Project/Project'
-import { IDisposable } from '/@/types/disposable'
-import { FileTab } from '../TabSystem/FileTab'
-import { SchemaScript } from './SchemaScript'
-import { SchemaManager } from '../JSONSchema/Manager'
-import { EventDispatcher } from '../Common/Event/EventDispatcher'
-import { AnyFileHandle } from '../FileSystem/Types'
-import { Tab } from '../TabSystem/CommonTab'
-import { ComponentSchemas } from '../Compiler/Worker/Plugins/CustomComponent/ComponentSchemas'
-import { loadMonaco, useMonaco } from '../../utils/libs/useMonaco'
-import { Task } from '../TaskManager/Task'
-
-let globalSchemas: Record = {}
-let loadedGlobalSchemas = false
-
-export class JsonDefaults extends EventDispatcher {
- protected loadedSchemas = false
- protected localSchemas: Record = {}
- protected disposables: IDisposable[] = []
- public readonly componentSchemas: ComponentSchemas
- protected task: Task | null = null
-
- constructor(protected project: Project) {
- super()
-
- this.componentSchemas = new ComponentSchemas(project)
- }
-
- get isReady() {
- return this.loadedSchemas && loadedGlobalSchemas
- }
-
- async activate() {
- console.time('[SETUP] JSONDefaults')
- await this.project.app.project.packIndexer.fired
-
- // Don't await to start loading schemas as soon as possible
- this.componentSchemas.activate()
-
- this.disposables = [
- // Updating currentContext/ references
- App.eventSystem.on('currentTabSwitched', (tab: Tab) => {
- if (
- tab instanceof FileTab &&
- App.fileType.isJsonFile(tab.getPath())
- )
- this.updateDynamicSchemas(tab.getPath())
- }),
- App.eventSystem.on('refreshCurrentContext', (filePath: string) =>
- this.updateDynamicSchemas(filePath)
- ),
- App.eventSystem.on('disableValidation', () => {
- this.setJSONDefaults(false)
- }),
- ].filter((disposable) => disposable !== undefined)
-
- await this.loadAllSchemas()
- await this.setJSONDefaults()
- console.timeEnd('[SETUP] JSONDefaults')
- }
-
- deactivate() {
- this.disposables.forEach((disposable) => disposable.dispose())
- this.componentSchemas.dispose()
- this.disposables = []
- this.task?.complete()
- this.task = null
- }
-
- async loadAllSchemas() {
- this.localSchemas = {}
- const app = await App.getApp()
- this.task = app.taskManager.create({
- icon: 'mdi-book-open-outline',
- name: 'taskManager.tasks.loadingSchemas.name',
- description: 'taskManager.tasks.loadingSchemas.description',
- totalTaskSteps: 10,
- })
-
- await app.dataLoader.fired
- this.task?.update(1)
- const packages = await app.dataLoader.readdir('data/packages')
- this.task?.update(2)
-
- // Static schemas
- for (const packageName of packages) {
- try {
- await this.loadStaticSchemas(
- await app.dataLoader.getFileHandle(
- `data/packages/${packageName}/schemas.json`
- ),
- packageName === 'minecraftBedrock'
- )
- } catch (err) {
- console.error(err)
- continue
- }
- }
- loadedGlobalSchemas = true
- this.task?.update(3)
-
- // Schema scripts
- await this.runSchemaScripts(app)
- this.task?.update(5)
- const tab = this.project.tabSystem?.selectedTab
- if (tab && tab instanceof FileTab) {
- const fileType = App.fileType.getId(tab.getPath())
- this.addSchemas(
- await this.requestSchemaFor(fileType, tab.getPath())
- )
- await this.runSchemaScripts(
- app,
- tab.isForeignFile ? undefined : tab.getPath()
- )
- }
-
- // Schemas generated from lightning cache
- this.addSchemas(await this.getDynamicSchemas())
- this.task?.update(4)
-
- this.loadedSchemas = true
- this.task?.update(6)
- this.task?.complete()
- }
-
- async setJSONDefaults(validate = true) {
- const schemas = Object.assign({}, globalSchemas, this.localSchemas)
-
- if (loadMonaco.hasFired) {
- const { languages } = await useMonaco()
-
- languages.json.jsonDefaults.setDiagnosticsOptions({
- enableSchemaRequest: false,
- allowComments: true,
- validate,
- schemas: Object.values(schemas),
- })
- }
-
- SchemaManager.setJSONDefaults(schemas)
-
- this.dispatch()
- }
-
- async reload() {
- const app = await App.getApp()
-
- app.windows.loadingWindow.open()
- this.loadedSchemas = false
- this.localSchemas = {}
- loadedGlobalSchemas = false
- globalSchemas = {}
- await this.deactivate()
- await this.activate()
- app.windows.loadingWindow.close()
- }
-
- async updateDynamicSchemas(filePath: string) {
- const app = await App.getApp()
- const fileType = App.fileType.getId(filePath)
-
- this.addSchemas(await this.requestSchemaFor(fileType, filePath))
- this.addSchemas(await this.requestSchemaFor(fileType))
- await this.runSchemaScripts(app, filePath)
- await this.setJSONDefaults()
- }
- async updateMultipleDynamicSchemas(filePaths: string[]) {
- const app = await App.getApp()
-
- const updatedFileTypes = new Set()
-
- for (const filePath of filePaths) {
- const fileType = App.fileType.getId(filePath)
- if (updatedFileTypes.has(fileType)) continue
-
- this.addSchemas(await this.requestSchemaFor(fileType))
- await this.runSchemaScripts(app, filePath)
- updatedFileTypes.add(fileType)
- }
-
- await this.setJSONDefaults()
- }
-
- addSchemas(addSchemas: IMonacoSchemaArrayEntry[]) {
- addSchemas.forEach((addSchema) => {
- if (this.localSchemas[addSchema.uri]) {
- if (addSchema.schema)
- this.localSchemas[addSchema.uri].schema = addSchema.schema
- if (addSchema.fileMatch)
- this.localSchemas[addSchema.uri].fileMatch =
- addSchema.fileMatch
- } else {
- this.localSchemas[addSchema.uri] = addSchema
- }
- })
- }
-
- async requestSchemaFor(fileType: string, fromFilePath?: string) {
- const packIndexer = this.project.packIndexer
- await packIndexer.fired
-
- return await packIndexer.service!.getSchemasFor(fileType, fromFilePath)
- }
- async getDynamicSchemas() {
- return (
- await Promise.all(
- App.fileType.getIds().map((id) => this.requestSchemaFor(id))
- )
- ).flat()
- }
- async loadStaticSchemas(
- fileHandle: AnyFileHandle,
- updateSchemaEntries = false
- ) {
- if (!loadedGlobalSchemas) {
- const file = await fileHandle.getFile()
- const schemas = JSON.parse(await file.text())
-
- for (const uri in schemas) {
- globalSchemas[uri] = { uri, schema: schemas[uri] }
- }
- }
-
- if (updateSchemaEntries) {
- // Fetch schema entry points
- const schemaEntries = App.fileType.getMonacoSchemaEntries()
-
- // Reset old file matchers
- schemaEntries.forEach((schemaEntry) => {
- if (!schemaEntry.uri) return
-
- const currSchema = globalSchemas[schemaEntry.uri]
-
- if (currSchema && currSchema.fileMatch && schemaEntry.fileMatch)
- currSchema.fileMatch = undefined
- })
-
- // Add schema entry points
- schemaEntries.forEach((schemaEntry) => {
- // Non-json files; e.g. .lang
- if (!schemaEntry.uri) return
-
- if (globalSchemas[schemaEntry.uri]) {
- if (schemaEntry.schema)
- globalSchemas[schemaEntry.uri].schema =
- schemaEntry.schema
-
- if (schemaEntry.fileMatch) {
- if (globalSchemas[schemaEntry.uri].fileMatch)
- globalSchemas[schemaEntry.uri].fileMatch!.push(
- ...schemaEntry.fileMatch
- )
- else
- globalSchemas[schemaEntry.uri].fileMatch =
- schemaEntry.fileMatch
- }
- } else {
- globalSchemas[schemaEntry.uri] = schemaEntry
- }
- })
- }
- }
-
- addSchemaEntries() {}
-
- async runSchemaScripts(app: App, filePath?: string) {
- const schemaScript = new SchemaScript(this, app, filePath)
- await schemaScript.runSchemaScripts(this.localSchemas)
- }
-}
diff --git a/src/components/Data/PackType.ts b/src/components/Data/PackType.ts
deleted file mode 100644
index 2ab768b8c..000000000
--- a/src/components/Data/PackType.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { Signal } from '../Common/Event/Signal'
-import { DataLoader } from './DataLoader'
-import {
- PackType as BasePackType,
- ProjectConfig,
- type IPackType,
-} from 'mc-project-core'
-
-export type { IPackType, TPackTypeId } from 'mc-project-core'
-
-/**
- * Utilities around bridge.'s pack definitions
- */
-export class PackTypeLibrary extends BasePackType {
- public readonly ready = new Signal()
-
- constructor(projectConfig?: ProjectConfig) {
- super(projectConfig)
- }
-
- async setup(dataLoader: DataLoader) {
- if (this.packTypes.length > 0) return
- await dataLoader.fired
-
- this.packTypes = (
- await dataLoader
- .readJSON('data/packages/minecraftBedrock/packDefinitions.json')
- .catch(() => [])
- )
- this.ready.dispatch()
- }
-}
diff --git a/src/components/Data/PackTypeViewer.vue b/src/components/Data/PackTypeViewer.vue
deleted file mode 100644
index 4f3832342..000000000
--- a/src/components/Data/PackTypeViewer.vue
+++ /dev/null
@@ -1,64 +0,0 @@
-
-
-
-
- {{ packType.icon }}
-
-
-
-
-
-
-
-
- Pack Version: v{{ packType.version.join('.') }}
-
-
- {{ t(`packType.${packType.id}.description`) }}
-
-
-
-
-
-
-
-
diff --git a/src/components/Data/RequiresMatcher/FailureMessage.ts b/src/components/Data/RequiresMatcher/FailureMessage.ts
deleted file mode 100644
index 9fed8e825..000000000
--- a/src/components/Data/RequiresMatcher/FailureMessage.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { translate } from '../../Locales/Manager'
-import { IFailure, IRequirements } from './RequiresMatcher'
-import { App } from '/@/App'
-
-export async function createFailureMessage(
- failure: IFailure,
- requirements: IRequirements
-) {
- const app = await App.getApp()
-
- const failureMessageHeader = translate(
- `windows.createPreset.disabledPreset.${failure.type}`
- )
- let failureMessageDetails
- switch (failure.type) {
- case 'experimentalGameplay':
- if (requirements.experimentalGameplay)
- failureMessageDetails = requirements.experimentalGameplay
- .map(
- (exp) =>
- `${
- exp.startsWith('!')
- ? translate('general.no')
- : ''
- } ${translate(
- `experimentalGameplay.${exp.replace(
- '!',
- ''
- )}.name`
- )}`
- )
- .join(', ')
-
- break
- case 'packTypes':
- if (requirements.packTypes)
- failureMessageDetails = requirements.packTypes
- .map(
- (packType) =>
- `${
- packType.startsWith('!')
- ? translate('general.no')
- : ''
- } ${translate(
- `packType.${packType.replace('!', '')}.name`
- )}`
- )
- .join(', ')
-
- break
- case 'targetVersion':
- if (Array.isArray(requirements.targetVersion))
- failureMessageDetails = requirements.targetVersion.join(' ')
- else if (
- requirements.targetVersion &&
- requirements.targetVersion.min &&
- requirements.targetVersion.max
- )
- failureMessageDetails = `Min: ${requirements.targetVersion.min} | Max: ${requirements.targetVersion.max}`
- break
- case 'manifestDependency':
- if (requirements.dependencies)
- failureMessageDetails = requirements.dependencies.join(', ')
- break
- }
-
- return failureMessageDetails
- ? `${failureMessageHeader}: ${failureMessageDetails}`
- : undefined
-}
diff --git a/src/components/Data/RequiresMatcher/RequiresMatcher.ts b/src/components/Data/RequiresMatcher/RequiresMatcher.ts
deleted file mode 100644
index 07487f01c..000000000
--- a/src/components/Data/RequiresMatcher/RequiresMatcher.ts
+++ /dev/null
@@ -1,236 +0,0 @@
-import { TCompareOperator, compareVersions } from 'bridge-common-utils'
-import { TPackTypeId } from '/@/components/Data/PackType'
-import { App } from '/@/App'
-import { getLatestFormatVersion } from '/@/components/Data/FormatVersions'
-import json5 from 'json5'
-
-export interface IRequirements {
- /**
- * Compare a version with the project's target version.
- */
- targetVersion?: [TCompareOperator, string] | { min: string; max: string }
- /**
- * Check for the status of experimental gameplay toggles in the project.
- */
- experimentalGameplay?: string[]
- /**
- * Check whether pack types are present in the project.
- */
- packTypes?: TPackTypeId[]
- /**
- * Check for manifest dependencies to be present in the pack.
- */
- dependencies?: { module_name: string; version?: string }[]
- /**
- * Whether all conditions must be met. If set to false, any condition met makes the matcher valid.
- */
- matchAll?: boolean
-}
-
-export interface IFailure {
- type:
- | 'targetVersion'
- | 'experimentalGameplay'
- | 'packTypes'
- | 'manifestDependency'
-}
-
-export class RequiresMatcher {
- protected experimentalGameplay: Record = {}
- protected projectTargetVersion: string = ''
- public failures: IFailure[] = []
- // The following properties will only be defined after calling setup()
- protected latestFormatVersion!: string
- protected bpManifest: any
- protected isSetup = false
- protected app!: App
-
- constructor() {}
-
- async setup() {
- if (this.isSetup) return
-
- this.app = await App.getApp()
- const [_, latestFormatVersion, bpManifest] = await Promise.all([
- this.app.projectManager.projectReady.fired,
- getLatestFormatVersion(),
- this.app.fileSystem
- .readJSON(
- this.app.project.config.resolvePackPath(
- 'behaviorPack',
- 'manifest.json'
- )
- )
- .catch(() => null),
- ])
-
- const config = this.app.project.config.get()
-
- this.experimentalGameplay = config.experimentalGameplay ?? {}
-
- this.latestFormatVersion = latestFormatVersion
- this.projectTargetVersion =
- config.targetVersion ?? this.latestFormatVersion
-
- this.bpManifest = bpManifest
- this.isSetup = true
- }
- protected resetFailures() {
- this.failures = []
- }
-
- isValid(requires?: IRequirements) {
- this.resetFailures()
-
- if (!requires) return true
- if (!this.isSetup)
- throw new Error(
- 'RequiresMatcher is not setup. Make sure to call setup() before isValid().'
- )
- requires.matchAll ??= true
-
- // Pack type
- const matchesPackTypes = this.app.project.hasPacks(
- requires.packTypes ?? []
- )
- // Target version
- const matchesTargetVersion =
- !requires.targetVersion ||
- (!Array.isArray(requires.targetVersion)
- ? compareVersions(
- this.projectTargetVersion,
- requires.targetVersion?.min ?? '1.8.0',
- '>='
- ) &&
- compareVersions(
- this.projectTargetVersion,
- requires.targetVersion?.max ?? '1.18.0',
- '<='
- )
- : compareVersions(
- this.projectTargetVersion,
- requires.targetVersion[1],
- requires.targetVersion[0]
- ))
- // Experimental gameplay
- const matchesExperimentalGameplay =
- !requires.experimentalGameplay ||
- requires.experimentalGameplay.every((experimentalFeature) =>
- experimentalFeature.startsWith('!')
- ? !this.experimentalGameplay[
- experimentalFeature.replace('!', '')
- ]
- : this.experimentalGameplay[experimentalFeature]
- )
- // Manifest dependencies
-
- const dependencies:
- | { module_name: string; version?: string }[]
- | undefined = this.bpManifest?.dependencies?.map((dep: any) => {
- if (dep?.module_name) {
- // Convert old module names to new naming convention
- switch (dep.module_name) {
- case 'mojang-minecraft':
- return {
- module_name: '@minecraft/server',
- version: dep.version,
- }
- case 'mojang-gametest':
- return {
- module_name: '@minecraft/server-gametest',
- version: dep.version,
- }
- case 'mojang-minecraft-server-ui':
- return {
- module_name: '@minecraft/server-ui',
- version: dep.version,
- }
- case 'mojang-minecraft-server-admin':
- return {
- module_name: '@minecraft/server-admin',
- version: dep.version,
- }
- case 'mojang-net':
- return {
- module_name: '@minecraft/server-net',
- version: dep.version,
- }
- default:
- return {
- module_name: dep.module_name,
- version: dep.version,
- }
- }
- } else {
- switch (dep.uuid ?? '') {
- case 'b26a4d4c-afdf-4690-88f8-931846312678':
- return {
- module_name: '@minecraft/server',
- version: dep.version,
- }
- case '6f4b6893-1bb6-42fd-b458-7fa3d0c89616':
- return {
- module_name: '@minecraft/server-gametest',
- version: dep.version,
- }
- case '2bd50a27-ab5f-4f40-a596-3641627c635e':
- return {
- module_name: '@minecraft/server-ui',
- version: dep.version,
- }
- case '53d7f2bf-bf9c-49c4-ad1f-7c803d947920':
- return {
- module_name: '@minecraft/server-admin',
- version: dep.version,
- }
- case '777b1798-13a6-401c-9cba-0cf17e31a81b':
- return {
- module_name: '@minecraft/server-net',
- version: dep.version,
- }
- default:
- return {
- module_name: dep.uuid ?? '',
- version: dep.version,
- }
- }
- }
- })
-
- const matchesManifestDependency =
- !requires.dependencies ||
- !dependencies ||
- requires?.dependencies.every((dep) => {
- for (const dependency of dependencies) {
- if (dependency.module_name !== dep.module_name) continue
- if (
- dependency.version &&
- dependency.version !== dep.version
- )
- continue
-
- return true
- }
-
- return false
- })
-
- if (!matchesPackTypes) this.failures.push({ type: 'packTypes' })
- if (!matchesTargetVersion) this.failures.push({ type: 'targetVersion' })
- if (!matchesExperimentalGameplay)
- this.failures.push({ type: 'experimentalGameplay' })
- if (!matchesManifestDependency)
- this.failures.push({ type: 'manifestDependency' })
-
- return requires.matchAll
- ? matchesPackTypes &&
- matchesExperimentalGameplay &&
- matchesTargetVersion &&
- matchesManifestDependency
- : (matchesPackTypes && requires.packTypes) ||
- (matchesExperimentalGameplay &&
- requires.experimentalGameplay) ||
- (matchesTargetVersion && requires.targetVersion) ||
- (matchesManifestDependency && requires.dependencies)
- }
-}
diff --git a/src/components/Data/SchemaScript.ts b/src/components/Data/SchemaScript.ts
deleted file mode 100644
index c90ca636c..000000000
--- a/src/components/Data/SchemaScript.ts
+++ /dev/null
@@ -1,190 +0,0 @@
-import json5 from 'json5'
-import { run } from '../Extensions/Scripts/run'
-import { getFilteredFormatVersions } from './FormatVersions'
-import { App } from '/@/App'
-import { walkObject } from 'bridge-common-utils'
-import { v4 as uuid } from 'uuid'
-import { compareVersions } from 'bridge-common-utils'
-import { TPackTypeId } from './PackType'
-import type { JsonDefaults } from './JSONDefaults'
-import { TComponentFileType } from '../Compiler/Worker/Plugins/CustomComponent/ComponentSchemas'
-
-export class SchemaScript {
- constructor(
- protected jsonDefaults: JsonDefaults,
- protected app: App,
- protected filePath?: string
- ) {}
-
- protected async runScript(scriptPath: string, script: string) {
- const fs = this.app.fileSystem
-
- let currentJson = {}
- let failedFileLoad = true
- if (this.filePath) {
- try {
- const currentFile = await this.app.project.getFileFromDiskOrTab(
- this.filePath
- )
-
- currentJson = json5.parse(await currentFile.text())
- failedFileLoad = false
- } catch {}
- }
-
- try {
- return await run({
- async: true,
- script,
- env: {
- readdir: (path: string) =>
- fs.readFilesFromDir(path).catch(() => []),
- uuid,
- getFormatVersions: getFilteredFormatVersions,
- getCacheDataFor: async (
- fileType: string,
- filePath?: string,
- cacheKey?: string
- ) => {
- const packIndexer = this.app.project.packIndexer
- await packIndexer.fired
-
- return packIndexer.service.getCacheDataFor(
- fileType,
- filePath,
- cacheKey
- )
- },
- getIndexedPaths: async (
- fileType?: string,
- sort?: boolean
- ) => {
- const packIndexer = this.app.project.packIndexer
- await packIndexer.fired
-
- const paths = await packIndexer.service.getAllFiles(
- fileType,
- sort
- )
- return paths
- },
- getProjectPrefix: () =>
- this.app.projectConfig.get().namespace ?? 'bridge',
- getProjectConfig: () => this.app.projectConfig.get(),
- getFileName: () =>
- !this.filePath
- ? undefined
- : this.filePath.split(/\/|\\/g).pop(),
- customComponents: (fileType: TComponentFileType) =>
- this.jsonDefaults.componentSchemas.get(fileType),
- get: (path: string) => {
- const data: any[] = []
- walkObject(path, currentJson, (d) => data.push(d))
- return data
- },
- compare: compareVersions,
- resolvePackPath: (
- packId?: TPackTypeId,
- filePath?: string
- ) =>
- this.app.projectConfig.resolvePackPath(
- packId,
- filePath
- ),
- failedCurrentFileLoad: failedFileLoad,
- },
- })
- } catch (err: any) {
- console.error(
- `Error evaluating schemaScript "${scriptPath}": ${err.message}`
- )
- }
- }
- protected processScriptResult(
- scriptPath: string,
- schemaScript: any,
- scriptResult: any,
- localSchemas: any
- ) {
- if (!scriptResult) return
- if (scriptPath.endsWith('.js')) {
- if (scriptResult.keep) return
-
- schemaScript = {
- ...schemaScript,
- type: scriptResult.type,
- generateFile: scriptResult.generateFile,
- }
- scriptResult = scriptResult.data
- }
-
- if (
- schemaScript.type === 'object' &&
- !Array.isArray(scriptResult) &&
- typeof scriptResult === 'object'
- ) {
- localSchemas[
- `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`
- ] = {
- uri: `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`,
- schema: {
- type: 'object',
- properties: scriptResult,
- },
- }
- } else if (schemaScript.type === 'custom') {
- localSchemas[
- `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`
- ] = {
- uri: `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`,
- schema: scriptResult,
- }
- } else {
- localSchemas[
- `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`
- ] = {
- uri: `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`,
- schema: {
- type: schemaScript.type === 'enum' ? 'string' : 'object',
- enum:
- schemaScript.type === 'enum' ? scriptResult : undefined,
- properties:
- schemaScript.type === 'properties'
- ? Object.fromEntries(
- scriptResult.map((res: string) => [res, {}])
- )
- : undefined,
- },
- }
- }
- }
-
- async runSchemaScripts(localSchemas: any) {
- const schemaScripts = await this.app.dataLoader.readJSON(
- 'data/packages/minecraftBedrock/schemaScripts.json'
- )
-
- const promises = []
-
- for (const [scriptPath, script] of Object.entries(schemaScripts)) {
- let schemaScript: any
- if (scriptPath.endsWith('.js')) schemaScript = { script }
- else schemaScript = script
-
- promises.push(
- this.runScript(scriptPath, schemaScript.script).then(
- (scriptResult) => {
- this.processScriptResult(
- scriptPath,
- schemaScript,
- scriptResult,
- localSchemas
- )
- }
- )
- )
- }
-
- await Promise.all(promises)
- }
-}
diff --git a/src/components/Data/TypeLoader.ts b/src/components/Data/TypeLoader.ts
deleted file mode 100644
index 7795097ad..000000000
--- a/src/components/Data/TypeLoader.ts
+++ /dev/null
@@ -1,245 +0,0 @@
-import { App } from '/@/App'
-import { IDisposable } from '/@/types/disposable'
-import { DataLoader } from './DataLoader'
-import { Tab } from '/@/components/TabSystem/CommonTab'
-import { FileTab } from '/@/components/TabSystem/FileTab'
-import {
- IRequirements,
- RequiresMatcher,
-} from './RequiresMatcher/RequiresMatcher'
-import { useMonaco } from '/@/utils/libs/useMonaco'
-import { v4 as uuid } from 'uuid'
-
-/**
- * A map of type locations to type defintions that have been loaded
- */
-const types = new Map()
-
-export class TypeLoader {
- protected disposables: IDisposable[] = []
- protected typeDisposables: IDisposable[] = []
- protected userTypeDisposables: IDisposable[] = []
- protected currentTypeEnv: string | null = null
- protected isLoading: boolean = false
-
- constructor(protected dataLoader: DataLoader) {}
-
- async activate(filePath?: string) {
- this.disposables = [
- App.eventSystem.on('currentTabSwitched', async (tab: Tab) => {
- if (!tab.isForeignFile && tab instanceof FileTab) {
- await this.setTypeEnv(tab.getPath())
-
- await this.loadUserTypes()
- }
- }),
- ]
- if (filePath) await this.setTypeEnv(filePath)
-
- await this.loadUserTypes()
- }
- deactivate() {
- this.currentTypeEnv = null
- this.typeDisposables.forEach((disposable) => disposable.dispose())
- this.disposables.forEach((disposable) => disposable.dispose())
- this.typeDisposables = []
- this.disposables = []
- }
-
- protected async load(typeLocations: [string, string?][]) {
- // Ignore if we are already loading types (e.g. if a tab has been switched while loading)
- if (this.isLoading) return []
- this.isLoading = true
-
- const app = await App.getApp()
-
- // Load the cache index which maps the urls to the location of the files in cache
- let cacheIndex: Record = {}
- try {
- cacheIndex = await app.fileSystem.readJSON(
- `~local/data/cache/types/index.json`
- )
- } catch {}
-
- // Create promises for loading each type definition. Once this resolves, the types will be cached appropriately
- const toCache = await Promise.all(
- typeLocations.map(([typeLocation, moduleName]) => {
- return new Promise<[string, string, boolean, string?]>(
- async (resolve) => {
- // Before we try to load anything, make sure the type definition hasn't already been loaded and set in the type map
- let src = types.get(typeLocation)
- if (src) resolve([typeLocation, src, false, moduleName])
-
- // Decide whether the type is being loaded from data or needs to be fetched externally
- const isFromData = typeLocation.startsWith('types/')
- if (isFromData) {
- // Load types directly from bridge.'s data
- await this.dataLoader.fired
-
- const file = await this.dataLoader.readFile(
- `data/packages/minecraftBedrock/${typeLocation}`
- )
- src = await file.text()
- resolve([typeLocation, src, false, moduleName])
- return
- }
-
- // First check cache to see if we have already cached the file, if so resolve with the file from cache
- const cacheLocation = cacheIndex[typeLocation]
- const file = cacheLocation
- ? await app.fileSystem
- .readFile(cacheLocation)
- .catch(() => null)
- : null
-
- // File is cached, so resolve with the file from cache
- if (file) {
- resolve([
- typeLocation,
- await file.text(),
- false,
- moduleName,
- ])
- return
- }
-
- // The file couldn't be fetched from cache (because it is not in index or at the path specified)
- // So we need to fetch it
- const res = await fetch(typeLocation).catch(() => null)
- // TODO: Maybe set a variable (failedToFetchAtLeastOnce) to later open an information window that tells the user that some types couldn't be fetched
-
- // If the fetch failed, resolve with an empty string but don't cache it
- const text = res ? await res.text() : ''
-
- resolve([typeLocation, text, text !== '', moduleName])
- }
- )
- })
- )
-
- for (const [typeLocation, definition, updateCache] of toCache) {
- // First, save types to 'types' map
- types.set(typeLocation, definition)
-
- // Then if don't need to update cache, continue processing the next type
- if (!updateCache) continue
-
- // Create a random file name for the file to be stored in cache under. We can't use the location since it is a url and contains illegal file name characters
- const cacheFile = `~local/data/cache/types/${uuid()}.d.ts`
- cacheIndex = {
- ...cacheIndex,
- [typeLocation]: cacheFile,
- }
- // Write the actual type definition in cache
- await app.fileSystem.writeFile(cacheFile, definition)
- }
-
- // Update the cache index
- await app.fileSystem.writeJSON(
- '~local/data/cache/types/index.json',
- cacheIndex
- )
-
- this.isLoading = false
-
- return toCache.map(
- ([typeLocation, definition, updateCache, moduleName]) => [
- typeLocation,
- this.wrapTypesInModule(definition, moduleName),
- ]
- )
- }
-
- async setTypeEnv(filePath: string) {
- if (filePath === this.currentTypeEnv) return
-
- const { languages, Uri } = await useMonaco()
-
- this.currentTypeEnv = filePath
- this.typeDisposables.forEach((disposable) => disposable.dispose())
- this.typeDisposables = []
-
- await App.fileType.ready.fired
- const { types = [] } = App.fileType.get(filePath) ?? {}
-
- const matcher = new RequiresMatcher()
- await matcher.setup()
-
- const libs = await this.load(
- types
- .map((type) => {
- if (typeof type === 'string') return [type]
-
- const { definition, requires, moduleName } = type
-
- if (!requires || matcher.isValid(requires as IRequirements))
- return [definition, moduleName]
-
- return []
- })
- .filter((type) => type[0]) as [string, string?][]
- )
-
- for (const [typePath, lib] of libs) {
- const uri = Uri.file(typePath)
- this.typeDisposables.push(
- languages.typescript.javascriptDefaults.addExtraLib(
- lib,
- uri.toString()
- ),
- languages.typescript.typescriptDefaults.addExtraLib(
- lib,
- uri.toString()
- )
- )
- }
- }
-
- async loadUserTypes() {
- const app = await App.getApp()
-
- await app.project.packIndexer.fired
- let allFiles
- try {
- allFiles = await app.project.packIndexer.service.getAllFiles()
- } catch {
- // We failed to access the pack indexer service -> fail silently
- return
- }
-
- const typeScriptFiles = allFiles.filter(
- (filePath) => filePath.endsWith('.ts') || filePath.endsWith('.js')
- )
-
- const { languages, Uri } = await useMonaco()
-
- this.userTypeDisposables.forEach((disposable) => {
- disposable.dispose()
- })
- this.userTypeDisposables = []
-
- for (const typeScriptFile of typeScriptFiles) {
- const fileUri = Uri.file(
- // This for some reason fixes monaco suggesting the wrong path when using quickfixes. See issue #932
- typeScriptFile.replace('/BP/', '/bp/')
- )
- const file = await app.fileSystem
- .readFile(typeScriptFile)
- .catch(() => null)
- if (!file) continue
-
- this.userTypeDisposables.push(
- languages.typescript.typescriptDefaults.addExtraLib(
- await file.text(),
- fileUri.toString()
- )
- )
- }
- }
-
- wrapTypesInModule(typeSrc: string, moduleName?: string) {
- if (!moduleName) return typeSrc
-
- return `declare module '${moduleName}' {\n${typeSrc}\n}`
- }
-}
diff --git a/src/components/Definitions/GoTo.ts b/src/components/Definitions/GoTo.ts
deleted file mode 100644
index 0a941e415..000000000
--- a/src/components/Definitions/GoTo.ts
+++ /dev/null
@@ -1,177 +0,0 @@
-import { getLocation } from '/@/utils/monaco/getLocation'
-import type {
- Uri,
- Range,
- editor,
- Position,
- CancellationToken,
-} from 'monaco-editor'
-import { App } from '/@/App'
-import { IDefinition } from '/@/components/Data/FileType'
-import { getJsonWordAtPosition } from '/@/utils/monaco/getJsonWord'
-import { ILightningInstruction } from '/@/components/PackIndexer/Worker/Main'
-import { run } from '/@/components/Extensions/Scripts/run'
-import { findFileExtension } from '/@/components/FileSystem/FindFile'
-import { findAsync } from '/@/utils/array/findAsync'
-import { AnyFileHandle } from '../FileSystem/Types'
-import { isMatch } from 'bridge-common-utils'
-import { getCacheScriptEnv } from '../PackIndexer/Worker/LightningCache/CacheEnv'
-import { useMonaco } from '../../utils/libs/useMonaco'
-
-export class DefinitionProvider {
- async provideDefinition(
- model: editor.IModel,
- position: Position,
- cancellationToken: CancellationToken
- ) {
- const app = await App.getApp()
- const { word, range } = await getJsonWordAtPosition(model, position)
- const currentPath = app.project.tabSystem?.selectedTab?.getPath()
- if (!currentPath) return
-
- const { definitions } = App.fileType.get(currentPath) ?? {}
- const lightningCache = await App.fileType.getLightningCache(currentPath)
-
- // lightningCache is string for lightning cache text scripts
- if (
- !definitions ||
- typeof lightningCache === 'string' ||
- lightningCache.length === 0
- )
- return
-
- const location = await getLocation(model, position)
- const { definitionId, transformedWord } = await this.getDefinition(
- word,
- location,
- lightningCache
- )
- if (!definitionId || !transformedWord) return
-
- let definition = definitions[definitionId]
- if (!definition) return
- if (!Array.isArray(definition)) definition = [definition]
-
- const connectedFiles = await this.getFilePath(
- transformedWord,
- definition
- )
-
- const { editor, Uri, Range } = await useMonaco()
-
- const result = await Promise.all(
- connectedFiles.map(async (filePath) => {
- const uri = Uri.file(filePath)
-
- if (!editor.getModel(uri)) {
- let fileHandle: AnyFileHandle
- try {
- fileHandle = await app.fileSystem.getFileHandle(
- filePath
- )
- } catch {
- return undefined
- }
-
- const model = editor.createModel(
- await fileHandle.getFile().then((file) => file.text()),
- undefined,
- uri
- )
-
- // Try to remove model after 5 seconds
- setTimeout(() => {
- // Model is not in use
- if (!app.project.getFileTab(fileHandle)) {
- model.dispose()
- }
- }, 5 * 1000)
- }
-
- return {
- uri,
- range: new Range(0, 0, Infinity, Infinity),
- }
- })
- )
-
- return <{ uri: Uri; range: Range }[]>(
- result.filter((res) => res !== undefined)
- )
- }
-
- async getDefinition(
- word: string,
- location: string,
- lightningCache: ILightningInstruction[]
- ) {
- const app = await App.getApp()
-
- let transformedWord: string | undefined = word
- const definitions = lightningCache
- .map(
- (def) =>
- [
- def.cacheKey,
- def.path,
- { script: def.script, filter: def.filter },
- ]
- )
- .filter((def) => def !== undefined)
-
- return {
- definitionId: await findAsync(
- definitions,
- async ([def, path, { script, filter }]) => {
- if (path === '') return false
-
- const matches = isMatch(location, path)
- if (matches) {
- if (filter && filter.includes(word))
- transformedWord = undefined
- if (transformedWord && script)
- transformedWord = await run({
- script,
- async: true,
- env: {
- ...getCacheScriptEnv(transformedWord, {
- fileSystem: app.fileSystem,
- config: app.project.config,
- }),
- },
- })
-
- return true
- }
- return false
- }
- )?.then((value) => value?.[0]),
- transformedWord,
- }
- }
-
- async getFilePath(word: string, definition: IDefinition[]) {
- const app = await App.getApp()
- const connectedFiles = []
-
- for (const def of definition) {
- // Direct references are e.g. loot table paths
- if (def.directReference) {
- connectedFiles.push(word)
- continue
- }
-
- const matches =
- (await app.project.packIndexer.service?.find(
- def.from,
- def.match,
- [word],
- true
- )) ?? []
-
- connectedFiles.push(...matches)
- }
-
- return connectedFiles
- }
-}
diff --git a/src/components/Developer/Actions.ts b/src/components/Developer/Actions.ts
deleted file mode 100644
index 91b3c8cc7..000000000
--- a/src/components/Developer/Actions.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import { del, set } from 'idb-keyval'
-import { SimpleAction, IActionConfig } from '../Actions/SimpleAction'
-import { comMojangKey } from '/@/components/OutputFolders/ComMojang/ComMojang'
-import { ConfirmationWindow } from '../Windows/Common/Confirm/ConfirmWindow'
-import { App } from '/@/App'
-
-const devActionConfigs: IActionConfig[] = [
- {
- icon: 'mdi-delete',
- name: '[Dev: Reset local fs]',
- description:
- '[Reset the local fs (navigator.storage.getDirectory()) to be completely emtpy]',
- onTrigger: async () => {
- const app = await App.getApp()
-
- const confirm = new ConfirmationWindow({
- description: '[Are you sure you want to reset the local fs?]',
- })
- confirm.open()
- const choice = await confirm.fired
-
- if (!choice) return
-
- await Promise.all([
- app.fileSystem.unlink('~local/data'),
- app.fileSystem.unlink('~local/projects'),
- app.fileSystem.unlink('~local/extensions'),
- ])
- },
- },
- {
- icon: 'mdi-delete',
- name: "[Dev: Reset 'com.mojang' folder]",
- description: "[Remove the 'com.mojang' folder from local storage]",
- onTrigger: async () => {
- del(comMojangKey)
- },
- },
- {
- icon: 'mdi-cancel',
- name: '[Dev: Clear app data]',
- description: '[Clear data from bridge-core/editor-packages repository]',
- onTrigger: async () => {
- await del('savedDataForVersion')
- },
- },
- {
- icon: 'mdi-refresh',
- name: '[Dev: Reset initial setup]',
- description: '[Resets editor type and com.mojang selection]',
- onTrigger: async () => {
- await del('didChooseEditorType')
- await del(comMojangKey)
- },
- },
- {
- icon: 'mdi-open-in-new',
- name: '[Dev: Open local fs]',
- description: '[Open the local fs within the editor]',
- onTrigger: async () => {
- const app = await App.getApp()
-
- app.viewFolders.addDirectoryHandle({
- directoryHandle: await navigator.storage.getDirectory(),
- startPath: '~local',
- })
- },
- },
- {
- icon: 'mdi-database-outline',
- name: '[Dev: Open data directory]',
- description: '[Open the data directory within the editor]',
- onTrigger: async () => {
- const app = await App.getApp()
-
- app.viewFolders.addDirectoryHandle({
- directoryHandle: app.dataLoader.baseDirectory,
- })
- },
- },
- {
- icon: 'mdi-minecraft',
- name: '[Dev: Open com.mojang]',
- description: '[Open the com.mojang folder within the editor]',
- onTrigger: async () => {
- const app = await App.getApp()
-
- if (!app.comMojang.setup.hasFired) return
-
- app.viewFolders.addDirectoryHandle({
- directoryHandle: app.comMojang.fileSystem.baseDirectory,
- })
- },
- },
-]
-
-export const devActions = devActionConfigs.map(
- (devActionConfig) => new SimpleAction(devActionConfig)
-)
diff --git a/src/components/Documentation/view.ts b/src/components/Documentation/view.ts
deleted file mode 100644
index 39a12dd7f..000000000
--- a/src/components/Documentation/view.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { translate } from '../Locales/Manager'
-import { InformationWindow } from '../Windows/Common/Information/InformationWindow'
-import { App } from '/@/App'
-
-export async function viewDocumentation(filePath: string, word?: string) {
- await App.fileType.ready.fired
- const t = (str: string) => translate(str)
-
- const { id, documentation } = App.fileType.get(filePath) ?? {}
-
- if (!documentation) {
- new InformationWindow({
- description: `[${t(
- 'actions.documentationLookup.noDocumentation'
- )} ${id ? t(`fileType.${id}`) : '"' + filePath + '"'}.]`,
- })
- return
- }
-
- let url = documentation.baseUrl
- if (word && (documentation.supportsQuerying ?? true)) url += `#${word}`
-
- App.openUrl(url)
-}
diff --git a/src/components/Editor/Editor.ts b/src/components/Editor/Editor.ts
new file mode 100644
index 000000000..ceaca771c
--- /dev/null
+++ b/src/components/Editor/Editor.ts
@@ -0,0 +1,17 @@
+import { ref, Ref } from 'vue'
+
+export class Editor {
+ public static sideCollapsed: Ref = ref(false)
+
+ public static showTabs() {
+ this.sideCollapsed.value = true
+ }
+
+ public static hideTabs() {
+ this.sideCollapsed.value = false
+ }
+
+ public static toggleTabs() {
+ this.sideCollapsed.value = !this.sideCollapsed.value
+ }
+}
diff --git a/src/components/Editor/Editor.vue b/src/components/Editor/Editor.vue
new file mode 100644
index 000000000..d0bcc0c32
--- /dev/null
+++ b/src/components/Editor/Editor.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Editors/BlockModel/Tab.ts b/src/components/Editors/BlockModel/Tab.ts
deleted file mode 100644
index 02be5f49a..000000000
--- a/src/components/Editors/BlockModel/Tab.ts
+++ /dev/null
@@ -1,215 +0,0 @@
-import { App } from '/@/App'
-import { RenderDataContainer } from '../GeometryPreview/Data/RenderContainer'
-import { GeometryPreviewTab } from '../GeometryPreview/Tab'
-import { FileTab } from '/@/components/TabSystem/FileTab'
-import { TabSystem } from '/@/components/TabSystem/TabSystem'
-import json5 from 'json5'
-import { FileWatcher } from '/@/components/FileSystem/FileWatcher'
-import { findFileExtension } from '/@/components/FileSystem/FindFile'
-import { walkObject } from 'bridge-common-utils'
-import { isValidPositionArray } from '/@/utils/minecraft/validPositionArray'
-import { markRaw } from 'vue'
-import { IOutlineBox } from '../GeometryPreview/Data/EntityData'
-import { compare as compareVersions } from 'compare-versions'
-
-export interface IBlockPreviewOptions {
- loadComponents?: boolean
-}
-
-const collisionComponents = {
- '1.16.100': [
- { name: 'minecraft:entity_collision', color: '#ffff00' },
- { name: 'minecraft:pick_collision', color: '#0000ff' },
- ],
- // Our auto-completions version this name change as '1.18.10' but the formatVersionCorrection plugin changes it to 1.18.0
- '1.18.0': [
- { name: 'minecraft:block_collision', color: '#ffff00' },
- { name: 'minecraft:aim_collision', color: '#0000ff' },
- ],
- '1.19.10': [
- { name: 'minecraft:collision_box', color: '#ffff00' },
- { name: 'minecraft:aim_collision', color: '#0000ff' },
- ],
-}
-function loadCollisionComponents(formatVersion = '1.16.100') {
- for (const [currFormatVersion, components] of Object.entries(
- collisionComponents
- )) {
- if (compareVersions(currFormatVersion, formatVersion, '<')) continue
-
- return components
- }
-}
-
-export class BlockModelTab extends GeometryPreviewTab {
- protected blockWatcher: FileWatcher
- protected blockJson: any = {}
- protected formatVersion = '1.16.100'
- protected previewOptions: IBlockPreviewOptions = {}
-
- constructor(
- protected blockFilePath: string,
- tab: FileTab,
- parent: TabSystem
- ) {
- super(tab, parent)
-
- this.blockWatcher = new FileWatcher(App.instance, blockFilePath)
-
- this.blockWatcher.on((file) => this.reload(file))
- }
-
- setPreviewOptions({ loadComponents = true }: IBlockPreviewOptions) {
- this.previewOptions = {
- loadComponents,
- }
- }
-
- async close() {
- const didClose = await super.close()
- if (didClose) this.blockWatcher.dispose()
-
- return didClose
- }
- async reload(file?: File) {
- if (!file) file = await this.blockWatcher.getFile()
-
- this._renderContainer?.dispose()
- this._renderContainer = undefined
- this.loadRenderContainer(file)
- }
-
- async loadRenderContainer(file: File) {
- if (this._renderContainer !== undefined) return
- await this.setupComplete
- const app = await App.getApp()
-
- await app.project.packIndexer.fired
- const packIndexer = app.project.packIndexer.service
- const config = app.project.config
- if (!packIndexer) return
-
- let rawJsonData: any = undefined
- try {
- rawJsonData = json5.parse(await file.text())
- } catch (err) {
- console.error(`Invalid JSON within block file`)
- return
- }
-
- this.blockJson = markRaw(rawJsonData?.['minecraft:block'] ?? {})
- this.formatVersion = rawJsonData?.format_version ?? '1.16.100'
-
- const blockCacheData = await packIndexer.getCacheDataFor(
- 'block',
- this.blockFilePath
- )
-
- const textureReferences: string[] = blockCacheData?.texture ?? []
- let terrainTexture: any
- try {
- terrainTexture =
- json5.parse(
- await app.project
- .getFileFromDiskOrTab(
- config.resolvePackPath(
- 'resourcePack',
- 'textures/terrain_texture.json'
- )
- )
- .then((file) => file.text())
- )?.['texture_data'] ?? {}
- } catch (err) {
- console.error(`Invalid JSON within terrain_texture.json file`)
- return
- }
- const connectedTextures = await Promise.all(
- textureReferences
- .map((ref) => terrainTexture[ref]?.textures ?? [])
- .flat()
- .map((texturePath) =>
- findFileExtension(
- app.fileSystem,
- config.resolvePackPath('resourcePack', texturePath),
- ['.tga', '.png', '.jpg', '.jpeg']
- )
- )
- )
- if (connectedTextures.length === 0) return
-
- const connectedGeometries = await packIndexer.find(
- 'geometry',
- 'identifier',
- blockCacheData.geometryIdentifier ?? []
- )
- if (connectedGeometries.length === 0) return
-
- this._renderContainer = markRaw(
- new RenderDataContainer(app, {
- identifier: blockCacheData.geometryIdentifier[0],
- texturePaths: (
- connectedTextures.filter(
- (texturePath) => texturePath !== undefined
- )
- ),
- connectedAnimations: new Set([]),
- })
- )
- this._renderContainer.createGeometry(connectedGeometries[0])
-
- // Once the renderContainer is ready loading, create the initial model...
- this.renderContainer.ready.then(() => {
- this.createModel()
- })
- // ...and listen to further changes to the files for hot-reloading
- this._renderContainer.on(() => {
- this.createModel()
- })
- }
-
- async createModel() {
- await super.createModel()
-
- if (this.previewOptions.loadComponents) {
- const collisionComponents =
- loadCollisionComponents(this.formatVersion) ?? []
- const outlineBoxes = collisionComponents
- .map(({ name, color }) => this.loadCollisionBoxes(name, color))
- .flat()
-
- this.createOutlineBoxes(outlineBoxes)
- }
- }
-
- findComponents(blockJson: any, id: string) {
- const components: any[] = []
- const onReach = (data: any) => components.push(data)
- const locations = [
- `components/${id}`,
- `permutations/*/components/${id}`,
- ]
- locations.forEach((loc) => walkObject(loc, blockJson, onReach))
-
- return components
- }
- loadCollisionBoxes(componentName: string, color: string) {
- return this.findComponents(this.blockJson, componentName)
- .filter(
- (collisionBox) =>
- isValidPositionArray(collisionBox.origin) &&
- isValidPositionArray(collisionBox.size)
- )
- .map(
- ({ origin, size }) =>
- {
- color,
- position: {
- x: origin[0] + size[0] / 2,
- y: origin[1],
- z: origin[2] + size[2] / 2,
- },
- size: { x: size[0], y: size[1], z: size[2] },
- }
- )
- }
-}
diff --git a/src/components/Editors/Blockbench/BlockbenchTab.ts b/src/components/Editors/Blockbench/BlockbenchTab.ts
deleted file mode 100644
index 5f542cad0..000000000
--- a/src/components/Editors/Blockbench/BlockbenchTab.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { TabSystem } from '../../TabSystem/TabSystem'
-import { IframeTab, IOpenWithPayload } from '../IframeTab/IframeTab'
-
-export interface IBlockbenchOptions {
- openWithPayload?: IOpenWithPayload
-}
-
-export const blockbenchUrl = import.meta.env.DEV
- ? 'http://localhost:5173'
- : 'https://blockbench.bridge-core.app'
-
-export class BlockbenchTab extends IframeTab {
- constructor(
- tabSystem: TabSystem,
- { openWithPayload }: IBlockbenchOptions = {}
- ) {
- super(tabSystem, {
- icon: '$blockbench',
- name: 'Blockbench',
- url: blockbenchUrl,
- iconColor: 'primary',
- openWithPayload,
- })
- }
-}
diff --git a/src/components/Editors/EntityModel/Tab.ts b/src/components/Editors/EntityModel/Tab.ts
deleted file mode 100644
index d842183f1..000000000
--- a/src/components/Editors/EntityModel/Tab.ts
+++ /dev/null
@@ -1,276 +0,0 @@
-import { App } from '/@/App'
-import { RenderDataContainer } from '../GeometryPreview/Data/RenderContainer'
-import { GeometryPreviewTab } from '../GeometryPreview/Tab'
-import { FileTab } from '/@/components/TabSystem/FileTab'
-import { TabSystem } from '/@/components/TabSystem/TabSystem'
-import json5 from 'json5'
-import { FileWatcher } from '/@/components/FileSystem/FileWatcher'
-import { InformationWindow } from '../../Windows/Common/Information/InformationWindow'
-import { markRaw } from 'vue'
-import { DropdownWindow } from '../../Windows/Common/Dropdown/DropdownWindow'
-
-export interface IPreviewOptions {
- clientEntityFilePath?: string
- loadServerEntity?: boolean
- geometryFilePath?: string
- geometryIdentifier?: string
-}
-
-export class EntityModelTab extends GeometryPreviewTab {
- protected previewOptions: IPreviewOptions = {}
- protected clientEntityWatcher?: FileWatcher
-
- constructor(options: IPreviewOptions, tab: FileTab, parent: TabSystem) {
- super(tab, parent)
-
- this.previewOptions = options
-
- if (this.clientEntityFilePath) {
- this.clientEntityWatcher = new FileWatcher(
- App.instance,
- this.clientEntityFilePath
- )
-
- this.clientEntityWatcher.on((file) => this.reload(file))
- } else {
- this.reload()
- }
- }
- setPreviewOptions(previewOptions: IPreviewOptions) {
- this.previewOptions = Object.assign(this.previewOptions, previewOptions)
- }
- get clientEntityFilePath() {
- return this.previewOptions.clientEntityFilePath
- }
- get geometryFilePath() {
- return this.previewOptions.geometryFilePath
- }
- get geometryIdentifier() {
- return this.previewOptions.geometryIdentifier
- }
-
- async close() {
- const didClose = await super.close()
- if (didClose) this.clientEntityWatcher?.dispose()
-
- return didClose
- }
-
- async reload(file?: File) {
- if (!file) file = await this.clientEntityWatcher?.getFile()
-
- const runningAnims = this._renderContainer?.runningAnimations
-
- this._renderContainer?.dispose()
- this._renderContainer = undefined
- this.loadRenderContainer(file, runningAnims)
- }
-
- async loadRenderContainer(file?: File, runningAnims = new Set()) {
- if (this._renderContainer !== undefined) return
- await this.setupComplete
- const app = await App.getApp()
-
- const packIndexer = app.project.packIndexer.service
- if (!packIndexer) return
-
- // No client entity connected, try to load from geometry only
- if (file === undefined) return await this.fallbackToOnlyGeometry()
-
- let clientEntity: any
- try {
- clientEntity =
- json5.parse(await file.text())?.['minecraft:client_entity'] ??
- {}
- } catch {
- return
- }
-
- const clientEntityData = await packIndexer.getCacheDataFor(
- 'clientEntity',
- this.clientEntityFilePath
- )
-
- const connectedTextures = clientEntityData.texturePath
-
- const connectedAnimations = await packIndexer.find(
- 'clientAnimation',
- 'identifier',
- clientEntityData.animationIdentifier ?? [],
- true
- )
- const connectedGeometries = await packIndexer.find(
- 'geometry',
- 'identifier',
- clientEntityData.geometryIdentifier ?? []
- )
- if (connectedGeometries.length === 0) {
- new InformationWindow({ description: 'preview.noGeometry' })
- this.close()
- return
- }
-
- const connectedParticles = await Promise.all(
- Object.entries(
- clientEntity?.description?.particle_effects ?? {}
- ).map(async ([shortName, particleId]) => {
- return [
- shortName,
- (
- await packIndexer.find(
- 'particle',
- 'identifier',
- [particleId],
- false
- )
- )[0],
- ]
- })
- )
-
- this._renderContainer = markRaw(
- new RenderDataContainer(app, {
- identifier: clientEntityData.geometryIdentifier[0],
- texturePaths: connectedTextures,
- connectedAnimations: new Set(
- clientEntityData.animationIdentifier
- ),
- })
- )
- this._renderContainer.createGeometry(connectedGeometries[0])
-
- connectedAnimations.forEach((filePath) =>
- this._renderContainer!.createAnimation(filePath)
- )
- connectedParticles.forEach(([shortName, filePath], i) => {
- if (!filePath) return
-
- this._renderContainer!.createParticle(shortName, filePath)
- })
-
- if (runningAnims)
- runningAnims.forEach((animId) =>
- this._renderContainer?.runningAnimations.add(animId)
- )
-
- // If requested, load server entity
- if (
- this.previewOptions.loadServerEntity &&
- clientEntity?.description?.identifier
- ) {
- const serverEntityFilePath = await packIndexer.find(
- 'entity',
- 'identifier',
- [clientEntity?.description?.identifier],
- false
- )
-
- if (serverEntityFilePath.length > 0) {
- this._renderContainer!.addServerEntity(serverEntityFilePath[0])
- }
- }
-
- // Once the renderContainer is ready loading, create the initial model...
- this.renderContainer.ready.then(() => {
- this.createModel()
- })
- // ...and listen to further changes to the files for hot-reloading
- this._renderContainer.on(() => {
- this.createModel()
- })
- }
-
- /**
- * Store chosen fallback texture to avoid showing the texture picker again upon reload
- */
- protected chosenFallbackTexturePath?: string
- /**
- * This function enables bridge. to display models without a client entity.
- * By default, bridge. will use the geometry file path and identifier as the geometry source and
- * the user is prompted to choose any entity/block texture file for the model
- */
- async fallbackToOnlyGeometry() {
- // No geometry file connected, no way to fallback to geometry only
- if (!this.geometryFilePath || !this.geometryIdentifier) return
-
- const app = await App.getApp()
- const packIndexer = app.project.packIndexer.service
-
- // Helper method for loading all textures from a specific textures/ subfolder
- const loadTextures = (location: 'entity' | 'blocks') =>
- app.fileSystem
- .readdir(
- app.project.config.resolvePackPath(
- 'resourcePack',
- `textures/${location}`
- )
- )
- .then((path) =>
- path.map((path) => ({
- text: `textures/${location}/${path}`,
- value: app.project.config.resolvePackPath(
- 'resourcePack',
- `textures/${location}/${path}`
- ),
- }))
- )
- .catch(() => <{ text: string; value: string }[]>[])
-
- if (this.chosenFallbackTexturePath === undefined) {
- // Load all textures from the entity and blocks folders
- const textures = (await loadTextures('entity')).concat(
- await loadTextures('blocks')
- )
-
- if (textures.length === 0) {
- new InformationWindow({
- description: 'preview.noTextures',
- })
-
- return
- }
-
- // Prompt user to select a texture
- const choiceWindow = new DropdownWindow({
- options: textures,
- name: 'preview.chooseTexture',
- })
-
- // Get selected texture
- this.chosenFallbackTexturePath = await choiceWindow.fired
- }
-
- // Load all available animations (file paths & identifiers)
- const allAnimations = await packIndexer.getAllFiles('clientAnimation')
- const allAnimationIdentifiers = await packIndexer.getCacheDataFor(
- 'clientAnimation',
- undefined,
- 'identifier'
- )
-
- // Create fallback render container
- this._renderContainer = markRaw(
- new RenderDataContainer(app, {
- identifier: this.geometryIdentifier,
- texturePaths: [this.chosenFallbackTexturePath],
- connectedAnimations: new Set(allAnimationIdentifiers),
- })
- )
-
- this._renderContainer.createGeometry(this.geometryFilePath)
-
- // Create animations so they are ready to be used within the render container
- allAnimations.forEach((filePath) =>
- this._renderContainer!.createAnimation(filePath)
- )
-
- // Once the renderContainer is ready loading, create the initial model...
- this.renderContainer.ready.then(() => {
- this.createModel()
- })
- // ...and listen to further changes to the files for hot-reloading
- this._renderContainer.on(() => {
- this.createModel()
- })
- }
-}
diff --git a/src/components/Editors/EntityModel/create/fromClientEntity.ts b/src/components/Editors/EntityModel/create/fromClientEntity.ts
deleted file mode 100644
index cd3685bd4..000000000
--- a/src/components/Editors/EntityModel/create/fromClientEntity.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { EntityModelTab } from '../Tab'
-import { FileTab } from '/@/components/TabSystem/FileTab'
-import { TabSystem } from '/@/components/TabSystem/TabSystem'
-
-export async function createFromClientEntity(
- tabSystem: TabSystem,
- tab: FileTab
-) {
- return new EntityModelTab(
- { clientEntityFilePath: tab.getPath() },
- tab,
- tabSystem
- )
-}
diff --git a/src/components/Editors/EntityModel/create/fromEntity.ts b/src/components/Editors/EntityModel/create/fromEntity.ts
deleted file mode 100644
index 1fd3efe8c..000000000
--- a/src/components/Editors/EntityModel/create/fromEntity.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import json5 from 'json5'
-import { FileTab } from '/@/components/TabSystem/FileTab'
-import { InformationWindow } from '/@/components/Windows/Common/Information/InformationWindow'
-import { EntityModelTab } from '../Tab'
-import { App } from '/@/App'
-import { TabSystem } from '/@/components/TabSystem/TabSystem'
-
-export async function createFromEntity(tabSystem: TabSystem, tab: FileTab) {
- const app = await App.getApp()
- await app.project.packIndexer.fired
- const packIndexer = app.project.packIndexer.service
-
- const file = await tab.getFile()
- const fileContent = await file.text()
-
- let entityData: any
- try {
- entityData = json5.parse(fileContent)?.['minecraft:entity'] ?? {}
- } catch {
- new InformationWindow({
- description: 'preview.invalidEntity',
- })
- return
- }
-
- const clientEntity = await packIndexer.find('clientEntity', 'identifier', [
- entityData?.description?.identifier,
- ])
- if (clientEntity.length === 0) {
- new InformationWindow({
- description: 'preview.failedClientEntityLoad',
- })
- return
- }
-
- const previewTab = new EntityModelTab(
- { clientEntityFilePath: clientEntity[0] },
- tab,
- tabSystem
- )
- previewTab.setPreviewOptions({ loadServerEntity: true })
-
- return previewTab
-}
diff --git a/src/components/Editors/EntityModel/create/fromGeometry.ts b/src/components/Editors/EntityModel/create/fromGeometry.ts
deleted file mode 100644
index b54969433..000000000
--- a/src/components/Editors/EntityModel/create/fromGeometry.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import json5 from 'json5'
-import { BlockModelTab } from '../../BlockModel/Tab'
-import { EntityModelTab } from '../Tab'
-import { transformOldModels } from '../transformOldModels'
-import { App } from '/@/App'
-import { FileTab } from '/@/components/TabSystem/FileTab'
-import { TabSystem } from '/@/components/TabSystem/TabSystem'
-import { DropdownWindow } from '/@/components/Windows/Common/Dropdown/DropdownWindow'
-import { InformationWindow } from '/@/components/Windows/Common/Information/InformationWindow'
-
-export async function createFromGeometry(tabSystem: TabSystem, tab: FileTab) {
- const app = await App.getApp()
- await app.project.packIndexer.fired
- const packIndexer = app.project.packIndexer.service
-
- const file = await tab.getFile()
- const fileContent = await file.text()
-
- let modelJson: any
- try {
- modelJson = transformOldModels(json5.parse(fileContent))
- } catch {
- return
- }
-
- const availableModels = modelJson['minecraft:geometry']
- .map((geo: any) => geo?.description?.identifier)
- .filter((id: string | undefined) => id !== undefined)
-
- // By default assume user wants to load first model
- let choice = availableModels[0]
- if (availableModels.length > 1) {
- // Prompt user to choose a model if multiple are available
- const choiceWindow = new DropdownWindow({
- default: availableModels[0],
- options: availableModels,
- name: 'preview.chooseGeometry',
- isClosable: false,
- })
- choice = await choiceWindow.fired
- }
- if (!choice) {
- new InformationWindow({ description: 'preview.noGeometry' })
- return
- }
-
- const clientEntity = await packIndexer.find(
- 'clientEntity',
- 'geometryIdentifier',
- [choice]
- )
-
- if (clientEntity.length === 0) {
- // Check whether geometry is connected to a block
- const block = await packIndexer.find('block', 'geometryIdentifier', [
- choice,
- ])
- // Connected block found
- if (block.length > 0) {
- const previewTab = new BlockModelTab(block[0], tab, tabSystem)
- previewTab.setPreviewOptions({ loadComponents: false })
- return previewTab
- }
- }
-
- /**
- * If we reach this point either...
- * - ...a connected client entity was found (clientEntity.length > 0)...
- * - ...or no connected client entity and no connected block was found -> Fallback to geometry preview in this case.
- */
- return new EntityModelTab(
- {
- clientEntityFilePath: clientEntity[0],
- geometryFilePath: tab.getPath(),
- geometryIdentifier: choice,
- },
- tab,
- tabSystem
- )
-}
diff --git a/src/components/Editors/EntityModel/transformOldModels.ts b/src/components/Editors/EntityModel/transformOldModels.ts
deleted file mode 100644
index 2249e3838..000000000
--- a/src/components/Editors/EntityModel/transformOldModels.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-export function transformOldModels(geometry: any) {
- // New model format
- if (geometry['minecraft:geometry']) return geometry
-
- // Old model format
- // Filter out format_version
- const models: any = Object.entries(geometry).filter(
- ([_, modelData]) => typeof modelData !== 'string'
- )
-
- const transformedModels = []
- for (const [modelId, model] of models) {
- transformedModels.push({
- description: {
- identifier: modelId,
- texture_width: model.texturewidth,
- texture_height: model.textureheight,
- },
- bones: model.bones,
- })
- }
-
- return {
- format_version: '1.12.0',
- 'minecraft:geometry': transformedModels,
- }
-}
diff --git a/src/components/Editors/GeometryPreview/AssetPreview/Window.ts b/src/components/Editors/GeometryPreview/AssetPreview/Window.ts
deleted file mode 100644
index d7e33bd91..000000000
--- a/src/components/Editors/GeometryPreview/AssetPreview/Window.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import AssetPreviewWindowComponent from './Window.vue'
-import type { StandaloneModelViewer } from 'bridge-model-viewer'
-import { markRaw, reactive } from 'vue'
-import { Color } from 'three'
-import { useBridgeModelViewer } from '/@/utils/libs/useModelViewer'
-import { NewBaseWindow } from '/@/components/Windows/NewBaseWindow'
-
-export interface IAssetPreviewWindowConfig {
- assetName: string
- textureUrl: string
- modelData: any
-}
-
-interface IAssetPreviewConfig {
- assetName: string
- previewScale: number
- outputResolution: number
- boneVisibility: Record
- backgroundColor: string
-}
-
-export class AssetPreviewWindow extends NewBaseWindow {
- protected textureUrl: string
- protected modelData: any
- protected modelViewer?: StandaloneModelViewer
- protected state = reactive({
- ...super.state,
- bones: {},
- assetName: '',
- backgroundColor: '#121212',
- previewScale: 1.5,
- outputResolution: 4,
- })
-
- constructor({
- assetName,
- textureUrl,
- modelData,
- }: IAssetPreviewWindowConfig) {
- super(AssetPreviewWindowComponent, true, false)
-
- this.state.assetName = assetName
- this.textureUrl = textureUrl
- this.modelData = markRaw(modelData)
-
- this.defineWindow()
- this.open()
- }
-
- async receiveCanvas(canvas: HTMLCanvasElement) {
- const { StandaloneModelViewer } = await useBridgeModelViewer()
-
- const modelViewer = new StandaloneModelViewer(
- canvas,
- this.modelData,
- this.textureUrl,
- {
- antialias: true,
- height: 500,
- width: 500,
- }
- )
-
- await modelViewer.loadedModel
-
- // Initialize bone visibility map
- this.state.bones = Object.fromEntries(
- modelViewer.getModel().bones.map((boneName) => [boneName, true])
- )
-
- // @ts-ignore
- modelViewer.scene.background = new Color(this.state.backgroundColor)
-
- modelViewer.positionCamera(this.state.previewScale)
-
- this.modelViewer = markRaw(modelViewer)
- }
-
- startClosing(wasCancelled: boolean) {
- this.close(
- wasCancelled
- ? null
- : {
- assetName: this.state.assetName,
- previewScale: this.state.previewScale,
- boneVisibility: this.state.bones,
- backgroundColor: this.state.backgroundColor,
- outputResolution: this.state.outputResolution,
- }
- )
- }
-}
diff --git a/src/components/Editors/GeometryPreview/AssetPreview/Window.vue b/src/components/Editors/GeometryPreview/AssetPreview/Window.vue
deleted file mode 100644
index a6bc384e4..000000000
--- a/src/components/Editors/GeometryPreview/AssetPreview/Window.vue
+++ /dev/null
@@ -1,136 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- {{ t('windows.assetPreview.backgroundColor') }}
-
-
-
-
-
- {{ t('windows.assetPreview.boneVisibility') }}
-
-
-
- onBoneVisibilityChange(boneName, isVisible)
- "
- hide-details
- dense
- />
-
-
-
-
-
-
-
- mdi-check
- {{ t('general.confirm') }}
-
-
-
-
-
-
diff --git a/src/components/Editors/GeometryPreview/Data/AnimationData.ts b/src/components/Editors/GeometryPreview/Data/AnimationData.ts
deleted file mode 100644
index dd4ebbe49..000000000
--- a/src/components/Editors/GeometryPreview/Data/AnimationData.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import json5 from 'json5'
-import { PreviewFileWatcher } from './PreviewFileWatcher'
-import { RenderDataContainer } from './RenderContainer'
-
-export class AnimationData extends PreviewFileWatcher {
- protected animationJson: any = {}
-
- constructor(
- protected parent: RenderDataContainer,
- animationFilePath: string,
- protected includedAnimationIdentifiers?: string[]
- ) {
- super(parent.app, animationFilePath)
- }
-
- async onChange(file: File, isInitial = false) {
- try {
- this.animationJson = json5.parse(await file.text())
- if (!isInitial) this.parent.onChange()
- } catch {
- // If parsing JSON fails, do nothing
- }
- }
-
- get includedAnimations(): [string, any][] {
- if (this.includedAnimationIdentifiers === undefined)
- return Object.entries(this.animationJson?.['animations'] ?? {})
-
- return Object.entries(this.animationJson['animations']).filter(
- ([_, { description }]) =>
- !this.includedAnimationIdentifiers!.includes(
- description.identifier
- )
- )
- }
-
- get identifiers(): string[] {
- return this.includedAnimations.map(([id]) => id)
- }
-}
diff --git a/src/components/Editors/GeometryPreview/Data/EntityData.ts b/src/components/Editors/GeometryPreview/Data/EntityData.ts
deleted file mode 100644
index 4ae42c89d..000000000
--- a/src/components/Editors/GeometryPreview/Data/EntityData.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-import json5 from 'json5'
-import { PreviewFileWatcher } from './PreviewFileWatcher'
-import { RenderDataContainer } from './RenderContainer'
-import { walkObject } from 'bridge-common-utils'
-
-export interface IOutlineBox {
- color: `#${string}`
- position: { x: number; y: number; z: number }
- size: { x: number; y: number; z: number }
-}
-
-export class EntityData extends PreviewFileWatcher {
- protected entityData: any = {}
-
- constructor(protected parent: RenderDataContainer, filePath: string) {
- super(parent.app, filePath)
- }
-
- async onChange(file: File, isInitial = false) {
- try {
- this.entityData = json5.parse(await file.text())
-
- if (!isInitial) this.parent.onChange()
- } catch {
- // If parsing JSON fails, do nothing
- }
- }
-
- findComponent(id: string) {
- const components: any[] = []
- const onReach = (data: any) => components.push(data)
- const locations = [`*/components/${id}`, `*/component_groups/*/${id}`]
- locations.forEach((loc) => walkObject(loc, this.entityData, onReach))
-
- return components
- }
-
- getSeatBoxHelpers() {
- const components = this.findComponent('minecraft:rideable')
-
- const playerSize = { x: 16 * 0.8, y: 16 * 1.8, z: 16 * 0.8 }
-
- return components
- .map((rideable) => rideable?.seats ?? [])
- .flat()
- .map((seat) => seat?.position)
- .filter(
- (position) =>
- Array.isArray(position) &&
- typeof position[0] === 'number' &&
- typeof position[1] === 'number' &&
- typeof position[2] === 'number'
- )
- ?.map(
- (position) =>
- {
- color: '#ff0000',
- position: {
- x: position[0] * -16,
- y: position[1] * 16,
- z: position[2] * -16,
- },
- size: playerSize,
- }
- )
- }
-
- getCollisionBoxes() {
- return this.findComponent('minecraft:collision_box')
- .filter(
- (collisionBox) =>
- typeof collisionBox?.width === 'number' &&
- typeof collisionBox?.height === 'number'
- )
- .map(
- (collisionBox) =>
- {
- color: '#ffff00',
- position: { x: 0, y: 0, z: 0 },
- size: {
- x: collisionBox.width * 16,
- y: collisionBox.height * 16,
- z: collisionBox.width * 16,
- },
- }
- )
- }
-
- getHitboxes() {
- return this.findComponent('minecraft:custom_hit_test')
- .map((customHitTest) => customHitTest?.hitboxes ?? [])
- .flat()
- .filter(
- (hitbox) =>
- typeof hitbox?.width === 'number' &&
- typeof hitbox?.height === 'number' &&
- (hitbox?.pivot === undefined ||
- (Array.isArray(hitbox?.pivot) &&
- typeof hitbox.pivot[0] === 'number' &&
- typeof hitbox.pivot[1] === 'number' &&
- typeof hitbox.pivot[2] === 'number'))
- )
- .map(
- (hitbox) =>
- {
- color: '#0000ff',
- position: {
- x: (hitbox?.pivot?.[0] ?? 0) * -16,
- y:
- ((hitbox?.pivot?.[1] ?? 0) -
- hitbox.height / 2) *
- 16,
- z: (hitbox?.pivot?.[2] ?? 0) * -16,
- },
- size: {
- x: hitbox.width * 16,
- y: hitbox.height * 16,
- z: hitbox.width * 16,
- },
- }
- )
- }
-}
diff --git a/src/components/Editors/GeometryPreview/Data/GeometryData.ts b/src/components/Editors/GeometryPreview/Data/GeometryData.ts
deleted file mode 100644
index d4eedbb20..000000000
--- a/src/components/Editors/GeometryPreview/Data/GeometryData.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import json5 from 'json5'
-import { transformOldModels } from '../../EntityModel/transformOldModels'
-import { PreviewFileWatcher } from './PreviewFileWatcher'
-import { RenderDataContainer } from './RenderContainer'
-
-export class GeometryData extends PreviewFileWatcher {
- protected geometryJson: any = {}
- protected selected!: string
-
- constructor(
- protected parent: RenderDataContainer,
- geometryFilePath: string,
- protected includedGeometryIdentifiers?: string[]
- ) {
- super(parent.app, geometryFilePath)
- }
-
- select(id: string) {
- this.selected = id
- }
-
- async onChange(file: File, isInitial = false) {
- try {
- this.geometryJson = transformOldModels(
- json5.parse(await file.text())
- )
- if (!isInitial) this.parent.onChange()
- } catch {
- // If parsing JSON fails, do nothing
- }
- }
-
- get includedGeometries(): any[] {
- if (this.includedGeometryIdentifiers === undefined)
- return this.geometryJson?.['minecraft:geometry'] ?? []
-
- return (
- this.geometryJson?.['minecraft:geometry']?.filter(
- ({ description }: any) =>
- !this.includedGeometryIdentifiers!.includes(
- description.identifier
- )
- ) ?? []
- )
- }
-
- get identifiers(): string[] {
- return this.includedGeometries.map(
- ({ description }) => description.identifier
- )
- }
- get geometry() {
- return this.includedGeometries.find(
- ({ description }) => description.identifier === this.selected
- )
- }
- get fallbackGeometry() {
- return this.includedGeometries[0]
- }
-}
diff --git a/src/components/Editors/GeometryPreview/Data/ParticleData.ts b/src/components/Editors/GeometryPreview/Data/ParticleData.ts
deleted file mode 100644
index 72e11c9ec..000000000
--- a/src/components/Editors/GeometryPreview/Data/ParticleData.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import json5 from 'json5'
-import { PreviewFileWatcher } from './PreviewFileWatcher'
-import { RenderDataContainer } from './RenderContainer'
-
-export class ParticleData extends PreviewFileWatcher {
- protected particleData: any = {}
-
- constructor(
- protected parent: RenderDataContainer,
- public readonly shortName: string | undefined,
- particleFilePath: string
- ) {
- super(parent.app, particleFilePath)
- }
-
- async onChange(file: File, isInitial = false) {
- try {
- this.particleData = json5.parse(await file.text())
-
- if (!isInitial) this.parent.onChange()
- } catch {
- // If parsing JSON fails, do nothing
- }
- }
-
- get identifier(): string | undefined {
- return this.particleData?.particle_effect?.description?.identifier
- }
- get json() {
- return this.particleData
- }
-}
diff --git a/src/components/Editors/GeometryPreview/Data/PreviewFileWatcher.ts b/src/components/Editors/GeometryPreview/Data/PreviewFileWatcher.ts
deleted file mode 100644
index 822fe4c43..000000000
--- a/src/components/Editors/GeometryPreview/Data/PreviewFileWatcher.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { App } from '/@/App'
-import { FileWatcher } from '/@/components/FileSystem/FileWatcher'
-
-export abstract class PreviewFileWatcher extends FileWatcher {
- constructor(app: App, filePath: string) {
- super(app, filePath)
-
- // Make sure that the initial setup is complete
- this.ready.on(() => {
- // Then, listen for any further changes
- this.on((file) => this.onChange(file))
- })
- }
-
- async setup(file: File) {
- return await this.onChange(await this.compileFile(file), true)
- }
- abstract onChange(file: File, isInitial?: boolean): Promise | void
-}
diff --git a/src/components/Editors/GeometryPreview/Data/RenderContainer.ts b/src/components/Editors/GeometryPreview/Data/RenderContainer.ts
deleted file mode 100644
index f2ce54132..000000000
--- a/src/components/Editors/GeometryPreview/Data/RenderContainer.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-/**
- * An RenderDataContainer instance holds references to all data needed to render a visual preview of an entity/block
- */
-
-import { EventDispatcher } from '/@/components/Common/Event/EventDispatcher'
-import { App } from '/@/App'
-import { GeometryData } from './GeometryData'
-import { AnimationData } from './AnimationData'
-import { ParticleData } from './ParticleData'
-import { EntityData } from './EntityData'
-
-export interface IRenderData {
- identifier: string
- texturePaths: string[]
- connectedAnimations: Set
-}
-
-export class RenderDataContainer extends EventDispatcher {
- protected _geometries: GeometryData[] = []
- protected _animations: AnimationData[] = []
- protected readyPromises: Promise[] = []
- protected _currentTexturePath: string
- protected _runningAnimations = new Set()
- protected particleEffects: ParticleData[] = []
- protected _serverEntity?: EntityData
-
- get ready() {
- return Promise.all(this.readyPromises)
- }
-
- constructor(public readonly app: App, protected renderData: IRenderData) {
- super()
- this._currentTexturePath = this.texturePaths[0]
- }
- update(renderData: IRenderData) {
- this.renderData = renderData
- }
-
- createGeometry(
- geometryFilePath: string,
- includedGeometryIdentifiers?: string[]
- ) {
- const geo = new GeometryData(
- this,
- geometryFilePath,
- includedGeometryIdentifiers
- )
- this._geometries.push(geo)
- this.readyPromises.push(geo.ready.fired)
- }
- createAnimation(
- animationFilePath: string,
- includedAnimationIdentifiers?: string[]
- ) {
- const anim = new AnimationData(
- this,
- animationFilePath,
- includedAnimationIdentifiers
- )
- this._animations.push(anim)
- this.readyPromises.push(anim.ready.fired)
- }
- createParticle(shortName: string | undefined, particleFilePath: string) {
- const particle = new ParticleData(this, shortName, particleFilePath)
- this.particleEffects.push(particle)
- this.readyPromises.push(particle.ready.fired)
- }
- addServerEntity(filePath: string) {
- this._serverEntity = new EntityData(this, filePath)
- this.readyPromises.push(this._serverEntity.ready.fired)
- }
- selectGeometry(id: string) {
- for (const geo of this._geometries) {
- if (geo.identifiers.includes(id)) return geo.select(id)
- }
- throw new Error(`Failed to find geometry with ID "${id}"`)
- }
- selectTexturePath(path: string) {
- this._currentTexturePath = path
- }
- onChange() {
- this.dispatch()
- }
-
- get identifier() {
- return this.renderData.identifier
- }
- get texturePaths() {
- return this.renderData.texturePaths
- }
- get geometryIdentifiers() {
- return this._geometries.map((geo) => geo.identifiers).flat()
- }
- get animations() {
- return this._animations
- .map((anim) => anim.includedAnimations)
- .flat()
- .filter(([anim]) => this.renderData.connectedAnimations.has(anim))
- }
- get runningAnimations() {
- return this._runningAnimations
- }
- get modelData() {
- for (const geo of this._geometries) {
- let currGeo = geo.geometry
- if (currGeo) return currGeo
- }
-
- return this._geometries[0].fallbackGeometry
- }
- get currentTexturePath() {
- return this._currentTexturePath
- }
- get particles() {
- return this.particleEffects.map(
- (effect) => [effect.shortName, effect.json]
- )
- }
- get serverEntity() {
- return this._serverEntity
- }
-
- activate() {
- this._geometries.forEach((geo) => geo.activate())
- }
- dispose() {
- this._geometries.forEach((geo) => geo.dispose())
- this._animations.forEach((anim) => anim.dispose())
- this._serverEntity?.dispose()
- }
-}
diff --git a/src/components/Editors/GeometryPreview/Tab.ts b/src/components/Editors/GeometryPreview/Tab.ts
deleted file mode 100644
index aab7710c6..000000000
--- a/src/components/Editors/GeometryPreview/Tab.ts
+++ /dev/null
@@ -1,492 +0,0 @@
-import type { Model } from 'bridge-model-viewer'
-import { App } from '/@/App'
-import { loadAsDataURL } from '/@/utils/loadAsDataUrl'
-import { ThreePreviewTab } from '../ThreePreview/ThreePreviewTab'
-import { SimpleAction } from '/@/components/Actions/SimpleAction'
-import { RenderDataContainer } from './Data/RenderContainer'
-import { DropdownWindow } from '/@/components/Windows/Common/Dropdown/DropdownWindow'
-import { MultiOptionsWindow } from '/@/components/Windows/Common/MultiOptions/Window'
-import type Wintersky from 'wintersky'
-import { FileTab } from '/@/components/TabSystem/FileTab'
-import { TabSystem } from '/@/components/TabSystem/TabSystem'
-import { IDisposable } from '/@/types/disposable'
-import { IOutlineBox } from './Data/EntityData'
-import { markRaw } from 'vue'
-import { Box3, Vector3, Color } from 'three'
-import { saveOrDownload } from '/@/components/FileSystem/saveOrDownload'
-import { wait } from '/@/utils/wait'
-import { AssetPreviewWindow } from './AssetPreview/Window'
-import { useWintersky } from '/@/utils/libs/useWintersky'
-import { useBridgeModelViewer } from '/@/utils/libs/useModelViewer'
-
-export abstract class GeometryPreviewTab extends ThreePreviewTab {
- protected winterskyScene!: Wintersky.Scene
- protected model?: Model
- protected _renderContainer?: RenderDataContainer
- protected boxHelperDisposables: IDisposable[] = []
-
- constructor(tab: FileTab, tabSystem: TabSystem) {
- super(tab, tabSystem)
- }
-
- async setup() {
- const { default: Wintersky } = await useWintersky()
-
- this.winterskyScene = markRaw(
- new Wintersky.Scene({
- fetchTexture: async (config) => {
- const app = await App.getApp()
-
- try {
- return await loadAsDataURL(
- config.particle_texture_path,
- app.project.fileSystem
- )
- } catch (err) {
- // Fallback to Wintersky's default handling of textures
- }
- },
- })
- )
- this.winterskyScene.global_options.loop_mode = 'once'
- this.winterskyScene.global_options.tick_rate = 60
- this.winterskyScene.global_options.max_emitter_particles = 1000
- this.winterskyScene.global_options.scale = 16
- this.setupComplete.once(() => this.scene.add(this.winterskyScene.space))
-
- await super.setup()
- }
-
- get renderContainer() {
- if (!this._renderContainer)
- throw new Error(`Preview.renderContainer was not defined yet`)
- return this._renderContainer
- }
-
- get icon() {
- return this.tab.icon
- }
- get iconColor() {
- return this.tab.iconColor
- }
-
- async onActivate() {
- await super.onActivate()
- this._renderContainer?.activate()
- }
-
- onCreate() {
- this.registerActions()
- }
-
- registerActions() {
- this.actions = []
- this.addAction(
- new SimpleAction({
- icon: 'mdi-refresh',
- name: 'general.reload',
- onTrigger: () => this.reload(),
- }),
- new SimpleAction({
- icon: 'mdi-image-outline',
- name: 'fileType.texture',
- isDisabled: () => {
- return (
- (this._renderContainer?.texturePaths?.length ?? 0) <= 1
- )
- },
- onTrigger: async () => {
- const textures = this.renderContainer.texturePaths
- const chooseTexture = new DropdownWindow({
- name: 'fileType.texture',
- isClosable: false,
- options: textures,
- default: this.renderContainer.currentTexturePath,
- })
- const choice = await chooseTexture.fired
-
- this.renderContainer.selectTexturePath(choice)
- this.createModel()
- },
- }),
- new SimpleAction({
- icon: 'mdi-cube-outline',
- name: 'fileType.geometry',
- isDisabled: () => {
- return (
- (this._renderContainer?.geometryIdentifiers?.length ??
- 0) <= 1
- )
- },
- onTrigger: async () => {
- const geomtries = this.renderContainer.geometryIdentifiers
- const chooseGeometry = new DropdownWindow({
- name: 'fileType.geometry',
- isClosable: false,
- options: geomtries,
- default: geomtries[0],
- })
- const choice = await chooseGeometry.fired
-
- this.renderContainer.selectGeometry(choice)
- this.createModel()
- },
- }),
- new SimpleAction({
- icon: 'mdi-movie-open-outline',
- name: 'fileType.clientAnimation',
- isDisabled: () => {
- return (this._renderContainer?.animations?.length ?? 0) == 0
- },
- onTrigger: async () => {
- const animations = this.renderContainer.animations
- const chooseAnimation = new MultiOptionsWindow({
- name: 'fileType.clientAnimation',
- options: animations.map(([animId]) => ({
- name: animId,
- isSelected:
- this.renderContainer.runningAnimations.has(
- animId
- ),
- })),
- })
- const choices = await chooseAnimation.fired
-
- this.model?.animator.pauseAll()
- this.renderContainer.runningAnimations.clear()
- choices.forEach((choice) => {
- this.renderContainer.runningAnimations.add(choice)
- })
- this.createModel()
- },
- }),
- new SimpleAction({
- icon: 'mdi-collage',
- name: '[Asset Preview]',
- onTrigger: () => {
- this.renderAssetPreview()
- },
- })
- )
- }
-
- abstract loadRenderContainer(file: File): Promise
-
- onDestroy() {
- this._renderContainer?.dispose()
- super.onDestroy()
- }
- onChange() {}
-
- protected async createModel() {
- if (!this._renderContainer) return
-
- const app = await App.getApp()
- const { Model } = await useBridgeModelViewer()
-
- if (this.model) {
- this.scene?.remove(this.model.getGroup())
- this.model.animator.disposeAnimations()
- this.boxHelperDisposables.forEach((disposable) =>
- disposable.dispose()
- )
- }
- this.registerActions()
-
- // No texture available for model -> nothing to render
- if (!this.renderContainer.currentTexturePath) return
-
- this.model = markRaw(
- new Model(
- this.renderContainer.modelData,
- await loadAsDataURL(
- this.renderContainer.currentTexturePath,
- app.fileSystem
- )
- )
- )
- await this.model.create()
-
- this.scene.add(this.model.getGroup())
- this.model.animator.setupWintersky(this.winterskyScene)
-
- const { default: Wintersky } = await useWintersky()
- this.renderContainer.particles.forEach(([shortName, json]) => {
- if (!shortName) return
-
- this.model?.animator.addEmitter(
- shortName,
- new Wintersky.Config(this.winterskyScene, json)
- )
- })
-
- for (const [animId, anim] of this.renderContainer.animations) {
- this.model.animator.addAnimation(animId, anim)
- }
- this.renderContainer.runningAnimations.forEach((animId) =>
- this.model?.animator.play(animId)
- )
-
- const serverEntity = this.renderContainer.serverEntity
- if (serverEntity) {
- this.boxHelperDisposables.push()
- this.createOutlineBoxes([
- ...serverEntity.getHitboxes(),
- ...serverEntity.getCollisionBoxes(),
- ...serverEntity.getSeatBoxHelpers(),
- ])
- }
-
- this.requestRendering()
- setTimeout(() => {
- this.requestRendering()
- }, 100)
- }
-
- protected createOutlineBoxes(boxes: IOutlineBox[]) {
- this.boxHelperDisposables = boxes.map((box) =>
- this.model!.createOutlineBox(box.color, box.position, box.size)
- )
- }
-
- protected render(checkShouldTick = true) {
- this.winterskyScene.updateFacingRotation(this.camera)
- super.render()
-
- if (checkShouldTick && this.model && this.model.shouldTick) {
- this.model.tick()
- if (this.isActive) this.requestRendering()
- }
- }
-
- async close() {
- const didClose = await super.close()
- if (didClose) {
- this._renderContainer?.dispose()
- this._renderContainer = undefined
- }
-
- return didClose
- }
-
- async renderAssetPreview() {
- if (!this._renderContainer || !this.renderContainer.currentTexturePath)
- return
-
- const { StandaloneModelViewer } = await useBridgeModelViewer()
-
- const fileSystem = this.parent.app.fileSystem
- const texture = await fileSystem.loadFileHandleAsDataUrl(
- await fileSystem.getFileHandle(
- this.renderContainer.currentTexturePath
- )
- )
-
- const configWindow = new AssetPreviewWindow({
- assetName: this.tab.getFileHandle().name.split('.').shift()!,
- modelData: this.renderContainer.modelData,
- textureUrl: texture,
- })
- const renderConfig = await configWindow.fired
- if (!renderConfig) return
- const {
- backgroundColor,
- boneVisibility,
- previewScale,
- outputResolution,
- } = renderConfig
- let assetName = renderConfig.assetName
-
- const modelCanvas = document.createElement('canvas')
- modelCanvas.width = 500 * outputResolution
- modelCanvas.height = 500 * outputResolution
-
- const modelViewer = new StandaloneModelViewer(
- modelCanvas,
- this.renderContainer.modelData,
- texture,
- {
- antialias: true,
- height: 500 * outputResolution,
- width: 500 * outputResolution,
- }
- )
- await modelViewer.loadedModel
-
- // Hide bones which were disabled by the user
- for (const [boneName, isVisible] of Object.entries(boneVisibility)) {
- if (!isVisible) modelViewer.getModel().hideBone(boneName)
- }
-
- // @ts-ignore
- modelViewer.scene.background = new Color(backgroundColor)
-
- const resultCanvas = document.createElement('canvas')
- resultCanvas.width = 1400 * outputResolution
- resultCanvas.height = 600 * outputResolution
- const resultCtx = resultCanvas.getContext('2d')
- if (!resultCtx) return
-
- resultCtx.imageSmoothingEnabled = false
-
- const model = modelViewer.getModel().getGroup()
- modelViewer.positionCamera(previewScale)
-
- modelViewer.requestRendering()
- await wait(100)
-
- const urls = []
- for (let i = 0; i < 5; i++) {
- if (i === 1) model.rotateY(Math.PI / 4)
- if (i !== 0) model.rotateY(Math.PI / 2)
-
- modelViewer.requestRendering(true)
- urls.push(modelCanvas.toDataURL('image/png'))
- }
-
- // Bottom
- const box = new Box3().setFromObject(model)
- const center = box.getCenter(new Vector3())
- model.position.setY(center.y)
- model.rotation.set(0.25 * Math.PI, 1.75 * Math.PI, 0.75 * Math.PI)
- modelViewer.positionCamera(previewScale, false)
- modelViewer.requestRendering(true)
- urls.push(modelCanvas.toDataURL('image/png'))
- model.position.setY(0)
-
- // Top
- model.rotation.set(0, 1.75 * Math.PI, 1.75 * Math.PI)
- modelViewer.positionCamera(previewScale, false)
- modelViewer.requestRendering(true)
- urls.push(modelCanvas.toDataURL('image/png'))
-
- // Load textures
- const authorImagePath = this.parent.project.config.getAuthorImage()
-
- const [entityTexture, authorImageUrl, ...modelRenders] =
- await Promise.all([
- this.loadImageFromDisk(this.renderContainer.currentTexturePath),
- authorImagePath
- ? this.loadImageFromDisk(authorImagePath)
- : null,
- ...urls.map((url) => this.loadImage(url)),
- ])
-
- resultCtx.fillStyle = backgroundColor
- resultCtx.fillRect(0, 0, resultCanvas.width, resultCanvas.height)
- resultCtx.drawImage(
- modelRenders[0],
- 0,
- 100 * outputResolution,
- 400 * outputResolution,
- 400 * outputResolution
- )
-
- for (let i = 0; i < 3; i++) {
- resultCtx.drawImage(
- modelRenders[i + 1],
- 650 * outputResolution,
- i * 200 * outputResolution,
- 200 * outputResolution,
- 200 * outputResolution
- )
- }
- for (let i = 0; i < 3; i++) {
- resultCtx.drawImage(
- modelRenders[i + 4],
- 1050 * outputResolution,
- i * 200 * outputResolution,
- 200 * outputResolution,
- 200 * outputResolution
- )
- }
-
- // Render texture correctly even if it's not square
- const xOffset =
- (entityTexture.width > entityTexture.height
- ? 0
- : (entityTexture.height - entityTexture.width) /
- entityTexture.width /
- 2) * 200
- const yOffset =
- (entityTexture.height > entityTexture.width
- ? 0
- : (entityTexture.width - entityTexture.height) /
- entityTexture.height /
- 2) * 200
- const xSize =
- entityTexture.width > entityTexture.height
- ? 200
- : (entityTexture.width / entityTexture.height) * 200
- const ySize =
- entityTexture.height > entityTexture.width
- ? 200
- : (entityTexture.height / entityTexture.width) * 200
-
- resultCtx.drawImage(
- entityTexture,
- (400 + xOffset) * outputResolution,
- (200 + yOffset) * outputResolution,
- xSize * outputResolution,
- ySize * outputResolution
- )
-
- // Draw watermark of author's logo
- if (authorImageUrl)
- resultCtx.drawImage(
- authorImageUrl,
- resultCanvas.width - 50 * outputResolution,
- resultCanvas.height - 50 * outputResolution,
- 50 * outputResolution,
- 50 * outputResolution
- )
-
- // Asset preview title
- if (assetName !== '') {
- // Prepare for writing text
- resultCtx.fillStyle = '#ffffff'
-
- resultCtx.font = 30 * outputResolution + 'px Arial'
- resultCtx.fillText(
- assetName,
- 20 * outputResolution,
- 50 * outputResolution
- )
- } else {
- assetName = this.tab.getFileHandle().name.split('.')[0]
- }
-
- await new Promise((resolve) => {
- resultCanvas.toBlob(async (blob) => {
- if (!blob) return
-
- await saveOrDownload(
- `previews/${assetName}.png`,
- new Uint8Array(await blob.arrayBuffer()),
- this.parent.project.fileSystem
- )
- resolve()
- })
- })
-
- modelViewer.dispose()
- }
-
- protected loadImage(imageSrc: string) {
- return new Promise(async (resolve, reject) => {
- const image = new Image()
- image.addEventListener('load', () => resolve(image))
-
- image.src = imageSrc
- })
- }
- protected async loadImageFromDisk(imageSrc: string) {
- const fileSystem = this.parent.app.fileSystem
-
- return await this.loadImage(
- await fileSystem.loadFileHandleAsDataUrl(
- await fileSystem.getFileHandle(imageSrc)
- )
- )
- }
-}
diff --git a/src/components/Editors/HTMLPreview/HTMLPreview.ts b/src/components/Editors/HTMLPreview/HTMLPreview.ts
deleted file mode 100644
index 561174e58..000000000
--- a/src/components/Editors/HTMLPreview/HTMLPreview.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-import { TabSystem } from '../../TabSystem/TabSystem'
-import { IDisposable } from '/@/types/disposable'
-import type { ThemeManager } from '/@/components/Extensions/Themes/ThemeManager'
-import { IframeTab } from '../IframeTab/IframeTab'
-import { Tab } from '../../TabSystem/CommonTab'
-import { AnyFileHandle } from '../../FileSystem/Types'
-import { iframeApiVersion } from '/@/utils/app/iframeApiVersion'
-import { translate } from '../../Locales/Manager'
-import { VirtualFile } from '../../FileSystem/Virtual/File'
-
-export class HTMLPreviewTab extends IframeTab {
- public rawHtml = ''
-
- protected defaultStyles = ``
- protected themeListener?: IDisposable
- protected fileListener?: IDisposable
- protected messageListener?: IDisposable
- protected scrollY = 0
- constructor(
- parent: TabSystem,
- protected previewOptions: {
- filePath?: string
- fileHandle: AnyFileHandle
- }
- ) {
- super(parent)
-
- const themeManager = parent.app.themeManager
- this.updateDefaultStyles(themeManager)
- this.themeListener = themeManager.on(() =>
- this.updateDefaultStyles(themeManager)
- )
-
- if (previewOptions.filePath)
- this.fileListener = parent.app.project.fileChange.on(
- previewOptions.filePath,
- async (file) => {
- await this.load(file)
- }
- )
-
- this.api.loaded.once(() => {
- this.api.on('saveScrollPosition', (scrollY) => {
- if (scrollY !== 0) this.scrollY = scrollY
- })
- })
- }
-
- async setup() {
- await this.load()
-
- await super.setup()
- }
- async onActivate() {
- await super.onActivate()
- await this.api.loaded.fired
-
- this.api.trigger('loadScrollPosition', this.scrollY)
- }
- onDeactivate() {
- this.messageListener?.dispose()
- this.messageListener = undefined
-
- super.onDeactivate()
- }
-
- onDestroy() {
- this.themeListener?.dispose()
- this.themeListener = undefined
- this.fileListener?.dispose()
- this.fileListener = undefined
- }
-
- get icon() {
- return 'mdi-language-html5'
- }
- get iconColor() {
- return 'behaviorPack'
- }
- get name() {
- return `${translate('preview.name')}: ${this.fileHandle.name}`
- }
- get fileHandle() {
- return this.previewOptions.fileHandle
- }
-
- get html() {
- return (
- this.rawHtml.replaceAll('href="#', 'href="about:srcdoc#') +
- `` +
- `` +
- ``
- )
- }
- updateDefaultStyles(themeManager: ThemeManager) {
- this.defaultStyles = `html {
- color: ${themeManager.getColor('text')};
- font-family: Roboto;
- }
-
- a {
- color: ${themeManager.getColor('primary')};
- }
-
- textarea, input {
- background-color: ${themeManager.getColor('background')};
- color: ${themeManager.getColor('text')};
- }`
-
- if (this.rawHtml !== '') this.updateHtml()
- }
-
- async updateHtml() {
- this.srcdoc = this.html
-
- await this.api.loaded.fired
- this.api.trigger('loadScrollPosition', this.scrollY)
- }
-
- async load(file?: File | VirtualFile) {
- if (!file) file = await this.fileHandle.getFile()
- this.rawHtml = await file.text()
-
- this.updateHtml()
- }
-
- async is(tab: Tab): Promise {
- return (
- tab instanceof HTMLPreviewTab &&
- (await tab.fileHandle.isSameEntry(this.fileHandle))
- )
- }
-}
diff --git a/src/components/Editors/IframeTab/API/Events/GenericEvent.ts b/src/components/Editors/IframeTab/API/Events/GenericEvent.ts
deleted file mode 100644
index eeb0aecb5..000000000
--- a/src/components/Editors/IframeTab/API/Events/GenericEvent.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { IframeApi } from '../IframeApi'
-import { IDisposable } from '/@/types/disposable'
-
-export abstract class GenericEvent {
- protected disposables: IDisposable[] = []
- constructor(protected api: IframeApi) {
- this.disposables.push(
- this.api.loaded.on(() => this.onApiLoaded(), true),
- this.api.loaded.once(() => this.setup(), true)!
- )
- }
-
- onApiLoaded() {}
-
- abstract setup(): Promise | void
-
- dispose() {
- this.disposables.forEach((disposable) => disposable.dispose())
- this.disposables = []
- }
-}
diff --git a/src/components/Editors/IframeTab/API/Events/Tab/OpenFile.ts b/src/components/Editors/IframeTab/API/Events/Tab/OpenFile.ts
deleted file mode 100644
index fc9a9ea83..000000000
--- a/src/components/Editors/IframeTab/API/Events/Tab/OpenFile.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { GenericEvent } from '../GenericEvent'
-
-export class OpenFileEvent extends GenericEvent {
- setup() {
- this.api.trigger('tab.openFile', this.api.openWithPayload)
- }
-}
diff --git a/src/components/Editors/IframeTab/API/Events/ThemeChange.ts b/src/components/Editors/IframeTab/API/Events/ThemeChange.ts
deleted file mode 100644
index 07ee99cc6..000000000
--- a/src/components/Editors/IframeTab/API/Events/ThemeChange.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { GenericEvent } from './GenericEvent'
-import { App } from '/@/App'
-
-export class ThemeChangeEvent extends GenericEvent {
- async setup() {
- const app = await App.getApp()
-
- this.disposables.push(
- app.themeManager.on(() => {
- this.api.trigger(
- 'themeManager.themeChange',
- app.themeManager.getCurrentTheme()
- )
- })
- )
-
- this.api.trigger(
- 'themeManager.themeChange',
- app.themeManager.getCurrentTheme()
- )
- }
-}
diff --git a/src/components/Editors/IframeTab/API/IframeApi.ts b/src/components/Editors/IframeTab/API/IframeApi.ts
deleted file mode 100644
index 40ce3c7a8..000000000
--- a/src/components/Editors/IframeTab/API/IframeApi.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-import { Channel } from 'bridge-iframe-api'
-import { GenericEvent } from './Events/GenericEvent'
-import { ThemeChangeEvent } from './Events/ThemeChange'
-import { GenericRequest } from './Requests/GenericRequest'
-import { ReadFileRequest } from './Requests/FileSystem/ReadFile'
-import { Signal } from '/@/components/Common/Event/Signal'
-import { IDisposable } from '/@/types/disposable'
-import { isNightly as isNightlyBuild } from '/@/utils/app/isNightly'
-import { version as appVersion } from '/@/utils/app/version'
-import { WriteFileRequest } from './Requests/FileSystem/WriteFile'
-import { ReadTextFileRequest } from './Requests/FileSystem/ReadTextFile'
-import { IframeTab } from '../IframeTab'
-import { OpenFileEvent } from './Events/Tab/OpenFile'
-import { openedFileReferenceName } from './Requests/FileSystem/ResolveFileReference'
-import { GetItemPreviewRequest } from './Requests/Project/GetItemPreview'
-import { ReadAsDataUrlRequest } from './Requests/FileSystem/ReadAsDataUrl'
-import { FindRequest } from './Requests/PackIndexer/Find'
-import { GetFileRequest } from './Requests/PackIndexer/GetFile'
-import { SetIsUnsavedRequest } from './Requests/Tab/SetIsUnsaved'
-import { PlatformRequest } from './Requests/Util/Platform'
-import { UpdateFileRequest } from './Requests/Dash/UpdateFile'
-import { SetIsLoadingRequest } from './Requests/Tab/SetIsLoading'
-import { wait } from '/@/utils/wait'
-
-export class IframeApi {
- didSetup = false
- loaded = new Signal()
- channelSetup = new Signal()
- protected disposables: IDisposable[] = []
- protected _channel?: Channel
- protected openFileEvent = new OpenFileEvent(this)
- protected events: GenericEvent[] = [new ThemeChangeEvent(this)]
- protected requests: GenericRequest[] = [
- // FileSystem
- new ReadFileRequest(this),
- new ReadTextFileRequest(this),
- new ReadAsDataUrlRequest(this),
- new WriteFileRequest(this),
-
- // Project
- new GetItemPreviewRequest(this),
-
- // Tab
- new SetIsUnsavedRequest(this),
- new SetIsLoadingRequest(this),
-
- // PackIndexer,
- new FindRequest(this),
- new GetFileRequest(this),
-
- // Dash
- new UpdateFileRequest(this),
-
- // Util
- new PlatformRequest(this),
- ]
-
- constructor(
- public readonly tab: IframeTab,
- protected iframe: HTMLIFrameElement
- ) {
- this.iframe.addEventListener('load', async () => {
- if (!iframe.src && !iframe.srcdoc) return
-
- this._channel = new Channel(this.iframe.contentWindow)
- this.channelSetup.dispatch()
-
- await wait(20)
- await this.channel.open()
-
- this.loaded.dispatch()
- this.onLoad()
- })
- }
-
- get app() {
- return this.tab.project.app
- }
-
- get openWithPayload() {
- const payload = this.tab.getOptions().openWithPayload ?? {}
-
- return {
- filePath: payload.filePath,
- fileReference: openedFileReferenceName,
- isReadOnly: payload.isReadOnly ?? false,
- }
- }
- get openedFileHandle() {
- return this.tab.getOptions().openWithPayload?.fileHandle ?? null
- }
- get openedFilePath() {
- return this.tab.getOptions().openWithPayload?.filePath ?? null
- }
-
- get channel() {
- if (!this._channel)
- throw new Error(
- 'Channel is not initialized yet. Make sure to await iframeApi.loaded.fired'
- )
- return this._channel
- }
-
- on(event: string, callback: (data: T, origin: string) => void) {
- return this.channel.on(event, callback)
- }
- trigger(event: string, data: T) {
- return this.channel.simpleTrigger(event, data)
- }
-
- protected onLoad() {
- this.disposables.forEach((disposable) => disposable.dispose())
- this.disposables = []
-
- this.trigger('app.buildInfo', {
- appVersion,
- isNightlyBuild,
- })
- }
- // The underlying tab is supposed to open a new file
- triggerOpenWith() {
- this.openFileEvent.setup()
- }
-
- dispose() {
- this.events.forEach((event) => event.dispose())
- this.events = []
- this.requests.forEach((request) => request.dispose())
- this.requests = []
- this.openFileEvent.dispose()
- }
-}
diff --git a/src/components/Editors/IframeTab/API/Requests/Dash/UpdateFile.ts b/src/components/Editors/IframeTab/API/Requests/Dash/UpdateFile.ts
deleted file mode 100644
index 49adbaf71..000000000
--- a/src/components/Editors/IframeTab/API/Requests/Dash/UpdateFile.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { IframeApi } from '../../IframeApi'
-import { resolveFileReferencePath } from '../FileSystem/ResolveFileReference'
-import { GenericRequest } from '../GenericRequest'
-
-export class UpdateFileRequest extends GenericRequest {
- protected filesToUpdate = new Set()
- protected updateScheduled = false
- constructor(api: IframeApi) {
- super('dash.updateFile', api)
- }
-
- async handle(fileReference: string, origin: string) {
- const filePath = resolveFileReferencePath(fileReference, this.api)
-
- this.filesToUpdate.add(filePath)
- this.scheduleUpdate()
- }
-
- scheduleUpdate() {
- if (this.updateScheduled) return
- this.updateScheduled = true
-
- // Update all files after 200ms
- setTimeout(() => {
- this.updateScheduled = false
-
- this.api.app.project.compilerService.updateFiles([
- ...this.filesToUpdate,
- ])
- this.filesToUpdate.clear()
- }, 200)
- }
-}
diff --git a/src/components/Editors/IframeTab/API/Requests/FileSystem/ReadAsDataUrl.ts b/src/components/Editors/IframeTab/API/Requests/FileSystem/ReadAsDataUrl.ts
deleted file mode 100644
index f074f88f6..000000000
--- a/src/components/Editors/IframeTab/API/Requests/FileSystem/ReadAsDataUrl.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { IframeApi } from '../../IframeApi'
-import { GenericRequest } from '../GenericRequest'
-import { resolveFileReference } from './ResolveFileReference'
-import { loadHandleAsDataURL } from '/@/utils/loadAsDataUrl'
-
-export class ReadAsDataUrlRequest extends GenericRequest {
- constructor(api: IframeApi) {
- super('fs.readAsDataUrl', api)
- }
-
- async handle(filePath: string, origin: string): Promise {
- const fileHandle = await resolveFileReference(filePath, this.api)
-
- return await loadHandleAsDataURL(fileHandle)
- }
-}
diff --git a/src/components/Editors/IframeTab/API/Requests/FileSystem/ReadFile.ts b/src/components/Editors/IframeTab/API/Requests/FileSystem/ReadFile.ts
deleted file mode 100644
index f13750c2e..000000000
--- a/src/components/Editors/IframeTab/API/Requests/FileSystem/ReadFile.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { IframeApi } from '../../IframeApi'
-import { GenericRequest } from '../GenericRequest'
-import { resolveFileReference } from './ResolveFileReference'
-
-export class ReadFileRequest extends GenericRequest {
- constructor(api: IframeApi) {
- super('fs.readFile', api)
- }
-
- async handle(filePath: string, origin: string): Promise {
- const fileHandle = await resolveFileReference(filePath, this.api)
- const file = await fileHandle.getFile()
-
- return new Uint8Array(await file.arrayBuffer())
- }
-}
diff --git a/src/components/Editors/IframeTab/API/Requests/FileSystem/ReadTextFile.ts b/src/components/Editors/IframeTab/API/Requests/FileSystem/ReadTextFile.ts
deleted file mode 100644
index dbb412c82..000000000
--- a/src/components/Editors/IframeTab/API/Requests/FileSystem/ReadTextFile.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { IframeApi } from '../../IframeApi'
-import { GenericRequest } from '../GenericRequest'
-import { resolveFileReference } from './ResolveFileReference'
-
-export class ReadTextFileRequest extends GenericRequest {
- constructor(api: IframeApi) {
- super('fs.readTextFile', api)
- }
-
- async handle(filePath: string, origin: string): Promise {
- const fileHandle = await resolveFileReference(filePath, this.api)
- const file = await fileHandle.getFile()
-
- return await file.text()
- }
-}
diff --git a/src/components/Editors/IframeTab/API/Requests/FileSystem/ResolveFileReference.ts b/src/components/Editors/IframeTab/API/Requests/FileSystem/ResolveFileReference.ts
deleted file mode 100644
index f4606fb40..000000000
--- a/src/components/Editors/IframeTab/API/Requests/FileSystem/ResolveFileReference.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { IframeApi } from '../../IframeApi'
-
-export const openedFileReferenceName = '~bridge://OPENED-FILE'
-
-export async function resolveFileReference(
- fileReference: string,
- api: IframeApi,
- createFile = false
-) {
- if (fileReference === openedFileReferenceName) {
- const fileHandle = api.openedFileHandle
-
- if (!fileHandle)
- throw new Error(
- `Failed to de-reference file reference to opened file!`
- )
-
- return fileHandle
- }
-
- return await api.app.fileSystem.getFileHandle(fileReference, createFile)
-}
-
-export function resolveFileReferencePath(
- fileReference: string,
- api: IframeApi
-) {
- if (fileReference === openedFileReferenceName) {
- const filePath = api.openedFilePath
-
- if (!filePath)
- throw new Error(
- `Failed to de-reference file reference to opened file!`
- )
-
- return filePath
- }
-
- return fileReference
-}
diff --git a/src/components/Editors/IframeTab/API/Requests/FileSystem/WriteFile.ts b/src/components/Editors/IframeTab/API/Requests/FileSystem/WriteFile.ts
deleted file mode 100644
index 1f84f04c2..000000000
--- a/src/components/Editors/IframeTab/API/Requests/FileSystem/WriteFile.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { IframeApi } from '../../IframeApi'
-import { GenericRequest } from '../GenericRequest'
-import { resolveFileReference } from './ResolveFileReference'
-import { App } from '/@/App'
-
-export interface IWriteFilePayload {
- filePath: string
- data: Uint8Array | string
-}
-
-export class WriteFileRequest extends GenericRequest {
- constructor(api: IframeApi) {
- super('fs.writeFile', api)
- }
-
- async handle(
- { filePath, data }: IWriteFilePayload,
- origin: string
- ): Promise {
- const app = await App.getApp()
-
- const fileHandle = await resolveFileReference(filePath, this.api, true)
-
- await app.fileSystem.write(fileHandle, data)
- }
-}
diff --git a/src/components/Editors/IframeTab/API/Requests/GenericRequest.ts b/src/components/Editors/IframeTab/API/Requests/GenericRequest.ts
deleted file mode 100644
index 4fae5fa07..000000000
--- a/src/components/Editors/IframeTab/API/Requests/GenericRequest.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { IframeApi } from '../IframeApi'
-import { IDisposable } from '/@/types/disposable'
-
-export abstract class GenericRequest {
- protected disposables: IDisposable[] = []
-
- constructor(name: string, protected api: IframeApi) {
- this.api.channelSetup.once(() => {
- this.disposables.push(
- this.api.channel.on(name, (data, origin) =>
- this.handle(data, origin)
- )
- )
- })
- }
-
- abstract handle(data: Payload, origin: string): Promise | Response
-
- dispose() {
- this.disposables.forEach((disposable) => disposable.dispose())
- this.disposables = []
- }
-}
diff --git a/src/components/Editors/IframeTab/API/Requests/PackIndexer/Find.ts b/src/components/Editors/IframeTab/API/Requests/PackIndexer/Find.ts
deleted file mode 100644
index da542ee89..000000000
--- a/src/components/Editors/IframeTab/API/Requests/PackIndexer/Find.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { IframeApi } from '../../IframeApi'
-import { GenericRequest } from '../GenericRequest'
-
-interface IRequestData {
- findFileType: string
- whereCacheKey: string
- matchesOneOf: string[]
- fetchAll?: boolean
-}
-
-export class FindRequest extends GenericRequest {
- constructor(api: IframeApi) {
- super('packIndexer.find', api)
- }
-
- async handle(
- { findFileType, whereCacheKey, matchesOneOf, fetchAll }: IRequestData,
- origin: string
- ) {
- const packIndexer = this.api.app.project.packIndexer
- await packIndexer.fired
-
- return await packIndexer.service.find(
- findFileType,
- whereCacheKey,
- matchesOneOf,
- fetchAll
- )
- }
-}
diff --git a/src/components/Editors/IframeTab/API/Requests/PackIndexer/GetFile.ts b/src/components/Editors/IframeTab/API/Requests/PackIndexer/GetFile.ts
deleted file mode 100644
index 149ed0882..000000000
--- a/src/components/Editors/IframeTab/API/Requests/PackIndexer/GetFile.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { IframeApi } from '../../IframeApi'
-import { resolveFileReferencePath } from '../FileSystem/ResolveFileReference'
-import { GenericRequest } from '../GenericRequest'
-import { App } from '/@/App'
-
-export class GetFileRequest extends GenericRequest<
- string,
- Record
-> {
- constructor(api: IframeApi) {
- super('packIndexer.getFile', api)
- }
-
- async handle(fileReference: string, origin: string) {
- const packIndexer = this.api.app.project.packIndexer
- await packIndexer.fired
-
- const filePath = resolveFileReferencePath(fileReference, this.api)
-
- const fileType = App.fileType.getId(filePath)
-
- return await packIndexer.service.getCacheDataFor(fileType, filePath)
- }
-}
diff --git a/src/components/Editors/IframeTab/API/Requests/Project/GetItemPreview.ts b/src/components/Editors/IframeTab/API/Requests/Project/GetItemPreview.ts
deleted file mode 100644
index 67c95c07f..000000000
--- a/src/components/Editors/IframeTab/API/Requests/Project/GetItemPreview.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import { IframeApi } from '../../IframeApi'
-import { GenericRequest } from '../GenericRequest'
-import { findFileExtension } from '/@/components/FileSystem/FindFile'
-
-export class GetItemPreviewRequest extends GenericRequest<
- string,
- string | null
-> {
- constructor(api: IframeApi) {
- super('project.getItemPreview', api)
- }
-
- async handle(identifier: string, origin: string) {
- const app = this.api.app
- const project = app.project
- const packIndexer = project.packIndexer
- const fs = app.fileSystem
- await packIndexer.fired
-
- const [identifierReference] = await packIndexer.service.find(
- 'item',
- 'identifier',
- [identifier],
- false
- )
-
- // Read item behavior file
- const itemBehaviorFile = await fs
- .readJSON(identifierReference)
- .catch(() => null)
- if (itemBehaviorFile === null) return null
-
- // Get 'minecraft:icon' component from item behavior
- const iconComponent =
- itemBehaviorFile['minecraft:item']?.components?.[
- 'minecraft:icon'
- ] ?? null
- if (iconComponent === null) return null
-
- // Get current texture name from icon component
- const iconTextureName = iconComponent.texture ?? null
- if (iconTextureName === null) return null
-
- // Lookup texture name within item_texture.json file
- const itemTextureFile = await fs
- .readJSON(
- project.config.resolvePackPath(
- 'resourcePack',
- 'textures/item_texture.json'
- )
- )
- .catch(() => null)
- if (itemTextureFile === null || !itemTextureFile.texture_data)
- return null
-
- const iconTextureDataObj =
- itemTextureFile.texture_data[iconTextureName] ?? null
- if (iconTextureDataObj === null) return null
-
- // Load icon texture path from icon texture data object ({ textures: '...' }, { textures: ['...'] } or '...')
- let iconTexturePath = null
- if (typeof iconTextureDataObj === 'string')
- iconTexturePath = iconTextureDataObj
- else if (
- typeof iconTextureDataObj === 'object' &&
- typeof iconTextureDataObj.textures === 'string'
- )
- iconTexturePath = iconTextureDataObj.textures
- else if (
- typeof iconTextureDataObj === 'object' &&
- Array.isArray(iconTextureDataObj.textures)
- )
- iconTexturePath = iconTextureDataObj.textures[0] ?? null
-
- if (iconTexturePath === null) return null
-
- // Find icon texture file extension
- const absolutePathWithoutExt = project.config.resolvePackPath(
- 'resourcePack',
- iconTexturePath
- )
- console.log(absolutePathWithoutExt)
-
- const absolutePath = await findFileExtension(
- fs,
- absolutePathWithoutExt,
- ['.png', '.jpg', '.jpeg', '.tga']
- )
- console.log(absolutePath)
- if (absolutePath === undefined) return null
-
- // Load file handle as data url
- const imageHandle = await fs
- .getFileHandle(absolutePath)
- .catch(() => null)
- if (imageHandle === null) return null
-
- return await fs.loadFileHandleAsDataUrl(imageHandle)
- }
-}
diff --git a/src/components/Editors/IframeTab/API/Requests/Tab/SetIsLoading.ts b/src/components/Editors/IframeTab/API/Requests/Tab/SetIsLoading.ts
deleted file mode 100644
index 5b8846061..000000000
--- a/src/components/Editors/IframeTab/API/Requests/Tab/SetIsLoading.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { IframeApi } from '../../IframeApi'
-import { GenericRequest } from '../GenericRequest'
-
-export class SetIsLoadingRequest extends GenericRequest {
- constructor(api: IframeApi) {
- super('tab.setIsLoading', api)
- }
-
- async handle(isLoading: boolean, origin: string) {
- this.api.tab.setIsLoading(isLoading)
- }
-}
diff --git a/src/components/Editors/IframeTab/API/Requests/Tab/SetIsUnsaved.ts b/src/components/Editors/IframeTab/API/Requests/Tab/SetIsUnsaved.ts
deleted file mode 100644
index c3e769615..000000000
--- a/src/components/Editors/IframeTab/API/Requests/Tab/SetIsUnsaved.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { IframeApi } from '../../IframeApi'
-import { GenericRequest } from '../GenericRequest'
-
-export class SetIsUnsavedRequest extends GenericRequest {
- constructor(api: IframeApi) {
- super('tab.setIsUnsaved', api)
- }
-
- async handle(isUnsaved: boolean, origin: string) {
- this.api.tab.setIsUnsaved(isUnsaved)
- }
-}
diff --git a/src/components/Editors/IframeTab/API/Requests/Util/Platform.ts b/src/components/Editors/IframeTab/API/Requests/Util/Platform.ts
deleted file mode 100644
index a93fa72ee..000000000
--- a/src/components/Editors/IframeTab/API/Requests/Util/Platform.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { IframeApi } from '../../IframeApi'
-import { GenericRequest } from '../GenericRequest'
-import { platform } from '/@/utils/os'
-
-export class PlatformRequest extends GenericRequest {
- constructor(api: IframeApi) {
- super('util.platform', api)
- }
-
- async handle(_: undefined, origin: string) {
- return platform()
- }
-}
diff --git a/src/components/Editors/IframeTab/IframeTab.ts b/src/components/Editors/IframeTab/IframeTab.ts
deleted file mode 100644
index baab70234..000000000
--- a/src/components/Editors/IframeTab/IframeTab.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import { TabSystem } from '../../TabSystem/TabSystem'
-import IframeTabComponent from './IframeTab.vue'
-import { Tab } from '../../TabSystem/CommonTab'
-import { IframeApi } from './API/IframeApi'
-import { markRaw } from 'vue'
-import { AnyFileHandle } from '../../FileSystem/Types'
-import { getFullScreenElement } from '../../TabSystem/TabContextMenu/Fullscreen'
-
-interface IIframeTabOptions {
- icon?: string
- name?: string
- url?: string
- html?: string
- iconColor?: string
- openWithPayload?: IOpenWithPayload
-}
-
-export interface IOpenWithPayload {
- filePath?: string
- fileHandle?: AnyFileHandle
- isReadOnly?: boolean
-}
-
-export class IframeTab extends Tab {
- component = IframeTabComponent
-
- private iframe = document.createElement('iframe')
- protected loaded: Promise
- protected api = markRaw(new IframeApi(this, this.iframe))
-
- constructor(parent: TabSystem, protected options: IIframeTabOptions = {}) {
- super(parent)
-
- this.isTemporary = false
- this.iframe.setAttribute(
- 'sandbox',
- 'allow-scripts allow-same-origin allow-modals allow-popups allow-forms allow-downloads'
- )
- this.loaded = new Promise((resolve) =>
- this.iframe.addEventListener('load', () => resolve())
- )
-
- if (this.url) this.setUrl(this.url)
- if (this.options.html) this.iframe.srcdoc = this.options.html
-
- this.iframe.width = '100%'
- this.iframe.style.display = 'none'
- this.iframe.style.position = 'absolute'
- this.iframe.classList.add('outlined')
- this.iframe.style.borderRadius = '12px'
- this.iframe.style.margin = '8px'
- getFullScreenElement()?.appendChild(this.iframe)
- }
-
- getOptions() {
- return this.options
- }
- setOpenWithPayload(payload?: IOpenWithPayload) {
- this.options.openWithPayload = payload
- if (payload) this.api.triggerOpenWith()
- }
-
- async setup() {
- await super.setup()
- }
- async onActivate() {
- await super.onActivate()
-
- this.isLoading = true
- await this.loaded
- this.isLoading = false
-
- // Only show iframe if tab is still active
- if (this.isActive) this.iframe.style.display = 'block'
- }
- onDeactivate() {
- super.onDeactivate()
- this.iframe.style.display = 'none'
- }
- onDestroy() {
- if (getFullScreenElement()?.contains(this.iframe))
- getFullScreenElement()?.removeChild(this.iframe)
-
- this.api.dispose()
- }
-
- get icon() {
- return this.options.icon ?? 'mdi-web'
- }
- get iconColor() {
- return this.options.iconColor
- }
- get name() {
- return this.options.name ?? 'Web'
- }
- get url() {
- return this.options.url
- }
- set srcdoc(val: string) {
- this.api.loaded.resetSignal()
- this.iframe.srcdoc = val
- }
- set src(val: string) {
- this.api.loaded.resetSignal()
- this.iframe.src = val
- }
-
- setUrl(url: string) {
- this.iframe.src = url
- }
-
- async is(tab: Tab): Promise {
- return tab instanceof IframeTab && tab.url === this.url
- }
-}
diff --git a/src/components/Editors/IframeTab/IframeTab.vue b/src/components/Editors/IframeTab/IframeTab.vue
deleted file mode 100644
index 8e42bff23..000000000
--- a/src/components/Editors/IframeTab/IframeTab.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/components/Editors/Image/ImageTab.ts b/src/components/Editors/Image/ImageTab.ts
deleted file mode 100644
index d349769c2..000000000
--- a/src/components/Editors/Image/ImageTab.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { FileTab, TReadOnlyMode } from '/@/components/TabSystem/FileTab'
-import { loadHandleAsDataURL } from '/@/utils/loadAsDataUrl'
-import ImageTabComponent from './ImageTab.vue'
-import { AnyFileHandle } from '../../FileSystem/Types'
-
-export class ImageTab extends FileTab {
- component = ImageTabComponent
- dataUrl?: string = undefined
-
- static is(fileHandle: AnyFileHandle) {
- const fileName = fileHandle.name
- return (
- fileName.endsWith('.png') ||
- fileName.endsWith('.jpg') ||
- fileName.endsWith('.jpeg')
- )
- }
-
- setReadOnly(val: TReadOnlyMode) {
- this.readOnlyMode = val
- }
-
- async onActivate() {
- this.dataUrl = await loadHandleAsDataURL(this.fileHandle)
- }
-
- get icon() {
- return 'mdi-file-image-outline'
- }
- get iconColor() {
- return 'resourcePack'
- }
-
- _save() {}
-}
diff --git a/src/components/Editors/Image/ImageTab.vue b/src/components/Editors/Image/ImageTab.vue
deleted file mode 100644
index 8a07972e2..000000000
--- a/src/components/Editors/Image/ImageTab.vue
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
![]()
-
-
-
-
-
-
diff --git a/src/components/Editors/Image/TargaTab.ts b/src/components/Editors/Image/TargaTab.ts
deleted file mode 100644
index 7aff2ac48..000000000
--- a/src/components/Editors/Image/TargaTab.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-import TgaLoader from 'tga-js'
-import { ImageTab } from './ImageTab'
-import { AnyFileHandle } from '../../FileSystem/Types'
-import { SimpleAction } from '../../Actions/SimpleAction'
-
-export class TargaTab extends ImageTab {
- protected tga = new TgaLoader()
- maskIsApplied: boolean = true
-
- static is(fileHandle: AnyFileHandle) {
- return fileHandle.name.endsWith('.tga')
- }
-
- async setup() {
- await super.setup()
-
- this.addAction(
- new SimpleAction({
- icon: 'mdi-image-filter-black-white',
- name: 'actions.tgaMaskToggle.name',
- onTrigger: async () => {
- if (this.maskIsApplied) {
- await this.applyUnmaskedImageUrl()
- this.maskIsApplied = false
- return
- }
-
- this.applyMaskedImageUrl()
- this.maskIsApplied = true
- },
- })
- )
- }
-
- async onActivate() {
- this.isLoading = true
-
- const file = await this.fileHandle.getFile()
- this.tga.load(new Uint8Array(await file.arrayBuffer()))
-
- this.isLoading = false
-
- if (this.maskIsApplied) {
- this.applyMaskedImageUrl()
- return
- }
-
- await this.applyUnmaskedImageUrl()
- }
-
- _save() {
- /// TODO: Save `this.dataUrl` value to `${this.fileHandle.name}.png` file
- }
-
- async saveAs() {
- /// TODO: Save `this.dataUrl` value to user input
- }
-
- applyMaskedImageUrl() {
- this.dataUrl = this.tga.getDataURL('image/png')
- }
-
- async applyUnmaskedImageUrl() {
- /// Get ImageData from TGALoader
- const { width, height, data } = this.tga.getImageData()
-
- // @ts-ignore OffscreenCanvas API types not available in TypeScript
- const offscreen = new OffscreenCanvas(width, height)
-
- /// Create context to contain new image
- const ctx = offscreen.getContext('2d')
- const imageData = ctx.createImageData(width, height)
-
- /// Rewrite ImageData
- /// Copies RGB channels from original data
- /// Clamps alpha channel to be fully opaque (white)
- const len = data.length
-
- for (let itr = 0; itr < len; itr += 4) {
- imageData.data[itr] = data[itr]
- imageData.data[itr + 1] = data[itr + 1]
- imageData.data[itr + 2] = data[itr + 2]
- imageData.data[itr + 3] = 255
- }
-
- ctx.putImageData(imageData, 0, 0)
-
- const canvasBlob = await offscreen.convertToBlob({
- type: 'image/png',
- })
-
- /// Convert OffscreenCanvas content to Blob, so it can be converted to base64
- const reader = new FileReader()
- reader.readAsDataURL(canvasBlob)
-
- reader.onload = () => {
- this.dataUrl = reader.result?.toString()
- }
-
- reader.onerror = () => {
- throw new Error('Failed reading OffscreenCanvas Blob')
- }
- }
-}
diff --git a/src/components/Editors/ParticlePreview/ParticlePreview.ts b/src/components/Editors/ParticlePreview/ParticlePreview.ts
deleted file mode 100644
index 6d22eb8ce..000000000
--- a/src/components/Editors/ParticlePreview/ParticlePreview.ts
+++ /dev/null
@@ -1,172 +0,0 @@
-import type Wintersky from 'wintersky'
-import type { Emitter, Config } from 'wintersky'
-import { ThreePreviewTab } from '../ThreePreview/ThreePreviewTab'
-import { SimpleAction } from '/@/components/Actions/SimpleAction'
-import json5 from 'json5'
-import { AxesHelper } from 'three'
-import { FileWatcher } from '/@/components/FileSystem/FileWatcher'
-import { ParticleWatcher } from './ParticleWatcher'
-import { TabSystem } from '/@/components/TabSystem/TabSystem'
-import { loadAsDataURL } from '/@/utils/loadAsDataUrl'
-import { App } from '/@/App'
-import { Signal } from '/@/components/Common/Event/Signal'
-import { FileTab } from '../../TabSystem/FileTab'
-import { markRaw } from 'vue'
-import { useWintersky } from '/@/utils/libs/useWintersky'
-
-export class ParticlePreviewTab extends ThreePreviewTab {
- protected emitter?: Emitter
- protected config?: Config
- protected fileWatcher?: FileWatcher
- protected isReloadingDone = new Signal()
-
- protected wintersky!: Wintersky.Scene
-
- constructor(tab: FileTab, tabSystem: TabSystem) {
- super(tab, tabSystem)
-
- this.setupComplete.once(() => {
- this.scene.add(new AxesHelper(16))
- this.wintersky.global_options.tick_rate = 60
- this.wintersky.global_options.max_emitter_particles = 1000
- this.wintersky.global_options.scale = 16
-
- // this.scene.add(new GridHelper(64, 64))
- })
- this.isReloadingDone.dispatch()
- }
-
- async setup() {
- const { default: Wintersky } = await useWintersky()
-
- this.wintersky = markRaw(
- new Wintersky.Scene({
- fetchTexture: async (config) => {
- const app = await App.getApp()
- const projectConfig = app.project.config
-
- try {
- return await loadAsDataURL(
- projectConfig.resolvePackPath(
- 'resourcePack',
- `${config.particle_texture_path}.png`
- ),
- app.fileSystem
- )
- } catch (err) {
- // Fallback to Wintersky's default handling of textures
- }
- },
- })
- )
-
- await super.setup()
- }
-
- async onActivate() {
- await super.onActivate()
- await this.onChange()
- }
- onDeactivate() {
- this.emitter?.stop(true)
-
- super.onDeactivate()
- }
- onDestroy() {
- this.fileWatcher?.dispose()
- this.emitter?.delete()
- super.onDestroy()
- }
-
- onCreate() {
- this.addAction(
- new SimpleAction({
- icon: 'mdi-refresh',
- name: 'general.reload',
- onTrigger: () => this.reload(),
- })
- )
- }
- async receiveCanvas(canvas: HTMLCanvasElement) {
- const shouldSetPosition = !this._camera
- await super.receiveCanvas(canvas)
- if (shouldSetPosition) this.camera.position.set(60, 30, 60)
- }
-
- async loadParticle(file?: File) {
- if (!this.fileWatcher)
- this.fileWatcher = markRaw(
- new ParticleWatcher(this, this.tab.getPath())
- )
- if (!file)
- file =
- (await this.fileWatcher?.requestFile(await this.getFile())) ??
- (await this.getFile())
-
- let particle: any
- try {
- particle = json5.parse(await file.text())
- } catch {
- return
- }
-
- const { default: Wintersky } = await useWintersky()
-
- this.emitter?.delete()
- if (!this.scene.children.includes(this.wintersky.space))
- this.scene.add(this.wintersky.space)
-
- this.config = markRaw(
- new Wintersky.Config(this.wintersky, particle, {
- path: this.tab.getPath(),
- })
- )
-
- this.emitter = markRaw(
- new Wintersky.Emitter(this.wintersky, this.config, {
- loop_mode: 'looping',
- parent_mode: 'world',
- })
- )
- // console.log(this.scene)
-
- this.emitter.start()
- }
-
- protected render() {
- // console.log('loop')
- this.controls?.update()
- this.wintersky.updateFacingRotation(this.camera)
- this.emitter?.tick()
-
- this.renderer?.render(this.scene, this.camera)
- this.renderingRequested = false
-
- if (this.isActive) this.requestRendering()
- }
-
- async onChange(file?: File) {
- await this.isReloadingDone.fired
- this.isReloadingDone.resetSignal()
-
- await this.loadParticle(file)
- this.isReloadingDone.dispatch()
- }
- async close() {
- const didClose = await super.close()
- if (didClose) this.fileWatcher?.dispose()
-
- return didClose
- }
-
- async reload() {
- await this.onChange()
- }
-
- get icon() {
- return this.tab.icon
- }
- get iconColor() {
- return this.tab.iconColor
- }
-}
diff --git a/src/components/Editors/ParticlePreview/ParticleWatcher.ts b/src/components/Editors/ParticlePreview/ParticleWatcher.ts
deleted file mode 100644
index 5c43c9a65..000000000
--- a/src/components/Editors/ParticlePreview/ParticleWatcher.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { PreviewFileWatcher } from '/@/components/Editors/GeometryPreview/Data/PreviewFileWatcher'
-import { ParticlePreviewTab } from './ParticlePreview'
-
-export class ParticleWatcher extends PreviewFileWatcher {
- constructor(protected tab: ParticlePreviewTab, filePath: string) {
- super(tab.tabSystem.app, filePath)
- }
-
- onChange(file: File) {
- this.tab.onChange(file)
- }
-}
diff --git a/src/components/Editors/Sound/SoundTab.ts b/src/components/Editors/Sound/SoundTab.ts
deleted file mode 100644
index 8a4f2af85..000000000
--- a/src/components/Editors/Sound/SoundTab.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-import { FileTab, TReadOnlyMode } from '/@/components/TabSystem/FileTab'
-import { loadHandleAsDataURL } from '/@/utils/loadAsDataUrl'
-import SoundTabComponent from './SoundTab.vue'
-import { AnyFileHandle } from '../../FileSystem/Types'
-import { addDisposableEventListener } from '/@/utils/disposableListener'
-import { IDisposable } from '/@/types/disposable'
-
-export class SoundTab extends FileTab {
- component = SoundTabComponent
- dataUrl?: string = undefined
-
- audio: HTMLAudioElement | null = null
- intervalId: number | null = null
- currentTime = 0
- timeTriggeredManually = false
- isPlaying = false
- loadedAudioMetadata = false
- audioShouldLoop = false
- disposables: IDisposable[] = []
-
- get icon() {
- return 'mdi-file-music-outline'
- }
- get iconColor() {
- return 'resourcePack'
- }
-
- static is(fileHandle: AnyFileHandle) {
- const fileName = fileHandle.name
- return fileName.endsWith('.mp3') || fileName.endsWith('.ogg')
- }
-
- setReadOnly(val: TReadOnlyMode) {
- this.readOnlyMode = val
- }
-
- // Tab events
- async setup() {
- this.dataUrl = await loadHandleAsDataURL(this.fileHandle)
- this.audio = document.createElement('audio')
- if (!this.audio) return
-
- this.audio.preload = 'metadata'
- this.audio.loop = this.audioShouldLoop
- this.audio.src = this.dataUrl
-
- this.intervalId = window.setInterval(
- () => this.updateCurrentTime(),
- 100
- )
-
- this.disposables = [
- addDisposableEventListener('play', () => this.onPlay(), this.audio),
- addDisposableEventListener(
- 'pause',
- () => this.onPause(),
- this.audio
- ),
- addDisposableEventListener(
- 'loadedmetadata',
- () => this.onLoadedMetadata(),
- this.audio
- ),
- ]
-
- await super.setup()
- }
- onDestroy() {
- if (this.intervalId) window.clearInterval(this.intervalId)
- this.intervalId = null
-
- this.disposables.forEach((disposable) => disposable.dispose())
- this.disposables = []
-
- this.audio?.pause()
- this.audio = null
- }
-
- // Sound element events
- onPlay() {
- this.isPlaying = true
- }
- onPause() {
- this.isPlaying = false
- }
- onLoadedMetadata() {
- this.loadedAudioMetadata = true
- }
-
- updateCurrentTime() {
- this.timeTriggeredManually = false
- this.currentTime = this.audio?.currentTime ?? 0
- }
- toggleAudioLoop() {
- this.audioShouldLoop = !this.audioShouldLoop
- if (this.audio) this.audio.loop = this.audioShouldLoop
- }
- setCurrentTime(time: number) {
- if (!this.audio) return
-
- if (!this.timeTriggeredManually) {
- this.timeTriggeredManually = true
- return
- }
- if (Number.isNaN(time)) return
-
- this.audio.currentTime = Math.round(time * 100) / 100
- }
-
- _save() {}
-}
diff --git a/src/components/Editors/Sound/SoundTab.vue b/src/components/Editors/Sound/SoundTab.vue
deleted file mode 100644
index bce13148c..000000000
--- a/src/components/Editors/Sound/SoundTab.vue
+++ /dev/null
@@ -1,105 +0,0 @@
-
-
-
-
-
-
- {{ extension }}
-
-
-
- mdi-all-inclusive
-
-
-
-
-
-
-
-
-
-
- {{ tab.isPlaying ? 'mdi-pause' : 'mdi-play' }}
-
-
-
-
-
-
-
- {{ roundedCurrentTime }} / {{ roundedTotalTime }}
-
-
- tab.setCurrentTime(n)"
- :min="0"
- :max="audio.duration"
- step="0.01"
- hide-details
- />
-
-
-
-
-
-
diff --git a/src/components/Editors/Text/TextTab.ts b/src/components/Editors/Text/TextTab.ts
deleted file mode 100644
index cacdd42e7..000000000
--- a/src/components/Editors/Text/TextTab.ts
+++ /dev/null
@@ -1,326 +0,0 @@
-import { FileTab, TReadOnlyMode } from '/@/components/TabSystem/FileTab'
-import TextTabComponent from './TextTab.vue'
-import type { editor } from 'monaco-editor'
-import { IDisposable } from '/@/types/disposable'
-import { App } from '/@/App'
-import { TabSystem } from '/@/components/TabSystem/TabSystem'
-import { settingsState } from '/@/components/Windows/Settings/SettingsState'
-import { debounce } from 'lodash-es'
-import { Signal } from '/@/components/Common/Event/Signal'
-import { AnyFileHandle } from '/@/components/FileSystem/Types'
-import { markRaw } from 'vue'
-import { loadMonaco, useMonaco } from '../../../utils/libs/useMonaco'
-import { wait } from '/@/utils/wait'
-
-const throttledCacheUpdate = debounce<(tab: TextTab) => Promise | void>(
- async (tab) => {
- if (!tab.editorModel || tab.editorModel.isDisposed()) return
-
- const fileContent = tab.editorModel?.getValue()
- const app = await App.getApp()
-
- app.project.fileChange.dispatch(tab.getPath(), await tab.getFile())
-
- await app.project.packIndexer.updateFile(
- tab.getPath(),
- fileContent,
- tab.isForeignFile,
- true
- )
- await app.project.jsonDefaults.updateDynamicSchemas(tab.getPath())
- },
- 600
-)
-
-export class TextTab extends FileTab {
- component = TextTabComponent
- editorModel: editor.ITextModel | undefined
- editorViewState: editor.ICodeEditorViewState | undefined
- disposables: (IDisposable | undefined)[] = []
- isActive = false
- protected modelLoaded = new Signal()
- protected initialVersionId: number = 0
-
- get editorInstance() {
- return this.parent.monacoEditor
- }
-
- constructor(
- parent: TabSystem,
- fileHandle: AnyFileHandle,
- readOnlyMode?: TReadOnlyMode
- ) {
- super(parent, fileHandle, readOnlyMode)
-
- this.fired.then(async () => {
- const app = await App.getApp()
- await app.projectManager.projectReady.fired
-
- app.project.tabActionProvider.addTabActions(this)
- })
- }
- async getFile() {
- if (!this.editorModel || this.editorModel.isDisposed())
- return await super.getFile()
-
- return new File([this.editorModel.getValue()], this.name)
- }
-
- updateUnsavedStatus() {
- if (!this.editorModel || this.editorModel.isDisposed()) return
-
- this.setIsUnsaved(
- this.initialVersionId !==
- this.editorModel?.getAlternativeVersionId()
- )
- }
-
- fileDidChange() {
- // Updates the isUnsaved status of the tab
- this.updateUnsavedStatus()
-
- super.fileDidChange()
- }
-
- async onActivate() {
- if (this.isActive) return
- this.isActive = true
-
- // Load monaco in
- if (!loadMonaco.hasFired) {
- this.isLoading = true
- loadMonaco.dispatch()
-
- // Monaco theme isn't loaded yet
- await this.parent.app.themeManager.applyMonacoTheme()
- }
-
- const { editor, Uri } = await useMonaco()
-
- await this.parent.fired //Make sure a monaco editor is loaded
- await wait(1)
- this.isLoading = false
-
- if (!this.editorModel || this.editorModel.isDisposed()) {
- const file = await this.fileHandle.getFile()
- const fileContent = await file.text()
- // This for some reason fixes monaco suggesting the wrong path for quickfixes #932
- const filePath = this.getPath()
- const uri = Uri.file(
- filePath.endsWith('.ts')
- ? filePath.replace('/BP/', '/bp/')
- : filePath
- )
-
- this.editorModel = markRaw(
- editor.getModel(uri) ??
- editor.createModel(
- fileContent,
- App.fileType.get(this.getPath())?.meta?.language,
- uri
- )
- )
- this.initialVersionId = this.editorModel.getAlternativeVersionId()
-
- this.modelLoaded.dispatch()
- await this.loadEditor(false)
- } else {
- await this.loadEditor()
- }
-
- this.disposables.push(
- this.editorModel?.onDidChangeContent(() => {
- throttledCacheUpdate(this)
- this.fileDidChange()
- })
- )
- this.disposables.push(
- this.editorInstance?.onDidFocusEditorText(() => {
- this.parent.setActive(true)
- })
- )
-
- this.editorInstance?.layout()
- super.onActivate()
- }
- async onDeactivate() {
- await super.onDeactivate()
-
- // MonacoEditor is defined
- if (this.tabSystem.hasFired) {
- const viewState = this.editorInstance.saveViewState()
- if (viewState) this.editorViewState = markRaw(viewState)
- }
-
- this.disposables.forEach((disposable) => disposable?.dispose())
- this.isActive = false
- }
- onDestroy() {
- this.disposables.forEach((disposable) => disposable?.dispose())
- this.editorModel?.dispose()
- this.editorModel = undefined
- this.editorViewState = undefined
- this.isActive = false
- this.modelLoaded.resetSignal()
- }
- updateParent(parent: TabSystem) {
- super.updateParent(parent)
- }
- focus() {
- this.editorInstance?.focus()
- }
-
- async loadEditor(shouldFocus = true) {
- await this.parent.fired //Make sure a monaco editor is loaded
-
- if (this.editorModel && !this.editorModel.isDisposed())
- this.editorInstance.setModel(this.editorModel)
- if (this.editorViewState)
- this.editorInstance.restoreViewState(this.editorViewState)
-
- this.editorInstance?.updateOptions({ readOnly: this.isReadOnly })
- if (shouldFocus) setTimeout(() => this.focus(), 10)
- }
-
- async _save() {
- this.isTemporary = false
-
- const app = await App.getApp()
- const action = this.editorInstance?.getAction(
- 'editor.action.formatDocument'
- )
- const fileType = App.fileType.get(this.getPath())
-
- const fileContentStr = this.editorModel?.getValue()
-
- if (
- // Make sure that there is fileContent to format,
- fileContentStr &&
- fileContentStr !== '' &&
- // ...that we have an action to trigger,
- action &&
- // ...that the file is a valid fileType,
- fileType &&
- // ...that formatOnSave is enabled,
- (settingsState?.general?.formatOnSave ?? true) &&
- // ...and that the current file type supports formatting
- (fileType?.formatOnSaveCapable ?? true)
- ) {
- // This is a terrible hack because we need to make sure that the formatter triggers the "onDidChangeContent" event
- // The promise returned by action.run() actually resolves before formatting is done so we need the "onDidChangeContent" event to tell when the formatter is done
- this.makeFakeEdit('\t')
-
- const editPromise = new Promise((resolve) => {
- if (!this.editorModel || this.editorModel.isDisposed())
- return resolve()
-
- const disposable = this.editorModel?.onDidChangeContent(() => {
- disposable?.dispose()
-
- resolve()
- })
- })
-
- const actionPromise = action.run()
-
- let didAnyFinish = false
- await Promise.race([
- // Wait for the action to finish
- Promise.all([editPromise, actionPromise]),
- // But don't wait longer than 1.5s, action then likely failed for some weird reason
- wait(1500).then(() => {
- if (didAnyFinish) return
-
- this.makeFakeEdit(null)
- }),
- ])
- didAnyFinish = true
- }
-
- await this.saveFile()
- }
- protected makeFakeEdit(text: string | null) {
- if (!text) {
- this.editorInstance.trigger('automatic', 'undo', null)
- } else {
- this.editorInstance.pushUndoStop()
- this.editorInstance?.executeEdits('automatic', [
- {
- forceMoveMarkers: false,
- range: {
- startLineNumber: 1,
- endLineNumber: 1,
- startColumn: 1,
- endColumn: 1,
- },
- text,
- },
- ])
- this.editorInstance.pushUndoStop()
- }
- }
- protected async saveFile() {
- if (this.editorModel && !this.editorModel.isDisposed()) {
- App.eventSystem.dispatch('beforeModifiedProject', null)
-
- const writeWorked = await this.writeFile(
- this.editorModel.getValue()
- )
-
- App.eventSystem.dispatch('modifiedProject', null)
-
- if (writeWorked) {
- this.setIsUnsaved(false)
- this.initialVersionId =
- this.editorModel.getAlternativeVersionId()
- }
- } else {
- console.error(`Cannot save file content without active editorModel`)
- }
- }
-
- setReadOnly(val: TReadOnlyMode) {
- this.readOnlyMode = val
- this.editorInstance?.updateOptions({ readOnly: val !== 'off' })
- }
-
- async paste() {
- if (this.isReadOnly) return
-
- this.focus()
- this.editorInstance?.trigger('keyboard', 'paste', {
- text: await navigator.clipboard.readText(),
- })
- }
- cut() {
- if (this.isReadOnly) return
-
- this.focus()
- document.execCommand('cut')
- }
- async close() {
- const didClose = await super.close()
-
- // We need to clear the lightning cache store from temporary data if the user doesn't save changes
- if (didClose && this.isUnsaved) {
- const app = await App.getApp()
-
- if (this.isForeignFile) {
- await app.fileSystem.unlink(this.getPath())
- } else {
- const file = await this.fileHandle.getFile()
- const fileContent = await file.text()
- await app.project.packIndexer.updateFile(
- this.getPath(),
- fileContent
- )
- }
- }
-
- return didClose
- }
-
- showContextMenu(event: MouseEvent) {
- this.parent.showCustomMonacoContextMenu(event, this)
- }
-}
diff --git a/src/components/Editors/Text/TextTab.vue b/src/components/Editors/Text/TextTab.vue
deleted file mode 100644
index 60ea2195c..000000000
--- a/src/components/Editors/Text/TextTab.vue
+++ /dev/null
@@ -1,64 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/components/Editors/ThreePreview/ThreePreviewTab.ts b/src/components/Editors/ThreePreview/ThreePreviewTab.ts
deleted file mode 100644
index 5de48e7b5..000000000
--- a/src/components/Editors/ThreePreview/ThreePreviewTab.ts
+++ /dev/null
@@ -1,145 +0,0 @@
-import ThreePreviewTabComponent from './ThreePreviewTab.vue'
-import { IDisposable } from '/@/types/disposable'
-import { PreviewTab } from '/@/components/TabSystem/PreviewTab'
-import {
- AmbientLight,
- Color,
- PerspectiveCamera,
- Scene,
- WebGLRenderer,
-} from 'three'
-import { Signal } from '/@/components/Common/Event/Signal'
-import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
-import { App } from '/@/App'
-import { markRaw } from 'vue'
-
-export abstract class ThreePreviewTab extends PreviewTab {
- public component = ThreePreviewTabComponent
- public readonly setupComplete = new Signal()
-
- protected disposables: IDisposable[] = []
-
- protected canvas?: HTMLCanvasElement
- protected renderer?: WebGLRenderer
- protected _camera?: PerspectiveCamera
- protected controls?: OrbitControls
- protected _scene?: Scene
- protected renderingRequested = false
- protected width: number = 0
- protected height: number = 0
-
- get scene() {
- if (!this._scene) throw new Error(`Scene is not defined yet`)
- return this._scene
- }
- get camera() {
- if (!this._camera) throw new Error(`Camera is not defined yet`)
- return this._camera
- }
-
- async is() {
- return false
- }
-
- async receiveCanvas(canvas: HTMLCanvasElement) {
- const app = await App.getApp()
-
- this.canvas = markRaw(canvas)
-
- this.renderer = markRaw(
- new WebGLRenderer({
- antialias: true,
- canvas,
- })
- )
- this.renderer.setPixelRatio(window.devicePixelRatio)
-
- if (!this._camera) {
- this._camera = markRaw(new PerspectiveCamera(70, 2, 0.1, 1000))
- this._camera.position.x = -16
- this._camera.position.y = 16
- this._camera.position.z = -16
- }
-
- this.controls?.dispose()
- this.controls = markRaw(new OrbitControls(this.camera, canvas))
- this.controls.addEventListener('change', () => {
- this.requestRendering()
- if (!this.parent.isActive.value) this.parent.setActive(true)
- })
-
- if (!this._scene) {
- this._scene = markRaw(new Scene())
- this._scene.add(new AmbientLight(0xffffff))
- }
-
- this._scene.background = new Color(
- app.themeManager.getColor('background')
- )
-
- this.disposables.push(
- app.windowResize.on(() => setTimeout(() => this.onResize()))
- )
-
- this.onResize()
-
- await this.fired
- this.setupComplete.dispatch()
- }
- async onActivate() {
- await this.setupComplete.fired
- await super.onActivate()
- const app = await App.getApp()
-
- this.disposables.push(
- app.themeManager.on(() => {
- const background = app.themeManager.getColor('background')
- this.scene.background = new Color(background)
- this.requestRendering()
- })
- )
- }
- onDeactivate() {
- this.setupComplete.resetSignal()
- this.controls?.dispose()
- this.renderer?.resetState()
- this.renderer?.dispose()
- this.renderer = undefined
- this.controls = undefined
- super.onDeactivate()
- }
-
- /**
- * @internal Do not call directly
- */
- protected render() {
- this.controls?.update()
- this.renderer?.render(this.scene, this.camera)
- this.renderingRequested = false
- }
-
- async requestRendering() {
- if (this.renderingRequested) return
-
- this.renderingRequested = true
- await this.setupComplete.fired
- requestAnimationFrame(() => this.render())
- }
- protected onResize() {
- const dimensions = this.canvas?.parentElement?.getBoundingClientRect()
- this.width = dimensions?.width ?? 0
- this.height = dimensions?.height ?? 0
-
- this.renderer?.setSize(this.width, this.height, true)
- if (this.camera) {
- this.camera.aspect = this.width / this.height
- this.camera.updateProjectionMatrix()
- }
- this.requestRendering()
- }
- protected async toOtherTabSystem(updateParentTabs?: boolean) {
- await super.toOtherTabSystem(updateParentTabs)
- await this.setupComplete.fired
- this.onResize()
- }
-}
diff --git a/src/components/Editors/ThreePreview/ThreePreviewTab.vue b/src/components/Editors/ThreePreview/ThreePreviewTab.vue
deleted file mode 100644
index 633b5e2d5..000000000
--- a/src/components/Editors/ThreePreview/ThreePreviewTab.vue
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-
diff --git a/src/components/Editors/TreeEditor/CompletionItems/FilterDuplicates.ts b/src/components/Editors/TreeEditor/CompletionItems/FilterDuplicates.ts
deleted file mode 100644
index 17f95cad0..000000000
--- a/src/components/Editors/TreeEditor/CompletionItems/FilterDuplicates.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { ICompletionItem } from '/@/components/JSONSchema/Schema/Schema'
-
-function getItemId(item: ICompletionItem) {
- return `t-${item.type}:v-${item.value}`
-}
-
-export function filterDuplicates(items: ICompletionItem[]): ICompletionItem[] {
- const seen = new Set()
- return items.filter((item) => {
- const id = getItemId(item)
-
- const result = !seen.has(id)
- seen.add(id)
-
- return result
- })
-}
diff --git a/src/components/Editors/TreeEditor/Highlight.vue b/src/components/Editors/TreeEditor/Highlight.vue
deleted file mode 100644
index d7ad870b6..000000000
--- a/src/components/Editors/TreeEditor/Highlight.vue
+++ /dev/null
@@ -1,113 +0,0 @@
-
-
-
-
-
- "{{ value }}"
-
- "
- {{ text }}{{
- tokens.length > i + 1 ? ':' : ''
- }} "
-
-
-
-
diff --git a/src/components/Editors/TreeEditor/History/CollectedEntry.ts b/src/components/Editors/TreeEditor/History/CollectedEntry.ts
deleted file mode 100644
index 1b665fc01..000000000
--- a/src/components/Editors/TreeEditor/History/CollectedEntry.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { HistoryEntry } from './HistoryEntry'
-
-export class CollectedEntry extends HistoryEntry {
- constructor(protected entries: HistoryEntry[]) {
- super()
- }
-
- get unselectTrees() {
- return this.entries.map((entry) => entry.unselectTrees).flat()
- }
-
- undo() {
- return new CollectedEntry(
- this.entries.reverse().map((entry) => entry.undo())
- )
- }
-}
diff --git a/src/components/Editors/TreeEditor/History/DeleteEntry.ts b/src/components/Editors/TreeEditor/History/DeleteEntry.ts
deleted file mode 100644
index 47a964e5b..000000000
--- a/src/components/Editors/TreeEditor/History/DeleteEntry.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Tree } from '../Tree/Tree'
-import { HistoryEntry } from './HistoryEntry'
-
-export class UndoDeleteEntry extends HistoryEntry {
- unselectTrees: Tree[] = []
-
- constructor(
- protected tree: Tree,
- protected index: number,
- protected key = ''
- ) {
- super()
- this.unselectTrees = [this.tree]
- }
-
- undo() {
- const parent = this.tree.getParent()
-
- if (!parent)
- throw new Error(
- `Invalid state: Undo delete action on global tree node`
- )
-
- if (parent.type === 'array')
- parent.children.splice(this.index, 0, this.tree)
- else parent.children.splice(this.index, 0, [this.key, this.tree])
-
- return new DeleteEntry(this.tree, this.index, this.key)
- }
-}
-
-export class DeleteEntry extends HistoryEntry {
- unselectTrees: Tree[] = []
-
- constructor(
- protected tree: Tree,
- protected index: number,
- protected key = ''
- ) {
- super()
- this.unselectTrees = [this.tree]
- }
-
- undo() {
- const parent = this.tree.getParent()
-
- if (!parent)
- throw new Error(
- `Invalid state: Redo delete action on global tree node`
- )
-
- parent.children.splice(this.index, 1)
-
- return new UndoDeleteEntry(this.tree, this.index, this.key)
- }
-}
diff --git a/src/components/Editors/TreeEditor/History/EditPropertyEntry.ts b/src/components/Editors/TreeEditor/History/EditPropertyEntry.ts
deleted file mode 100644
index 0c87ad417..000000000
--- a/src/components/Editors/TreeEditor/History/EditPropertyEntry.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { ObjectTree } from '../Tree/ObjectTree'
-import { Tree } from '../Tree/Tree'
-import { HistoryEntry } from './HistoryEntry'
-
-export class EditPropertyEntry extends HistoryEntry {
- unselectTrees: Tree[]
-
- constructor(
- protected parent: ObjectTree,
- protected oldValue: string,
- protected newValue: string
- ) {
- super()
- this.unselectTrees = [parent]
- }
-
- undo() {
- this.parent.updatePropertyName(this.newValue, this.oldValue)
-
- return new EditPropertyEntry(this.parent, this.newValue, this.oldValue)
- }
-}
diff --git a/src/components/Editors/TreeEditor/History/EditValueEntry.ts b/src/components/Editors/TreeEditor/History/EditValueEntry.ts
deleted file mode 100644
index cda6e1311..000000000
--- a/src/components/Editors/TreeEditor/History/EditValueEntry.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { ObjectTree } from '../Tree/ObjectTree'
-import { PrimitiveTree } from '../Tree/PrimitiveTree'
-import { Tree } from '../Tree/Tree'
-import { HistoryEntry } from './HistoryEntry'
-
-export class EditValueEntry extends HistoryEntry {
- unselectTrees: Tree[]
-
- constructor(protected tree: PrimitiveTree, protected value: any) {
- super()
- this.unselectTrees = [tree]
- }
-
- undo() {
- const oldValue = `${this.tree.value}`
-
- this.tree.edit(this.value)
-
- return new EditValueEntry(this.tree, oldValue)
- }
-}
diff --git a/src/components/Editors/TreeEditor/History/EditorHistory.ts b/src/components/Editors/TreeEditor/History/EditorHistory.ts
deleted file mode 100644
index 537995fa3..000000000
--- a/src/components/Editors/TreeEditor/History/EditorHistory.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import type { TreeEditor } from '../TreeEditor'
-import { CollectedEntry } from './CollectedEntry'
-import type { HistoryEntry } from './HistoryEntry'
-import { EventDispatcher } from '/@/components/Common/Event/EventDispatcher'
-
-/**
- * Dispatches an event when the isUnsaved status of the editor changes:
- *
- * - false > tab contains no changes
- * - true > tab is unsaved
- */
-
-export class EditorHistory extends EventDispatcher {
- public readonly changed = new EventDispatcher()
- protected undoStack: HistoryEntry[] = []
- protected redoStack: HistoryEntry[] = []
- protected lastUndoLength = 0
-
- constructor(protected parent: TreeEditor) {
- super()
- }
-
- get length() {
- return this.undoStack.length
- }
-
- updateHasChanges() {
- this.dispatch(this.undoStack.length !== this.lastUndoLength)
- }
- saveState() {
- this.lastUndoLength = this.undoStack.length
- this.updateHasChanges()
- }
-
- undo() {
- const entry = this.undoStack.pop()
- if (!entry) return
-
- entry.unselectTrees.forEach((tree) =>
- this.parent.removeSelectionOf(tree)
- )
-
- this.redoStack.push(entry.undo())
-
- this.updateHasChanges()
- this.changed.dispatch()
- }
-
- redo() {
- const entry = this.redoStack.pop()
- if (!entry) return
-
- entry.unselectTrees.forEach((tree) =>
- this.parent.removeSelectionOf(tree)
- )
-
- this.undoStack.push(entry.undo())
-
- this.updateHasChanges()
- this.changed.dispatch()
- }
-
- push(entry: HistoryEntry) {
- this.undoStack.push(entry)
- this.redoStack = []
-
- this.updateHasChanges()
- this.changed.dispatch()
- }
-
- pushAll(entries: HistoryEntry[]) {
- if (entries.length === 0) return
- else if (entries.length === 1) this.push(entries[0])
- else this.push(new CollectedEntry(entries))
- }
-}
diff --git a/src/components/Editors/TreeEditor/History/HistoryEntry.ts b/src/components/Editors/TreeEditor/History/HistoryEntry.ts
deleted file mode 100644
index 73058b23f..000000000
--- a/src/components/Editors/TreeEditor/History/HistoryEntry.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import type { Tree } from '../Tree/Tree'
-
-export abstract class HistoryEntry {
- public abstract readonly unselectTrees: Tree[]
- abstract undo(): HistoryEntry
-}
diff --git a/src/components/Editors/TreeEditor/History/MoveEntry.ts b/src/components/Editors/TreeEditor/History/MoveEntry.ts
deleted file mode 100644
index fb459ae38..000000000
--- a/src/components/Editors/TreeEditor/History/MoveEntry.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { ArrayTree } from '../Tree/ArrayTree'
-import { ObjectTree } from '../Tree/ObjectTree'
-import { Tree } from '../Tree/Tree'
-import { HistoryEntry } from './HistoryEntry'
-
-export class MoveEntry extends HistoryEntry {
- unselectTrees: Tree[] = []
-
- constructor(
- protected oldParent: ArrayTree | ObjectTree,
- protected tree: Tree,
- protected index: number,
- protected key = ''
- ) {
- super()
-
- this.unselectTrees = [this.tree]
- }
-
- undo() {
- const parent = this.tree.getParent()
-
- if (!parent)
- throw new Error(
- `Invalid state: Undo delete action on global tree node`
- )
-
- const oldIndex = this.tree.findParentIndex()
- this.tree.delete()
-
- if (this.oldParent.type === 'array')
- this.oldParent.children.splice(this.index, 0, this.tree)
- else
- this.oldParent.children.splice(this.index, 0, [this.key, this.tree])
-
- this.tree.setParent(this.oldParent)
-
- return new MoveEntry(parent, this.tree, oldIndex, this.key)
- }
-}
diff --git a/src/components/Editors/TreeEditor/History/ReplaceTree.ts b/src/components/Editors/TreeEditor/History/ReplaceTree.ts
deleted file mode 100644
index 4fb6e02f9..000000000
--- a/src/components/Editors/TreeEditor/History/ReplaceTree.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Tree } from '../Tree/Tree'
-import { HistoryEntry } from './HistoryEntry'
-
-export class ReplaceTreeEntry extends HistoryEntry {
- unselectTrees: Tree[]
-
- constructor(
- protected oldTree: Tree,
- protected newTree: Tree
- ) {
- super()
- this.unselectTrees = [newTree, oldTree]
- }
-
- undo() {
- this.newTree.replace(this.oldTree)
-
- return new ReplaceTreeEntry(this.newTree, this.oldTree)
- }
-}
diff --git a/src/components/Editors/TreeEditor/InlineDiagnostic.vue b/src/components/Editors/TreeEditor/InlineDiagnostic.vue
deleted file mode 100644
index f44ccd519..000000000
--- a/src/components/Editors/TreeEditor/InlineDiagnostic.vue
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
- mdi-arrow-down
-
- {{ diagnostic.message }}
-
-
-
-
diff --git a/src/components/Editors/TreeEditor/Tab.ts b/src/components/Editors/TreeEditor/Tab.ts
deleted file mode 100644
index 0b6fce259..000000000
--- a/src/components/Editors/TreeEditor/Tab.ts
+++ /dev/null
@@ -1,204 +0,0 @@
-import { FileTab, TReadOnlyMode } from '/@/components/TabSystem/FileTab'
-import TreeTabComponent from './Tab.vue'
-import { App } from '/@/App'
-import { TabSystem } from '/@/components/TabSystem/TabSystem'
-import { TreeEditor } from './TreeEditor'
-import json5 from 'json5'
-import { settingsState } from '/@/components/Windows/Settings/SettingsState'
-import { debounce } from 'lodash-es'
-import { InformationWindow } from '../../Windows/Common/Information/InformationWindow'
-import { TreeValueSelection } from './TreeSelection'
-import { PrimitiveTree } from './Tree/PrimitiveTree'
-import { AnyFileHandle } from '../../FileSystem/Types'
-import { HistoryEntry } from './History/HistoryEntry'
-
-const throttledCacheUpdate = debounce<(tab: TreeTab) => Promise | void>(
- async (tab) => {
- const fileContent = tab.treeEditor.toJsonString()
- const app = await App.getApp()
-
- app.project.fileChange.dispatch(tab.getPath(), await tab.getFile())
-
- await app.project.packIndexer.updateFile(
- tab.getPath(),
- fileContent,
- tab.isForeignFile,
- true
- )
- await app.project.jsonDefaults.updateDynamicSchemas(tab.getPath())
- },
- 600
-)
-
-export class TreeTab extends FileTab {
- component = TreeTabComponent
- protected _treeEditor?: TreeEditor
-
- constructor(
- parent: TabSystem,
- fileHandle: AnyFileHandle,
- readOnlyMode?: TReadOnlyMode
- ) {
- super(parent, fileHandle, readOnlyMode)
-
- this.fired.then(async () => {
- const app = await App.getApp()
- await app.projectManager.projectReady.fired
-
- app.project.tabActionProvider.addTabActions(this)
- })
- }
-
- get app() {
- return this.parent.app
- }
- get project() {
- return this.parent.project
- }
-
- static is(fileHandle: AnyFileHandle) {
- return (
- settingsState?.editor?.jsonEditor === 'treeEditor' &&
- fileHandle.name.endsWith('.json')
- )
- }
- get treeEditor() {
- if (!this._treeEditor)
- throw new Error(`Trying to access TreeEditor before it was setup.`)
- return this._treeEditor
- }
- async setup() {
- let json: unknown
- try {
- const fileStr = await this.fileHandle
- .getFile()
- .then((file) => file.text())
-
- if (fileStr === '') json = {}
- else json = json5.parse(fileStr.replaceAll('\\n', '\\\\n'))
- } catch {
- new InformationWindow({
- name: 'windows.invalidJson.title',
- description: 'windows.invalidJson.description',
- })
- this.close()
- return
- }
-
- this._treeEditor = new TreeEditor(this, json)
-
- await super.setup()
- }
- async getFile() {
- return new File([this.treeEditor.toJsonString()], this.name)
- }
-
- updateCache() {
- throttledCacheUpdate(this)
- }
-
- async onActivate() {
- await super.onActivate()
-
- this.treeEditor.activate()
- }
- async onDeactivate() {
- await super.onDeactivate()
-
- this._treeEditor?.deactivate()
- }
-
- loadEditor() {}
- setReadOnly(val: TReadOnlyMode) {
- this.readOnlyMode = val
- }
-
- async _save() {
- this.isTemporary = false
-
- const fileContent = this.treeEditor.toJsonString(true)
-
- const writeWorked = await this.writeFile(fileContent)
- if (writeWorked) this.treeEditor.saveState()
- }
-
- async paste() {
- if (this.isReadOnly) return
-
- const text = await navigator.clipboard.readText()
-
- let data: any = undefined
- // Try parsing clipboard text
- try {
- data = json5.parse(text)
- } catch {
- // Parsing fails, now try again with brackets around text
- // -> To support pasting text like this: "minecraft:can_fly": {}
- try {
- data = json5.parse(`{${text}}`)
- } catch {
- return
- }
- }
- if (data === undefined) return
-
- this.treeEditor.addFromJSON(data)
- }
-
- async copy() {
- let copyText = ''
-
- this.treeEditor.forEachSelection((sel) => {
- const tree = sel.getTree()
-
- if (sel instanceof TreeValueSelection) {
- if ((tree).isValueSelected)
- copyText += tree.toJSON()
- else copyText += tree.key
- } else {
- copyText += `"${tree.key}": ${JSON.stringify(
- sel.getTree().toJSON(),
- null,
- '\t'
- )}`
- }
- })
-
- if (copyText !== '') await navigator.clipboard.writeText(copyText)
- }
-
- async cut() {
- if (this.isReadOnly) return
-
- await this.copy()
- const entries: HistoryEntry[] = []
- this.treeEditor.forEachSelection((sel) => {
- sel.dispose()
- const entry = sel.delete()
- if (entry) entries.push(entry)
- })
-
- this.treeEditor.pushAllHistoryEntries(entries)
- }
-
- async close() {
- const didClose = await super.close()
-
- // We need to clear the lightning cache store from temporary data if the user doesn't save changes
- if (!this.isForeignFile && didClose && this.isUnsaved) {
- // TODO: Well... this looks completely messed up. Look into what's the correct way to fix it. Should foreign files really get unlinked or can we just remove this?
- if (this.isForeignFile) {
- await this.app.fileSystem.unlink(this.getPath())
- } else {
- const file = await this.fileHandle.getFile()
- const fileContent = await file.text()
- await this.project.packIndexer.updateFile(
- this.getPath(),
- fileContent
- )
- }
- }
-
- return didClose
- }
-}
diff --git a/src/components/Editors/TreeEditor/Tab.vue b/src/components/Editors/TreeEditor/Tab.vue
deleted file mode 100644
index 0940ed006..000000000
--- a/src/components/Editors/TreeEditor/Tab.vue
+++ /dev/null
@@ -1,584 +0,0 @@
-
-
-
-
-
-
-
- mdi-chevron-right
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ getIcon(item) }}
-
-
- {{ item.label }}
-
-
-
-
-
-
-
- mdi-alphabetical
-
-
- {{ t('editors.treeEditor.forceValue') }}
-
-
-
onAdd(s, true)"
- @keydown.enter="mayTrigger"
- @keydown.tab="mayTrigger"
- :disabled="isGlobal"
- :items="valueSuggestions"
- :item-value="(item) => item"
- :menu-props="{
- maxHeight: 120,
- top: false,
- contentClass: 'json-editor-suggestions-menu',
- rounded: 'lg',
- 'nudge-top': -8,
- transition: 'slide-y-transition',
- }"
- :label="t('editors.treeEditor.addValue')"
- class="mx-4"
- outlined
- dense
- hide-details
- enterkeyhint="enter"
- aria-autocomplete="false"
- spellcheck="false"
- auto-select-first
- >
-
-
-
- {{ getIcon(item) }}
-
-
- {{ item.label }}
-
-
-
-
-
onEdit(s)"
- @keydown.enter="mayTrigger"
- @keydown.tab="mayTrigger"
- :disabled="isGlobal"
- :items="editSuggestions"
- :item-value="(item) => item"
- :menu-props="{
- maxHeight: 120,
- top: false,
- contentClass: 'json-editor-suggestions-menu',
- rounded: 'lg',
- 'nudge-top': -8,
- transition: 'slide-y-transition',
- }"
- :label="t('editors.treeEditor.edit')"
- outlined
- dense
- hide-details
- enterkeyhint="enter"
- aria-autocomplete="false"
- spellcheck="false"
- >
-
-
-
- {{ getIcon(item) }}
-
-
- {{ item.label }}
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/Editors/TreeEditor/Tree/ArrayTree.ts b/src/components/Editors/TreeEditor/Tree/ArrayTree.ts
deleted file mode 100644
index 3769935c2..000000000
--- a/src/components/Editors/TreeEditor/Tree/ArrayTree.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-import { createTree } from './createTree'
-import { Tree, treeElementHeight } from './Tree'
-import ArrayTreeComponent from './CommonTree.vue'
-import type { ObjectTree } from './ObjectTree'
-import { markRaw } from 'vue'
-import { TreeEditor } from '../TreeEditor'
-
-export class ArrayTree extends Tree> {
- public component = markRaw(ArrayTreeComponent)
- public _isOpen = false
- public readonly type = 'array'
- protected _children: Tree[]
-
- constructor(
- parent: ObjectTree | ArrayTree | TreeEditor | null,
- protected _value: Array
- ) {
- super(parent)
- this._children = _value.map((val) => createTree(this, val))
- }
-
- get height() {
- if (!this.isOpen) return treeElementHeight
-
- return (
- 2 * treeElementHeight +
- this._children.reduce((prev, val) => prev + val.height, 0)
- )
- }
- get children() {
- return this._children
- }
- get hasChildren() {
- return this._children.length > 0
- }
- get isOpen() {
- if (!this.hasChildren) return false
- return this._isOpen
- }
-
- get(path: (string | number)[]) {
- if (path.length === 0) return this
-
- const currentKey = path.shift()
- if (typeof currentKey !== 'number') return null
-
- const child = this.children[currentKey]
-
- if (!child) return null
-
- return child.get(path)
- }
-
- hasChild(child: Tree) {
- return this.children.includes(child)
- }
- addChild(child: Tree) {
- this._children.push(child)
- }
-
- setOpen(val: boolean, force = false) {
- if (this.hasChildren || force) this._isOpen = val
- }
- toggleOpen() {
- this.setOpen(!this._isOpen)
- }
-
- toJSON() {
- return this._children.map((child) => child.toJSON())
- }
- updatePropertyName(oldIndex: number, newIndex: number) {
- let oldTree = this.children[oldIndex]
- let newTree = this.children[newIndex]
- this.children[newIndex] = oldTree
- delete this.children[oldIndex]
-
- return {
- undo: () => {
- this.children[oldIndex] = oldTree
- this.children[newIndex] = newTree
- },
- }
- }
-
- validate() {
- super.validate()
-
- this.children.forEach((child) => child.requestValidation())
- }
-
- protected _cachedChildHasDiagnostics: boolean | null = null
- get childHasDiagnostics() {
- if (this._cachedChildHasDiagnostics !== null)
- return this._cachedChildHasDiagnostics
-
- this._cachedChildHasDiagnostics = this.children.some((child) => {
- if (child.type === 'array' || child.type === 'object')
- return (
- !!child.highestSeverityDiagnostic ||
- (child).childHasDiagnostics
- )
- return !!child.highestSeverityDiagnostic
- })
-
- return this._cachedChildHasDiagnostics
- }
-
- clearDiagnosticsCache() {
- super.clearDiagnosticsCache()
- this._cachedChildHasDiagnostics = null
- }
-}
diff --git a/src/components/Editors/TreeEditor/Tree/CommonTree.vue b/src/components/Editors/TreeEditor/Tree/CommonTree.vue
deleted file mode 100644
index 236c1aa50..000000000
--- a/src/components/Editors/TreeEditor/Tree/CommonTree.vue
+++ /dev/null
@@ -1,207 +0,0 @@
-
-
-
-
- mdi-chevron-right
-
-
-
-
-
- s: {{ tree.type }} p: {{ tree.parent.type }}
-
-
-
- {{ hideBracketsWithinTreeEditor ? undefined : ':' }}
-
-
-
-
- {{ openingBracket }}
-
-
-
-
-
-
- mdi-alert-circle-outline
-
-
- {{ t('editors.treeEditor.childHasError') }}
-
-
-
-
- {{ closingBracket }}
-
-
-
-
-
-
-
diff --git a/src/components/Editors/TreeEditor/Tree/ObjectTree.ts b/src/components/Editors/TreeEditor/Tree/ObjectTree.ts
deleted file mode 100644
index e63920f22..000000000
--- a/src/components/Editors/TreeEditor/Tree/ObjectTree.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-import { createTree } from './createTree'
-import { Tree, treeElementHeight } from './Tree'
-import ObjecTreeComponent from './CommonTree.vue'
-import type { ArrayTree } from './ArrayTree'
-import { set, del, markRaw } from 'vue'
-import { TreeEditor } from '../TreeEditor'
-
-export class ObjectTree extends Tree