├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── build-tauri-app.yml
│ ├── build-web-app.yml
│ └── check.yml
├── .gitignore
├── .prettierrc.json
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── index.html
├── node_modules
└── vue-template-compiler
│ ├── LICENSE
│ ├── README.md
│ ├── browser.js
│ ├── build.js
│ ├── index.js
│ ├── package.json
│ └── types
│ └── index.d.ts
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── .nojekyll
├── changelog.html
├── img
│ ├── icons
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── android-chrome-maskable-192x192.png
│ │ ├── android-chrome-maskable-512x512.png
│ │ ├── apple-touch-icon-152x152.png
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon.svg
│ │ ├── msapplication-icon-144x144.png
│ │ ├── nightly
│ │ │ ├── android-chrome-192x192.png
│ │ │ ├── android-chrome-512x512.png
│ │ │ ├── android-chrome-maskable-192x192.png
│ │ │ ├── android-chrome-maskable-512x512.png
│ │ │ ├── apple-touch-icon-152x152.png
│ │ │ ├── favicon-16x16.png
│ │ │ ├── favicon-32x32.png
│ │ │ ├── favicon.svg
│ │ │ ├── msapplication-icon-144x144.png
│ │ │ └── safari-pinned-tab.svg
│ │ └── safari-pinned-tab.svg
│ ├── install-screenshots
│ │ ├── narrow
│ │ │ ├── screenshot-1.png
│ │ │ ├── screenshot-2.png
│ │ │ └── screenshot-3.png
│ │ └── wide
│ │ │ ├── screenshot-1.png
│ │ │ └── screenshot-2.png
│ ├── social-preview-rounded.png
│ └── social-preview.png
├── packages.zip
└── robots.txt
├── scripts
├── build.mjs
├── buildApp.mjs
└── buildChangelog.mjs
├── src-tauri
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── build.rs
├── icons
│ ├── 128x128.png
│ ├── 128x128@2x.png
│ ├── 32x32.png
│ ├── Square107x107Logo.png
│ ├── Square142x142Logo.png
│ ├── Square150x150Logo.png
│ ├── Square284x284Logo.png
│ ├── Square30x30Logo.png
│ ├── Square310x310Logo.png
│ ├── Square44x44Logo.png
│ ├── Square71x71Logo.png
│ ├── Square89x89Logo.png
│ ├── StoreLogo.png
│ ├── icon.icns
│ ├── icon.ico
│ └── icon.png
├── src
│ ├── discord.rs
│ ├── fs_extra.rs
│ ├── main.rs
│ ├── terminal.rs
│ ├── watch.rs
│ └── zip.rs
├── tauri.conf.json
└── tauri.windows.conf.json
├── src
├── App.ts
├── App.vue
├── components
│ ├── Actions
│ │ ├── Action.ts
│ │ ├── ActionManager.ts
│ │ ├── ActionViewer.vue
│ │ ├── Actions.ts
│ │ ├── KeyBinding.ts
│ │ ├── KeyBindingManager.ts
│ │ ├── SimpleAction.ts
│ │ └── Utils.ts
│ ├── App
│ │ ├── Icon
│ │ │ ├── Blockbench.vue
│ │ │ └── IconMap.ts
│ │ ├── Install.ts
│ │ ├── Mobile.ts
│ │ ├── ServiceWorker.ts
│ │ ├── Tauri
│ │ │ └── TauriUpdater.ts
│ │ ├── Vue.ts
│ │ └── Vuetify.ts
│ ├── BedrockWorlds
│ │ ├── BlockLibrary
│ │ │ ├── BlockLibrary.ts
│ │ │ └── loadImage.ts
│ │ ├── LevelDB
│ │ │ ├── Comparators
│ │ │ │ └── Bytewise.ts
│ │ │ ├── FileMetaData.ts
│ │ │ ├── Key
│ │ │ │ ├── AsUsableKey.ts
│ │ │ │ └── GetKeyType.ts
│ │ │ ├── LevelDB.ts
│ │ │ ├── LogReader.ts
│ │ │ ├── Manifest.ts
│ │ │ ├── MemoryCache.ts
│ │ │ ├── Record.ts
│ │ │ ├── RequestStatus.ts
│ │ │ ├── Table
│ │ │ │ ├── BlockHandle.ts
│ │ │ │ ├── BlockSeeker.ts
│ │ │ │ ├── Footer.ts
│ │ │ │ └── Table.ts
│ │ │ ├── Uint8ArrayUtils
│ │ │ │ ├── Equals.ts
│ │ │ │ ├── Reader.ts
│ │ │ │ ├── ToUint8Array.ts
│ │ │ │ └── Unpack.ts
│ │ │ └── Version.ts
│ │ ├── Render
│ │ │ ├── Neighbours.ts
│ │ │ ├── Tab.ts
│ │ │ ├── VoxelFaces.ts
│ │ │ └── World
│ │ │ │ └── SubChunk.ts
│ │ └── WorldFormat
│ │ │ ├── Block.ts
│ │ │ ├── Chunk.ts
│ │ │ ├── EDimension.ts
│ │ │ ├── EKeyTypeTags.ts
│ │ │ ├── World.ts
│ │ │ └── readNbt.ts
│ ├── BottomPanel
│ │ ├── BottomPanel.css
│ │ ├── BottomPanel.tsx
│ │ ├── BottomPanel.vue
│ │ ├── PanelContent.tsx
│ │ ├── TabBar.tsx
│ │ └── Terminal
│ │ │ ├── Input.tsx
│ │ │ ├── Output.tsx
│ │ │ ├── Terminal.css
│ │ │ └── Terminal.ts
│ ├── CommandBar
│ │ ├── AddFiles.ts
│ │ ├── CommandBar.vue
│ │ ├── State.ts
│ │ └── Window.vue
│ ├── Common
│ │ ├── Event
│ │ │ ├── EventDispatcher.ts
│ │ │ ├── EventSystem.ts
│ │ │ └── Signal.ts
│ │ ├── GlobalMutex.ts
│ │ ├── Mutex.ts
│ │ ├── PersistentQueue.ts
│ │ ├── Progress.ts
│ │ ├── Queue.ts
│ │ └── WindowResize.ts
│ ├── Compiler
│ │ ├── Actions
│ │ │ ├── RecompileChanges.ts
│ │ │ └── RestartWatchMode.ts
│ │ ├── Compiler.ts
│ │ ├── LogPanel
│ │ │ └── Panel.tsx
│ │ ├── Sidebar
│ │ │ └── create.ts
│ │ ├── Window
│ │ │ ├── BuildProfiles.vue
│ │ │ ├── Content.vue
│ │ │ ├── Logs.vue
│ │ │ ├── OutputFolders.vue
│ │ │ ├── WatchMode.vue
│ │ │ ├── WatchMode
│ │ │ │ └── SettingSheet.vue
│ │ │ └── Window.ts
│ │ └── Worker
│ │ │ ├── Console.ts
│ │ │ ├── FileSystem.ts
│ │ │ ├── Plugins
│ │ │ ├── CustomCommands
│ │ │ │ └── generateSchemas.ts
│ │ │ └── CustomComponent
│ │ │ │ └── ComponentSchemas.ts
│ │ │ ├── Service.ts
│ │ │ └── TauriFs.ts
│ ├── Composables
│ │ ├── Display
│ │ │ └── useDisplay.ts
│ │ ├── DoubleClick.ts
│ │ ├── LongPress.ts
│ │ ├── Sidebar
│ │ │ └── useSidebarState.ts
│ │ ├── UseProject.ts
│ │ ├── UseTabSystem.ts
│ │ ├── useDarkMode.ts
│ │ ├── useHighContrast.ts
│ │ └── useTranslations.ts
│ ├── ContextMenu
│ │ ├── ContextMenu.ts
│ │ ├── ContextMenu.vue
│ │ ├── List.vue
│ │ └── showContextMenu.ts
│ ├── Data
│ │ ├── DataLoader.ts
│ │ ├── FileType.ts
│ │ ├── FormatVersions.ts
│ │ ├── JSONDefaults.ts
│ │ ├── PackType.ts
│ │ ├── PackTypeViewer.vue
│ │ ├── RequiresMatcher
│ │ │ ├── FailureMessage.ts
│ │ │ └── RequiresMatcher.ts
│ │ ├── SchemaScript.ts
│ │ └── TypeLoader.ts
│ ├── Definitions
│ │ └── GoTo.ts
│ ├── Developer
│ │ └── Actions.ts
│ ├── Documentation
│ │ └── view.ts
│ ├── Editors
│ │ ├── BlockModel
│ │ │ └── Tab.ts
│ │ ├── Blockbench
│ │ │ └── BlockbenchTab.ts
│ │ ├── EntityModel
│ │ │ ├── Tab.ts
│ │ │ ├── create
│ │ │ │ ├── fromClientEntity.ts
│ │ │ │ ├── fromEntity.ts
│ │ │ │ └── fromGeometry.ts
│ │ │ └── transformOldModels.ts
│ │ ├── GeometryPreview
│ │ │ ├── AssetPreview
│ │ │ │ ├── Window.ts
│ │ │ │ └── Window.vue
│ │ │ ├── Data
│ │ │ │ ├── AnimationData.ts
│ │ │ │ ├── EntityData.ts
│ │ │ │ ├── GeometryData.ts
│ │ │ │ ├── ParticleData.ts
│ │ │ │ ├── PreviewFileWatcher.ts
│ │ │ │ └── RenderContainer.ts
│ │ │ └── Tab.ts
│ │ ├── HTMLPreview
│ │ │ └── HTMLPreview.ts
│ │ ├── IframeTab
│ │ │ ├── API
│ │ │ │ ├── Events
│ │ │ │ │ ├── GenericEvent.ts
│ │ │ │ │ ├── Tab
│ │ │ │ │ │ └── OpenFile.ts
│ │ │ │ │ └── ThemeChange.ts
│ │ │ │ ├── IframeApi.ts
│ │ │ │ └── Requests
│ │ │ │ │ ├── Dash
│ │ │ │ │ └── UpdateFile.ts
│ │ │ │ │ ├── FileSystem
│ │ │ │ │ ├── ReadAsDataUrl.ts
│ │ │ │ │ ├── ReadFile.ts
│ │ │ │ │ ├── ReadTextFile.ts
│ │ │ │ │ ├── ResolveFileReference.ts
│ │ │ │ │ └── WriteFile.ts
│ │ │ │ │ ├── GenericRequest.ts
│ │ │ │ │ ├── PackIndexer
│ │ │ │ │ ├── Find.ts
│ │ │ │ │ └── GetFile.ts
│ │ │ │ │ ├── Project
│ │ │ │ │ └── GetItemPreview.ts
│ │ │ │ │ ├── Tab
│ │ │ │ │ ├── SetIsLoading.ts
│ │ │ │ │ └── SetIsUnsaved.ts
│ │ │ │ │ └── Util
│ │ │ │ │ └── Platform.ts
│ │ │ ├── IframeTab.ts
│ │ │ └── IframeTab.vue
│ │ ├── Image
│ │ │ ├── ImageTab.ts
│ │ │ ├── ImageTab.vue
│ │ │ └── TargaTab.ts
│ │ ├── ParticlePreview
│ │ │ ├── ParticlePreview.ts
│ │ │ └── ParticleWatcher.ts
│ │ ├── Sound
│ │ │ ├── SoundTab.ts
│ │ │ └── SoundTab.vue
│ │ ├── Text
│ │ │ ├── TextTab.ts
│ │ │ └── TextTab.vue
│ │ ├── ThreePreview
│ │ │ ├── ThreePreviewTab.ts
│ │ │ └── ThreePreviewTab.vue
│ │ └── TreeEditor
│ │ │ ├── CompletionItems
│ │ │ └── FilterDuplicates.ts
│ │ │ ├── Highlight.vue
│ │ │ ├── History
│ │ │ ├── CollectedEntry.ts
│ │ │ ├── DeleteEntry.ts
│ │ │ ├── EditPropertyEntry.ts
│ │ │ ├── EditValueEntry.ts
│ │ │ ├── EditorHistory.ts
│ │ │ ├── HistoryEntry.ts
│ │ │ ├── MoveEntry.ts
│ │ │ └── ReplaceTree.ts
│ │ │ ├── InlineDiagnostic.vue
│ │ │ ├── Tab.ts
│ │ │ ├── Tab.vue
│ │ │ ├── Tree
│ │ │ ├── ArrayTree.ts
│ │ │ ├── CommonTree.vue
│ │ │ ├── ObjectTree.ts
│ │ │ ├── PrimitiveTree.ts
│ │ │ ├── PrimitiveTree.vue
│ │ │ ├── Tree.ts
│ │ │ ├── TreeChildren.vue
│ │ │ └── createTree.ts
│ │ │ ├── TreeEditor.ts
│ │ │ ├── TreeSelection.ts
│ │ │ └── mayCastTo.ts
│ ├── Extensions
│ │ ├── ActiveStatus.ts
│ │ ├── Extension.ts
│ │ ├── ExtensionLoader.ts
│ │ ├── FileDefinition
│ │ │ └── load.ts
│ │ ├── GlobalExtensionLoader.ts
│ │ ├── InstallFiles.ts
│ │ ├── Scripts
│ │ │ ├── JsRuntime.ts
│ │ │ ├── Modules
│ │ │ │ ├── CommandBar.ts
│ │ │ │ ├── ModelViewer.ts
│ │ │ │ ├── Tab.ts
│ │ │ │ ├── TabAction.ts
│ │ │ │ ├── Three.ts
│ │ │ │ ├── comMojang.ts
│ │ │ │ ├── compareVersions.ts
│ │ │ │ ├── env.ts
│ │ │ │ ├── fetchDefinition.ts
│ │ │ │ ├── fflate.ts
│ │ │ │ ├── fs.ts
│ │ │ │ ├── globals.ts
│ │ │ │ ├── import.ts
│ │ │ │ ├── json5.ts
│ │ │ │ ├── monaco.ts
│ │ │ │ ├── notifications.ts
│ │ │ │ ├── path.ts
│ │ │ │ ├── persistentStorage.ts
│ │ │ │ ├── project.ts
│ │ │ │ ├── reactivity.ts
│ │ │ │ ├── settings.ts
│ │ │ │ ├── sidebar.ts
│ │ │ │ ├── theme.ts
│ │ │ │ ├── toolbar.ts
│ │ │ │ ├── ui.ts
│ │ │ │ ├── utils.ts
│ │ │ │ └── windows.ts
│ │ │ ├── loadScripts.ts
│ │ │ ├── require.ts
│ │ │ ├── run.ts
│ │ │ └── types.d.ts
│ │ ├── Settings
│ │ │ └── ExtensionSetting.ts
│ │ ├── Styles
│ │ │ └── createStyle.ts
│ │ ├── Themes
│ │ │ ├── Default.ts
│ │ │ ├── DefaultTheme
│ │ │ │ └── ColorCodes.ts
│ │ │ ├── MonacoSubTheme.ts
│ │ │ ├── Theme.ts
│ │ │ └── ThemeManager.ts
│ │ └── UI
│ │ │ ├── load.ts
│ │ │ └── store.ts
│ ├── FileDropper
│ │ └── FileDropper.ts
│ ├── FileSystem
│ │ ├── CombinedFs.ts
│ │ ├── Common.ts
│ │ ├── Fast
│ │ │ └── getDirectoryHandle.ts
│ │ ├── FileSystem.ts
│ │ ├── FileWatcher.ts
│ │ ├── FindFile.ts
│ │ ├── Pickers
│ │ │ └── showFolderPicker.ts
│ │ ├── Polyfill.ts
│ │ ├── Setup.ts
│ │ ├── Types.ts
│ │ ├── Virtual
│ │ │ ├── Comlink.ts
│ │ │ ├── DirectoryHandle.ts
│ │ │ ├── File.ts
│ │ │ ├── FileHandle.ts
│ │ │ ├── Handle.ts
│ │ │ ├── IDB.ts
│ │ │ ├── ProjectWindow.ts
│ │ │ ├── Stores
│ │ │ │ ├── BaseStore.ts
│ │ │ │ ├── Deserialize.ts
│ │ │ │ ├── IndexedDb.ts
│ │ │ │ ├── Memory.ts
│ │ │ │ └── TauriFs.ts
│ │ │ ├── VirtualWritable.ts
│ │ │ ├── getParent.ts
│ │ │ └── pathFromHandle.ts
│ │ ├── Zip
│ │ │ ├── GenericUnzipper.ts
│ │ │ ├── StreamingUnzipper.ts
│ │ │ ├── Unzipper.ts
│ │ │ └── ZipDirectory.ts
│ │ └── saveOrDownload.ts
│ ├── FindAndReplace
│ │ ├── Controls
│ │ │ ├── SearchType.vue
│ │ │ └── searchType.ts
│ │ ├── FilePath.vue
│ │ ├── Match.vue
│ │ ├── Tab.ts
│ │ ├── Tab.vue
│ │ ├── Utils.ts
│ │ └── Worker
│ │ │ └── Worker.ts
│ ├── Greet
│ │ └── Greet.vue
│ ├── ImportFile
│ │ ├── BBModel.ts
│ │ ├── BasicFile.ts
│ │ ├── Brproject.ts
│ │ ├── Importer.ts
│ │ ├── MCAddon.ts
│ │ ├── MCPack.ts
│ │ ├── Manager.ts
│ │ └── ZipImporter.ts
│ ├── ImportFolder
│ │ ├── ImportProjects.ts
│ │ └── Manager.ts
│ ├── InfoPanel
│ │ ├── InfoPanel.ts
│ │ └── InfoPanel.vue
│ ├── JSONSchema
│ │ ├── Manager.ts
│ │ ├── Registry.ts
│ │ └── Schema
│ │ │ ├── AdditionalProperties.ts
│ │ │ ├── AllOf.ts
│ │ │ ├── AnyOf.ts
│ │ │ ├── Const.ts
│ │ │ ├── Default.ts
│ │ │ ├── DeprecationMessage.ts
│ │ │ ├── DoNotSuggest.ts
│ │ │ ├── ElseSchema.ts
│ │ │ ├── Enum.ts
│ │ │ ├── IfSchema.ts
│ │ │ ├── Items.ts
│ │ │ ├── Not.ts
│ │ │ ├── OneOf.ts
│ │ │ ├── Parent.ts
│ │ │ ├── PatternProperties.ts
│ │ │ ├── Properties.ts
│ │ │ ├── PropertyNames.ts
│ │ │ ├── Ref.ts
│ │ │ ├── Required.ts
│ │ │ ├── Root.ts
│ │ │ ├── Schema.ts
│ │ │ ├── ThenSchema.ts
│ │ │ └── Type.ts
│ ├── Languages
│ │ ├── Common
│ │ │ └── ColorCodes.ts
│ │ ├── Json
│ │ │ ├── ColorPicker
│ │ │ │ ├── Color.ts
│ │ │ │ ├── ColorPicker.ts
│ │ │ │ ├── Data.ts
│ │ │ │ ├── findColors.ts
│ │ │ │ └── parse
│ │ │ │ │ ├── hex.ts
│ │ │ │ │ ├── main.ts
│ │ │ │ │ └── rgb.ts
│ │ │ ├── Highlighter.ts
│ │ │ ├── Main.ts
│ │ │ └── supportsLookbehind.ts
│ │ ├── Lang.ts
│ │ ├── Lang
│ │ │ ├── Data.ts
│ │ │ └── guessValue.ts
│ │ ├── Language.ts
│ │ ├── LanguageManager.ts
│ │ ├── Mcfunction.ts
│ │ ├── Mcfunction
│ │ │ ├── Data.ts
│ │ │ ├── ResolvedCommandArguments.ts
│ │ │ ├── TargetSelector
│ │ │ │ ├── SelectorArguments.ts
│ │ │ │ └── isWithin.ts
│ │ │ ├── TokenProvider.ts
│ │ │ ├── Validator.ts
│ │ │ ├── WithinJson.ts
│ │ │ ├── inSelector.ts
│ │ │ └── strMatch.ts
│ │ └── MoLang.ts
│ ├── Locales
│ │ └── Manager.ts
│ ├── Mixins
│ │ ├── AppToolbarHeight.ts
│ │ ├── DevMode.ts
│ │ ├── EnablePackSpider.ts
│ │ ├── Highlighter.ts
│ │ ├── TranslationMixin.ts
│ │ └── WindowControlsOverlay.ts
│ ├── Notifications
│ │ ├── Errors.ts
│ │ ├── Notification.ts
│ │ ├── PersistentNotification.ts
│ │ ├── create.ts
│ │ ├── state.ts
│ │ └── warn.ts
│ ├── OutputFolders
│ │ └── ComMojang
│ │ │ ├── ComMojang.ts
│ │ │ ├── ProjectLoader.ts
│ │ │ └── Sidebar
│ │ │ ├── ProjectHeader.vue
│ │ │ ├── ViewProject.ts
│ │ │ └── ViewProject.vue
│ ├── PackExplorer
│ │ ├── Actions
│ │ │ ├── ToBridgeFolderProject.ts
│ │ │ └── ToLocalProject.ts
│ │ ├── HomeView
│ │ │ ├── BridgeFolderBtn.vue
│ │ │ ├── CreateProjectBtn.vue
│ │ │ ├── HomeView.vue
│ │ │ ├── ImportOldProjects.vue
│ │ │ ├── Project.vue
│ │ │ ├── SetupHint.vue
│ │ │ └── SetupView.vue
│ │ ├── PackExplorer.ts
│ │ ├── PackExplorer.vue
│ │ └── ProjectDisplay.vue
│ ├── PackIndexer
│ │ ├── PackIndexer.ts
│ │ └── Worker
│ │ │ ├── LightningCache
│ │ │ ├── CacheEnv.ts
│ │ │ ├── LightningCache.ts
│ │ │ ├── LightningStore.ts
│ │ │ └── Script.ts
│ │ │ ├── Main.ts
│ │ │ └── PackSpider
│ │ │ └── PackSpider.ts
│ ├── Projects
│ │ ├── CreateProject
│ │ │ ├── CreateFile.vue
│ │ │ ├── CreateProject.ts
│ │ │ ├── CreateProject.vue
│ │ │ ├── ExperimentalGameplay.vue
│ │ │ ├── Files
│ │ │ │ ├── BP
│ │ │ │ │ ├── CreateTick.ts
│ │ │ │ │ ├── GameTest.ts
│ │ │ │ │ └── Player.ts
│ │ │ │ ├── Bridge
│ │ │ │ │ └── Compiler.ts
│ │ │ │ ├── Config.ts
│ │ │ │ ├── CreateFile.ts
│ │ │ │ ├── DenoConfig.ts
│ │ │ │ ├── GitIgnore.ts
│ │ │ │ ├── Lang.ts
│ │ │ │ ├── Manifest.ts
│ │ │ │ ├── PackIcon.ts
│ │ │ │ ├── RP
│ │ │ │ │ ├── BiomesClient.ts
│ │ │ │ │ ├── Blocks.ts
│ │ │ │ │ ├── FlipbookTextures.ts
│ │ │ │ │ ├── ItemTexture.ts
│ │ │ │ │ ├── SoundDefinitions.ts
│ │ │ │ │ ├── Sounds.ts
│ │ │ │ │ ├── Splashes.ts
│ │ │ │ │ └── TerrainTexture.ts
│ │ │ │ └── SP
│ │ │ │ │ ├── Lang.ts
│ │ │ │ │ └── Skins.ts
│ │ │ └── Packs
│ │ │ │ ├── BP.ts
│ │ │ │ ├── Bridge.ts
│ │ │ │ ├── Pack.ts
│ │ │ │ ├── RP.ts
│ │ │ │ ├── SP.ts
│ │ │ │ ├── WT.ts
│ │ │ │ └── worlds.ts
│ │ ├── Export
│ │ │ ├── AsBrproject.ts
│ │ │ ├── AsMcaddon.ts
│ │ │ ├── AsMctemplate.ts
│ │ │ └── Extensions
│ │ │ │ ├── Exporter.ts
│ │ │ │ └── Provider.ts
│ │ ├── Import
│ │ │ ├── ImportNew.ts
│ │ │ ├── fromBrproject.ts
│ │ │ ├── fromMcaddon.ts
│ │ │ └── fromMcpack.ts
│ │ ├── Project
│ │ │ ├── BedrockProject.ts
│ │ │ ├── Config.ts
│ │ │ ├── FileChangeRegistry.ts
│ │ │ ├── Project.ts
│ │ │ ├── loadIcon.ts
│ │ │ ├── loadManifest.ts
│ │ │ └── loadPacks.ts
│ │ ├── ProjectChooser
│ │ │ ├── AddPack.ts
│ │ │ ├── ProjectChooser.ts
│ │ │ └── ProjectChooser.vue
│ │ ├── ProjectManager.ts
│ │ ├── RecentFiles.ts
│ │ ├── RecentProjects.ts
│ │ └── Title.ts
│ ├── Sidebar
│ │ ├── Button.vue
│ │ ├── Content
│ │ │ ├── Action.vue
│ │ │ ├── ActionBar.vue
│ │ │ ├── Main.vue
│ │ │ ├── SelectableSidebarAction.ts
│ │ │ ├── SidebarAction.ts
│ │ │ └── SidebarContent.ts
│ │ ├── Manager.ts
│ │ ├── Sidebar.vue
│ │ ├── SidebarElement.ts
│ │ └── setup.ts
│ ├── Snippets
│ │ ├── Loader.ts
│ │ ├── Monaco.ts
│ │ └── Snippet.ts
│ ├── Solid
│ │ ├── Directives
│ │ │ ├── Model
│ │ │ │ └── Model.ts
│ │ │ └── Ripple
│ │ │ │ ├── Ripple.css
│ │ │ │ └── Ripple.ts
│ │ ├── DirectoryViewer
│ │ │ ├── Common
│ │ │ │ ├── Name.css
│ │ │ │ └── Name.tsx
│ │ │ └── DirectoryView.tsx
│ │ ├── Icon
│ │ │ ├── IconText.tsx
│ │ │ └── SolidIcon.tsx
│ │ ├── Inputs
│ │ │ ├── Button
│ │ │ │ └── SolidButton.tsx
│ │ │ ├── IconButton
│ │ │ │ └── IconButton.tsx
│ │ │ └── TextField
│ │ │ │ └── TextField.tsx
│ │ ├── Logo.tsx
│ │ ├── SolidRef.ts
│ │ ├── SolidSpacer.tsx
│ │ ├── Window
│ │ │ ├── Manager.tsx
│ │ │ └── Window.tsx
│ │ ├── toSignal.ts
│ │ └── toVue.ts
│ ├── StartParams
│ │ ├── Action
│ │ │ ├── openFileUrl.ts
│ │ │ ├── openRawFile.ts
│ │ │ ├── sidebarState.ts
│ │ │ └── viewExtension.ts
│ │ └── Manager.ts
│ ├── TabSystem
│ │ ├── CommonTab.ts
│ │ ├── FileTab.ts
│ │ ├── MonacoHolder.ts
│ │ ├── OpenedFiles.ts
│ │ ├── PreviewTab.ts
│ │ ├── Tab.vue
│ │ ├── TabActions
│ │ │ ├── Action.vue
│ │ │ ├── ActionBar.vue
│ │ │ └── Provider.ts
│ │ ├── TabBar.vue
│ │ ├── TabContextMenu
│ │ │ └── Fullscreen.ts
│ │ ├── TabProvider.ts
│ │ ├── TabSystem.ts
│ │ ├── TabSystem.vue
│ │ ├── Util
│ │ │ └── FolderDifference.ts
│ │ └── WelcomeScreen.vue
│ ├── TaskManager
│ │ ├── SimpleWorkerTask.ts
│ │ ├── Task.ts
│ │ ├── TaskManager.ts
│ │ └── WorkerTask.ts
│ ├── Toolbar
│ │ ├── Category
│ │ │ ├── download.ts
│ │ │ ├── file.ts
│ │ │ ├── help.ts
│ │ │ ├── project.ts
│ │ │ ├── settings.ts
│ │ │ └── tools.ts
│ │ ├── Divider.ts
│ │ ├── Main.vue
│ │ ├── Menu
│ │ │ ├── Activator.vue
│ │ │ ├── Button.vue
│ │ │ └── MenuList.vue
│ │ ├── Toolbar.ts
│ │ ├── ToolbarButton.ts
│ │ ├── ToolbarCategory.ts
│ │ ├── WindowAction.vue
│ │ ├── WindowControls.vue
│ │ └── setupDefaults.ts
│ ├── UIElements
│ │ ├── DirectoryViewer
│ │ │ ├── Common
│ │ │ │ ├── BaseWrapper.ts
│ │ │ │ ├── BasicIconName.vue
│ │ │ │ ├── DraggingWrapper.ts
│ │ │ │ └── Name.vue
│ │ │ ├── ContextMenu
│ │ │ │ ├── Actions
│ │ │ │ │ ├── ConnectedFiles.ts
│ │ │ │ │ ├── Download.ts
│ │ │ │ │ ├── Edit.ts
│ │ │ │ │ ├── Edit
│ │ │ │ │ │ ├── Copy.ts
│ │ │ │ │ │ ├── Delete.ts
│ │ │ │ │ │ ├── Duplicate.ts
│ │ │ │ │ │ ├── Paste.ts
│ │ │ │ │ │ └── Rename.ts
│ │ │ │ │ ├── FindInFolder.ts
│ │ │ │ │ ├── ImportFile.ts
│ │ │ │ │ ├── Open.ts
│ │ │ │ │ ├── OpenInSplitScreen.ts
│ │ │ │ │ ├── OpenWith.ts
│ │ │ │ │ ├── OpenWith
│ │ │ │ │ │ ├── Blockbench.ts
│ │ │ │ │ │ ├── HTMLPreviewer.ts
│ │ │ │ │ │ ├── Snowstorm.ts
│ │ │ │ │ │ ├── TextEditor.ts
│ │ │ │ │ │ └── TreeEditor.ts
│ │ │ │ │ ├── Refresh.ts
│ │ │ │ │ ├── RevealInFileExplorer.ts
│ │ │ │ │ ├── RevealPath.ts
│ │ │ │ │ └── ViewCompilerOutput.ts
│ │ │ │ ├── File.ts
│ │ │ │ └── Folder.ts
│ │ │ ├── DirectoryStore.ts
│ │ │ ├── DirectoryView
│ │ │ │ ├── DirectoryView.vue
│ │ │ │ └── DirectoryWrapper.ts
│ │ │ ├── DirectoryViewer.vue
│ │ │ └── FileView
│ │ │ │ ├── FileView.vue
│ │ │ │ └── FileWrapper.ts
│ │ ├── Logo.vue
│ │ ├── ProjectDisplay.vue
│ │ ├── SelectedStatus.vue
│ │ ├── Sheet.vue
│ │ └── ToggleSheet.vue
│ ├── ViewFolders
│ │ ├── ViewFolders.ts
│ │ └── ViewFolders.vue
│ ├── WelcomeAlert
│ │ └── Alert.vue
│ └── Windows
│ │ ├── About
│ │ └── AboutWindow.tsx
│ │ ├── BrowserUnsupported
│ │ ├── BrowserUnsupported.ts
│ │ └── BrowserUnsupported.vue
│ │ ├── Changelog
│ │ ├── Changelog.ts
│ │ └── Changelog.vue
│ │ ├── Collect.vue
│ │ ├── Common
│ │ ├── Confirm
│ │ │ ├── ConfirmWindow.ts
│ │ │ └── ConfirmWindow.vue
│ │ ├── Dropdown
│ │ │ ├── Dropdown.vue
│ │ │ └── DropdownWindow.ts
│ │ ├── FilePath
│ │ │ ├── Window.ts
│ │ │ └── Window.vue
│ │ ├── Information
│ │ │ ├── Information.vue
│ │ │ └── InformationWindow.ts
│ │ ├── Input
│ │ │ ├── Input.vue
│ │ │ └── InputWindow.ts
│ │ └── MultiOptions
│ │ │ ├── Window.ts
│ │ │ └── Window.vue
│ │ ├── Error
│ │ └── ErrorWindow.tsx
│ │ ├── ExtensionStore
│ │ ├── ExtensionActions.ts
│ │ ├── ExtensionCard.vue
│ │ ├── ExtensionStore.ts
│ │ ├── ExtensionStore.vue
│ │ ├── ExtensionTag.ts
│ │ └── ExtensionViewer.ts
│ │ ├── InformedChoice
│ │ ├── InformedChoice.ts
│ │ └── InformedChoice.vue
│ │ ├── Layout
│ │ ├── BaseWindow.vue
│ │ ├── Sidebar.ts
│ │ ├── Sidebar
│ │ │ ├── Group.vue
│ │ │ └── Item.vue
│ │ ├── SidebarWindow.vue
│ │ └── Toolbar
│ │ │ ├── Button.vue
│ │ │ ├── DisplayAction.vue
│ │ │ ├── Mac.vue
│ │ │ ├── Mac
│ │ │ ├── Button.vue
│ │ │ └── WindowControls.vue
│ │ │ └── Windows.vue
│ │ ├── LoadingWindow
│ │ ├── LoadingWindow.ts
│ │ └── LoadingWindow.vue
│ │ ├── NewBaseWindow.ts
│ │ ├── Project
│ │ └── CreatePreset
│ │ │ ├── CreateFile.ts
│ │ │ ├── ExpandFile.ts
│ │ │ ├── PresetItem.ts
│ │ │ ├── PresetPath.vue
│ │ │ ├── PresetScript.ts
│ │ │ ├── PresetWindow.ts
│ │ │ ├── PresetWindow.vue
│ │ │ └── TransformString.ts
│ │ ├── Settings
│ │ ├── Controls
│ │ │ ├── ActionViewer
│ │ │ │ ├── ActionViewer.ts
│ │ │ │ └── ActionViewer.vue
│ │ │ ├── Button
│ │ │ │ ├── Button.ts
│ │ │ │ └── Button.vue
│ │ │ ├── ButtonToggle
│ │ │ │ ├── ButtonToggle.ts
│ │ │ │ └── ButtonToggle.vue
│ │ │ ├── Control.ts
│ │ │ ├── FontSelection.ts
│ │ │ ├── Selection
│ │ │ │ ├── BridgeConfigSelection.ts
│ │ │ │ ├── Selection.ts
│ │ │ │ └── Selection.vue
│ │ │ ├── Sidebar
│ │ │ │ ├── Sidebar.ts
│ │ │ │ └── Sidebar.vue
│ │ │ ├── TextField
│ │ │ │ ├── TextField.ts
│ │ │ │ └── TextField.vue
│ │ │ └── Toggle
│ │ │ │ ├── Toggle.ts
│ │ │ │ └── Toggle.vue
│ │ ├── SettingsSidebar.ts
│ │ ├── SettingsState.ts
│ │ ├── SettingsWindow.ts
│ │ ├── SettingsWindow.vue
│ │ └── setupSettings.ts
│ │ ├── Socials
│ │ ├── Main.vue
│ │ └── SocialsWindow.ts
│ │ ├── UnsavedFile
│ │ ├── UnsavedFile.ts
│ │ └── UnsavedFile.vue
│ │ ├── Update
│ │ └── UpdateWindow.tsx
│ │ ├── WindowState.ts
│ │ ├── Windows.ts
│ │ └── create.ts
├── locales
│ ├── de.json
│ ├── en.json
│ ├── ja.json
│ ├── ko.json
│ ├── languages.json
│ ├── nl.json
│ ├── ru.json
│ ├── zh-CN.json
│ └── zh-TW.json
├── main.css
├── main.ts
├── types
│ ├── Activatable.ts
│ ├── LocalFontAccess.d.ts
│ ├── StructuredClone.d.ts
│ ├── Vite.d.ts
│ ├── disposable.ts
│ ├── quick-score.d.ts
│ ├── shims-path.ts
│ ├── shims-tsx.d.ts
│ ├── shims-vue.d.ts
│ └── tgaJS.ts
└── utils
│ ├── MoLangJS.ts
│ ├── app
│ ├── dashVersion.ts
│ ├── dataPackage.ts
│ ├── iframeApiVersion.ts
│ ├── isNightly.ts
│ └── version.ts
│ ├── array
│ └── findAsync.ts
│ ├── baseUrl.ts
│ ├── canvasToBlob.ts
│ ├── constants.ts
│ ├── directory
│ ├── findSuitableName.ts
│ └── getEntries.ts
│ ├── disposableListener.ts
│ ├── disposableTimeout.ts
│ ├── file
│ ├── dirExists.ts
│ ├── fileExists.ts
│ ├── getIcon.ts
│ ├── isAccepted.ts
│ ├── isSameEntry.ts
│ ├── loadAllFiles.ts
│ ├── moveHandle.ts
│ ├── renameHandle.ts
│ ├── tryCreateFile.ts
│ ├── tryCreateFolder.ts
│ ├── tryMove.ts
│ ├── tryRename.ts
│ └── writableToUint8Array.ts
│ ├── fs.ts
│ ├── getBridgeFolderPath.ts
│ ├── getStorageDirectory.ts
│ ├── inferType.ts
│ ├── isNode.ts
│ ├── isWritableData.ts
│ ├── iterateDir.ts
│ ├── libs
│ ├── internal
│ │ ├── jsoncParser.ts
│ │ ├── quickScore.ts
│ │ └── vueTemplateCompiler.ts
│ ├── useJsoncParser.ts
│ ├── useModelViewer.ts
│ ├── useMonaco.ts
│ ├── useQuickScore.ts
│ ├── useVueTemplateCompiler.ts
│ └── useWintersky.ts
│ ├── loadAsDataUrl.ts
│ ├── manifest
│ └── getPackId.ts
│ ├── math
│ ├── clamp.ts
│ └── randomInt.ts
│ ├── minecraft
│ └── validPositionArray.ts
│ ├── monaco
│ ├── getArrayValue.ts
│ ├── getJsonWord.ts
│ ├── getLocation.ts
│ └── withinQuotes.ts
│ ├── os.ts
│ ├── path.ts
│ ├── pointerDevice.ts
│ ├── revealInFileExplorer.ts
│ ├── setRichPresence.ts
│ ├── string
│ ├── closestMatch.ts
│ └── editDistance.ts
│ ├── typeof.ts
│ ├── wait.ts
│ ├── whenIdle.ts
│ └── worker
│ ├── inject.ts
│ └── setup.ts
├── tailwind.config.js
├── tsconfig.json
└── vite.config.ts
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 | ---
8 |
9 | **Summary**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 |
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. Observe result
19 |
20 | **Observed behavior**
21 | A clear and concise description of what happened
22 | **Expected behavior**
23 | A clear and concise description of what you expected to happen.
24 |
25 | **Screenshots / File Attachments**
26 | If applicable, add screenshots to help explain your problem or upload files to help us reproduce the bug.
27 |
28 | **Platform (please complete the following information):**
29 |
30 | - OS: [e.g. Windows 10]
31 | - App Version: [e.g. 0.13.3]
32 |
33 | **Additional context**
34 | Add any other context about the problem here.
35 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: feature
6 | assignees: ''
7 | ---
8 |
9 | **Summary**
10 | A clear and concise description of the feature request.
11 |
12 | **Alternatives/Considerations**
13 | A clear and concise description of any alternative solutions or features you've considered.
14 |
15 | **Additional context/Motivation**
16 | Add any other context or screenshots about the feature request here.
17 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 | ## Motivation
4 |
5 | ## Additional Context
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /dist
3 | /compilerTypes
4 |
5 | node_modules/*
6 | !node_modules/vue-template-compiler
7 |
8 | # local env files
9 | .env
10 | .env.local
11 | .env.*.local
12 |
13 | # Log files
14 | npm-debug.log*
15 | yarn-debug.log*
16 | yarn-error.log*
17 | pnpm-debug.log*
18 |
19 | # Editor directories and files
20 | .idea
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 | todos.md
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "useTabs": true,
4 | "tabWidth": 4,
5 | "semi": false,
6 | "singleQuote": true,
7 | "printWidth": 80
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["esbenp.prettier-vscode", "octref.vetur"]
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Debug in Edge",
6 | "type": "msedge",
7 | "request": "launch",
8 | "url": "http://localhost:8080",
9 | "webRoot": "${workspaceFolder}"
10 | },
11 | {
12 | "name": "Debug in Chrome",
13 | "type": "chrome",
14 | "request": "launch",
15 | "url": "http://localhost:8080",
16 | "webRoot": "${workspaceFolder}"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "**/node_modules": true
4 | },
5 | "editor.defaultFormatter": "esbenp.prettier-vscode",
6 | "editor.formatOnSave": true,
7 | "rust-analyzer.linkedProjects": [".\\src-tauri\\Cargo.toml"]
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "type": "npm",
6 | "script": "dev",
7 | "problemMatcher": [],
8 | "label": "npm: dev",
9 | "detail": "vue-cli-service serve"
10 | }
11 | ]
12 | }
--------------------------------------------------------------------------------
/node_modules/vue-template-compiler/index.js:
--------------------------------------------------------------------------------
1 | try {
2 | var vueVersion = require('vue').version
3 | } catch (e) {}
4 |
5 | var packageName = require('./package.json').name
6 | var packageVersion = require('./package.json').version
7 | if (vueVersion && vueVersion !== packageVersion) {
8 | throw new Error(
9 | '\n\nVue packages version mismatch:\n\n' +
10 | '- vue@' + vueVersion + '\n' +
11 | '- ' + packageName + '@' + packageVersion + '\n\n' +
12 | 'This may cause things to work incorrectly. Make sure to use the same version for both.\n' +
13 | 'If you are using vue-loader@>=10.0, simply update vue-template-compiler.\n' +
14 | 'If you are using vue-loader@<10.0 or vueify, re-installing vue-loader/vueify should bump ' + packageName + ' to the latest.\n'
15 | )
16 | }
17 |
18 | module.exports = require('./build')
19 |
--------------------------------------------------------------------------------
/node_modules/vue-template-compiler/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-template-compiler",
3 | "version": "2.7.8",
4 | "description": "template compiler for Vue 2.0",
5 | "main": "index.js",
6 | "unpkg": "browser.js",
7 | "jsdelivr": "browser.js",
8 | "browser": "browser.js",
9 | "types": "types/index.d.ts",
10 | "files": [
11 | "types/*.d.ts",
12 | "*.js"
13 | ],
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/vuejs/vue.git"
17 | },
18 | "keywords": [
19 | "vue",
20 | "compiler"
21 | ],
22 | "author": "Evan You",
23 | "license": "MIT",
24 | "bugs": {
25 | "url": "https://github.com/vuejs/vue/issues"
26 | },
27 | "homepage": "https://github.com/vuejs/vue/tree/dev/packages/vue-template-compiler#readme",
28 | "dependencies": {
29 | "de-indent": "^1.0.2",
30 | "he": "^1.2.0"
31 | },
32 | "devDependencies": {
33 | "vue": "file:../.."
34 | }
35 | }
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/.nojekyll
--------------------------------------------------------------------------------
/public/changelog.html:
--------------------------------------------------------------------------------
1 |
Changes
2 |
3 | - Updated Korean translations thanks to @desire918 (https://github.com/bridge-core/editor/pull/1187)
4 | - Attempts to fix mac build updating now working
5 |
6 |
--------------------------------------------------------------------------------
/public/img/icons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/icons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/img/icons/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/icons/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/img/icons/android-chrome-maskable-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/icons/android-chrome-maskable-192x192.png
--------------------------------------------------------------------------------
/public/img/icons/android-chrome-maskable-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/icons/android-chrome-maskable-512x512.png
--------------------------------------------------------------------------------
/public/img/icons/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/icons/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/public/img/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/public/img/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/public/img/icons/msapplication-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/icons/msapplication-icon-144x144.png
--------------------------------------------------------------------------------
/public/img/icons/nightly/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/icons/nightly/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/img/icons/nightly/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/icons/nightly/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/img/icons/nightly/android-chrome-maskable-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/icons/nightly/android-chrome-maskable-192x192.png
--------------------------------------------------------------------------------
/public/img/icons/nightly/android-chrome-maskable-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/icons/nightly/android-chrome-maskable-512x512.png
--------------------------------------------------------------------------------
/public/img/icons/nightly/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/icons/nightly/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/public/img/icons/nightly/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/icons/nightly/favicon-16x16.png
--------------------------------------------------------------------------------
/public/img/icons/nightly/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/icons/nightly/favicon-32x32.png
--------------------------------------------------------------------------------
/public/img/icons/nightly/msapplication-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/icons/nightly/msapplication-icon-144x144.png
--------------------------------------------------------------------------------
/public/img/install-screenshots/narrow/screenshot-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/install-screenshots/narrow/screenshot-1.png
--------------------------------------------------------------------------------
/public/img/install-screenshots/narrow/screenshot-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/install-screenshots/narrow/screenshot-2.png
--------------------------------------------------------------------------------
/public/img/install-screenshots/narrow/screenshot-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/install-screenshots/narrow/screenshot-3.png
--------------------------------------------------------------------------------
/public/img/install-screenshots/wide/screenshot-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/install-screenshots/wide/screenshot-1.png
--------------------------------------------------------------------------------
/public/img/install-screenshots/wide/screenshot-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/install-screenshots/wide/screenshot-2.png
--------------------------------------------------------------------------------
/public/img/social-preview-rounded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/social-preview-rounded.png
--------------------------------------------------------------------------------
/public/img/social-preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/img/social-preview.png
--------------------------------------------------------------------------------
/public/packages.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/public/packages.zip
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/scripts/build.mjs:
--------------------------------------------------------------------------------
1 | import { buildChangelog } from './buildChangelog.mjs'
2 |
3 | buildChangelog().catch((err) => {
4 | console.error(err)
5 | process.exit(1)
6 | })
7 |
--------------------------------------------------------------------------------
/scripts/buildApp.mjs:
--------------------------------------------------------------------------------
1 | import { build } from 'vite'
2 |
3 | build()
4 |
--------------------------------------------------------------------------------
/scripts/buildChangelog.mjs:
--------------------------------------------------------------------------------
1 | import MarkdownIt from 'markdown-it'
2 | import { promises as fs } from 'fs'
3 | import fetch from 'node-fetch'
4 |
5 | export async function buildChangelog() {
6 | let headers
7 | const GITHUB_TOKEN = process.env.GITHUB_TOKEN
8 |
9 | if (GITHUB_TOKEN !== undefined) {
10 | headers = {
11 | Authorization: `Bearer ${GITHUB_TOKEN}`,
12 | }
13 | }
14 |
15 | const html = await fetch(
16 | 'https://api.github.com/repos/bridge-core/editor/releases',
17 | {
18 | headers: headers,
19 | }
20 | )
21 | .then((response) => response.json())
22 | .then((data) => new MarkdownIt().render(data[0].body))
23 |
24 | await fs.writeFile('public/changelog.html', html)
25 | }
26 |
--------------------------------------------------------------------------------
/src-tauri/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 |
5 |
--------------------------------------------------------------------------------
/src-tauri/build.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | tauri_build::build()
3 | }
4 |
--------------------------------------------------------------------------------
/src-tauri/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/src-tauri/icons/128x128.png
--------------------------------------------------------------------------------
/src-tauri/icons/128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/src-tauri/icons/128x128@2x.png
--------------------------------------------------------------------------------
/src-tauri/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/src-tauri/icons/32x32.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square107x107Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/src-tauri/icons/Square107x107Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square142x142Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/src-tauri/icons/Square142x142Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square150x150Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/src-tauri/icons/Square150x150Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square284x284Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/src-tauri/icons/Square284x284Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square30x30Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/src-tauri/icons/Square30x30Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square310x310Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/src-tauri/icons/Square310x310Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square44x44Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/src-tauri/icons/Square44x44Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square71x71Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/src-tauri/icons/Square71x71Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square89x89Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/src-tauri/icons/Square89x89Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/StoreLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/src-tauri/icons/StoreLogo.png
--------------------------------------------------------------------------------
/src-tauri/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/src-tauri/icons/icon.icns
--------------------------------------------------------------------------------
/src-tauri/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/src-tauri/icons/icon.ico
--------------------------------------------------------------------------------
/src-tauri/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bridge-core/editor/e5823b65aee1b9f1a9d08a7fa8f4ef4e9597c920/src-tauri/icons/icon.png
--------------------------------------------------------------------------------
/src-tauri/tauri.windows.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "build": {
3 | "beforeDevCommand": "SET VITE_IS_TAURI_APP=true && npm run dev",
4 | "beforeBuildCommand": "SET VITE_IS_TAURI_APP=true && npm run build"
5 | },
6 | "tauri": {
7 | "windows": [
8 | {
9 | "label": "main",
10 | "decorations": false,
11 | "fullscreen": false,
12 | "height": 900,
13 | "resizable": true,
14 | "title": "",
15 | "width": 1200,
16 | "fileDropEnabled": false,
17 | "maximized": true
18 | }
19 | ]
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/App/Icon/IconMap.ts:
--------------------------------------------------------------------------------
1 | import BlockbenchIcon from './Blockbench.vue'
2 |
3 | export const iconMap = {
4 | blockbench: BlockbenchIcon,
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/App/Mobile.ts:
--------------------------------------------------------------------------------
1 | import { ref, watch } from 'vue'
2 | import { Framework } from 'vuetify'
3 | import { EventDispatcher } from '../Common/Event/EventDispatcher'
4 | import { App } from '/@/App'
5 |
6 | export class Mobile {
7 | public readonly change = new EventDispatcher()
8 | public readonly is = ref(this.isCurrentDevice())
9 |
10 | constructor(protected vuetify: Framework) {
11 | watch(vuetify.breakpoint, () => {
12 | this.is.value = this.isCurrentDevice()
13 | this.change.dispatch(vuetify.breakpoint.mobile)
14 | })
15 |
16 | App.getApp().then(() => {
17 | setTimeout(
18 | () => this.change.dispatch(vuetify.breakpoint.mobile),
19 | 10
20 | )
21 | })
22 | }
23 |
24 | isCurrentDevice() {
25 | return this.vuetify?.breakpoint?.mobile
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/App/ServiceWorker.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | import { registerSW } from 'virtual:pwa-register'
4 | import { createNotification } from '/@/components/Notifications/create'
5 | import { set } from 'idb-keyval'
6 |
7 | const updateSW = registerSW({
8 | async onNeedRefresh() {
9 | console.log('New content is available; please refresh.')
10 |
11 | await set('firstStartAfterUpdate', true)
12 |
13 | createNotification({
14 | icon: 'mdi-update',
15 | color: 'primary',
16 | message: 'sidebar.notifications.updateAvailable.message',
17 | textColor: 'white',
18 | onClick: () => updateSW(),
19 | })
20 | },
21 | onOfflineReady() {
22 | // bridge. is ready to work offline
23 | console.log('bridge. is ready to work offline')
24 | },
25 | })
26 |
--------------------------------------------------------------------------------
/src/components/App/Vue.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuetify from 'vuetify'
3 | import { LocaleManager } from '../Locales/Manager'
4 | import { vuetify } from './Vuetify'
5 | import AppComponent from '/@/App.vue'
6 |
7 | Vue.use(Vuetify)
8 | Vue.config.productionTip = false
9 |
10 | export const vue = new Vue({
11 | vuetify,
12 | render: (h) => h(AppComponent),
13 | })
14 |
15 | LocaleManager.setDefaultLanguage().then(() => {
16 | vue.$mount('#app')
17 | })
18 |
--------------------------------------------------------------------------------
/src/components/App/Vuetify.ts:
--------------------------------------------------------------------------------
1 | import Vuetify from 'vuetify'
2 | import { iconMap } from './Icon/IconMap'
3 |
4 | export const vuetify = new Vuetify({
5 | breakpoint: {
6 | mobileBreakpoint: 'xs',
7 | },
8 | icons: {
9 | iconfont: 'mdi',
10 | values: Object.fromEntries(
11 | Object.entries(iconMap).map(([name, icon]) => [
12 | name,
13 | { component: icon },
14 | ])
15 | ),
16 | },
17 | theme: {
18 | options: {
19 | customProperties: true,
20 | variations: false,
21 | },
22 | },
23 | })
24 |
--------------------------------------------------------------------------------
/src/components/BedrockWorlds/BlockLibrary/loadImage.ts:
--------------------------------------------------------------------------------
1 | import { findFileExtension } from '/@/components/FileSystem/FindFile'
2 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
3 |
4 | export async function loadImage(fileSystem: FileSystem, filePath: string) {
5 | // TODO: Support .tga files
6 | const realPath = await findFileExtension(fileSystem, filePath, [
7 | '.png',
8 | '.jpg',
9 | '.jpeg',
10 | ])
11 |
12 | if (!realPath) return null
13 |
14 | const file = await fileSystem.readFile(realPath)
15 |
16 | return await createImageBitmap(
17 | file.isVirtual ? await file.toBlobFile() : file
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/BedrockWorlds/LevelDB/Comparators/Bytewise.ts:
--------------------------------------------------------------------------------
1 | export class BytewiseComparator {
2 | constructor() {}
3 |
4 | public compare(a: Uint8Array, b: Uint8Array): number {
5 | if (a.length === b.length) {
6 | return this.compareFixedLength(a, b)
7 | } else {
8 | const minLength = Math.min(a.length, b.length)
9 | const res = this.compareFixedLength(
10 | a.slice(0, minLength),
11 | b.slice(0, minLength)
12 | )
13 |
14 | if (res !== 0) return res
15 |
16 | return a.length - b.length > 0 ? 1 : -1
17 | }
18 | }
19 |
20 | /**
21 | * Assumption: a and b are of equal length
22 | */
23 | protected compareFixedLength(a: Uint8Array, b: Uint8Array) {
24 | for (let i = 0; i < a.length; i++) {
25 | if (a[i] !== b[i]) {
26 | const res = a[i] - b[i]
27 |
28 | return res > 0 ? 1 : -1
29 | }
30 | }
31 |
32 | return 0
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/BedrockWorlds/LevelDB/FileMetaData.ts:
--------------------------------------------------------------------------------
1 | import { Table } from './Table/Table'
2 |
3 | export interface IFileMetaData {
4 | fileNumber: number
5 | fileSize: number
6 | smallestKey: Uint8Array
7 | largestKey: Uint8Array
8 | }
9 | export class FileMetaData {
10 | public fileNumber: number
11 | public fileSize: number
12 | public smallestKey: Uint8Array
13 | public largestKey: Uint8Array
14 | public table?: Table
15 |
16 | constructor({
17 | fileNumber,
18 | fileSize,
19 | smallestKey,
20 | largestKey,
21 | }: IFileMetaData) {
22 | this.fileNumber = fileNumber
23 | this.fileSize = fileSize
24 | this.smallestKey = smallestKey
25 | this.largestKey = largestKey
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/BedrockWorlds/LevelDB/Key/AsUsableKey.ts:
--------------------------------------------------------------------------------
1 | export function asUsableKey(key: Uint8Array) {
2 | return key.slice(0, key.length - 8)
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/BedrockWorlds/LevelDB/Key/GetKeyType.ts:
--------------------------------------------------------------------------------
1 | export function getKeyType(key: Uint8Array) {
2 | return key.slice(key.length - 8, key.length - 7)[0]
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/BedrockWorlds/LevelDB/Record.ts:
--------------------------------------------------------------------------------
1 | export enum ELogRecordType {
2 | // Zero is reserved for preallocated files
3 | Zero = 0,
4 |
5 | Full = 1,
6 |
7 | // Data split across multiple records
8 | First = 2,
9 | Middle = 3,
10 | Last = 4,
11 |
12 | // Util
13 | InvalidRecord = Last + 1,
14 | Undefined = Last + 1,
15 | }
16 |
17 | interface IRecord {
18 | type: ELogRecordType
19 | data?: Uint8Array
20 | length?: number
21 | checksum?: number
22 | }
23 | export class Record {
24 | public type: ELogRecordType
25 | public checksum?: number
26 | public length?: number
27 | public data?: Uint8Array
28 |
29 | constructor({ type, checksum, data, length }: IRecord) {
30 | this.type = type
31 | this.checksum = checksum
32 | this.data = data
33 | this.length = length
34 | }
35 | }
36 |
37 | export class UndefinedRecord extends Record {
38 | constructor() {
39 | super({ type: ELogRecordType.Undefined })
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/BedrockWorlds/LevelDB/RequestStatus.ts:
--------------------------------------------------------------------------------
1 | export enum ERequestState {
2 | Success,
3 | Deleted,
4 | NotFound,
5 | Undefined,
6 | }
7 |
8 | export class RequestStatus {
9 | constructor(public value?: T, public state = ERequestState.Success) {}
10 |
11 | static createNotFound() {
12 | return new RequestStatus(undefined, ERequestState.NotFound)
13 | }
14 | static createDeleted() {
15 | return new RequestStatus(undefined, ERequestState.Deleted)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/BedrockWorlds/LevelDB/Uint8ArrayUtils/Equals.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Given two Uint8Arrays, returns true if they are equal, false otherwise.
3 | */
4 | export function equals(a: Uint8Array, b: Uint8Array): boolean {
5 | if (a.length !== b.length) {
6 | return false
7 | }
8 | for (let i = 0; i < a.length; i++) {
9 | if (a[i] !== b[i]) {
10 | return false
11 | }
12 | }
13 | return true
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/BedrockWorlds/LevelDB/Uint8ArrayUtils/ToUint8Array.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Convert the signed integer n to an Uint8Array representing a little-endian, 32 bit signed integer
3 | * @param n
4 | * @returns Uint8Array[4]
5 | */
6 | export function toUint8Array(n: number) {
7 | const buffer = new ArrayBuffer(4)
8 | const view = new DataView(buffer)
9 | view.setInt32(0, n, true)
10 | return new Uint8Array(buffer)
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/BedrockWorlds/Render/Neighbours.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Array to store all neighbours of a voxel, useful for iterating
3 | */
4 | export const VoxelNeighbours = [
5 | [0, 0, 0], // Self
6 | [-1, 0, 0], // Left
7 | [1, 0, 0], // Right
8 | [0, -1, 0], // Down
9 | [0, 1, 0], // Up
10 | [0, 0, -1], // Back
11 | [0, 0, 1], // Front
12 | ]
13 |
--------------------------------------------------------------------------------
/src/components/BedrockWorlds/WorldFormat/Block.ts:
--------------------------------------------------------------------------------
1 | export interface IBlock {
2 | version: 17879555
3 | name: string
4 | value?: number
5 | states?: IBlockStates
6 | }
7 | export interface IBlockStates {
8 | [key: string]: unknown
9 | }
10 |
11 | export class Block implements IBlock {
12 | public readonly version = 17879555
13 | public readonly name: string
14 | public readonly states: IBlockStates
15 |
16 | constructor(identifier: string, states?: IBlockStates) {
17 | this.name = identifier
18 | this.states = states ?? {}
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/BedrockWorlds/WorldFormat/EDimension.ts:
--------------------------------------------------------------------------------
1 | export enum EDimension {
2 | Overworld = 0,
3 | Nether = 1,
4 | TheEnd = 2,
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/BedrockWorlds/WorldFormat/EKeyTypeTags.ts:
--------------------------------------------------------------------------------
1 | export enum EKeyTypeTag {
2 | ChunkVersion = 44,
3 | Data2D = 45,
4 | Data2DLegacy = 46,
5 | SubChunkPrefix = 47,
6 | LegacyTerrain = 48,
7 | BlockEntity = 49,
8 | Entity = 50,
9 | PendingTicks = 51,
10 | BlockExtraData = 52,
11 | BiomeState = 53,
12 | FinalizedState = 54,
13 | BorderBlocks = 56,
14 | HardCodedSpawnAreas = 57,
15 | RandomTicks = 58,
16 | Checksums = 59,
17 | OldChunkVersion = 118,
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/BedrockWorlds/WorldFormat/readNbt.ts:
--------------------------------------------------------------------------------
1 | import { protoLE } from 'prismarine-nbt'
2 | import { Buffer } from 'buffer'
3 |
4 | export function readNbt(nbtData: Uint8Array, offset = 0) {
5 | const { data, metadata } = protoLE.parsePacketBuffer(
6 | 'nbt',
7 | Buffer.from(nbtData),
8 | offset
9 | )
10 |
11 | return {
12 | data,
13 | size: metadata.size,
14 | }
15 | }
16 |
17 | export function readAllNbt(nbtData: Uint8Array, count = 1) {
18 | const resData = []
19 |
20 | let lastOffset = 0
21 | for (let i = 0; i < count; i++) {
22 | const { data, size } = readNbt(nbtData, lastOffset)
23 | resData.push(data)
24 | lastOffset += size
25 | }
26 |
27 | return {
28 | data: resData,
29 | size: lastOffset,
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/BottomPanel/PanelContent.tsx:
--------------------------------------------------------------------------------
1 | import { Component, Show } from 'solid-js'
2 | import { Dynamic } from 'solid-js/web'
3 | import { toSignal } from '../Solid/toSignal'
4 | import { toVue } from '../Solid/toVue'
5 | import { TabBar } from './TabBar'
6 | import { App } from '/@/App'
7 |
8 | export const PanelContent: Component = (props) => {
9 | const [activeTab] = toSignal(App.bottomPanel.activeTab)
10 | const [tabs] = toSignal(App.bottomPanel.tabs)
11 |
12 | return (
13 |
14 |
1}>
15 |
16 |
17 |
18 |
25 |
26 |
27 |
28 | )
29 | }
30 |
31 | export const VuePanelContent = toVue(PanelContent)
32 |
--------------------------------------------------------------------------------
/src/components/BottomPanel/Terminal/Terminal.css:
--------------------------------------------------------------------------------
1 | .terminal-line {
2 | line-break: normal;
3 | }
4 |
5 | .terminal-output-container {
6 | /* full height - terminal input bar height - cwd display height - a few pixels to prevent double scrollbar */
7 | height: calc(100% - 58px - 33px - 2px);
8 | overflow-y: auto;
9 | display: flex;
10 | flex-direction: column-reverse;
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/CommandBar/State.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from 'vue'
2 | import { SimpleAction } from '../Actions/SimpleAction'
3 |
4 | export const CommandBarState = reactive({
5 | isWindowOpen: false,
6 | shouldRender: false, // Property is automatically updated
7 | closeDelay: null,
8 | })
9 | const CommandBarActions = new Set()
10 | export function addCommandBarAction(action: SimpleAction) {
11 | CommandBarActions.add(action)
12 |
13 | return {
14 | dispose: () => {
15 | CommandBarActions.delete(action)
16 | },
17 | }
18 | }
19 | export function getCommandBarActions() {
20 | return Array.from(CommandBarActions)
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/Common/Progress.ts:
--------------------------------------------------------------------------------
1 | import { EventDispatcher } from '/@/components/Common/Event/EventDispatcher'
2 |
3 | export class Progress extends EventDispatcher<[number, number]> {
4 | constructor(
5 | protected current: number,
6 | protected total: number,
7 | protected prevTotal: number
8 | ) {
9 | super()
10 | }
11 |
12 | addToCurrent(value?: number) {
13 | this.current += value ?? 1
14 | this.dispatch([this.getCurrent(), this.getTotal()])
15 | }
16 | addToTotal(value?: number) {
17 | this.total += value ?? 1
18 | this.dispatch([this.getCurrent(), this.getTotal()])
19 | }
20 |
21 | getTotal() {
22 | return this.total > this.prevTotal ? this.total : this.prevTotal
23 | }
24 | getCurrent() {
25 | return this.current
26 | }
27 |
28 | get isDone() {
29 | return this.getCurrent() === this.getTotal()
30 | }
31 |
32 | setTotal(val: number) {
33 | this.total = val
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/Common/WindowResize.ts:
--------------------------------------------------------------------------------
1 | import { EventDispatcher } from '/@/components/Common/Event/EventDispatcher'
2 | import { debounce } from 'lodash-es'
3 | import { reactive } from 'vue'
4 | import { App } from '/@/App'
5 |
6 | export class WindowResize extends EventDispatcher<[number, number]> {
7 | public readonly state = reactive({
8 | currentHeight: window.innerHeight,
9 | currentWidth: window.innerWidth,
10 | })
11 |
12 | constructor() {
13 | super()
14 |
15 | window.addEventListener(
16 | 'resize',
17 | debounce(() => this.dispatch(), 50, { trailing: true })
18 | )
19 |
20 | this.on(([newWidth, newHeight]) => {
21 | this.state.currentWidth = newWidth
22 | this.state.currentHeight = newHeight
23 | })
24 |
25 | App.getApp().then((app) =>
26 | app.projectManager.projectReady.fired.then(() => this.dispatch())
27 | )
28 | }
29 |
30 | dispatch() {
31 | super.dispatch([window.innerWidth, window.innerHeight])
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/Compiler/Actions/RecompileChanges.ts:
--------------------------------------------------------------------------------
1 | import { App } from '/@/App'
2 | import { SimpleAction } from '../../Actions/SimpleAction'
3 |
4 | export const recompileChangesConfig = {
5 | icon: 'mdi-cog-outline',
6 | name: 'actions.recompileChanges.name',
7 | description: 'actions.recompileChanges.description',
8 | onTrigger: async () => {
9 | const app = await App.getApp()
10 | const project = app.project
11 |
12 | project.packIndexer.deactivate()
13 | await project.packIndexer.activate(true)
14 |
15 | const [changedFiles, deletedFiles] = await project.packIndexer.fired
16 |
17 | await project.compilerService.start(changedFiles, deletedFiles)
18 | },
19 | }
20 |
21 | export const recompileChangesAction = new SimpleAction(recompileChangesConfig)
22 |
--------------------------------------------------------------------------------
/src/components/Compiler/Compiler.ts:
--------------------------------------------------------------------------------
1 | import type { DashService } from './Worker/Service'
2 | import CompilerWorker from './Worker/Service?worker'
3 | import { wrap } from 'comlink'
4 | import { setupWorker } from '/@/utils/worker/setup'
5 |
6 | const worker = new CompilerWorker()
7 | export const DashCompiler = wrap(worker)
8 |
9 | setupWorker(worker)
10 |
--------------------------------------------------------------------------------
/src/components/Compiler/Window/BuildProfiles.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
27 |
--------------------------------------------------------------------------------
/src/components/Compiler/Window/WatchMode/SettingSheet.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/components/Composables/Display/useDisplay.ts:
--------------------------------------------------------------------------------
1 | import { computed } from 'vue'
2 | import { vuetify } from '../../App/Vuetify'
3 |
4 | export function useDisplay() {
5 | return {
6 | isMobile: computed(() => vuetify.framework.breakpoint.mobile),
7 | isMinimalDisplay: computed(() => {
8 | return !vuetify.framework.breakpoint.mdAndUp
9 | }),
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/Composables/DoubleClick.ts:
--------------------------------------------------------------------------------
1 | import { pointerDevice } from '/@/utils/pointerDevice'
2 |
3 | export function useDoubleClick(
4 | onClick: (isDoubleClick: boolean, ...args: T[]) => void,
5 | alwaysTriggerSingleClick: boolean = false
6 | ) {
7 | let timer: number | null = null
8 | let clickedAmount = 0
9 |
10 | return (...args: T[]) => {
11 | if (pointerDevice.value === 'touch') return onClick(false, ...args)
12 |
13 | if (clickedAmount === 0) {
14 | clickedAmount++
15 | if (alwaysTriggerSingleClick) onClick(false, ...args)
16 |
17 | timer = window.setTimeout(() => {
18 | clickedAmount = 0
19 | timer = null
20 | if (!alwaysTriggerSingleClick) onClick(false, ...args)
21 | }, 500)
22 | } else {
23 | if (timer) window.clearTimeout(timer)
24 | clickedAmount = 0
25 | onClick(true, ...args)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/Composables/Sidebar/useSidebarState.ts:
--------------------------------------------------------------------------------
1 | import { computed, watch } from 'vue'
2 | import { settingsState } from '../../Windows/Settings/SettingsState'
3 | import { App } from '/@/App'
4 |
5 | export function useSidebarState() {
6 | const isNavVisible = computed(() => App.sidebar.isNavigationVisible.value)
7 | const isContentVisible = computed(
8 | () => isNavVisible.value && App.sidebar.isContentVisible.value
9 | )
10 | const isAttachedRight = computed(
11 | () => settingsState.sidebar && settingsState.sidebar.isSidebarRight
12 | )
13 |
14 | return {
15 | isNavVisible,
16 | isContentVisible,
17 | isAttachedRight,
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Composables/UseProject.ts:
--------------------------------------------------------------------------------
1 | import { Ref, ref, watch, onUnmounted, nextTick, watchEffect } from 'vue'
2 | import { Project } from '../Projects/Project/Project'
3 | import { App } from '/@/App'
4 | import { IDisposable } from '/@/types/disposable'
5 |
6 | export function useProject() {
7 | const project = [>ref(null)
8 | let disposable: IDisposable | null = null
9 |
10 | App.getApp().then(async (app) => {
11 | await app.projectManager.projectReady.fired
12 | project.value = app.project
13 |
14 | disposable = App.eventSystem.on('projectChanged', (newProject) => {
15 | project.value = newProject
16 | })
17 | })
18 |
19 | onUnmounted(() => {
20 | disposable?.dispose()
21 | disposable = null
22 | })
23 |
24 | return {
25 | project,
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/Composables/UseTabSystem.ts:
--------------------------------------------------------------------------------
1 | import { computed, ref } from 'vue'
2 | import { useProject } from './UseProject'
3 |
4 | export function useTabSystem(tabSystemId = ref(0)) {
5 | const { project } = useProject()
6 | const tabSystem = computed(
7 | () => project.value?.tabSystems[tabSystemId.value]
8 | )
9 | const activeTabSystem = computed(() => project.value?.tabSystem)
10 | const tabSystems = computed(() => project.value?.tabSystems)
11 | const shouldRenderWelcomeScreen = computed(() => {
12 | return (
13 | tabSystems.value &&
14 | (tabSystems.value?.[0].shouldRender.value ||
15 | tabSystems.value?.[1].shouldRender.value)
16 | )
17 | })
18 |
19 | return {
20 | tabSystem,
21 | activeTabSystem,
22 | tabSystems,
23 | shouldRenderWelcomeScreen,
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/Composables/useDarkMode.ts:
--------------------------------------------------------------------------------
1 | import { computed } from 'vue'
2 | import { vuetify } from '../App/Vuetify'
3 |
4 | export function useDarkMode() {
5 | return {
6 | isDarkMode: computed(() => vuetify.framework.theme.dark),
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/Composables/useHighContrast.ts:
--------------------------------------------------------------------------------
1 | import { computed } from 'vue'
2 | import { settingsState } from '../Windows/Settings/SettingsState'
3 |
4 | export function useHighContrast() {
5 | return {
6 | highContrast: computed(
7 | () => settingsState.appearance?.highContrast ?? false
8 | ),
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/Composables/useTranslations.ts:
--------------------------------------------------------------------------------
1 | import { LocaleManager } from '../Locales/Manager'
2 |
3 | export function useTranslations() {
4 | return {
5 | t: (translationKey?: string) => LocaleManager.translate(translationKey),
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/Data/PackType.ts:
--------------------------------------------------------------------------------
1 | import { Signal } from '../Common/Event/Signal'
2 | import { DataLoader } from './DataLoader'
3 | import {
4 | PackType as BasePackType,
5 | ProjectConfig,
6 | type IPackType,
7 | } from 'mc-project-core'
8 |
9 | export type { IPackType, TPackTypeId } from 'mc-project-core'
10 |
11 | /**
12 | * Utilities around bridge.'s pack definitions
13 | */
14 | export class PackTypeLibrary extends BasePackType {
15 | public readonly ready = new Signal()
16 |
17 | constructor(projectConfig?: ProjectConfig) {
18 | super(projectConfig)
19 | }
20 |
21 | async setup(dataLoader: DataLoader) {
22 | if (this.packTypes.length > 0) return
23 | await dataLoader.fired
24 |
25 | this.packTypes = (
26 | await dataLoader
27 | .readJSON('data/packages/minecraftBedrock/packDefinitions.json')
28 | .catch(() => [])
29 | )
30 | this.ready.dispatch()
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/Documentation/view.ts:
--------------------------------------------------------------------------------
1 | import { translate } from '../Locales/Manager'
2 | import { InformationWindow } from '../Windows/Common/Information/InformationWindow'
3 | import { App } from '/@/App'
4 |
5 | export async function viewDocumentation(filePath: string, word?: string) {
6 | await App.fileType.ready.fired
7 | const t = (str: string) => translate(str)
8 |
9 | const { id, documentation } = App.fileType.get(filePath) ?? {}
10 |
11 | if (!documentation) {
12 | new InformationWindow({
13 | description: `[${t(
14 | 'actions.documentationLookup.noDocumentation'
15 | )} ${id ? t(`fileType.${id}`) : '"' + filePath + '"'}.]`,
16 | })
17 | return
18 | }
19 |
20 | let url = documentation.baseUrl
21 | if (word && (documentation.supportsQuerying ?? true)) url += `#${word}`
22 |
23 | App.openUrl(url)
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/Editors/Blockbench/BlockbenchTab.ts:
--------------------------------------------------------------------------------
1 | import { TabSystem } from '../../TabSystem/TabSystem'
2 | import { IframeTab, IOpenWithPayload } from '../IframeTab/IframeTab'
3 |
4 | export interface IBlockbenchOptions {
5 | openWithPayload?: IOpenWithPayload
6 | }
7 |
8 | export const blockbenchUrl = import.meta.env.DEV
9 | ? 'http://localhost:5173'
10 | : 'https://blockbench.bridge-core.app'
11 |
12 | export class BlockbenchTab extends IframeTab {
13 | constructor(
14 | tabSystem: TabSystem,
15 | { openWithPayload }: IBlockbenchOptions = {}
16 | ) {
17 | super(tabSystem, {
18 | icon: '$blockbench',
19 | name: 'Blockbench',
20 | url: blockbenchUrl,
21 | iconColor: 'primary',
22 | openWithPayload,
23 | })
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/Editors/EntityModel/create/fromClientEntity.ts:
--------------------------------------------------------------------------------
1 | import { EntityModelTab } from '../Tab'
2 | import { FileTab } from '/@/components/TabSystem/FileTab'
3 | import { TabSystem } from '/@/components/TabSystem/TabSystem'
4 |
5 | export async function createFromClientEntity(
6 | tabSystem: TabSystem,
7 | tab: FileTab
8 | ) {
9 | return new EntityModelTab(
10 | { clientEntityFilePath: tab.getPath() },
11 | tab,
12 | tabSystem
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/Editors/EntityModel/transformOldModels.ts:
--------------------------------------------------------------------------------
1 | export function transformOldModels(geometry: any) {
2 | // New model format
3 | if (geometry['minecraft:geometry']) return geometry
4 |
5 | // Old model format
6 | // Filter out format_version
7 | const models: any = Object.entries(geometry).filter(
8 | ([_, modelData]) => typeof modelData !== 'string'
9 | )
10 |
11 | const transformedModels = []
12 | for (const [modelId, model] of models) {
13 | transformedModels.push({
14 | description: {
15 | identifier: modelId,
16 | texture_width: model.texturewidth,
17 | texture_height: model.textureheight,
18 | },
19 | bones: model.bones,
20 | })
21 | }
22 |
23 | return {
24 | format_version: '1.12.0',
25 | 'minecraft:geometry': transformedModels,
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/Editors/GeometryPreview/Data/ParticleData.ts:
--------------------------------------------------------------------------------
1 | import json5 from 'json5'
2 | import { PreviewFileWatcher } from './PreviewFileWatcher'
3 | import { RenderDataContainer } from './RenderContainer'
4 |
5 | export class ParticleData extends PreviewFileWatcher {
6 | protected particleData: any = {}
7 |
8 | constructor(
9 | protected parent: RenderDataContainer,
10 | public readonly shortName: string | undefined,
11 | particleFilePath: string
12 | ) {
13 | super(parent.app, particleFilePath)
14 | }
15 |
16 | async onChange(file: File, isInitial = false) {
17 | try {
18 | this.particleData = json5.parse(await file.text())
19 |
20 | if (!isInitial) this.parent.onChange()
21 | } catch {
22 | // If parsing JSON fails, do nothing
23 | }
24 | }
25 |
26 | get identifier(): string | undefined {
27 | return this.particleData?.particle_effect?.description?.identifier
28 | }
29 | get json() {
30 | return this.particleData
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/Editors/GeometryPreview/Data/PreviewFileWatcher.ts:
--------------------------------------------------------------------------------
1 | import { App } from '/@/App'
2 | import { FileWatcher } from '/@/components/FileSystem/FileWatcher'
3 |
4 | export abstract class PreviewFileWatcher extends FileWatcher {
5 | constructor(app: App, filePath: string) {
6 | super(app, filePath)
7 |
8 | // Make sure that the initial setup is complete
9 | this.ready.on(() => {
10 | // Then, listen for any further changes
11 | this.on((file) => this.onChange(file))
12 | })
13 | }
14 |
15 | async setup(file: File) {
16 | return await this.onChange(await this.compileFile(file), true)
17 | }
18 | abstract onChange(file: File, isInitial?: boolean): Promise | void
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Editors/IframeTab/API/Events/GenericEvent.ts:
--------------------------------------------------------------------------------
1 | import { IframeApi } from '../IframeApi'
2 | import { IDisposable } from '/@/types/disposable'
3 |
4 | export abstract class GenericEvent {
5 | protected disposables: IDisposable[] = []
6 | constructor(protected api: IframeApi) {
7 | this.disposables.push(
8 | this.api.loaded.on(() => this.onApiLoaded(), true),
9 | this.api.loaded.once(() => this.setup(), true)!
10 | )
11 | }
12 |
13 | onApiLoaded() {}
14 |
15 | abstract setup(): Promise | void
16 |
17 | dispose() {
18 | this.disposables.forEach((disposable) => disposable.dispose())
19 | this.disposables = []
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/Editors/IframeTab/API/Events/Tab/OpenFile.ts:
--------------------------------------------------------------------------------
1 | import { GenericEvent } from '../GenericEvent'
2 |
3 | export class OpenFileEvent extends GenericEvent {
4 | setup() {
5 | this.api.trigger('tab.openFile', this.api.openWithPayload)
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/Editors/IframeTab/API/Events/ThemeChange.ts:
--------------------------------------------------------------------------------
1 | import { GenericEvent } from './GenericEvent'
2 | import { App } from '/@/App'
3 |
4 | export class ThemeChangeEvent extends GenericEvent {
5 | async setup() {
6 | const app = await App.getApp()
7 |
8 | this.disposables.push(
9 | app.themeManager.on(() => {
10 | this.api.trigger(
11 | 'themeManager.themeChange',
12 | app.themeManager.getCurrentTheme()
13 | )
14 | })
15 | )
16 |
17 | this.api.trigger(
18 | 'themeManager.themeChange',
19 | app.themeManager.getCurrentTheme()
20 | )
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/Editors/IframeTab/API/Requests/FileSystem/ReadAsDataUrl.ts:
--------------------------------------------------------------------------------
1 | import { IframeApi } from '../../IframeApi'
2 | import { GenericRequest } from '../GenericRequest'
3 | import { resolveFileReference } from './ResolveFileReference'
4 | import { loadHandleAsDataURL } from '/@/utils/loadAsDataUrl'
5 |
6 | export class ReadAsDataUrlRequest extends GenericRequest {
7 | constructor(api: IframeApi) {
8 | super('fs.readAsDataUrl', api)
9 | }
10 |
11 | async handle(filePath: string, origin: string): Promise {
12 | const fileHandle = await resolveFileReference(filePath, this.api)
13 |
14 | return await loadHandleAsDataURL(fileHandle)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/Editors/IframeTab/API/Requests/FileSystem/ReadFile.ts:
--------------------------------------------------------------------------------
1 | import { IframeApi } from '../../IframeApi'
2 | import { GenericRequest } from '../GenericRequest'
3 | import { resolveFileReference } from './ResolveFileReference'
4 |
5 | export class ReadFileRequest extends GenericRequest {
6 | constructor(api: IframeApi) {
7 | super('fs.readFile', api)
8 | }
9 |
10 | async handle(filePath: string, origin: string): Promise {
11 | const fileHandle = await resolveFileReference(filePath, this.api)
12 | const file = await fileHandle.getFile()
13 |
14 | return new Uint8Array(await file.arrayBuffer())
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/Editors/IframeTab/API/Requests/FileSystem/ReadTextFile.ts:
--------------------------------------------------------------------------------
1 | import { IframeApi } from '../../IframeApi'
2 | import { GenericRequest } from '../GenericRequest'
3 | import { resolveFileReference } from './ResolveFileReference'
4 |
5 | export class ReadTextFileRequest extends GenericRequest {
6 | constructor(api: IframeApi) {
7 | super('fs.readTextFile', api)
8 | }
9 |
10 | async handle(filePath: string, origin: string): Promise {
11 | const fileHandle = await resolveFileReference(filePath, this.api)
12 | const file = await fileHandle.getFile()
13 |
14 | return await file.text()
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/Editors/IframeTab/API/Requests/FileSystem/WriteFile.ts:
--------------------------------------------------------------------------------
1 | import { IframeApi } from '../../IframeApi'
2 | import { GenericRequest } from '../GenericRequest'
3 | import { resolveFileReference } from './ResolveFileReference'
4 | import { App } from '/@/App'
5 |
6 | export interface IWriteFilePayload {
7 | filePath: string
8 | data: Uint8Array | string
9 | }
10 |
11 | export class WriteFileRequest extends GenericRequest {
12 | constructor(api: IframeApi) {
13 | super('fs.writeFile', api)
14 | }
15 |
16 | async handle(
17 | { filePath, data }: IWriteFilePayload,
18 | origin: string
19 | ): Promise {
20 | const app = await App.getApp()
21 |
22 | const fileHandle = await resolveFileReference(filePath, this.api, true)
23 |
24 | await app.fileSystem.write(fileHandle, data)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/Editors/IframeTab/API/Requests/GenericRequest.ts:
--------------------------------------------------------------------------------
1 | import { IframeApi } from '../IframeApi'
2 | import { IDisposable } from '/@/types/disposable'
3 |
4 | export abstract class GenericRequest {
5 | protected disposables: IDisposable[] = []
6 |
7 | constructor(name: string, protected api: IframeApi) {
8 | this.api.channelSetup.once(() => {
9 | this.disposables.push(
10 | this.api.channel.on(name, (data, origin) =>
11 | this.handle(data, origin)
12 | )
13 | )
14 | })
15 | }
16 |
17 | abstract handle(data: Payload, origin: string): Promise | Response
18 |
19 | dispose() {
20 | this.disposables.forEach((disposable) => disposable.dispose())
21 | this.disposables = []
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/Editors/IframeTab/API/Requests/PackIndexer/Find.ts:
--------------------------------------------------------------------------------
1 | import { IframeApi } from '../../IframeApi'
2 | import { GenericRequest } from '../GenericRequest'
3 |
4 | interface IRequestData {
5 | findFileType: string
6 | whereCacheKey: string
7 | matchesOneOf: string[]
8 | fetchAll?: boolean
9 | }
10 |
11 | export class FindRequest extends GenericRequest {
12 | constructor(api: IframeApi) {
13 | super('packIndexer.find', api)
14 | }
15 |
16 | async handle(
17 | { findFileType, whereCacheKey, matchesOneOf, fetchAll }: IRequestData,
18 | origin: string
19 | ) {
20 | const packIndexer = this.api.app.project.packIndexer
21 | await packIndexer.fired
22 |
23 | return await packIndexer.service.find(
24 | findFileType,
25 | whereCacheKey,
26 | matchesOneOf,
27 | fetchAll
28 | )
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/Editors/IframeTab/API/Requests/PackIndexer/GetFile.ts:
--------------------------------------------------------------------------------
1 | import { IframeApi } from '../../IframeApi'
2 | import { resolveFileReferencePath } from '../FileSystem/ResolveFileReference'
3 | import { GenericRequest } from '../GenericRequest'
4 | import { App } from '/@/App'
5 |
6 | export class GetFileRequest extends GenericRequest<
7 | string,
8 | Record
9 | > {
10 | constructor(api: IframeApi) {
11 | super('packIndexer.getFile', api)
12 | }
13 |
14 | async handle(fileReference: string, origin: string) {
15 | const packIndexer = this.api.app.project.packIndexer
16 | await packIndexer.fired
17 |
18 | const filePath = resolveFileReferencePath(fileReference, this.api)
19 |
20 | const fileType = App.fileType.getId(filePath)
21 |
22 | return await packIndexer.service.getCacheDataFor(fileType, filePath)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/Editors/IframeTab/API/Requests/Tab/SetIsLoading.ts:
--------------------------------------------------------------------------------
1 | import { IframeApi } from '../../IframeApi'
2 | import { GenericRequest } from '../GenericRequest'
3 |
4 | export class SetIsLoadingRequest extends GenericRequest {
5 | constructor(api: IframeApi) {
6 | super('tab.setIsLoading', api)
7 | }
8 |
9 | async handle(isLoading: boolean, origin: string) {
10 | this.api.tab.setIsLoading(isLoading)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/Editors/IframeTab/API/Requests/Tab/SetIsUnsaved.ts:
--------------------------------------------------------------------------------
1 | import { IframeApi } from '../../IframeApi'
2 | import { GenericRequest } from '../GenericRequest'
3 |
4 | export class SetIsUnsavedRequest extends GenericRequest {
5 | constructor(api: IframeApi) {
6 | super('tab.setIsUnsaved', api)
7 | }
8 |
9 | async handle(isUnsaved: boolean, origin: string) {
10 | this.api.tab.setIsUnsaved(isUnsaved)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/Editors/IframeTab/API/Requests/Util/Platform.ts:
--------------------------------------------------------------------------------
1 | import { IframeApi } from '../../IframeApi'
2 | import { GenericRequest } from '../GenericRequest'
3 | import { platform } from '/@/utils/os'
4 |
5 | export class PlatformRequest extends GenericRequest {
6 | constructor(api: IframeApi) {
7 | super('util.platform', api)
8 | }
9 |
10 | async handle(_: undefined, origin: string) {
11 | return platform()
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/Editors/Image/ImageTab.ts:
--------------------------------------------------------------------------------
1 | import { FileTab, TReadOnlyMode } from '/@/components/TabSystem/FileTab'
2 | import { loadHandleAsDataURL } from '/@/utils/loadAsDataUrl'
3 | import ImageTabComponent from './ImageTab.vue'
4 | import { AnyFileHandle } from '../../FileSystem/Types'
5 |
6 | export class ImageTab extends FileTab {
7 | component = ImageTabComponent
8 | dataUrl?: string = undefined
9 |
10 | static is(fileHandle: AnyFileHandle) {
11 | const fileName = fileHandle.name
12 | return (
13 | fileName.endsWith('.png') ||
14 | fileName.endsWith('.jpg') ||
15 | fileName.endsWith('.jpeg')
16 | )
17 | }
18 |
19 | setReadOnly(val: TReadOnlyMode) {
20 | this.readOnlyMode = val
21 | }
22 |
23 | async onActivate() {
24 | this.dataUrl = await loadHandleAsDataURL(this.fileHandle)
25 | }
26 |
27 | get icon() {
28 | return 'mdi-file-image-outline'
29 | }
30 | get iconColor() {
31 | return 'resourcePack'
32 | }
33 |
34 | _save() {}
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/Editors/Image/ImageTab.vue:
--------------------------------------------------------------------------------
1 |
2 | ]
6 |
![]()
12 |
13 |
14 |
15 |
23 |
24 |
31 |
--------------------------------------------------------------------------------
/src/components/Editors/ParticlePreview/ParticleWatcher.ts:
--------------------------------------------------------------------------------
1 | import { PreviewFileWatcher } from '/@/components/Editors/GeometryPreview/Data/PreviewFileWatcher'
2 | import { ParticlePreviewTab } from './ParticlePreview'
3 |
4 | export class ParticleWatcher extends PreviewFileWatcher {
5 | constructor(protected tab: ParticlePreviewTab, filePath: string) {
6 | super(tab.tabSystem.app, filePath)
7 | }
8 |
9 | onChange(file: File) {
10 | this.tab.onChange(file)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/Editors/ThreePreview/ThreePreviewTab.vue:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 |
42 |
--------------------------------------------------------------------------------
/src/components/Editors/TreeEditor/CompletionItems/FilterDuplicates.ts:
--------------------------------------------------------------------------------
1 | import { ICompletionItem } from '/@/components/JSONSchema/Schema/Schema'
2 |
3 | function getItemId(item: ICompletionItem) {
4 | return `t-${item.type}:v-${item.value}`
5 | }
6 |
7 | export function filterDuplicates(items: ICompletionItem[]): ICompletionItem[] {
8 | const seen = new Set()
9 | return items.filter((item) => {
10 | const id = getItemId(item)
11 |
12 | const result = !seen.has(id)
13 | seen.add(id)
14 |
15 | return result
16 | })
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/Editors/TreeEditor/History/CollectedEntry.ts:
--------------------------------------------------------------------------------
1 | import { HistoryEntry } from './HistoryEntry'
2 |
3 | export class CollectedEntry extends HistoryEntry {
4 | constructor(protected entries: HistoryEntry[]) {
5 | super()
6 | }
7 |
8 | get unselectTrees() {
9 | return this.entries.map((entry) => entry.unselectTrees).flat()
10 | }
11 |
12 | undo() {
13 | return new CollectedEntry(
14 | this.entries.reverse().map((entry) => entry.undo())
15 | )
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/Editors/TreeEditor/History/EditPropertyEntry.ts:
--------------------------------------------------------------------------------
1 | import { ObjectTree } from '../Tree/ObjectTree'
2 | import { Tree } from '../Tree/Tree'
3 | import { HistoryEntry } from './HistoryEntry'
4 |
5 | export class EditPropertyEntry extends HistoryEntry {
6 | unselectTrees: Tree[]
7 |
8 | constructor(
9 | protected parent: ObjectTree,
10 | protected oldValue: string,
11 | protected newValue: string
12 | ) {
13 | super()
14 | this.unselectTrees = [parent]
15 | }
16 |
17 | undo() {
18 | this.parent.updatePropertyName(this.newValue, this.oldValue)
19 |
20 | return new EditPropertyEntry(this.parent, this.newValue, this.oldValue)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/Editors/TreeEditor/History/EditValueEntry.ts:
--------------------------------------------------------------------------------
1 | import { ObjectTree } from '../Tree/ObjectTree'
2 | import { PrimitiveTree } from '../Tree/PrimitiveTree'
3 | import { Tree } from '../Tree/Tree'
4 | import { HistoryEntry } from './HistoryEntry'
5 |
6 | export class EditValueEntry extends HistoryEntry {
7 | unselectTrees: Tree[]
8 |
9 | constructor(protected tree: PrimitiveTree, protected value: any) {
10 | super()
11 | this.unselectTrees = [tree]
12 | }
13 |
14 | undo() {
15 | const oldValue = `${this.tree.value}`
16 |
17 | this.tree.edit(this.value)
18 |
19 | return new EditValueEntry(this.tree, oldValue)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/Editors/TreeEditor/History/HistoryEntry.ts:
--------------------------------------------------------------------------------
1 | import type { Tree } from '../Tree/Tree'
2 |
3 | export abstract class HistoryEntry {
4 | public abstract readonly unselectTrees: Tree[]
5 | abstract undo(): HistoryEntry
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/Editors/TreeEditor/History/ReplaceTree.ts:
--------------------------------------------------------------------------------
1 | import { Tree } from '../Tree/Tree'
2 | import { HistoryEntry } from './HistoryEntry'
3 |
4 | export class ReplaceTreeEntry extends HistoryEntry {
5 | unselectTrees: Tree[]
6 |
7 | constructor(
8 | protected oldTree: Tree,
9 | protected newTree: Tree
10 | ) {
11 | super()
12 | this.unselectTrees = [newTree, oldTree]
13 | }
14 |
15 | undo() {
16 | this.newTree.replace(this.oldTree)
17 |
18 | return new ReplaceTreeEntry(this.newTree, this.oldTree)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/Editors/TreeEditor/InlineDiagnostic.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 | mdi-arrow-down
14 |
15 | {{ diagnostic.message }}
16 |
17 |
18 |
19 |
31 |
--------------------------------------------------------------------------------
/src/components/Editors/TreeEditor/Tree/createTree.ts:
--------------------------------------------------------------------------------
1 | import { ObjectTree } from './ObjectTree'
2 | import { Tree } from './Tree'
3 | import { ArrayTree } from './ArrayTree'
4 | import { PrimitiveTree } from './PrimitiveTree'
5 | import { TreeEditor } from '../TreeEditor'
6 |
7 | export function createTree(
8 | parent: ObjectTree | ArrayTree | TreeEditor,
9 | value: unknown
10 | ): Tree {
11 | if (value === null) return new PrimitiveTree(parent, null)
12 | else if (Array.isArray(value)) return new ArrayTree(parent, value)
13 | else if (typeof value === 'object') return new ObjectTree(parent, value!)
14 | else if (['string', 'number', 'boolean'].includes(typeof value))
15 | return new PrimitiveTree(parent, value)
16 | else
17 | throw new Error(`Undefined type handler: "${typeof value}" -> ${value}`)
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/Editors/TreeEditor/mayCastTo.ts:
--------------------------------------------------------------------------------
1 | export const mayCastTo = {
2 | string: [],
3 | number: ['string'],
4 | boolean: ['string'],
5 | null: ['string'],
6 | integer: ['string', 'number'],
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/Extensions/FileDefinition/load.ts:
--------------------------------------------------------------------------------
1 | import json5 from 'json5'
2 | import { AnyDirectoryHandle } from '../../FileSystem/Types'
3 | import { App } from '/@/App'
4 | import { IDisposable } from '/@/types/disposable'
5 | import { iterateDir } from '/@/utils/iterateDir'
6 |
7 | export function loadFileDefinitions(
8 | baseDirectory: AnyDirectoryHandle,
9 | disposables: IDisposable[]
10 | ) {
11 | return iterateDir(baseDirectory, async (fileHandle) => {
12 | const file = await fileHandle.getFile()
13 | const fileDefinition = json5.parse(await file.text())
14 |
15 | disposables.push(App.fileType.addPluginFileType(fileDefinition))
16 | })
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/JsRuntime.ts:
--------------------------------------------------------------------------------
1 | import { Runtime } from '@bridge-editor/js-runtime'
2 | import { App } from '/@/App'
3 | import { dirname } from '/@/utils/path'
4 |
5 | export class JsRuntime extends Runtime {
6 | async readFile(filePath: string) {
7 | const app = await App.getApp()
8 |
9 | // Convince TypeScript that this is a real "File" and not a "VirtualFile"
10 | // Because our VirtualFile implements all File methods the JS runtime needs
11 | const file = await app.fileSystem.readFile(filePath)
12 |
13 | return file
14 | }
15 |
16 | run(filePath: string, env: any = {}, fileContent?: string) {
17 | return super.run(
18 | filePath,
19 | Object.assign(env, {
20 | require: (x: string) => this.require(x, dirname(filePath), env),
21 | }),
22 | fileContent
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/Modules/CommandBar.ts:
--------------------------------------------------------------------------------
1 | import { IModuleConfig } from '../types'
2 | import { SimpleAction, IActionConfig } from '/@/components/Actions/SimpleAction'
3 |
4 | export const CommandBarExtensionItems = new Set()
5 |
6 | export const CommandBarModule = ({ disposables }: IModuleConfig) => {
7 | return {
8 | registerAction(actionConfig: IActionConfig) {
9 | const action = new SimpleAction(actionConfig)
10 | CommandBarExtensionItems.add(action)
11 |
12 | const disposable = {
13 | dispose: () => {
14 | CommandBarExtensionItems.delete(action)
15 | },
16 | }
17 |
18 | disposables.push(disposable)
19 |
20 | return disposable
21 | },
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/Modules/ModelViewer.ts:
--------------------------------------------------------------------------------
1 | import { useBridgeModelViewer } from '/@/utils/libs/useModelViewer'
2 |
3 | export const ModelViewerModule = async () => {
4 | const { Model, StandaloneModelViewer } = await useBridgeModelViewer()
5 |
6 | return {
7 | Model,
8 | StandaloneModelViewer,
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/Modules/Three.ts:
--------------------------------------------------------------------------------
1 | export const ThreeModule = () => import('three')
2 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/Modules/compareVersions.ts:
--------------------------------------------------------------------------------
1 | import { compareVersions } from 'bridge-common-utils'
2 |
3 | export const CompareVersions = () => ({
4 | compare: compareVersions,
5 | })
6 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/Modules/fetchDefinition.ts:
--------------------------------------------------------------------------------
1 | import { App } from '/@/App'
2 | import { IModuleConfig } from '../types'
3 |
4 | export const FetchDefinitionModule = ({}: IModuleConfig) => ({
5 | fetchDefinition: async (
6 | fileType: string,
7 | fetchDefs: string[],
8 | fetchSearch: string,
9 | fetchAll = false
10 | ) => {
11 | const app = await App.getApp()
12 | const packIndexer = app.project?.packIndexer
13 | if (!packIndexer) return []
14 |
15 | const files = await Promise.all(
16 | fetchDefs.map(
17 | fetchDef =>
18 | packIndexer.service?.find(
19 | fileType,
20 | fetchDef,
21 | [fetchSearch],
22 | fetchAll
23 | ) ?? []
24 | )
25 | )
26 |
27 | return files.flat()
28 | },
29 | })
30 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/Modules/fflate.ts:
--------------------------------------------------------------------------------
1 | export const FflateModule = () => import('fflate')
2 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/Modules/globals.ts:
--------------------------------------------------------------------------------
1 | import { IModuleConfig } from '../types'
2 | import { App } from '/@/App'
3 |
4 | let cachedGlobals: Record | undefined = undefined
5 |
6 | // App.eventSystem.on('projectChanged', () => {
7 | // cachedGlobals = undefined
8 | // })
9 |
10 | export const GlobalsModule = async ({}: IModuleConfig) => {
11 | try {
12 | if (cachedGlobals === undefined) {
13 | return new Promise>((resolve) => {
14 | App.ready.once(async (app) => {
15 | cachedGlobals = await app.fileSystem
16 | .readJSON(`${app.project.projectPath}/globals.json`)
17 | .catch(() => {})
18 | resolve(cachedGlobals!)
19 | })
20 | })
21 | }
22 | } catch {
23 | return {}
24 | }
25 |
26 | return { ...cachedGlobals }
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/Modules/json5.ts:
--------------------------------------------------------------------------------
1 | import json5 from 'json5'
2 |
3 | export const Json5Module = () => ({
4 | parse: (str: string) => json5.parse(str),
5 | stringify: (
6 | obj: any,
7 | replacer?: ((this: any, key: string, value: any) => any) | undefined,
8 | space?: string | number | undefined
9 | ) => JSON.stringify(obj, replacer, space),
10 | })
11 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/Modules/monaco.ts:
--------------------------------------------------------------------------------
1 | import { IModuleConfig } from '../types'
2 | import { useMonaco } from '../../../../utils/libs/useMonaco'
3 | import type { languages } from 'monaco-editor'
4 |
5 | export const MonacoModule = ({ disposables }: IModuleConfig) => ({
6 | registerDocumentFormattingEditProvider: async (
7 | languageId: string,
8 | provider: languages.DocumentFormattingEditProvider
9 | ) => {
10 | const { languages } = await useMonaco()
11 |
12 | disposables.push(
13 | languages.registerDocumentFormattingEditProvider(
14 | languageId,
15 | provider
16 | )
17 | )
18 | },
19 | })
20 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/Modules/notifications.ts:
--------------------------------------------------------------------------------
1 | import { IModuleConfig } from '../types'
2 | import { INotificationConfig } from '/@/components/Notifications/Notification'
3 | import { createNotification } from '/@/components/Notifications/create'
4 | import { createErrorNotification } from '/@/components/Notifications/Errors'
5 |
6 | export const NotificationModule = ({ disposables }: IModuleConfig) => ({
7 | create(config: INotificationConfig) {
8 | const notification = createNotification(config)
9 | disposables.push(notification)
10 | return notification
11 | },
12 | createError(error: Error) {
13 | const notification = createErrorNotification(error)
14 | disposables.push(notification)
15 | return notification
16 | },
17 | })
18 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/Modules/path.ts:
--------------------------------------------------------------------------------
1 | import * as path from '/@/utils/path'
2 |
3 | export const PathModule = () => path
4 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/Modules/persistentStorage.ts:
--------------------------------------------------------------------------------
1 | import { IModuleConfig } from '../types'
2 | import { IDBWrapper } from '/@/components/FileSystem/Virtual/IDB'
3 |
4 | export const idbExtensionStore = new IDBWrapper('persistent-extension-storage')
5 |
6 | export const PersistentStorageModule = ({ extensionId }: IModuleConfig) => ({
7 | async save(data: any) {
8 | await idbExtensionStore.set(extensionId, data)
9 | },
10 |
11 | async load() {
12 | return await idbExtensionStore.get(extensionId)
13 | },
14 |
15 | async delete() {
16 | await idbExtensionStore.del(extensionId)
17 | },
18 | })
19 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/Modules/reactivity.ts:
--------------------------------------------------------------------------------
1 | import {
2 | computed,
3 | del,
4 | markRaw,
5 | reactive,
6 | readonly,
7 | ref,
8 | set,
9 | shallowReactive,
10 | shallowReadonly,
11 | watch,
12 | watchEffect,
13 | shallowRef,
14 | } from 'vue'
15 | import { IModuleConfig } from '../types'
16 | import { SimpleAction, IActionConfig } from '/@/components/Actions/SimpleAction'
17 |
18 | export const CommandBarExtensionItems = new Set()
19 |
20 | export const ReactivityModule = () => {
21 | return {
22 | ref,
23 | shallowRef,
24 | computed,
25 | reactive,
26 | shallowReactive,
27 | readonly,
28 | shallowReadonly,
29 | markRaw,
30 | watch,
31 | watchEffect,
32 | del,
33 | set,
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/Modules/settings.ts:
--------------------------------------------------------------------------------
1 | export const SettingsModule = () => ({})
2 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/Modules/theme.ts:
--------------------------------------------------------------------------------
1 | import { App } from '/@/App'
2 | import { IModuleConfig } from '../types'
3 | import { TColorName } from '../../Themes/ThemeManager'
4 |
5 | export const ThemeModule = async ({ disposables }: IModuleConfig) => {
6 | const app = await App.getApp()
7 | const themeManager = app.themeManager
8 |
9 | return {
10 | onChange: (func: (mode: 'dark' | 'light') => void) => {
11 | const disposable = themeManager.on(func)
12 | disposables.push(disposable)
13 | return disposable
14 | },
15 | getCurrentMode() {
16 | return app.themeManager.getCurrentMode()
17 | },
18 | getColor(name: TColorName) {
19 | return themeManager.getColor(name)
20 | },
21 | getHighlighterInfo(name: string) {
22 | return themeManager.getHighlighterInfo(name)
23 | },
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/Modules/toolbar.ts:
--------------------------------------------------------------------------------
1 | import { App } from '/@/App'
2 | import { ToolbarCategory } from '/@/components/Toolbar/ToolbarCategory'
3 | import { IModuleConfig } from '../types'
4 |
5 | export const ToolbarModule = async ({ disposables }: IModuleConfig) => ({
6 | ToolbarCategory,
7 | actionManager: (await App.getApp()).actionManager,
8 | addCategory(category: ToolbarCategory) {
9 | App.toolbar.addCategory(category)
10 | disposables.push(category)
11 | },
12 | })
13 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/Modules/ui.ts:
--------------------------------------------------------------------------------
1 | import { IModuleConfig } from '../types'
2 | import BaseWindow from '/@/components/Windows/Layout/BaseWindow.vue'
3 | import SidebarWindow from '/@/components/Windows/Layout/SidebarWindow.vue'
4 | import DirectoryViewer from '/@/components/UIElements/DirectoryViewer/DirectoryViewer.vue'
5 | import BridgeSheet from '/@/components/UIElements/Sheet.vue'
6 |
7 | export const UIModule = async ({ uiStore }: IModuleConfig) => {
8 | await uiStore?.allLoaded.fired
9 |
10 | return {
11 | ...uiStore?.UI,
12 | BuiltIn: {
13 | BaseWindow,
14 | SidebarWindow,
15 | DirectoryViewer,
16 | BridgeSheet,
17 | },
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/Modules/utils.ts:
--------------------------------------------------------------------------------
1 | import { IModuleConfig } from '../types'
2 | import { App } from '/@/App'
3 |
4 | export const UtilsModule = ({}: IModuleConfig) => ({
5 | openExternal: (url: string) => App.openUrl(url, undefined, true),
6 | })
7 |
--------------------------------------------------------------------------------
/src/components/Extensions/Scripts/types.d.ts:
--------------------------------------------------------------------------------
1 | import { IDisposable } from '../../Types/disposable'
2 | import { TUIStore } from '../UI/store'
3 |
4 | export interface IModuleConfig {
5 | extensionId: string
6 | uiStore?: TUIStore
7 | disposables: IDisposable[]
8 | isGlobal: boolean
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/Extensions/Settings/ExtensionSetting.ts:
--------------------------------------------------------------------------------
1 | import json5 from 'json5'
2 | import { AnyDirectoryHandle } from '../../FileSystem/Types'
3 | import { IPresetFieldOpts } from '/@/components/Windows/Project/CreatePreset/PresetWindow'
4 | import { iterateDir } from '/@/utils/iterateDir'
5 |
6 | export interface ISettingDef {
7 | name: string
8 | fields: [string, string, IPresetFieldOpts][]
9 | }
10 |
11 | export class ExtensionSetting {
12 | static async load(baseDirectory: AnyDirectoryHandle) {
13 | iterateDir(baseDirectory, async (fileHandle) => {
14 | const fileContent = await fileHandle
15 | .getFile()
16 | .then((file) => file.text())
17 |
18 | let settingsDefinition: ISettingDef = json5.parse(fileContent)
19 | })
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/Extensions/Styles/createStyle.ts:
--------------------------------------------------------------------------------
1 | export function createStyleSheet(styles: string) {
2 | const styleTag = document.createElement('style')
3 | styleTag.innerHTML = styles
4 | document.head.appendChild(styleTag)
5 |
6 | return {
7 | dispose: () => {
8 | if (document.head.contains(styleTag))
9 | document.head.removeChild(styleTag)
10 | },
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/Extensions/UI/store.ts:
--------------------------------------------------------------------------------
1 | import { v4 as uuid } from 'uuid'
2 | import { Signal } from '../../Common/Event/Signal'
3 | import { basename, extname } from '/@/utils/path'
4 |
5 | export type TUIStore = ReturnType
6 | export function createUIStore() {
7 | let UI: any = {}
8 | let storeUUID: string | null = uuid()
9 |
10 | return {
11 | get UI() {
12 | return UI
13 | },
14 | allLoaded: new Signal(),
15 | set(path: string[], component: () => Promise) {
16 | let current = UI
17 |
18 | while (path.length > 1) {
19 | const key = path.shift()
20 | if (current[key] === undefined) current[key] = {}
21 |
22 | current = current[key]
23 | }
24 |
25 | const key = path.shift()
26 | current[basename(key, extname(key))] = component
27 | },
28 | dispose() {
29 | UI = null
30 | storeUUID = null
31 | },
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/FileSystem/CombinedFs.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * A FileSystem that switches between the dataLoader virtual FS & normal FS based on the file path
3 | */
4 | import { IGetHandleConfig } from './Common'
5 | import { AnyDirectoryHandle } from './Types'
6 | import { DataLoader } from '/@/components/Data/DataLoader'
7 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
8 |
9 | export class CombinedFileSystem extends FileSystem {
10 | constructor(
11 | baseDirectory: AnyDirectoryHandle,
12 | protected dataLoader: DataLoader
13 | ) {
14 | super(baseDirectory)
15 | }
16 |
17 | getDirectoryHandle(path: string, opts: IGetHandleConfig) {
18 | if (path.startsWith('data/packages/'))
19 | return this.dataLoader.getDirectoryHandle(path, opts)
20 | else if (path.startsWith('file:///data/packages/'))
21 | return this.dataLoader.getDirectoryHandle(path.slice(8), opts)
22 | else return super.getDirectoryHandle(path, opts)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/FileSystem/Common.ts:
--------------------------------------------------------------------------------
1 | export interface IMkdirConfig {
2 | recursive: boolean
3 | }
4 |
5 | export interface IGetHandleConfig {
6 | create: boolean
7 | createOnce: boolean
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/FileSystem/FindFile.ts:
--------------------------------------------------------------------------------
1 | import { FileSystem } from './FileSystem'
2 |
3 | export async function findFileExtension(
4 | fileSystem: FileSystem,
5 | basePath: string,
6 | possibleExtensions: string[]
7 | ) {
8 | for (const extension of possibleExtensions) {
9 | const currPath = `${basePath}${extension}`
10 | if (await fileSystem.fileExists(currPath)) return currPath
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/FileSystem/Types.ts:
--------------------------------------------------------------------------------
1 | import { VirtualDirectoryHandle } from './Virtual/DirectoryHandle'
2 | import { VirtualFileHandle } from './Virtual/FileHandle'
3 | import { VirtualHandle } from './Virtual/Handle'
4 |
5 | export type AnyHandle =
6 | | FileSystemFileHandle
7 | | FileSystemDirectoryHandle
8 | | VirtualHandle
9 | export type AnyDirectoryHandle =
10 | | FileSystemDirectoryHandle
11 | | VirtualDirectoryHandle
12 | export type AnyFileHandle = FileSystemFileHandle | VirtualFileHandle
13 |
--------------------------------------------------------------------------------
/src/components/FileSystem/Virtual/Stores/Deserialize.ts:
--------------------------------------------------------------------------------
1 | import type { BaseStore, TStoreType } from './BaseStore'
2 | import { IndexedDbStore } from './IndexedDb'
3 | import { MemoryStore } from './Memory'
4 | import { TauriFsStore } from './TauriFs'
5 |
6 | export function deserializeStore(data: any & { type: TStoreType }): BaseStore {
7 | if (typeof data !== 'object' || data === null)
8 | throw new Error('BaseStore deserialization data must be an object')
9 | if (!('type' in data))
10 | throw new Error(
11 | 'BaseStore deserialization data must have a type property'
12 | )
13 |
14 | switch (data.type) {
15 | case 'idbStore':
16 | return IndexedDbStore.deserialize(data)
17 | case 'memoryStore':
18 | return MemoryStore.deserialize(data)
19 | case 'tauriFsStore':
20 | return TauriFsStore.deserialize(data)
21 | default:
22 | throw new Error(`Unknown base store type: ${data.type}`)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/FileSystem/Virtual/getParent.ts:
--------------------------------------------------------------------------------
1 | import { VirtualDirectoryHandle } from './DirectoryHandle'
2 | import { BaseStore } from './Stores/BaseStore'
3 |
4 | export function getParent(baseStore: BaseStore, basePath: string[]) {
5 | return new VirtualDirectoryHandle(
6 | baseStore,
7 | // Base path always contains itself so new directory handle name is at index - 2
8 | basePath.length > 1 ? basePath[basePath.length - 2] : '',
9 | basePath.slice(0, -1)
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/FileSystem/Virtual/pathFromHandle.ts:
--------------------------------------------------------------------------------
1 | import { AnyHandle } from '../Types'
2 | import { BaseVirtualHandle } from './Handle'
3 |
4 | export async function pathFromHandle(handle: AnyHandle) {
5 | if (!import.meta.env.VITE_IS_TAURI_APP)
6 | throw new Error('Can only get path from handle in Tauri builds')
7 | if (!(handle instanceof BaseVirtualHandle))
8 | throw new Error(`Expected a virtual handle`)
9 |
10 | const { TauriFsStore } = await import('./Stores/TauriFs')
11 |
12 | const baseStore = handle.getBaseStore()
13 | if (!(baseStore instanceof TauriFsStore))
14 | throw new Error(
15 | `Expected a TauriFsStore instance to back VirtualHandle`
16 | )
17 |
18 | return await baseStore.resolvePath(handle.idbKey)
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/FileSystem/Zip/GenericUnzipper.ts:
--------------------------------------------------------------------------------
1 | import { ITaskDetails, Task } from '/@/components/TaskManager/Task'
2 | import { TaskManager } from '/@/components/TaskManager/TaskManager'
3 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
4 | import { AnyDirectoryHandle } from '../Types'
5 |
6 | export abstract class GenericUnzipper {
7 | protected fileSystem: FileSystem
8 | protected task?: Task
9 |
10 | constructor(protected directory: AnyDirectoryHandle) {
11 | this.fileSystem = new FileSystem(directory)
12 | }
13 |
14 | createTask(
15 | taskManager: TaskManager,
16 | taskDetails: ITaskDetails = {
17 | icon: 'mdi-folder-zip',
18 | name: 'taskManager.tasks.unzipper.name',
19 | description: 'taskManager.tasks.unzipper.description',
20 | }
21 | ) {
22 | this.task = taskManager.create(taskDetails)
23 | }
24 |
25 | abstract unzip(data: T): Promise
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/FindAndReplace/Controls/SearchType.vue:
--------------------------------------------------------------------------------
1 |
2 | $emit('input', val)"
7 | >
8 |
9 | mdi-format-letter-case-upper
10 |
11 |
12 |
13 | mdi-format-letter-case
14 |
15 |
16 |
21 | mdi-regex
22 |
23 |
24 |
25 |
26 |
34 |
--------------------------------------------------------------------------------
/src/components/FindAndReplace/Controls/searchType.ts:
--------------------------------------------------------------------------------
1 | export const searchType = {
2 | matchCase: 0,
3 | ignoreCase: 1,
4 | useRegExp: 2,
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/FindAndReplace/Match.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ item.isStartOfFile ? '' : '...' }}{{ item.beforeMatch }}
4 | {{ item.match }}
11 | {{
12 | replaceWith
13 | }}
14 | {{ item.afterMatch }}...
15 |
16 |
17 |
18 |
28 |
29 |
35 |
--------------------------------------------------------------------------------
/src/components/FindAndReplace/Utils.ts:
--------------------------------------------------------------------------------
1 | import escapeRegExpString from 'escape-string-regexp'
2 | import { searchType } from './Controls/searchType'
3 |
4 | export function processFileText(
5 | fileText: string,
6 | regExp: RegExp,
7 | replaceWith: string
8 | ) {
9 | return fileText.replace(regExp, (substring, ...groups) => {
10 | groups = groups.slice(0, -2)
11 | // This allows users to reference capture groups like this: {0}
12 | return replaceWith.replace(
13 | /{(\d+)}/g,
14 | (match, ...replaceGroups) =>
15 | groups[Number(replaceGroups[0])] ?? match
16 | )
17 | })
18 | }
19 |
20 | export function createRegExp(searchFor: string, type: number) {
21 | let regExp: RegExp
22 | try {
23 | regExp = new RegExp(
24 | type === searchType.useRegExp
25 | ? searchFor
26 | : escapeRegExpString(searchFor),
27 | `g${type === searchType.ignoreCase ? 'i' : ''}`
28 | )
29 | } catch {
30 | return
31 | }
32 | return regExp
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/ImportFile/Brproject.ts:
--------------------------------------------------------------------------------
1 | import { FileDropper } from '/@/components/FileDropper/FileDropper'
2 | import { FileImporter } from './Importer'
3 | import { AnyFileHandle } from '../FileSystem/Types'
4 | import { importFromBrproject } from '../Projects/Import/fromBrproject'
5 |
6 | export class BrprojectImporter extends FileImporter {
7 | constructor(fileDropper: FileDropper) {
8 | super(['.brproject'], fileDropper)
9 | }
10 |
11 | async onImport(fileHandle: AnyFileHandle) {
12 | await importFromBrproject(fileHandle)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/ImportFile/Importer.ts:
--------------------------------------------------------------------------------
1 | import { AnyFileHandle } from '../FileSystem/Types'
2 | import type { FileDropper } from '/@/components/FileDropper/FileDropper'
3 | import { IDisposable } from '/@/types/disposable'
4 |
5 | export abstract class FileImporter {
6 | protected disposables: IDisposable[] = []
7 |
8 | constructor(
9 | extensions: string[],
10 | fileDropper: FileDropper,
11 | defaultImporter = false
12 | ) {
13 | for (const extension of extensions) {
14 | this.disposables.push(
15 | fileDropper.addFileImporter(extension, this.onImport.bind(this))
16 | )
17 | }
18 |
19 | if (defaultImporter) {
20 | this.disposables.push(
21 | fileDropper.setDefaultFileImporter(this.onImport.bind(this))
22 | )
23 | }
24 | }
25 |
26 | protected abstract onImport(fileHandle: AnyFileHandle): Promise | void
27 |
28 | dispose() {
29 | this.disposables.forEach((disposable) => disposable.dispose())
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/ImportFile/MCAddon.ts:
--------------------------------------------------------------------------------
1 | import { FileDropper } from '/@/components/FileDropper/FileDropper'
2 | import { FileImporter } from './Importer'
3 | import { App } from '/@/App'
4 | import { AnyFileHandle } from '../FileSystem/Types'
5 | import { importFromMcaddon } from '../Projects/Import/fromMcaddon'
6 |
7 | export class MCAddonImporter extends FileImporter {
8 | constructor(fileDropper: FileDropper) {
9 | super(['.mcaddon'], fileDropper)
10 | }
11 |
12 | async onImport(fileHandle: AnyFileHandle) {
13 | const app = await App.getApp()
14 |
15 | app.windows.loadingWindow.open()
16 |
17 | try {
18 | await importFromMcaddon(fileHandle)
19 | } catch (err) {
20 | console.error(err)
21 | } finally {
22 | app.windows.loadingWindow.close()
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/ImportFile/MCPack.ts:
--------------------------------------------------------------------------------
1 | import { FileDropper } from '/@/components/FileDropper/FileDropper'
2 | import { FileImporter } from './Importer'
3 | import { App } from '/@/App'
4 | import { AnyFileHandle } from '../FileSystem/Types'
5 | import { importFromMcpack } from '../Projects/Import/fromMcpack'
6 |
7 | export class MCPackImporter extends FileImporter {
8 | constructor(fileDropper: FileDropper) {
9 | super(['.mcpack'], fileDropper)
10 | }
11 |
12 | async onImport(fileHandle: AnyFileHandle) {
13 | const app = await App.getApp()
14 |
15 | app.windows.loadingWindow.open()
16 |
17 | try {
18 | await importFromMcpack(fileHandle)
19 | } catch (err) {
20 | console.error(err)
21 | } finally {
22 | app.windows.loadingWindow.close()
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/ImportFile/Manager.ts:
--------------------------------------------------------------------------------
1 | import { MCAddonImporter } from './MCAddon'
2 | import { BasicFileImporter } from './BasicFile'
3 | import type { FileDropper } from '/@/components/FileDropper/FileDropper'
4 | import { BrprojectImporter } from './Brproject'
5 | import { BBModelImporter } from '/@/components/ImportFile/BBModel'
6 | import { ZipImporter } from './ZipImporter'
7 | import { MCPackImporter } from './MCPack'
8 |
9 | export class FileImportManager {
10 | constructor(fileDropper: FileDropper) {
11 | new MCAddonImporter(fileDropper)
12 | new BasicFileImporter(fileDropper)
13 | new BrprojectImporter(fileDropper)
14 | new BBModelImporter(fileDropper)
15 | new ZipImporter(fileDropper)
16 | new MCPackImporter(fileDropper)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/InfoPanel/InfoPanel.ts:
--------------------------------------------------------------------------------
1 | export interface IPanelOptions {
2 | type: 'info' | 'warning' | 'error' | 'success'
3 | text: string
4 | isDismissible?: boolean
5 | }
6 |
7 | export class InfoPanel {
8 | protected type: 'info' | 'warning' | 'error' | 'success'
9 | protected text: string
10 | protected isDismissible: boolean
11 | protected isVisible: boolean = true
12 |
13 | constructor({ type, text, isDismissible }: IPanelOptions) {
14 | this.type = type
15 | this.text = text
16 | this.isDismissible = isDismissible ?? false
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/InfoPanel/InfoPanel.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 | {{ t(infoPanel.text) }}
10 |
11 |
12 |
13 |
24 |
--------------------------------------------------------------------------------
/src/components/JSONSchema/Schema/AllOf.ts:
--------------------------------------------------------------------------------
1 | import { ParentSchema } from './Parent'
2 | import { RootSchema } from './Root'
3 | import { Schema } from './Schema'
4 |
5 | export class AllOfSchema extends ParentSchema {
6 | protected children: Schema[]
7 |
8 | constructor(location: string, key: string, value: unknown) {
9 | super(location, key, value)
10 |
11 | if (!Array.isArray(value))
12 | throw new Error(
13 | `"allOf" schema must be of type array, found "${typeof value}"`
14 | )
15 |
16 | this.children = value.map(
17 | (val) => new RootSchema(this.location, 'allOf', val)
18 | )
19 | }
20 |
21 | validate(obj: unknown) {
22 | const allDiagnostics = this.children
23 | .map((child) => child.validate(obj))
24 | .flat()
25 |
26 | if (allDiagnostics.length === 0) return []
27 | return allDiagnostics
28 | }
29 | isValid(obj: unknown) {
30 | return this.children.every((child) => child.isValid(obj))
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/JSONSchema/Schema/Const.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from './Schema'
2 |
3 | export class ConstSchema extends Schema {
4 | public readonly types = []
5 | getSchemasFor() {
6 | return []
7 | }
8 |
9 | getCompletionItems() {
10 | return [
11 | { type: 'value', label: `${this.value}`, value: this.value },
12 | ]
13 | }
14 |
15 | validate(val: unknown) {
16 | if (this.value !== val)
17 | return [
18 | {
19 | severity: 'warning',
20 | message: `Found "${val}" here; expected "${this.value}"`,
21 | },
22 | ]
23 | return []
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/JSONSchema/Schema/Default.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from './Schema'
2 |
3 | export class DefaultSchema extends Schema {
4 | public readonly types = []
5 | getSchemasFor() {
6 | return []
7 | }
8 |
9 | getCompletionItems() {
10 | // TODO - Support object and array values
11 | if (typeof this.value !== 'object' && !Array.isArray(this.value))
12 | return [
13 | {
14 | type: 'value',
15 | label: `${this.value}`,
16 | value: this.value,
17 | },
18 | ]
19 | else return []
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/JSONSchema/Schema/DeprecationMessage.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from './Schema'
2 |
3 | export class DeprecationMessageSchema extends Schema {
4 | public readonly types = []
5 |
6 | constructor(location: string, key: string, value: unknown) {
7 | if (typeof value !== 'string') {
8 | throw new Error(
9 | `[${location}] Type of "deprecationMessage" must be string, found ${typeof value}`
10 | )
11 | }
12 |
13 | super(location, key, value)
14 | }
15 |
16 | getSchemasFor() {
17 | return []
18 | }
19 |
20 | getCompletionItems() {
21 | return []
22 | }
23 |
24 | validate() {
25 | return [
26 | {
27 | message: this.value,
28 | severity: 'warning',
29 | },
30 | ]
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/JSONSchema/Schema/DoNotSuggest.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from './Schema'
2 |
3 | export class DoNotSuggestSchema extends Schema {
4 | public readonly types = []
5 |
6 | constructor(location: string, key: string, value: unknown) {
7 | if (typeof value !== 'boolean') {
8 | throw new Error(
9 | `[${location}] Type of "doNotSuggest" must be boolean, found ${typeof value}`
10 | )
11 | }
12 |
13 | super(location, key, value)
14 | }
15 |
16 | getSchemasFor() {
17 | return []
18 | }
19 |
20 | getCompletionItems() {
21 | return []
22 | }
23 |
24 | validate() {
25 | return []
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/JSONSchema/Schema/IfSchema.ts:
--------------------------------------------------------------------------------
1 | import { RootSchema } from './Root'
2 | import { Schema } from './Schema'
3 |
4 | export class IfSchema extends Schema {
5 | public readonly schemaType = 'ifSchema'
6 | protected rootSchema?: RootSchema
7 | public readonly types = []
8 |
9 | constructor(location: string, key: string, value: unknown) {
10 | super(location, key, value)
11 |
12 | if (typeof value !== 'boolean')
13 | this.rootSchema = new RootSchema(this.location, 'if', value)
14 | }
15 |
16 | getSchemasFor() {
17 | return []
18 | }
19 |
20 | getCompletionItems() {
21 | return []
22 | }
23 |
24 | validate() {
25 | return []
26 | }
27 |
28 | isTrue(obj: unknown) {
29 | if (typeof this.value === 'boolean') return this.value
30 |
31 | return this.rootSchema?.isValid(obj)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/Languages/Common/ColorCodes.ts:
--------------------------------------------------------------------------------
1 | export const colorCodes = [
2 | [/§4[^§]*/, 'colorCode.darkRed'],
3 | [/§c[^§]*/, 'colorCode.red'],
4 | [/§6[^§]*/, 'colorCode.gold'],
5 | [/§e[^§]*/, 'colorCode.yellow'],
6 | [/§2[^§]*/, 'colorCode.darkGreen'],
7 | [/§a[^§]*/, 'colorCode.green'],
8 | [/§b[^§]*/, 'colorCode.aqua'],
9 | [/§3[^§]*/, 'colorCode.darkAqua'],
10 | [/§1[^§]*/, 'colorCode.darkBlue'],
11 | [/§9[^§]*/, 'colorCode.blue'],
12 | [/§d[^§]*/, 'colorCode.lightPurple'],
13 | [/§5[^§]*/, 'colorCode.darkPurple'],
14 | [/§f[^§]*/, 'colorCode.white'],
15 | [/§7[^§]*/, 'colorCode.gray'],
16 | [/§8[^§]*/, 'colorCode.darkGray'],
17 | [/§0[^§]*/, 'colorCode.black'],
18 | [/§g[^§]*/, 'colorCode.minecoinGold'],
19 | [/§o[^§]*/, 'colorCode.italic'],
20 | [/§l[^§]*/, 'colorCode.bold'],
21 | [/§n[^§]*/, 'colorCode.underline'],
22 | ]
23 |
--------------------------------------------------------------------------------
/src/components/Languages/Json/ColorPicker/Data.ts:
--------------------------------------------------------------------------------
1 | import { markRaw } from 'vue'
2 | import { App } from '/@/App'
3 | import { Signal } from '/@/components/Common/Event/Signal'
4 |
5 | export class ColorData extends Signal {
6 | protected _data?: any
7 |
8 | async loadColorData() {
9 | const app = await App.getApp()
10 |
11 | this._data = markRaw(
12 | await app.dataLoader.readJSON(
13 | `data/packages/minecraftBedrock/location/validColor.json`
14 | )
15 | )
16 |
17 | this.dispatch()
18 | }
19 |
20 | async getDataForCurrentTab() {
21 | await this.fired
22 |
23 | const app = await App.getApp()
24 |
25 | const currentTab = app.project.tabSystem?.selectedTab
26 | if (!currentTab) return {}
27 |
28 | // Get the file definition id of the currently opened tab
29 | const id = App.fileType.getId(currentTab.getPath())
30 |
31 | // Get the color locations for this file type
32 | return this._data[id]
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/Languages/Json/ColorPicker/parse/rgb.ts:
--------------------------------------------------------------------------------
1 | import { Color } from '../Color'
2 |
3 | export function parseRgb(rgbArr: number[]) {
4 | return new Color({
5 | red: rgbArr[0] / 255,
6 | green: rgbArr[1] / 255,
7 | blue: rgbArr[2] / 255,
8 | alpha: 1,
9 | })
10 | }
11 |
12 | export function parseRgba(rgbArr: number[]) {
13 | return new Color({
14 | red: rgbArr[0] / 255,
15 | green: rgbArr[1] / 255,
16 | blue: rgbArr[2] / 255,
17 | alpha: rgbArr[3] / 255,
18 | })
19 | }
20 |
21 | export function parseRgbDec(rgbArr: number[]) {
22 | return new Color({
23 | red: rgbArr[0],
24 | green: rgbArr[1],
25 | blue: rgbArr[2],
26 | alpha: 1,
27 | })
28 | }
29 |
30 | export function parseRgbaDec(rgbArr: number[]) {
31 | return new Color({
32 | red: rgbArr[0],
33 | green: rgbArr[1],
34 | blue: rgbArr[2],
35 | alpha: rgbArr[3],
36 | })
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/Languages/Json/supportsLookbehind.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * A function that tests for RegExp lookahead support.
3 | */
4 | export function supportsLookbehind(): boolean {
5 | try {
6 | const regExp = new RegExp('(?([
8 | new MoLangLanguage(),
9 | new LangLanguage(),
10 | new McfunctionLanguage(),
11 | ])
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/Languages/Mcfunction/ResolvedCommandArguments.ts:
--------------------------------------------------------------------------------
1 | import { ICommandArgument } from './Data'
2 |
3 | export class ResolvedCommandArguments {
4 | constructor(
5 | protected args: ICommandArgument[] = [],
6 | public readonly lastParsedIndex: number = 0,
7 | public readonly isValidResult: boolean = true
8 | ) {}
9 |
10 | static from(other: ResolvedCommandArguments[]) {
11 | const biggestLastParsed =
12 | other
13 | .filter((a) => a.isValidResult)
14 | .sort((a, b) => b.lastParsedIndex - a.lastParsedIndex)[0]
15 | ?.lastParsedIndex ?? 0
16 |
17 | return new ResolvedCommandArguments(
18 | other.map((x) => x.args).flat(),
19 | biggestLastParsed
20 | )
21 | }
22 |
23 | get arguments() {
24 | return this.args
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/Languages/Mcfunction/inSelector.ts:
--------------------------------------------------------------------------------
1 | export function inSelector(command: string) {
2 | return command.indexOf('[') > command.indexOf(']')
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/Languages/Mcfunction/strMatch.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * A function that returns whether string a matches string b fully or partially or not at all.
3 | */
4 | export function strMatch(a: string, b: string) {
5 | if (a === b) return 'full'
6 | else if (a.includes(b)) return 'partial'
7 | else return 'none'
8 | }
9 |
10 | /**
11 | * A function that returns whether string a matches one string from array b fully, partially or not at all.
12 | */
13 | export function strMatchArray(a: string, b: string[]) {
14 | if (b.includes(a)) return 'full'
15 | else if (b.some((x) => x.includes(a))) return 'partial'
16 | else return 'none'
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/Mixins/DevMode.ts:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | import { settingsState } from '/@/components/Windows/Settings/SettingsState'
3 |
4 | export const DevModeMixin = {
5 | computed: {
6 | isDevMode() {
7 | return settingsState?.developers?.isDevMode ?? false
8 | },
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/Mixins/EnablePackSpider.ts:
--------------------------------------------------------------------------------
1 | import { settingsState } from '../Windows/Settings/SettingsState'
2 |
3 | export const EnablePackSpiderMixin = {
4 | data: () => ({
5 | settingsState,
6 | }),
7 | computed: {
8 | enablePackSpider() {
9 | return settingsState?.general?.enablePackSpider ?? false
10 | },
11 | },
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/Mixins/Highlighter.ts:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | import { App } from '/@/App'
3 |
4 | const toPropertyName = (name: string) => `${name}Def`
5 | export const HighlighterMixin = (defNames: string[]) => ({
6 | data: () =>
7 | Object.fromEntries(defNames.map((name) => [toPropertyName(name), {}])),
8 | mounted() {
9 | App.instance.themeManager.on(this.onThemeChanged)
10 | this.onThemeChanged()
11 | },
12 | destroyed() {
13 | App.instance.themeManager.off(this.onThemeChanged)
14 | },
15 | methods: {
16 | onThemeChanged() {
17 | defNames.forEach((name) => {
18 | this[
19 | toPropertyName(name)
20 | ] = App.instance.themeManager.getHighlighterInfo(name)
21 | })
22 | },
23 | },
24 | })
25 |
--------------------------------------------------------------------------------
/src/components/Mixins/TranslationMixin.ts:
--------------------------------------------------------------------------------
1 | import { translate } from '../Locales/Manager'
2 |
3 | export const TranslationMixin = {
4 | methods: {
5 | t(translationKey?: string) {
6 | return translate(translationKey)
7 | },
8 | },
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/Mixins/WindowControlsOverlay.ts:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 |
3 | export const WindowControlsOverlayMixin = {
4 | data: () => ({
5 | windowControlsOverlay:
6 | navigator.windowControlsOverlay &&
7 | navigator.windowControlsOverlay.visible,
8 | }),
9 | created() {
10 | if (import.meta.env.VITE_IS_TAURI_APP) {
11 | this.windowControlsOverlay = true
12 | } else if (navigator.windowControlsOverlay) {
13 | navigator.windowControlsOverlay.addEventListener(
14 | 'geometrychange',
15 | (event) => {
16 | this.windowControlsOverlay = event.visible
17 | }
18 | )
19 | }
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/Notifications/PersistentNotification.ts:
--------------------------------------------------------------------------------
1 | import { createStore, get, set } from 'idb-keyval'
2 | import { INotificationConfig, Notification } from './Notification'
3 |
4 | const store = createStore('app-notifications', 'app-notification-store')
5 |
6 | export class PersistentNotification extends Notification {
7 | constructor(config: INotificationConfig) {
8 | super(config)
9 | if (!config.id) throw new Error(`PersistentNotification requires an id`)
10 | }
11 |
12 | async show() {
13 | if (await get(this.id, store)) return
14 |
15 | super.show()
16 | }
17 | dispose() {
18 | super.dispose()
19 |
20 | set(this.id, true, store)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/Notifications/create.ts:
--------------------------------------------------------------------------------
1 | import { NotificationStore } from './state'
2 | import { v4 as uuid } from 'uuid'
3 | import Vue from 'vue'
4 | import { IDisposable } from '/@/types/disposable'
5 | import { INotificationConfig, Notification } from './Notification'
6 |
7 | /**
8 | * Creates a new notification
9 | * @param config
10 | */
11 | export function createNotification(config: INotificationConfig) {
12 | return new Notification(config)
13 | }
14 |
15 | export function clearAllNotifications() {
16 | // @ts-expect-error
17 | if (typeof navigator.clearAppBadge === 'function') navigator.clearAppBadge()
18 |
19 | for (const [key] of Object.entries(NotificationStore)) {
20 | Vue.delete(NotificationStore, key)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/Notifications/state.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Reactive vue store for the Notification API
3 | */
4 |
5 | import { reactive } from 'vue'
6 | import { Notification } from './Notification'
7 |
8 | export const NotificationStore: Record = reactive({})
9 |
--------------------------------------------------------------------------------
/src/components/Notifications/warn.ts:
--------------------------------------------------------------------------------
1 | import { InformationWindow } from '../Windows/Common/Information/InformationWindow'
2 | import { createNotification } from './create'
3 |
4 | export function emitWarning(title: string, message: string) {
5 | const notification = createNotification({
6 | color: 'warning',
7 | icon: 'mdi-alert',
8 | message: title,
9 | onClick: () => {
10 | notification.dispose()
11 |
12 | new InformationWindow({
13 | description: message,
14 | title: title,
15 | })
16 | },
17 | })
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/OutputFolders/ComMojang/Sidebar/ProjectHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
24 |
--------------------------------------------------------------------------------
/src/components/OutputFolders/ComMojang/Sidebar/ViewProject.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
18 |
19 |
31 |
--------------------------------------------------------------------------------
/src/components/PackExplorer/Actions/ToBridgeFolderProject.ts:
--------------------------------------------------------------------------------
1 | import { Project } from '/@/components/Projects/Project/Project'
2 |
3 | export const ToBridgeFolderProjectAction = (project: Project) => ({
4 | name: 'packExplorer.move.toBridgeFolder',
5 | icon: 'mdi-folder-sync-outline',
6 | onTrigger: async () => {
7 | let didSetupBridgeFolder = project.app.bridgeFolderSetup.hasFired
8 | if (!didSetupBridgeFolder)
9 | didSetupBridgeFolder = await project.app.setupBridgeFolder()
10 |
11 | if (didSetupBridgeFolder) project.switchProjectType()
12 | },
13 | })
14 |
--------------------------------------------------------------------------------
/src/components/PackExplorer/Actions/ToLocalProject.ts:
--------------------------------------------------------------------------------
1 | import { Project } from '/@/components/Projects/Project/Project'
2 |
3 | export const ToLocalProjectAction = (project: Project) => ({
4 | name: 'packExplorer.move.toLocal',
5 | icon: 'mdi-lock-open-outline',
6 | onTrigger: async () => {
7 | project.switchProjectType()
8 | },
9 | })
10 |
--------------------------------------------------------------------------------
/src/components/PackExplorer/HomeView/SetupHint.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ t('general.setup') }}
5 | mdi-arrow-right
6 |
7 |
8 |
9 |
10 |
15 |
--------------------------------------------------------------------------------
/src/components/PackExplorer/PackExplorer.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
27 |
--------------------------------------------------------------------------------
/src/components/PackExplorer/ProjectDisplay.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
37 |
--------------------------------------------------------------------------------
/src/components/PackIndexer/Worker/LightningCache/CacheEnv.ts:
--------------------------------------------------------------------------------
1 | import { TPackTypeId } from '/@/components/Data/PackType'
2 | import { findFileExtension } from '/@/components/FileSystem/FindFile'
3 | import { ProjectConfig } from '/@/components/Projects/Project/Config'
4 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
5 |
6 | export function getCacheScriptEnv(
7 | value: string,
8 | ctx: { fileSystem: FileSystem; config: ProjectConfig }
9 | ) {
10 | return {
11 | Bridge: {
12 | value,
13 | withExtension: (basePath: string, extensions: string[]) =>
14 | findFileExtension(ctx.fileSystem, basePath, extensions),
15 | resolvePackPath: (packId: TPackTypeId, filePath: string) =>
16 | ctx.config.resolvePackPath(packId, filePath),
17 | },
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/PackIndexer/Worker/LightningCache/Script.ts:
--------------------------------------------------------------------------------
1 | import { run } from '/@/components/Extensions/Scripts/run'
2 |
3 | export function runScript(script: string) {
4 | const module: any = { exports: undefined }
5 |
6 | run({ script, env: { module } })
7 |
8 | return module.exports
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Files/BP/CreateTick.ts:
--------------------------------------------------------------------------------
1 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
2 | import { ICreateProjectOptions } from '/@/components/Projects/CreateProject/CreateProject'
3 | import { CreateFile } from '../CreateFile'
4 |
5 | export class CreateTick extends CreateFile {
6 | public readonly id = 'tick'
7 |
8 | async create(fs: FileSystem, createOptions: ICreateProjectOptions) {
9 | await fs.writeJSON(`BP/functions/tick.json`, { values: [] }, true)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Files/BP/GameTest.ts:
--------------------------------------------------------------------------------
1 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
2 | import { ICreateProjectOptions } from '/@/components/Projects/CreateProject/CreateProject'
3 | import { CreateFile } from '../CreateFile'
4 |
5 | export class CreateGameTestMain extends CreateFile {
6 | public readonly id = 'gameTestMain'
7 | public isConfigurable = false
8 |
9 | async create(fs: FileSystem, createOptions: ICreateProjectOptions) {
10 | if (createOptions.experimentalGameplay.enableGameTestFramework) {
11 | await fs.mkdir('BP/scripts', { recursive: true })
12 | await fs.writeFile('BP/scripts/main.js', '')
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Files/BP/Player.ts:
--------------------------------------------------------------------------------
1 | import { App } from '/@/App'
2 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
3 | import { CreateFile } from '../CreateFile'
4 |
5 | export class CreatePlayer extends CreateFile {
6 | public readonly id = 'player'
7 |
8 | async create(fs: FileSystem) {
9 | const app = await App.getApp()
10 | await app.dataLoader.fired
11 | const defaultPlayer = await app.dataLoader.readJSON(
12 | 'data/packages/minecraftBedrock/vanilla/player.json'
13 | )
14 |
15 | await fs.writeJSON('BP/entities/player.json', defaultPlayer, true)
16 | await fs.writeFile('BP/loot_tables/empty.json', '{}')
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Files/Bridge/Compiler.ts:
--------------------------------------------------------------------------------
1 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
2 | import { ICreateProjectOptions } from '/@/components/Projects/CreateProject/CreateProject'
3 | import { CreateFile } from '../CreateFile'
4 |
5 | export class CreateCompilerConfig extends CreateFile {
6 | public readonly id = 'compilerConfig'
7 | public isConfigurable = false
8 |
9 | async create(fs: FileSystem, createOptions: ICreateProjectOptions) {
10 | await fs.mkdir('.bridge/compiler')
11 | // Default compiler config moved to project config
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Files/CreateFile.ts:
--------------------------------------------------------------------------------
1 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
2 | import { ICreateProjectOptions } from '../CreateProject'
3 |
4 | export abstract class CreateFile {
5 | public abstract id: string
6 | public icon = 'mdi-file-outline'
7 | public isActive = true
8 | public isConfigurable = true
9 |
10 | abstract create(
11 | fs: FileSystem,
12 | projectOptions: ICreateProjectOptions
13 | ): Promise | any
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Files/DenoConfig.ts:
--------------------------------------------------------------------------------
1 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
2 | import { ICreateProjectOptions } from '/@/components/Projects/CreateProject/CreateProject'
3 | import { CreateFile } from './CreateFile'
4 |
5 | export class CreateDenoConfig extends CreateFile {
6 | public readonly id = 'denoConfig'
7 |
8 | async create(fs: FileSystem, createOptions: ICreateProjectOptions) {
9 | await fs.writeJSON(
10 | `deno.json`,
11 | {
12 | tasks: {
13 | install_dash:
14 | 'deno install -A --reload -f -n dash_compiler https://raw.githubusercontent.com/bridge-core/deno-dash-compiler/main/mod.ts',
15 | watch: 'dash_compiler build --mode development && dash_compiler watch',
16 | build: 'dash_compiler build',
17 | },
18 | },
19 | true
20 | )
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Files/GitIgnore.ts:
--------------------------------------------------------------------------------
1 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
2 | import { ICreateProjectOptions } from '/@/components/Projects/CreateProject/CreateProject'
3 | import { CreateFile } from './CreateFile'
4 |
5 | export class CreateGitIgnore extends CreateFile {
6 | public readonly id = 'gitignore'
7 |
8 | async create(fs: FileSystem, createOptions: ICreateProjectOptions) {
9 | await fs.writeFile(
10 | `.gitignore`,
11 | `Desktop.ini
12 | .DS_Store
13 | !.bridge/
14 | .bridge/*
15 | !.bridge/compiler/
16 | !.bridge/extensions
17 | builds`
18 | )
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Files/RP/BiomesClient.ts:
--------------------------------------------------------------------------------
1 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
2 | import { ICreateProjectOptions } from '/@/components/Projects/CreateProject/CreateProject'
3 | import { CreateFile } from '../CreateFile'
4 |
5 | export class CreateBiomesClient extends CreateFile {
6 | public readonly id = 'biomesClient'
7 |
8 | async create(fs: FileSystem, createOptions: ICreateProjectOptions) {
9 | await fs.writeJSON(
10 | `RP/biomes_client.json`,
11 | {
12 | biomes: {},
13 | },
14 | true
15 | )
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Files/RP/Blocks.ts:
--------------------------------------------------------------------------------
1 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
2 | import { ICreateProjectOptions } from '/@/components/Projects/CreateProject/CreateProject'
3 | import { CreateFile } from '../CreateFile'
4 |
5 | export class CreateBlocks extends CreateFile {
6 | public readonly id = 'blocks'
7 |
8 | async create(fs: FileSystem, createOptions: ICreateProjectOptions) {
9 | await fs.writeJSON(
10 | `RP/blocks.json`,
11 | {
12 | format_version: [1, 1, 0],
13 | },
14 | true
15 | )
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Files/RP/FlipbookTextures.ts:
--------------------------------------------------------------------------------
1 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
2 | import { ICreateProjectOptions } from '/@/components/Projects/CreateProject/CreateProject'
3 | import { CreateFile } from '../CreateFile'
4 |
5 | export class CreateFlipbookTextures extends CreateFile {
6 | public readonly id = 'flipbookTextures'
7 |
8 | async create(fs: FileSystem, createOptions: ICreateProjectOptions) {
9 | await fs.mkdir('RP/textures', { recursive: true })
10 |
11 | await fs.writeJSON(`RP/textures/flipbook_textures.json`, [], true)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Files/RP/ItemTexture.ts:
--------------------------------------------------------------------------------
1 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
2 | import { ICreateProjectOptions } from '/@/components/Projects/CreateProject/CreateProject'
3 | import { CreateFile } from '../CreateFile'
4 |
5 | export class CreateItemTexture extends CreateFile {
6 | public readonly id = 'itemTexture'
7 |
8 | async create(fs: FileSystem, createOptions: ICreateProjectOptions) {
9 | await fs.mkdir('RP/textures', { recursive: true })
10 |
11 | await fs.writeJSON(
12 | `RP/textures/item_texture.json`,
13 | {
14 | resource_pack_name: createOptions.name,
15 | texture_name: 'atlas.items',
16 | texture_data: {},
17 | },
18 | true
19 | )
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Files/RP/SoundDefinitions.ts:
--------------------------------------------------------------------------------
1 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
2 | import { ICreateProjectOptions } from '/@/components/Projects/CreateProject/CreateProject'
3 | import { CreateFile } from '../CreateFile'
4 |
5 | export class CreateSoundDefintions extends CreateFile {
6 | public readonly id = 'soundDefinitions'
7 |
8 | async create(fs: FileSystem, createOptions: ICreateProjectOptions) {
9 | await fs.mkdir('RP/sounds', { recursive: true })
10 | await fs.writeJSON(
11 | `RP/sounds/sound_definitions.json`,
12 | {
13 | format_version: '1.14.0',
14 | sound_definitions: {},
15 | },
16 | true
17 | )
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Files/RP/Sounds.ts:
--------------------------------------------------------------------------------
1 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
2 | import { ICreateProjectOptions } from '/@/components/Projects/CreateProject/CreateProject'
3 | import { CreateFile } from '../CreateFile'
4 |
5 | export class CreateSounds extends CreateFile {
6 | public readonly id = 'sounds'
7 |
8 | async create(fs: FileSystem, createOptions: ICreateProjectOptions) {
9 | await fs.writeJSON(`RP/sounds.json`, {}, true)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Files/RP/Splashes.ts:
--------------------------------------------------------------------------------
1 | import { ICreateProjectOptions } from "../../CreateProject";
2 | import { CreateFile } from "../CreateFile";
3 | import { FileSystem } from "/@/components/FileSystem/FileSystem";
4 |
5 |
6 |
7 | export class CreateSplashes extends CreateFile {
8 | public readonly id = 'splashes'
9 |
10 | async create(fs: FileSystem, projectOptions: ICreateProjectOptions) {
11 | await fs.writeJSON(
12 | `RP/splashes.json`,
13 | {
14 | canMerge: false,
15 | splashes: []
16 | },
17 | true
18 | )
19 | }
20 | }
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Files/RP/TerrainTexture.ts:
--------------------------------------------------------------------------------
1 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
2 | import { ICreateProjectOptions } from '/@/components/Projects/CreateProject/CreateProject'
3 | import { CreateFile } from '../CreateFile'
4 |
5 | export class CreateTerrainTexture extends CreateFile {
6 | public readonly id = 'terrainTexture'
7 |
8 | async create(fs: FileSystem, createOptions: ICreateProjectOptions) {
9 | await fs.mkdir('RP/textures', { recursive: true })
10 |
11 | await fs.writeJSON(
12 | `RP/textures/terrain_texture.json`,
13 | {
14 | num_mip_levels: 4,
15 | padding: 8,
16 | resource_pack_name: createOptions.name,
17 | texture_name: 'atlas.terrain',
18 | texture_data: {},
19 | },
20 | true
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Files/SP/Skins.ts:
--------------------------------------------------------------------------------
1 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
2 | import { ICreateProjectOptions } from '/@/components/Projects/CreateProject/CreateProject'
3 | import { CreateFile } from '../CreateFile'
4 |
5 | export class CreateSkins extends CreateFile {
6 | public readonly id = 'skins'
7 |
8 | create(fs: FileSystem, createOptions: ICreateProjectOptions) {
9 | return fs.writeJSON(
10 | `SP/skins.json`,
11 | {
12 | geometry: 'skinpacks/skins.json',
13 | skins: [],
14 | serialize_name: createOptions.namespace,
15 | localization_name: createOptions.namespace,
16 | },
17 | true
18 | )
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Packs/BP.ts:
--------------------------------------------------------------------------------
1 | import { CreatePack } from './Pack'
2 | import { CreateManifest } from '../Files/Manifest'
3 | import { CreateLang } from '../Files/Lang'
4 | import { CreatePackIcon } from '../Files/PackIcon'
5 | import { CreateTick } from '../Files/BP/CreateTick'
6 | import { CreateGameTestMain } from '../Files/BP/GameTest'
7 | import { CreatePlayer } from '../Files/BP/Player'
8 |
9 | export class CreateBP extends CreatePack {
10 | protected readonly packPath = 'BP'
11 | public createFiles = [
12 | new CreateManifest(this.packPath),
13 | new CreateLang(this.packPath),
14 | new CreatePackIcon(this.packPath),
15 | new CreateTick(),
16 | new CreateGameTestMain(),
17 | new CreatePlayer(),
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Packs/Bridge.ts:
--------------------------------------------------------------------------------
1 | import { CreateCompilerConfig } from '../Files/Bridge/Compiler'
2 | import { CreatePack } from './Pack'
3 |
4 | export class CreateBridge extends CreatePack {
5 | protected readonly packPath = '.bridge'
6 | public createFiles = [new CreateCompilerConfig()]
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Packs/Pack.ts:
--------------------------------------------------------------------------------
1 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
2 | import { ICreateProjectOptions } from '../CreateProject'
3 | import { CreateFile } from '../Files/CreateFile'
4 |
5 | export type TPackType = 'BP' | 'RP' | 'SP' | 'WT' | '.bridge' | 'worlds'
6 |
7 | export abstract class CreatePack {
8 | protected abstract packPath: TPackType
9 | public abstract createFiles: CreateFile[]
10 |
11 | async create(fs: FileSystem, createOptions: ICreateProjectOptions) {
12 | await fs.mkdir(this.packPath)
13 | for (const createFile of this.createFiles) {
14 | if (!createFile.isActive) continue
15 |
16 | await createFile.create(fs, createOptions)
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Packs/SP.ts:
--------------------------------------------------------------------------------
1 | import { CreatePack } from './Pack'
2 | import { CreateManifest } from '../Files/Manifest'
3 | import { CreateLang } from '../Files/SP/Lang'
4 | import { CreatePackIcon } from '../Files/PackIcon'
5 | import { CreateSkins } from '../Files/SP/Skins'
6 |
7 | export class CreateSP extends CreatePack {
8 | protected readonly packPath = 'SP'
9 | public createFiles = [
10 | new CreateManifest(this.packPath),
11 | new CreateLang(this.packPath),
12 | new CreatePackIcon(this.packPath),
13 | new CreateSkins(),
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Packs/WT.ts:
--------------------------------------------------------------------------------
1 | import { CreateManifest } from '../Files/Manifest'
2 | import { CreatePack } from './Pack'
3 |
4 | export class CreateWT extends CreatePack {
5 | protected readonly packPath = 'WT'
6 | public createFiles = [new CreateManifest(this.packPath)]
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/Projects/CreateProject/Packs/worlds.ts:
--------------------------------------------------------------------------------
1 | import { CreatePack } from './Pack'
2 |
3 | export class CreateWorlds extends CreatePack {
4 | protected readonly packPath = 'worlds'
5 | public createFiles = []
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/Projects/Export/Extensions/Exporter.ts:
--------------------------------------------------------------------------------
1 | export interface IExporter {
2 | icon: string
3 | name: string
4 | isDisabled?: () => Promise | boolean
5 | export: () => Promise
6 | }
7 |
8 | export class Exporter {
9 | constructor(protected config: IExporter) {}
10 |
11 | get displayData() {
12 | return {
13 | icon: this.config.icon,
14 | name: this.config.name,
15 | }
16 | }
17 |
18 | isDisabled() {
19 | if (typeof this.config.isDisabled === 'function')
20 | return this.config.isDisabled()
21 | }
22 |
23 | export() {
24 | if (typeof this.config.export === 'function')
25 | return this.config.export()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/Projects/Export/Extensions/Provider.ts:
--------------------------------------------------------------------------------
1 | import { Exporter } from './Exporter'
2 | import { IActionConfig } from '/@/components/Actions/SimpleAction'
3 |
4 | /**
5 | * A class that stores exporters registered by extensions
6 | */
7 | export class ExportProvider {
8 | protected exports = new Set()
9 |
10 | /**
11 | * Register an exporter
12 | *
13 | * @param exporter An exporter provided by an extension
14 | * @returns a disposable that unregisters the exporter
15 | */
16 | public register(exporter: Exporter) {
17 | this.exports.add(exporter)
18 |
19 | return {
20 | dispose: () => this.exports.delete(exporter),
21 | }
22 | }
23 |
24 | public async getExporters(): Promise {
25 | return await Promise.all(
26 | [...this.exports].map(async (exporter) => ({
27 | ...exporter.displayData,
28 | isDisabled: await exporter.isDisabled(),
29 | onTrigger: () => exporter.export(),
30 | }))
31 | )
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/Projects/Project/FileChangeRegistry.ts:
--------------------------------------------------------------------------------
1 | import { VirtualFile } from '../../FileSystem/Virtual/File'
2 | import { EventSystem } from '/@/components/Common/Event/EventSystem'
3 |
4 | export class FileChangeRegistry extends EventSystem {
5 | constructor() {
6 | super([], true)
7 | }
8 | dispatch(name: string, fileContent: T) {
9 | // We always want to dispatch the event for "any" file changed
10 | this.any.dispatch([name, fileContent])
11 |
12 | if (this.hasEvent(name)) {
13 | // Specific events only get triggered when a listener is registered already
14 | super.dispatch(name, fileContent)
15 | }
16 | }
17 | on(name: string, listener: (data: T) => void) {
18 | if (!this.hasEvent(name)) this.create(name)
19 |
20 | return super.on(name, listener)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/Projects/Project/loadIcon.ts:
--------------------------------------------------------------------------------
1 | import type { Project } from './Project'
2 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
3 | import { loadAsDataURL } from '/@/utils/loadAsDataUrl'
4 | import { join } from '/@/utils/path'
5 |
6 | export async function loadIcon(project: Project, fileSystem: FileSystem) {
7 | const config = project.config
8 |
9 | const packPaths = Object.values(config.getAvailablePacks())
10 |
11 | if (packPaths.length === 0) return
12 |
13 | return await loadAsDataURL(
14 | join(packPaths[0], 'pack_icon.png'),
15 | fileSystem
16 | ).catch(() => undefined)
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/Projects/RecentFiles.ts:
--------------------------------------------------------------------------------
1 | import { App } from '/@/App'
2 | import { PersistentQueue } from '../Common/PersistentQueue'
3 |
4 | export interface IRecentFile {
5 | icon: string
6 | color?: string
7 | name: string
8 | path: string
9 | }
10 |
11 | export class RecentFiles extends PersistentQueue {
12 | constructor(app: App, savePath: string) {
13 | super(app, 5, savePath)
14 | }
15 |
16 | isEquals(file1: IRecentFile, file2: IRecentFile) {
17 | return file1.path === file2.path
18 | }
19 |
20 | removeFile(filePath: string) {
21 | return this.remove({
22 | icon: '',
23 | name: '',
24 | path: filePath,
25 | })
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/Projects/RecentProjects.ts:
--------------------------------------------------------------------------------
1 | import { App } from '/@/App'
2 | import { PersistentQueue } from '../Common/PersistentQueue'
3 | import { IProjectData } from './Project/Project'
4 |
5 | export class RecentProjects extends PersistentQueue> {
6 | constructor(app: App, savePath: string) {
7 | super(app, 5, savePath)
8 | }
9 |
10 | protected isEquals(
11 | file1: Partial,
12 | file2: Partial
13 | ) {
14 | return file1.path === file2.path
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/Projects/Title.ts:
--------------------------------------------------------------------------------
1 | import { ref } from 'vue'
2 | import { virtualProjectName } from './Project/Project'
3 | import { isNightly } from '/@/utils/app/isNightly'
4 |
5 | const appName = isNightly ? 'bridge. Nightly' : 'bridge. v2'
6 | export class Title {
7 | protected titleTag: HTMLTitleElement
8 | public current = ref('')
9 | public appName = appName
10 |
11 | constructor() {
12 | this.titleTag = document.head.getElementsByTagName('title')[0]
13 | }
14 |
15 | setProject(projectName: string) {
16 | if (projectName === virtualProjectName) {
17 | this.titleTag.innerText = ''
18 | this.current.value = ''
19 | } else {
20 | this.titleTag.innerText = projectName
21 | this.current.value = projectName
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/Sidebar/Content/SelectableSidebarAction.ts:
--------------------------------------------------------------------------------
1 | import { SidebarAction, ISidebarAction } from './SidebarAction'
2 | import type { SidebarContent } from './SidebarContent'
3 |
4 | export class SelectableSidebarAction extends SidebarAction {
5 | protected _isSelected = false
6 |
7 | constructor(protected parent: SidebarContent, config: ISidebarAction) {
8 | super(config)
9 | if (!this.parent.selectedAction) this.select()
10 | }
11 | get isSelected() {
12 | return this._isSelected
13 | }
14 |
15 | select() {
16 | this.parent.unselectAllActions()
17 | this._isSelected = true
18 | this.parent.selectedAction = this
19 | }
20 | unselect() {
21 | if (this._isSelected) this.parent.selectedAction = undefined
22 | this._isSelected = false
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/Sidebar/Content/SidebarAction.ts:
--------------------------------------------------------------------------------
1 | export interface ISidebarAction {
2 | id?: string
3 | icon: string
4 | name: string
5 | color?: string
6 |
7 | onTrigger?: (event: MouseEvent) => void
8 | }
9 | export class SidebarAction {
10 | constructor(protected config: ISidebarAction) {}
11 |
12 | getConfig() {
13 | return this.config
14 | }
15 |
16 | trigger(event: MouseEvent) {
17 | this.config.onTrigger?.(event)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Solid/Directives/Ripple/Ripple.css:
--------------------------------------------------------------------------------
1 | .solid-ripple-container {
2 | isolation: isolate;
3 | position: relative;
4 | overflow: hidden;
5 | }
6 |
7 | .solid-ripple {
8 | position: absolute;
9 | background: #fff;
10 |
11 | transform: translate(50%, 50%) scale(0);
12 |
13 | pointer-events: none;
14 | opacity: 0.6;
15 | }
16 | .solid-ripple-active {
17 | animation: solid-ripple-scale-up 0.4s ease-in-out;
18 | animation-fill-mode: forwards;
19 | }
20 | .solid-ripple-fade-out {
21 | transform: translate(50%, 50%) scale(1);
22 | opacity: 0.19;
23 | animation: solid-ripple-fade-out 0.4s ease-in-out;
24 | }
25 |
26 | .theme--light .solid-ripple {
27 | background: #000;
28 | }
29 |
30 | @keyframes solid-ripple-scale-up {
31 | to {
32 | transform: translate(50%, 50%) scale(1);
33 | opacity: 0.2;
34 | }
35 | }
36 |
37 | @keyframes solid-ripple-fade-out {
38 | to {
39 | opacity: 0;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/Solid/DirectoryViewer/Common/Name.css:
--------------------------------------------------------------------------------
1 | .directory-viewer-name {
2 | cursor: pointer;
3 | transition: background-color 0.2s ease-in-out;
4 |
5 | /** New web thingy: https://web.dev/content-visibility/ */
6 | content-visibility: auto;
7 | contain-intrinsic-size: 24px;
8 | }
9 | .directory-viewer-name.selected {
10 | background: var(--v-background-base);
11 | outline: none;
12 | }
13 | .directory-viewer-name:focus {
14 | outline: none;
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/Solid/Icon/IconText.tsx:
--------------------------------------------------------------------------------
1 | import { Component } from 'solid-js'
2 | import { useRipple } from '../Directives/Ripple/Ripple'
3 | import { SolidIcon } from './SolidIcon'
4 |
5 | interface IconTextProps {
6 | icon: string
7 | text: string
8 | opacity?: number
9 | color?: string
10 | }
11 |
12 | /**
13 | * A component that displays text with an icon next to it
14 | */
15 | export const IconText: Component = (props) => {
16 | const ripple = useRipple()
17 | const styles = {
18 | 'white-space': 'nowrap',
19 | overflow: 'hidden',
20 | 'text-overflow': 'ellipsis',
21 | width: '100%',
22 | } as const
23 |
24 | return (
25 |
26 |
31 | {props.text}
32 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/Solid/Logo.tsx:
--------------------------------------------------------------------------------
1 | import { Component } from 'solid-js'
2 | import { isNightly } from '/@/utils/app/isNightly'
3 |
4 | export const SolidBridgeLogo: Component<{
5 | class: string
6 | }> = (props) => {
7 | const logoPath = isNightly
8 | ? `/img/icons/nightly/favicon.svg`
9 | : `/img/icons/favicon.svg`
10 |
11 | return (
12 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Solid/SolidRef.ts:
--------------------------------------------------------------------------------
1 | import { createSignal } from 'solid-js'
2 |
3 | export function createRef(val: T) {
4 | const [ref, setRef] = createSignal(val)
5 |
6 | return {
7 | get value() {
8 | return ref()
9 | },
10 | set value(val: T) {
11 | setRef(() => val)
12 | },
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/Solid/SolidSpacer.tsx:
--------------------------------------------------------------------------------
1 | import { Component } from 'solid-js'
2 |
3 | export const SolidSpacer: Component = () => {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/Solid/toSignal.ts:
--------------------------------------------------------------------------------
1 | import { Ref, watch, watchEffect } from 'vue'
2 | import { createSignal, onCleanup } from 'solid-js'
3 |
4 | export function toSignal(ref: Ref) {
5 | const [signal, setSignal] = createSignal(ref.value, { equals: false })
6 |
7 | const dispose = watchEffect(() => {
8 | setSignal(() => ref.value)
9 | })
10 |
11 | onCleanup(() => {
12 | dispose()
13 | })
14 |
15 | return [
16 | signal,
17 | (val: T) => {
18 | ref.value = val
19 | return val
20 | },
21 | ] as const
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/StartParams/Action/openFileUrl.ts:
--------------------------------------------------------------------------------
1 | import { basename } from '/@/utils/path'
2 | import type { IStartAction } from '../Manager'
3 | import { VirtualFileHandle } from '/@/components/FileSystem/Virtual/FileHandle'
4 | import { App } from '/@/App'
5 |
6 | export const openFileUrl: IStartAction = {
7 | type: 'raw',
8 | name: 'openFileUrl',
9 |
10 | onTrigger: async (value: string) => {
11 | const resp = await fetch(value).catch(() => null)
12 | if (!resp) return
13 |
14 | const file = new VirtualFileHandle(
15 | null,
16 | basename(value),
17 | new Uint8Array(await resp.arrayBuffer())
18 | )
19 |
20 | const app = await App.getApp()
21 |
22 | if (app.isNoProjectSelected) return
23 |
24 | await app.projectManager.projectReady.fired
25 |
26 | await app.fileDropper.importFile(file)
27 | },
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/StartParams/Action/sidebarState.ts:
--------------------------------------------------------------------------------
1 | import type { IStartAction } from '../Manager'
2 | import { App } from '/@/App'
3 |
4 | export const setSidebarState: IStartAction = {
5 | type: 'raw',
6 | name: 'setSidebarState',
7 | onTrigger: async (value: string) => {
8 | if (value === 'hidden') {
9 | App.sidebar.toggleSidebarContent(null)
10 | App.sidebar.forcedInitialState.value = true
11 | }
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/StartParams/Action/viewExtension.ts:
--------------------------------------------------------------------------------
1 | import type { IStartAction } from '../Manager'
2 | import { App } from '/@/App'
3 |
4 | export const viewExtension: IStartAction = {
5 | type: 'encoded',
6 | name: 'viewExtension',
7 | onTrigger: async (value: string) => {
8 | const app = await App.getApp()
9 | app.windows.extensionStore.open(value)
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/TabSystem/OpenedFiles.ts:
--------------------------------------------------------------------------------
1 | import { settingsState } from '/@/components/Windows/Settings/SettingsState'
2 | import { App } from '/@/App'
3 | import { PersistentQueue } from '/@/components/Common/PersistentQueue'
4 | import { TabSystem } from '/@/components/TabSystem/TabSystem'
5 |
6 | export class OpenedFiles extends PersistentQueue {
7 | constructor(protected tabSystem: TabSystem, app: App, savePath: string) {
8 | super(app, Infinity, savePath)
9 | }
10 |
11 | async restoreTabs() {
12 | if (settingsState?.general?.restoreTabs ?? true) {
13 | await this.fired
14 |
15 | for (const file of this.queue.elements.reverse()) {
16 | try {
17 | // Try to restore tab
18 | await this.tabSystem.openPath(file, {
19 | selectTab: file == this.queue.elements[0],
20 | isTemporary: false,
21 | })
22 | } catch {}
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/TabSystem/PreviewTab.ts:
--------------------------------------------------------------------------------
1 | import { translate } from '../Locales/Manager'
2 | import { Tab } from './CommonTab'
3 | import { FileTab } from './FileTab'
4 | import { TabSystem } from './TabSystem'
5 |
6 | export abstract class PreviewTab extends Tab {
7 | public readonly isForeignFile = true
8 | static is() {
9 | return false
10 | }
11 |
12 | constructor(protected tab: FileTab, parent: TabSystem) {
13 | super(parent)
14 |
15 | this.onCreate()
16 | }
17 | onCreate() {}
18 | async onActivate() {
19 | this.onChange()
20 | this.isActive = true
21 | }
22 |
23 | get name() {
24 | return `${translate('preview.name')}: ${this.tab.name}`
25 | }
26 |
27 | abstract onChange(): Promise | void
28 |
29 | save() {}
30 | getFile() {
31 | return this.tab.getFile()
32 | }
33 | abstract reload(): Promise | void
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/TabSystem/TabActions/ActionBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
22 |
23 |
31 |
--------------------------------------------------------------------------------
/src/components/TabSystem/TabProvider.ts:
--------------------------------------------------------------------------------
1 | import { ImageTab } from '../Editors/Image/ImageTab'
2 | import { TargaTab } from '../Editors/Image/TargaTab'
3 | import { SoundTab } from '../Editors/Sound/SoundTab'
4 | import { TextTab } from '../Editors/Text/TextTab'
5 | import { TreeTab } from '../Editors/TreeEditor/Tab'
6 | import { FileTab } from './FileTab'
7 |
8 | export class TabProvider {
9 | protected static _tabs = new Set([
10 | TextTab,
11 | TreeTab,
12 | ImageTab,
13 | TargaTab,
14 | SoundTab,
15 | ])
16 | static get tabs() {
17 | return [...this._tabs].reverse()
18 | }
19 |
20 | static register(tab: typeof FileTab) {
21 | this._tabs.add(tab)
22 |
23 | return {
24 | dispose: () => this._tabs.delete(tab),
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/TaskManager/SimpleWorkerTask.ts:
--------------------------------------------------------------------------------
1 | import { Progress } from '../Common/Progress'
2 | import { EventDispatcher } from '/@/components/Common/Event/EventDispatcher'
3 |
4 | export abstract class SimpleTaskService extends EventDispatcher<
5 | [number, number]
6 | > {
7 | protected lastDispatch = 0
8 | public progress = new Progress(0, 100, 100)
9 |
10 | constructor() {
11 | super()
12 | this.progress.on(this.dispatch.bind(this))
13 | }
14 |
15 | dispatch(data: [number, number]) {
16 | // Always send last data batch
17 | if (data[0] === data[1]) super.dispatch(data)
18 |
19 | // Otherwise, first check that we don't send too many messages to the main thread
20 | if (this.lastDispatch + 200 > Date.now()) return
21 |
22 | super.dispatch(data)
23 | this.lastDispatch = Date.now()
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/TaskManager/TaskManager.ts:
--------------------------------------------------------------------------------
1 | import { ITaskDetails, Task } from './Task'
2 | import { ref } from 'vue'
3 |
4 | export const tasks = ref([])
5 |
6 | export class TaskManager {
7 | create(taskDetails: ITaskDetails) {
8 | const task = new Task(this, taskDetails)
9 | tasks.value.push(task)
10 | return task
11 | }
12 | delete(task: Task) {
13 | tasks.value = tasks.value.filter((t) => t !== task)
14 | }
15 |
16 | get hasRunningTasks() {
17 | return tasks.value.length > 0
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Toolbar/Category/download.ts:
--------------------------------------------------------------------------------
1 | import { vuetify } from '../../App/Vuetify'
2 | import { ToolbarButton } from '../ToolbarButton'
3 | import { App } from '/@/App'
4 |
5 | export function setupDownloadButton(app: App) {
6 | App.toolbar.add(
7 | new ToolbarButton(
8 | 'mdi-download',
9 | 'toolbar.download.name',
10 | () => {
11 | App.openUrl(
12 | 'https://bridge-core.app/guide/download/',
13 | undefined,
14 | true
15 | )
16 | },
17 | { value: !import.meta.env.VITE_IS_TAURI_APP }
18 | )
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/Toolbar/Category/settings.ts:
--------------------------------------------------------------------------------
1 | import { ToolbarButton } from '../ToolbarButton'
2 | import { App } from '/@/App'
3 |
4 | export function setupSettingsButton(app: App) {
5 | App.toolbar.add(
6 | new ToolbarButton(
7 | 'mdi-cog',
8 | 'actions.settings.name',
9 | () => {
10 | app.windows.settings.open()
11 | },
12 | { value: true }
13 | )
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/Toolbar/Divider.ts:
--------------------------------------------------------------------------------
1 | import { v4 as uuid } from 'uuid'
2 | import { EventDispatcher } from '/@/components/Common/Event/EventDispatcher'
3 |
4 | export class Divider extends EventDispatcher {
5 | public readonly id = uuid()
6 | public readonly type = 'divider'
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/Toolbar/Menu/Activator.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
39 |
--------------------------------------------------------------------------------
/src/components/Toolbar/Menu/Button.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | {{ t(displayName) }}
7 |
8 |
9 |
10 |
11 |
24 |
25 |
31 |
--------------------------------------------------------------------------------
/src/components/Toolbar/Toolbar.ts:
--------------------------------------------------------------------------------
1 | import { ToolbarCategory } from './ToolbarCategory'
2 | import { del, reactive, set } from 'vue'
3 | import { showContextMenu } from '../ContextMenu/showContextMenu'
4 |
5 | export class Toolbar {
6 | protected state: Record = reactive({})
7 |
8 | addCategory(category: ToolbarCategory) {
9 | set(this.state, category.id, category)
10 | }
11 | add(item: any) {
12 | set(this.state, item.id, item)
13 | }
14 | disposeCategory(category: ToolbarCategory) {
15 | del(this.state, category.id)
16 | }
17 |
18 | showMobileMenu(event: MouseEvent) {
19 | showContextMenu(
20 | event,
21 | Object.values(this.state)
22 | .map((category) => [
23 | { type: 'divider' },
24 | category.toNestedMenu(),
25 | ])
26 | .flat(1)
27 | .slice(1)
28 | )
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/Toolbar/ToolbarButton.ts:
--------------------------------------------------------------------------------
1 | import { v4 as uuid } from 'uuid'
2 | import { EventDispatcher } from '../Common/Event/EventDispatcher'
3 | import { UnwrapNestedRefs } from 'vue'
4 |
5 | export class ToolbarButton extends EventDispatcher {
6 | public readonly id = uuid()
7 | protected type = 'button'
8 | constructor(
9 | protected icon: string,
10 | protected name: string,
11 | protected callback: () => void,
12 | protected shouldRender: UnwrapNestedRefs<{ value: boolean }>
13 | ) {
14 | super()
15 | }
16 |
17 | toNestedMenu() {
18 | return {
19 | type: 'button',
20 | icon: this.icon,
21 | name: this.name,
22 | onTrigger: () => this.trigger(),
23 | }
24 | }
25 |
26 | trigger() {
27 | this.callback()
28 | this.dispatch()
29 | }
30 |
31 | dispose() {}
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/Toolbar/WindowAction.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ icon }}
4 |
5 |
6 |
7 |
18 |
19 |
29 |
--------------------------------------------------------------------------------
/src/components/Toolbar/setupDefaults.ts:
--------------------------------------------------------------------------------
1 | import { App } from '/@/App'
2 | import { setupFileCategory } from './Category/file'
3 | import { setupHelpCategory } from './Category/help'
4 | import { setupToolsCategory } from './Category/tools'
5 | import { setupProjectCategory } from './Category/project'
6 | import { setupSettingsButton } from './Category/settings'
7 | import { setupDownloadButton } from './Category/download'
8 |
9 | export async function setupDefaultMenus(app: App) {
10 | setupProjectCategory(app)
11 | setupSettingsButton(app)
12 | setupFileCategory(app)
13 | setupToolsCategory(app)
14 | setupHelpCategory(app)
15 | setupDownloadButton(app)
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/UIElements/DirectoryViewer/Common/BasicIconName.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ icon }}
5 |
6 |
7 |
15 | {{ name }}
16 |
17 |
18 |
19 |
20 |
34 |
35 |
42 |
--------------------------------------------------------------------------------
/src/components/UIElements/DirectoryViewer/Common/DraggingWrapper.ts:
--------------------------------------------------------------------------------
1 | import { ref } from 'vue'
2 |
3 | export const isDraggingWrapper = ref(false)
4 |
--------------------------------------------------------------------------------
/src/components/UIElements/DirectoryViewer/ContextMenu/Actions/Download.ts:
--------------------------------------------------------------------------------
1 | import { BaseWrapper } from '../../Common/BaseWrapper'
2 | import { download } from '/@/components/FileSystem/saveOrDownload'
3 | import { ZipDirectory } from '/@/components/FileSystem/Zip/ZipDirectory'
4 |
5 | export const DownloadAction = (baseWrapper: BaseWrapper) => ({
6 | icon:
7 | baseWrapper.kind === 'directory'
8 | ? 'mdi-folder-download-outline'
9 | : 'mdi-file-download-outline',
10 | name: 'actions.download.name',
11 | onTrigger: async () => {
12 | if (baseWrapper.kind === 'file') {
13 | const file: File = await baseWrapper.handle.getFile()
14 |
15 | download(baseWrapper.name, new Uint8Array(await file.arrayBuffer()))
16 | } else {
17 | const zip = new ZipDirectory(baseWrapper.handle)
18 | download(`${baseWrapper.name}.zip`, await zip.package())
19 | }
20 | },
21 | })
22 |
--------------------------------------------------------------------------------
/src/components/UIElements/DirectoryViewer/ContextMenu/Actions/Edit/Copy.ts:
--------------------------------------------------------------------------------
1 | import { App } from '/@/App'
2 | import { AnyHandle } from '/@/components/FileSystem/Types'
3 | import { BaseWrapper } from '/@/components/UIElements/DirectoryViewer/Common/BaseWrapper'
4 |
5 | interface IClipboard {
6 | item: AnyHandle | null
7 | }
8 | export const clipboard: IClipboard = {
9 | item: null,
10 | }
11 |
12 | export const CopyAction = (baseWrapper: BaseWrapper) => ({
13 | icon: 'mdi-content-copy',
14 | name: 'actions.copy.name',
15 |
16 | onTrigger: async () => {
17 | clipboard.item = baseWrapper.handle
18 | },
19 | })
20 |
--------------------------------------------------------------------------------
/src/components/UIElements/DirectoryViewer/ContextMenu/Actions/Edit/Duplicate.ts:
--------------------------------------------------------------------------------
1 | import { clipboard } from './Copy'
2 | import { PasteAction } from './Paste'
3 | import { BaseWrapper } from '/@/components/UIElements/DirectoryViewer/Common/BaseWrapper'
4 |
5 | export const DuplicateAction = (baseWrapper: BaseWrapper) => ({
6 | icon: 'mdi-content-duplicate',
7 | name: 'actions.duplicate.name',
8 |
9 | onTrigger: async () => {
10 | const parent = baseWrapper.getParent()
11 | if (!parent) return
12 |
13 | clipboard.item = baseWrapper.handle
14 |
15 | const newHandle = await PasteAction(parent).onTrigger()
16 | if (!newHandle) return
17 |
18 | const newWrapper = parent.getChild(newHandle.name)
19 | if (!newWrapper) return
20 |
21 | newWrapper.isEditingName.value = true
22 | },
23 | })
24 |
--------------------------------------------------------------------------------
/src/components/UIElements/DirectoryViewer/ContextMenu/Actions/Edit/Rename.ts:
--------------------------------------------------------------------------------
1 | import { BaseWrapper } from '/@/components/UIElements/DirectoryViewer/Common/BaseWrapper'
2 |
3 | export const RenameAction = (baseWrapper: BaseWrapper) => ({
4 | icon: 'mdi-pencil-outline',
5 | name: 'actions.rename.name',
6 |
7 | onTrigger: () => {
8 | baseWrapper.isEditingName.value = true
9 | },
10 | })
11 |
--------------------------------------------------------------------------------
/src/components/UIElements/DirectoryViewer/ContextMenu/Actions/Open.ts:
--------------------------------------------------------------------------------
1 | import { FileWrapper } from '/@/components/UIElements/DirectoryViewer/FileView/FileWrapper'
2 | import { App } from '/@/App'
3 |
4 | export const OpenAction = (fileWrapper: FileWrapper) => ({
5 | icon: 'mdi-plus',
6 | name: 'actions.open.name',
7 |
8 | onTrigger: async () => {
9 | const app = await App.getApp()
10 | app.project.openFile(fileWrapper.handle, {
11 | readOnlyMode: fileWrapper.options.isReadOnly ? 'forced' : 'off',
12 | })
13 | },
14 | })
15 |
--------------------------------------------------------------------------------
/src/components/UIElements/DirectoryViewer/ContextMenu/Actions/OpenInSplitScreen.ts:
--------------------------------------------------------------------------------
1 | import { FileWrapper } from '/@/components/UIElements/DirectoryViewer/FileView/FileWrapper'
2 | import { App } from '/@/App'
3 |
4 | export const OpenInSplitScreenAction = (fileWrapper: FileWrapper) => ({
5 | icon: 'mdi-arrow-split-vertical',
6 | name: 'actions.openInSplitScreen.name',
7 | onTrigger: async () => {
8 | const app = await App.getApp()
9 |
10 | app.project.openFile(fileWrapper.handle, {
11 | openInSplitScreen: true,
12 | readOnlyMode: fileWrapper.options.isReadOnly ? 'forced' : 'off',
13 | })
14 | },
15 | })
16 |
--------------------------------------------------------------------------------
/src/components/UIElements/DirectoryViewer/ContextMenu/Actions/OpenWith/Blockbench.ts:
--------------------------------------------------------------------------------
1 | import { FileWrapper } from '../../../FileView/FileWrapper'
2 | import { App } from '/@/App'
3 | import { BlockbenchTab } from '/@/components/Editors/Blockbench/BlockbenchTab'
4 | import { IframeTab } from '/@/components/Editors/IframeTab/IframeTab'
5 |
6 | export const BlockbenchAction = (fileWrapper: FileWrapper) => {
7 | return fileWrapper.path?.includes('/models/')
8 | ? {
9 | icon: '$blockbench',
10 | name: 'openWith.blockbench',
11 | onTrigger: async () => {
12 | const app = await App.getApp()
13 | const tabSystem = app.tabSystem
14 |
15 | if (!tabSystem) return
16 |
17 | const tab = new BlockbenchTab(tabSystem, {
18 | openWithPayload: {
19 | fileHandle: fileWrapper.handle,
20 | filePath: fileWrapper.path ?? undefined,
21 | },
22 | })
23 | tabSystem.add(tab)
24 | },
25 | }
26 | : null
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/UIElements/DirectoryViewer/ContextMenu/Actions/OpenWith/HTMLPreviewer.ts:
--------------------------------------------------------------------------------
1 | import { FileWrapper } from '/@/components/UIElements/DirectoryViewer/FileView/FileWrapper'
2 | import { App } from '/@/App'
3 | import { HTMLPreviewTab } from '/@/components/Editors/HTMLPreview/HTMLPreview'
4 | import { AnyFileHandle } from '/@/components/FileSystem/Types'
5 |
6 | export const HTMLPreviewerAction = (
7 | fileHandle: AnyFileHandle,
8 | filePath?: string
9 | ) =>
10 | fileHandle.name.endsWith('.html')
11 | ? {
12 | icon: 'mdi-language-html5',
13 | name: 'openWith.htmlPreviewer',
14 | onTrigger: async () => {
15 | const app = await App.getApp()
16 | const tabSystem = app.tabSystem
17 | if (!tabSystem) return
18 |
19 | tabSystem.add(
20 | new HTMLPreviewTab(tabSystem, {
21 | fileHandle: fileHandle,
22 | filePath: filePath ?? undefined,
23 | })
24 | )
25 | },
26 | }
27 | : null
28 |
--------------------------------------------------------------------------------
/src/components/UIElements/DirectoryViewer/ContextMenu/Actions/OpenWith/TreeEditor.ts:
--------------------------------------------------------------------------------
1 | import { FileWrapper } from '/@/components/UIElements/DirectoryViewer/FileView/FileWrapper'
2 | import { App } from '/@/App'
3 | import { TreeTab } from '/@/components/Editors/TreeEditor/Tab'
4 |
5 | export const TreeEditorAction = (fileWrapper: FileWrapper) =>
6 | fileWrapper.name.endsWith('.json')
7 | ? {
8 | icon: 'mdi-file-tree-outline',
9 | name: 'openWith.treeEditor',
10 | onTrigger: async () => {
11 | const app = await App.getApp()
12 | const tabSystem = app.tabSystem
13 | if (!tabSystem) return
14 |
15 | tabSystem.add(
16 | new TreeTab(
17 | tabSystem,
18 | fileWrapper.handle,
19 | fileWrapper.options.isReadOnly ? 'forced' : 'off'
20 | )
21 | )
22 | },
23 | }
24 | : null
25 |
--------------------------------------------------------------------------------
/src/components/UIElements/DirectoryViewer/ContextMenu/Actions/Refresh.ts:
--------------------------------------------------------------------------------
1 | import { DirectoryWrapper } from '/@/components/UIElements/DirectoryViewer/DirectoryView/DirectoryWrapper'
2 |
3 | export const RefreshAction = (directoryWrapper: DirectoryWrapper) => ({
4 | icon: 'mdi-refresh',
5 | name: 'general.refresh',
6 |
7 | onTrigger: () => {
8 | directoryWrapper.refresh()
9 | },
10 | })
11 |
--------------------------------------------------------------------------------
/src/components/UIElements/DirectoryViewer/ContextMenu/Actions/RevealPath.ts:
--------------------------------------------------------------------------------
1 | import { isUsingFileSystemPolyfill } from '/@/components/FileSystem/Polyfill'
2 | import { BaseWrapper } from '/@/components/UIElements/DirectoryViewer/Common/BaseWrapper'
3 | import { InformationWindow } from '/@/components/Windows/Common/Information/InformationWindow'
4 |
5 | export const RevealFilePathAction = (baseWrapper: BaseWrapper) =>
6 | isUsingFileSystemPolyfill.value || import.meta.env.VITE_IS_TAURI_APP
7 | ? null
8 | : {
9 | icon:
10 | baseWrapper.kind === 'directory'
11 | ? 'mdi-folder-marker-outline'
12 | : 'mdi-file-marker-outline',
13 | name: 'actions.revealPath.name',
14 | onTrigger: async () => {
15 | new InformationWindow({
16 | name: 'actions.revealPath.name',
17 | description: `[${baseWrapper.path}]`,
18 | isPersistent: false,
19 | }).open()
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/UIElements/DirectoryViewer/FileView/FileView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
26 |
--------------------------------------------------------------------------------
/src/components/UIElements/Logo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
--------------------------------------------------------------------------------
/src/components/UIElements/SelectedStatus.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | mdi-checkbox-marked-circle-outline
6 |
7 |
8 | {{ t('general.active') }}
9 |
10 |
11 |
12 |
13 | mdi-checkbox-blank-circle-outline
14 |
15 |
16 | {{ t('general.inactive') }}
17 |
18 |
19 |
20 |
21 |
22 |
36 |
--------------------------------------------------------------------------------
/src/components/ViewFolders/ViewFolders.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 |
28 |
--------------------------------------------------------------------------------
/src/components/Windows/BrowserUnsupported/BrowserUnsupported.ts:
--------------------------------------------------------------------------------
1 | import { NewBaseWindow } from '../NewBaseWindow'
2 | import BrowserUnsupportedComponent from './BrowserUnsupported.vue'
3 | import { App } from '/@/App'
4 |
5 | export class BrowserUnsupportedWindow extends NewBaseWindow {
6 | constructor() {
7 | super(BrowserUnsupportedComponent)
8 | this.defineWindow()
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/Windows/Changelog/Changelog.ts:
--------------------------------------------------------------------------------
1 | import ChangelogComponent from './Changelog.vue'
2 | import { App } from '/@/App'
3 | import { baseUrl } from '/@/utils/baseUrl'
4 | import { version } from '/@/utils/app/version'
5 | import { NewBaseWindow } from '../NewBaseWindow'
6 |
7 | export class ChangelogWindow extends NewBaseWindow {
8 | changelog: string | undefined
9 | version: string | undefined
10 |
11 | constructor() {
12 | super(ChangelogComponent)
13 | this.defineWindow()
14 | }
15 |
16 | async open() {
17 | const app = await App.getApp()
18 | app.windows.loadingWindow.open()
19 |
20 | await fetch(baseUrl + 'changelog.html')
21 | .then((response) => response.text())
22 | .then((html) => {
23 | this.changelog = html
24 | this.version = version
25 | })
26 |
27 | app.windows.loadingWindow.close()
28 | super.open()
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/Windows/Collect.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
15 |
30 |
--------------------------------------------------------------------------------
/src/components/Windows/Common/MultiOptions/Window.ts:
--------------------------------------------------------------------------------
1 | import MultiWindowComponent from './Window.vue'
2 | import { NewBaseWindow } from '../../NewBaseWindow'
3 |
4 | export interface IOption {
5 | name: string
6 | isSelected: boolean
7 | }
8 | export interface IMultiOptionsWindowConfig {
9 | name: string
10 | options: IOption[]
11 | isClosable?: boolean
12 | }
13 |
14 | export class MultiOptionsWindow extends NewBaseWindow {
15 | constructor(protected config: IMultiOptionsWindowConfig) {
16 | super(MultiWindowComponent, true, false)
17 |
18 | this.defineWindow()
19 | this.open()
20 | }
21 |
22 | get name() {
23 | return this.config.name
24 | }
25 | get options() {
26 | return this.config.options
27 | }
28 | get isClosable() {
29 | return this.config.isClosable
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/Windows/ExtensionStore/ExtensionTag.ts:
--------------------------------------------------------------------------------
1 | import { SidebarItem } from '/@/components/Windows/Layout/Sidebar'
2 | import { ExtensionStoreWindow } from './ExtensionStore'
3 |
4 | export class ExtensionTag {
5 | protected icon: string
6 | protected text: string
7 | protected color?: string
8 |
9 | constructor(protected parent: ExtensionStoreWindow, tagName: string) {
10 | this.icon = this.parent.getTagIcon(tagName)
11 | this.text = tagName
12 | this.color = this.parent.getTagColor(tagName)
13 | }
14 |
15 | getText() {
16 | return this.text
17 | }
18 |
19 | asSidebarElement() {
20 | return new SidebarItem({
21 | id: this.text.toLowerCase(),
22 | text: this.text,
23 | color: this.color,
24 | icon: this.icon,
25 | })
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/Windows/Layout/Toolbar/Button.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ icon }}
5 |
6 |
7 |
8 |
9 |
24 |
--------------------------------------------------------------------------------
/src/components/Windows/LoadingWindow/LoadingWindow.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
30 |
--------------------------------------------------------------------------------
/src/components/Windows/Project/CreatePreset/PresetItem.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ISidebarItemConfig,
3 | SidebarItem,
4 | } from '/@/components/Windows/Layout/Sidebar'
5 |
6 | export class PresetItem extends SidebarItem {
7 | public readonly resetState: () => void
8 |
9 | constructor(config: ISidebarItemConfig & { resetState: () => void }) {
10 | super(config)
11 |
12 | this.resetState = config.resetState
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/Windows/Project/CreatePreset/TransformString.ts:
--------------------------------------------------------------------------------
1 | export function transformString(
2 | str: string,
3 | inject: string[],
4 | models: Record
5 | ) {
6 | inject.forEach((val) => (str = str.replaceAll(`{{${val}}}`, models[val])))
7 | return str
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/Windows/Settings/Controls/ActionViewer/ActionViewer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
--------------------------------------------------------------------------------
/src/components/Windows/Settings/Controls/Button/Button.ts:
--------------------------------------------------------------------------------
1 | import { Control, IControl } from '../Control'
2 | import ButtonComponent from './Button.vue'
3 | import Vue from 'vue'
4 |
5 | export class Button extends Control {
6 | constructor(config: {
7 | name: string
8 | category: string
9 | description: string
10 | onClick: () => void
11 | }) {
12 | super(ButtonComponent, { ...config, key: 'N/A' })
13 | }
14 |
15 | matches(filter: string) {
16 | return (
17 | this.config.name.includes(filter) ||
18 | this.config.description.includes(filter)
19 | )
20 | }
21 | onChange = async () => {}
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/Windows/Settings/Controls/Button/Button.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ t(config.name) }}
5 |
6 |
7 | {{ t(config.description) }}
8 |
9 |
10 |
11 |
21 |
--------------------------------------------------------------------------------
/src/components/Windows/Settings/Controls/ButtonToggle/ButtonToggle.ts:
--------------------------------------------------------------------------------
1 | import { Control, IControl } from '../Control'
2 | import ButtonToggleComponent from './ButtonToggle.vue'
3 |
4 | export interface IButtonToggle extends IControl {
5 | options: string[]
6 | }
7 |
8 | export class ButtonToggle extends Control {
9 | constructor(config: IButtonToggle) {
10 | super(ButtonToggleComponent, config)
11 | }
12 |
13 | matches(filter: string) {
14 | return (
15 | this.config.name.toLowerCase().includes(filter) ||
16 | this.config.description.toLowerCase().includes(filter) ||
17 | this.config.options.some((option) =>
18 | option.toLowerCase().includes(filter)
19 | )
20 | )
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/Windows/Settings/Controls/Selection/BridgeConfigSelection.ts:
--------------------------------------------------------------------------------
1 | import { App } from '/@/App'
2 | import { Selection } from './Selection'
3 |
4 | export class BridgeConfigSelection extends Selection | string> {
5 | get value() {
6 | return App.getApp().then(
7 | (app) => (app.projectConfig.get().bridge)?.[this.config.key]
8 | )
9 | }
10 | set value(val) {
11 | App.getApp().then(async (app) => {
12 | if (!app.projectConfig.get().bridge)
13 | app.projectConfig.get().bridge = {}
14 | ;(app.projectConfig.get().bridge)[this.config.key] = val
15 | await app.projectConfig.save()
16 | })
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/Windows/Settings/Controls/Selection/Selection.ts:
--------------------------------------------------------------------------------
1 | import { Control, IControl } from '../Control'
2 | import SelectionComponent from './Selection.vue'
3 |
4 | export interface ISelectionControl extends IControl {
5 | options: (string | { text: string; value: string })[]
6 | onClick?: () => Promise | void
7 | }
8 |
9 | export class Selection extends Control> {
10 | constructor(config: ISelectionControl) {
11 | super(SelectionComponent, config)
12 | }
13 |
14 | matches(filter: string) {
15 | return (
16 | this.config.name.toLowerCase().includes(filter) ||
17 | this.config.description.toLowerCase().includes(filter) ||
18 | this.config.options.some((option) =>
19 | typeof option === 'string'
20 | ? option.toLowerCase().includes(filter)
21 | : option.text.toLowerCase().includes(filter)
22 | )
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/Windows/Settings/Controls/Sidebar/Sidebar.ts:
--------------------------------------------------------------------------------
1 | import { Control, IControl } from '../Control'
2 | import SidebarComponent from './Sidebar.vue'
3 | import { App } from '/@/App'
4 | import { translate } from '/@/components/Locales/Manager'
5 |
6 | export class Sidebar extends Control {
7 | constructor(config: IControl) {
8 | super(SidebarComponent, config)
9 | }
10 |
11 | matches(filter: string) {
12 | return Object.values(App.sidebar.elements).some((sidebar) =>
13 | translate(sidebar.displayName).includes(filter)
14 | )
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/Windows/Settings/Controls/TextField/TextField.ts:
--------------------------------------------------------------------------------
1 | import { Control, IControl } from '../Control'
2 | import TextFieldComponent from './TextField.vue'
3 |
4 | export class TextField extends Control> {
5 | constructor(config: IControl) {
6 | super(TextFieldComponent, config)
7 | }
8 |
9 | matches(filter: string) {
10 | return (
11 | this.config.name.toLowerCase().includes(filter) ||
12 | this.config.description.toLowerCase().includes(filter)
13 | )
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/Windows/Settings/Controls/Toggle/Toggle.ts:
--------------------------------------------------------------------------------
1 | import { Control, IControl } from '../Control'
2 | import ToggleComponent from './Toggle.vue'
3 |
4 | export class Toggle extends Control {
5 | constructor(config: IControl) {
6 | super(ToggleComponent, config)
7 | }
8 |
9 | matches(filter: string) {
10 | return (
11 | this.config.name.toLowerCase().includes(filter) ||
12 | this.config.description.toLowerCase().includes(filter)
13 | )
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/Windows/Settings/Controls/Toggle/Toggle.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ t(config.name) }}
4 |
12 |
13 |
14 |
15 |
37 |
--------------------------------------------------------------------------------
/src/components/Windows/Settings/SettingsState.ts:
--------------------------------------------------------------------------------
1 | import { reactive, set } from 'vue'
2 |
3 | export let settingsState: Record> = reactive({
4 | sidebar: {},
5 | })
6 |
7 | export function setSettingsState(
8 | state: Record>
9 | ) {
10 | for (const key in state) {
11 | set(settingsState, key, state[key])
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/Windows/Socials/SocialsWindow.ts:
--------------------------------------------------------------------------------
1 | import { NewBaseWindow } from '../NewBaseWindow'
2 | import SocialsComponent from './Main.vue'
3 |
4 | export class SocialsWindow extends NewBaseWindow {
5 | constructor() {
6 | super(SocialsComponent)
7 | this.defineWindow()
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/locales/languages.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "english",
4 | "name": "English",
5 | "file": "en.json",
6 | "codes": ["en-US", "en-UK"]
7 | },
8 | {
9 | "id": "german",
10 | "name": "Deutsch",
11 | "file": "de.json",
12 | "codes": ["de-DE"]
13 | },
14 | {
15 | "id": "japanese",
16 | "name": "日本語",
17 | "file": "ja.json",
18 | "codes": ["ja-JP"]
19 | },
20 | {
21 | "id": "korean",
22 | "name": "한국어",
23 | "file": "ko.json",
24 | "codes": ["ko-KR"]
25 | },
26 | {
27 | "id": "chineseCN",
28 | "name": "中文",
29 | "file": "zh-CN.json",
30 | "codes": ["zh-CN"]
31 | },
32 | {
33 | "id": "chineseTW",
34 | "name": "中文(臺灣)",
35 | "file": "zh-TW.json",
36 | "codes": ["zh-TW"]
37 | },
38 | {
39 | "id": "netherlands",
40 | "name": "Nederlands",
41 | "file": "nl.json",
42 | "codes": ["nl-NL"]
43 | },
44 | {
45 | "id": "russian",
46 | "name": "Русский",
47 | "file": "ru.json",
48 | "codes": ["ru-RU"]
49 | }
50 | ]
51 |
--------------------------------------------------------------------------------
/src/main.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/src/types/Activatable.ts:
--------------------------------------------------------------------------------
1 | export interface IActivatable {
2 | activate: () => Promise | void
3 | deactivate: () => Promise | void
4 | }
5 |
--------------------------------------------------------------------------------
/src/types/LocalFontAccess.d.ts:
--------------------------------------------------------------------------------
1 | declare interface Window {
2 | queryLocalFonts?(): Promise
3 | }
4 |
--------------------------------------------------------------------------------
/src/types/StructuredClone.d.ts:
--------------------------------------------------------------------------------
1 | declare interface Window {
2 | structuredClone?(obj: T): T
3 | }
4 |
--------------------------------------------------------------------------------
/src/types/Vite.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | interface ImportMetaEnv {
4 | readonly VITE_IS_TAURI_APP: string
5 | // more env variables...
6 | }
7 |
8 | interface ImportMeta {
9 | readonly env: ImportMetaEnv
10 | }
11 |
--------------------------------------------------------------------------------
/src/types/disposable.ts:
--------------------------------------------------------------------------------
1 | export interface IDisposable {
2 | dispose: () => void
3 | }
4 |
--------------------------------------------------------------------------------
/src/types/quick-score.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'quick-score' {
2 | declare class QuickScore {
3 | constructor(words: T[]) {}
4 |
5 | search(
6 | pattern: string
7 | ): {
8 | item: T
9 | score: number
10 | matches: [number, number][]
11 | }[]
12 | }
13 |
14 | declare function quickScore(word: string, pattern: string): number
15 | }
16 |
--------------------------------------------------------------------------------
/src/types/shims-path.ts:
--------------------------------------------------------------------------------
1 | declare module 'path-browserify' {
2 | import path from 'path'
3 | export default path
4 | }
5 |
--------------------------------------------------------------------------------
/src/types/shims-tsx.d.ts:
--------------------------------------------------------------------------------
1 | import Vue, { VNode } from 'vue'
2 |
3 | declare global {
4 | namespace JSX {
5 | // tslint:disable no-empty-interface
6 | interface Element extends VNode {}
7 | // tslint:disable no-empty-interface
8 | interface ElementClass extends Vue {}
9 | interface IntrinsicElements {
10 | [elem: string]: any
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/types/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import Vue from 'vue'
3 | export default Vue
4 | }
5 |
--------------------------------------------------------------------------------
/src/types/tgaJS.ts:
--------------------------------------------------------------------------------
1 | declare module 'tga-js' {
2 | export default class TGALoader {
3 | load(uint8arr: Uint8Array): void
4 | open(filePath: string, onLoad: () => void): void
5 | getDataURL(mimeType: 'image/png'): string
6 | getImageData(imageData?: ImageData): ImageData
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/utils/app/dashVersion.ts:
--------------------------------------------------------------------------------
1 | import packageConfig from '../../../package.json'
2 |
3 | let version = packageConfig.dependencies['@bridge-editor/dash-compiler']
4 |
5 | if (
6 | version.startsWith('^') ||
7 | version.startsWith('~') ||
8 | version.startsWith('>') ||
9 | version.startsWith('<')
10 | )
11 | version = version.substring(1)
12 | else if (version.startsWith('>=') || version.startsWith('<='))
13 | version = version.substring(2)
14 |
15 | export const dashVersion = version
16 |
--------------------------------------------------------------------------------
/src/utils/app/dataPackage.ts:
--------------------------------------------------------------------------------
1 | export const zipSize = 960509
--------------------------------------------------------------------------------
/src/utils/app/iframeApiVersion.ts:
--------------------------------------------------------------------------------
1 | import packageConfig from '../../../package.json'
2 |
3 | let version = packageConfig.dependencies['bridge-iframe-api']
4 |
5 | if (
6 | version.startsWith('^') ||
7 | version.startsWith('~') ||
8 | version.startsWith('>') ||
9 | version.startsWith('<')
10 | )
11 | version = version.substring(1)
12 | else if (version.startsWith('>=') || version.startsWith('<='))
13 | version = version.substring(2)
14 |
15 | export const iframeApiVersion = version
16 |
--------------------------------------------------------------------------------
/src/utils/app/isNightly.ts:
--------------------------------------------------------------------------------
1 | export const isNightly = location.origin === 'https://nightly.bridge-core.app'
2 |
--------------------------------------------------------------------------------
/src/utils/app/version.ts:
--------------------------------------------------------------------------------
1 | import packageConfig from '../../../package.json'
2 |
3 | export const version = packageConfig.version
4 |
--------------------------------------------------------------------------------
/src/utils/array/findAsync.ts:
--------------------------------------------------------------------------------
1 | export async function findAsync(arr: T[], cb: (e: T) => Promise) {
2 | const results = await Promise.all(arr.map(cb))
3 | const index = results.findIndex((result) => result)
4 | return arr[index]
5 | }
6 |
--------------------------------------------------------------------------------
/src/utils/baseUrl.ts:
--------------------------------------------------------------------------------
1 | export const baseUrl = import.meta.env.BASE_URL
2 |
--------------------------------------------------------------------------------
/src/utils/canvasToBlob.ts:
--------------------------------------------------------------------------------
1 | export function toBlob(canvas: HTMLCanvasElement) {
2 | return new Promise((resolve, reject) => {
3 | canvas.toBlob((blob) => {
4 | if (blob) {
5 | resolve(blob)
6 | } else {
7 | reject(new Error('Canvas is empty'))
8 | }
9 | }, 'image/png')
10 | })
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/constants.ts:
--------------------------------------------------------------------------------
1 | import { platform } from './os'
2 |
3 | export const platformRedoBinding =
4 | platform() === 'darwin' ? 'Ctrl + Shift + Z' : 'Ctrl + Y'
5 |
--------------------------------------------------------------------------------
/src/utils/directory/getEntries.ts:
--------------------------------------------------------------------------------
1 | import { AnyDirectoryHandle } from '/@/components/FileSystem/Types'
2 |
3 | export async function getEntries(directoryHandle: AnyDirectoryHandle) {
4 | const entries = []
5 |
6 | for await (const entry of directoryHandle.values()) {
7 | entries.push(entry)
8 | }
9 |
10 | return entries
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/disposableListener.ts:
--------------------------------------------------------------------------------
1 | export function addDisposableEventListener(
2 | event: string,
3 | listener: (event: any) => void,
4 | eventTarget: EventTarget = window
5 | ) {
6 | eventTarget.addEventListener(event, listener)
7 |
8 | return {
9 | dispose: () => {
10 | eventTarget.removeEventListener(event, listener)
11 | },
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/utils/disposableTimeout.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * A function that scheducles a callback function after x milliseconds.
3 | * @param callback The callback function to execute.
4 | * @param milliseconds The number of milliseconds to wait before executing the callback.
5 | * @returns {IDisposable}
6 | */
7 | export function disposableTimeout(callback: () => void, milliseconds: number) {
8 | let timeoutId: number | null
9 | // setTimeout
10 | timeoutId = (setTimeout(() => {
11 | timeoutId = null
12 | callback()
13 | }, milliseconds))
14 |
15 | return {
16 | dispose: () => {
17 | if (timeoutId) clearTimeout(timeoutId)
18 | timeoutId = null
19 | },
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/utils/file/dirExists.ts:
--------------------------------------------------------------------------------
1 | import { AnyDirectoryHandle } from '/@/components/FileSystem/Types'
2 |
3 | export async function dirExists(
4 | directoryHandle: AnyDirectoryHandle,
5 | name: string
6 | ) {
7 | return directoryHandle
8 | .getDirectoryHandle(name)
9 | .then(() => true)
10 | .catch(() => false)
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/file/fileExists.ts:
--------------------------------------------------------------------------------
1 | import { AnyDirectoryHandle } from '/@/components/FileSystem/Types'
2 |
3 | export async function fileExists(
4 | directoryHandle: AnyDirectoryHandle,
5 | name: string
6 | ) {
7 | return directoryHandle
8 | .getFileHandle(name)
9 | .then(() => true)
10 | .catch(() => false)
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/file/getIcon.ts:
--------------------------------------------------------------------------------
1 | import { extname } from '../path'
2 |
3 | const extIconMap: Record = {
4 | 'mdi-file-image-outline': [
5 | '.jpg',
6 | '.jpeg',
7 | '.png',
8 | '.gif',
9 | '.bmp',
10 | '.webp',
11 | '.tga',
12 | ],
13 | 'mdi-code-json': ['.json'],
14 | 'mdi-volume-high': ['.mp3', '.wav', '.fsb', '.ogg'],
15 | 'mdi-language-html5': ['.html'],
16 | 'mdi-language-typescript': ['.ts', '.tsx'],
17 | 'mdi-language-javascript': ['.js', '.jsx'],
18 | 'mdi-web': ['.lang'],
19 | }
20 |
21 | export function getDefaultFileIcon(name: string) {
22 | const ext = extname(name)
23 | for (const [icon, exts] of Object.entries(extIconMap)) {
24 | if (exts.includes(ext)) return icon
25 | }
26 |
27 | return 'mdi-file-outline'
28 | }
29 |
--------------------------------------------------------------------------------
/src/utils/file/isAccepted.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns whether a file is accepted by the given "accept" filter
3 | *
4 | * @param file The file to check
5 | * @param accept The accept filter (see HTML file input)
6 | */
7 | export function isFileAccepted(file: File, accept?: string) {
8 | if (!accept || accept === '*') {
9 | return true
10 | }
11 |
12 | const acceptedFiles = accept.split(',')
13 | const fileName = file.name || ''
14 | const mimeType = file.type || ''
15 | const baseMimeType = mimeType.replace(/\/.*$/, '')
16 |
17 | return acceptedFiles.some((type) => {
18 | const validType = type.trim()
19 | if (validType.charAt(0) === '.') {
20 | return fileName.toLowerCase().endsWith(validType.toLowerCase())
21 | } else if (/\/\*$/.test(validType)) {
22 | // This is something like a image/* mime type
23 | return baseMimeType === validType.replace(/\/.*$/, '')
24 | }
25 | return mimeType === validType
26 | })
27 | }
28 |
--------------------------------------------------------------------------------
/src/utils/file/isSameEntry.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BaseVirtualHandle,
3 | VirtualHandle,
4 | } from '/@/components/FileSystem/Virtual/Handle'
5 |
6 | export function isSameEntry(
7 | entry1: FileSystemHandle | VirtualHandle,
8 | entry2: FileSystemHandle | VirtualHandle
9 | ) {
10 | if (
11 | entry1 instanceof BaseVirtualHandle &&
12 | entry2 instanceof BaseVirtualHandle
13 | ) {
14 | return entry1.isSameEntry(entry2)
15 | } else if (
16 | entry1 instanceof BaseVirtualHandle ||
17 | entry2 instanceof BaseVirtualHandle
18 | ) {
19 | return false
20 | }
21 |
22 | return entry1.isSameEntry(entry2)
23 | }
24 |
--------------------------------------------------------------------------------
/src/utils/file/loadAllFiles.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AnyDirectoryHandle,
3 | AnyFileHandle,
4 | } from '/@/components/FileSystem/Types'
5 |
6 | export async function loadAllFiles(
7 | directoryHandle: AnyDirectoryHandle,
8 | path = directoryHandle.name
9 | ) {
10 | if (path === '') path = '~local'
11 |
12 | const files: { handle: AnyFileHandle; path: string }[] = []
13 |
14 | for await (const handle of directoryHandle.values()) {
15 | if (handle.kind === 'file' && handle.name !== '.DS_Store') {
16 | files.push({
17 | handle,
18 | path: `${path}/${handle.name}`,
19 | })
20 | } else if (handle.kind === 'directory') {
21 | files.push(
22 | ...(await loadAllFiles(handle, `${path}/${handle.name}`))
23 | )
24 | }
25 | }
26 |
27 | return files
28 | }
29 |
--------------------------------------------------------------------------------
/src/utils/file/writableToUint8Array.ts:
--------------------------------------------------------------------------------
1 | const textEncoder = new TextEncoder()
2 |
3 | export async function writableToUint8Array(data: BufferSource | Blob | string) {
4 | let rawData: Uint8Array
5 | if (typeof data === 'string') rawData = textEncoder.encode(data)
6 | else if (data instanceof Blob)
7 | rawData = await data
8 | .arrayBuffer()
9 | .then((buffer) => new Uint8Array(buffer))
10 | else if (!ArrayBuffer.isView(data)) rawData = new Uint8Array(data)
11 | else rawData = new Uint8Array(data.buffer)
12 |
13 | return rawData
14 | }
15 |
--------------------------------------------------------------------------------
/src/utils/fs.ts:
--------------------------------------------------------------------------------
1 | import { App } from '/@/App'
2 | import { FileSystem } from '/@/components/FileSystem/FileSystem'
3 |
4 | export function getFileSystem() {
5 | return new Promise(resolve => {
6 | App.ready.once(app => {
7 | resolve(app.fileSystem)
8 | })
9 | })
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/getBridgeFolderPath.ts:
--------------------------------------------------------------------------------
1 | import { get } from 'idb-keyval'
2 |
3 | let cachedPath: string | undefined = undefined
4 |
5 | export async function getBridgeFolderPath() {
6 | if (!import.meta.env.VITE_IS_TAURI_APP)
7 | throw new Error(`This function is only available in Tauri apps.`)
8 | if (cachedPath) return cachedPath
9 |
10 | const { appLocalDataDir, join } = await import('@tauri-apps/api/path')
11 |
12 | const configuredPath = await get('bridgeFolderPath')
13 | if (configuredPath) {
14 | cachedPath = configuredPath
15 | return cachedPath
16 | }
17 |
18 | cachedPath = await join(await appLocalDataDir(), 'bridge')
19 | return cachedPath
20 | }
21 |
--------------------------------------------------------------------------------
/src/utils/inferType.ts:
--------------------------------------------------------------------------------
1 | const numberRegExp = /^\d+(\.\d+)?$/
2 |
3 | export function inferType(value: string) {
4 | let transformedValue: string | number | boolean | null = value
5 |
6 | if (typeof value === 'boolean' || value === 'true' || value === 'false')
7 | transformedValue = typeof value === 'boolean' ? value : value === 'true'
8 | else if (numberRegExp.test(value)) transformedValue = Number(value)
9 | else if (value === 'null' || value === null) transformedValue = null
10 |
11 | numberRegExp.lastIndex = 0
12 |
13 | return transformedValue
14 | }
15 |
--------------------------------------------------------------------------------
/src/utils/isNode.ts:
--------------------------------------------------------------------------------
1 | export const isNode = () => {
2 | try {
3 | return typeof process !== 'undefined' && process.release.name === 'node'
4 | } catch {
5 | return false
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/utils/isWritableData.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * A function that returns whether the given data is writable using the FileSystem Access API
3 | */
4 | export function isWritableData(data: any): boolean {
5 | return (
6 | typeof data === 'string' ||
7 | data instanceof Blob ||
8 | data instanceof File ||
9 | data instanceof ArrayBuffer ||
10 | data?.buffer instanceof ArrayBuffer
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/src/utils/libs/internal/jsoncParser.ts:
--------------------------------------------------------------------------------
1 | export { getLocation, visit } from 'jsonc-parser'
2 |
--------------------------------------------------------------------------------
/src/utils/libs/internal/quickScore.ts:
--------------------------------------------------------------------------------
1 | export { QuickScore } from 'quick-score'
2 |
--------------------------------------------------------------------------------
/src/utils/libs/internal/vueTemplateCompiler.ts:
--------------------------------------------------------------------------------
1 | export { parseComponent } from 'vue-template-compiler'
2 |
--------------------------------------------------------------------------------
/src/utils/libs/useJsoncParser.ts:
--------------------------------------------------------------------------------
1 | export async function useJsoncParser() {
2 | return await import('./internal/jsoncParser')
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/libs/useModelViewer.ts:
--------------------------------------------------------------------------------
1 | export async function useBridgeModelViewer() {
2 | return await import('bridge-model-viewer')
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/libs/useMonaco.ts:
--------------------------------------------------------------------------------
1 | import { Signal } from '../../components/Common/Event/Signal'
2 |
3 | export const loadMonaco = new Signal()
4 |
5 | export async function useMonaco() {
6 | await loadMonaco.fired
7 | return await import('monaco-editor')
8 | }
9 |
--------------------------------------------------------------------------------
/src/utils/libs/useQuickScore.ts:
--------------------------------------------------------------------------------
1 | export async function useQuickScore() {
2 | return await import('./internal/quickScore')
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/libs/useVueTemplateCompiler.ts:
--------------------------------------------------------------------------------
1 | export async function useVueTemplateCompiler() {
2 | return await import('./internal/vueTemplateCompiler')
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/libs/useWintersky.ts:
--------------------------------------------------------------------------------
1 | export async function useWintersky() {
2 | return await import('wintersky')
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/manifest/getPackId.ts:
--------------------------------------------------------------------------------
1 | import { TPackTypeId } from 'mc-project-core'
2 |
3 | export interface IManifestModule {
4 | type: 'data' | 'resources' | 'skin_pack' | 'world_template'
5 | }
6 |
7 | export function getPackId(modules: IManifestModule[]): TPackTypeId | undefined {
8 | for (const { type } of modules) {
9 | switch (type) {
10 | case 'data':
11 | return 'behaviorPack'
12 | case 'resources':
13 | return 'resourcePack'
14 | case 'skin_pack':
15 | return 'skinPack'
16 | case 'world_template':
17 | return 'worldTemplate'
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/utils/math/clamp.ts:
--------------------------------------------------------------------------------
1 | export function clamp(x: number, min: number, max: number) {
2 | return Math.min(Math.max(x, min), max)
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/math/randomInt.ts:
--------------------------------------------------------------------------------
1 | export function randomInt(min: number, max: number, inclusive = true) {
2 | if (inclusive) max++
3 | else min++
4 | return Math.floor(Math.random() * (max - min)) + min
5 | }
6 |
--------------------------------------------------------------------------------
/src/utils/minecraft/validPositionArray.ts:
--------------------------------------------------------------------------------
1 | export function isValidPositionArray(
2 | array: unknown
3 | ): array is [number, number, number] {
4 | return (
5 | Array.isArray(array) &&
6 | array.length === 3 &&
7 | typeof array[0] === 'number' &&
8 | typeof array[1] === 'number' &&
9 | typeof array[2] === 'number'
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/monaco/getLocation.ts:
--------------------------------------------------------------------------------
1 | import type { editor, Position } from 'monaco-editor'
2 | import { useJsoncParser } from '../libs/useJsoncParser'
3 |
4 | export async function getLocation(
5 | model: editor.ITextModel,
6 | position: Position,
7 | removeFinalIndex = true
8 | ): Promise {
9 | const { getLocation: jsoncGetLocation } = await useJsoncParser()
10 | const locationArr = jsoncGetLocation(
11 | model.getValue(),
12 | model.getOffsetAt(position)
13 | ).path
14 |
15 | // Lightning cache definition implicitly indexes arrays so we need to remove indexes if they are at the last path position
16 | if (
17 | removeFinalIndex &&
18 | !isNaN(Number(locationArr[locationArr.length - 1]))
19 | ) {
20 | locationArr.pop()
21 | }
22 |
23 | return locationArr.join('/')
24 | }
25 |
--------------------------------------------------------------------------------
/src/utils/monaco/withinQuotes.ts:
--------------------------------------------------------------------------------
1 | import type { editor, Position, Range } from 'monaco-editor'
2 |
3 | export function isWithinQuotes(model: editor.ITextModel, position: Position) {
4 | let line: string
5 | try {
6 | line = model.getLineContent(position.lineNumber)
7 | } catch {
8 | return false
9 | }
10 |
11 | const wordStart = getPreviousQuote(line, position.column)
12 | const wordEnd = getNextQuote(line, position.column)
13 | return wordStart && wordEnd
14 | }
15 |
16 | function getNextQuote(line: string, startIndex: number) {
17 | for (let i = startIndex - 1; i < line.length; i++) {
18 | if (line[i] === '"') return true
19 | }
20 | return false
21 | }
22 | function getPreviousQuote(line: string, startIndex: number) {
23 | for (let i = startIndex - 2; i > 0; i--) {
24 | if (line[i] === '"') return true
25 | }
26 | return false
27 | }
28 |
--------------------------------------------------------------------------------
/src/utils/os.ts:
--------------------------------------------------------------------------------
1 | // import { createErrorNotification } from '/@/appCycle/Errors'
2 |
3 | import { settingsState } from '/@/components/Windows/Settings/SettingsState'
4 |
5 | export function platform() {
6 | if (
7 | settingsState?.developers?.simulateOS &&
8 | settingsState.developers.simulateOS !== 'auto'
9 | )
10 | return <'win32' | 'linux' | 'darwin'>settingsState.developers.simulateOS
11 |
12 | const platform = navigator.platform.toLowerCase()
13 | if (platform.includes('win')) return 'win32'
14 | else if (platform.includes('linux')) return 'linux'
15 | else if (platform.includes('mac')) return 'darwin'
16 |
17 | console.error(`Unknown platform: ${platform}`)
18 | return 'win32'
19 |
20 | // Breaks vue components \_o_/
21 | // createErrorNotification(new Error(`Unknown platform: ${platform}`))
22 | }
23 |
--------------------------------------------------------------------------------
/src/utils/path.ts:
--------------------------------------------------------------------------------
1 | import path from 'path-browserify'
2 |
3 | export const dirname = path.dirname
4 | export const join = path.join
5 | export const extname = path.extname
6 | export const basename = path.basename
7 | export const resolve = path.resolve
8 | export const relative = path.relative
9 | export const isAbsolute = path.isAbsolute
10 | export const parse = path.parse
11 |
--------------------------------------------------------------------------------
/src/utils/pointerDevice.ts:
--------------------------------------------------------------------------------
1 | import { ref } from 'vue'
2 |
3 | type TPointerType = 'touch' | 'mouse' | 'pen'
4 | export const pointerDevice = ref('mouse')
5 |
6 | window.addEventListener(
7 | 'pointerdown',
8 | (event) => {
9 | pointerDevice.value = event.pointerType
10 | },
11 | { passive: true }
12 | )
13 |
--------------------------------------------------------------------------------
/src/utils/revealInFileExplorer.ts:
--------------------------------------------------------------------------------
1 | export async function revealInFileExplorer(path: string) {
2 | if (!import.meta.env.VITE_IS_TAURI_APP)
3 | throw new Error('This action is only available on Tauri builds')
4 |
5 | const { invoke } = await import('@tauri-apps/api/tauri')
6 |
7 | await invoke('reveal_in_file_explorer', {
8 | path,
9 | })
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/setRichPresence.ts:
--------------------------------------------------------------------------------
1 | interface RichPresenceOpts {
2 | details: string
3 | state: string
4 | }
5 |
6 | export async function setRichPresence(opts: RichPresenceOpts) {
7 | if (!import.meta.env.VITE_IS_TAURI_APP) return
8 |
9 | const { getCurrent } = await import('@tauri-apps/api/window')
10 | const window = await getCurrent()
11 |
12 | window.emit('setRichPresence', opts)
13 | }
14 |
15 | setRichPresence({
16 | details: 'Developing add-ons...',
17 | state: 'Idle',
18 | })
19 |
--------------------------------------------------------------------------------
/src/utils/string/closestMatch.ts:
--------------------------------------------------------------------------------
1 | import { editDistance } from './editDistance'
2 |
3 | /**
4 | * Return the closest match to a string in a list of strings.
5 | */
6 | export function closestMatch(
7 | str: string,
8 | strings: string[],
9 | threshold = 0.3
10 | ): string | null {
11 | const distances = strings.map((s) => editDistance(str, s))
12 | const min = Math.min(...distances)
13 | const index = distances.findIndex((d) => d === min)
14 | if (min / str.length > threshold) return null
15 |
16 | return strings[index]
17 | }
18 |
--------------------------------------------------------------------------------
/src/utils/typeof.ts:
--------------------------------------------------------------------------------
1 | export function getTypeOf(val: unknown) {
2 | if (Array.isArray(val)) return 'array'
3 | else if (val === null) return 'null'
4 | else if (typeof val === 'number') {
5 | if (Number.isInteger(val)) return 'integer'
6 | else return 'number'
7 | }
8 |
9 | return typeof val
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/wait.ts:
--------------------------------------------------------------------------------
1 | export function wait(timeMs: number) {
2 | return new Promise((resolve) => setTimeout(() => resolve(), timeMs))
3 | }
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "jsxImportSource": "solid-js",
8 | "importHelpers": true,
9 | "moduleResolution": "node",
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "resolveJsonModule": true,
13 | "allowSyntheticDefaultImports": true,
14 | "sourceMap": true,
15 | "baseUrl": ".",
16 | "types": [
17 | "@types/wicg-file-system-access",
18 | "node",
19 | "vite-plugin-pwa/client"
20 | ],
21 | "paths": {
22 | "/@/*": ["src/*"]
23 | },
24 | "lib": ["esnext", "dom", "dom.iterable", "scripthost", "WebWorker"],
25 | "forceConsistentCasingInFileNames": true
26 | },
27 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
28 | "exclude": ["node_modules"]
29 | }
30 |
--------------------------------------------------------------------------------