├── .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 | 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 | 14 | 15 | 27 | -------------------------------------------------------------------------------- /src/components/Compiler/Window/WatchMode/SettingSheet.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 4 | 5 | 24 | -------------------------------------------------------------------------------- /src/components/OutputFolders/ComMojang/Sidebar/ViewProject.vue: -------------------------------------------------------------------------------- 1 | 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 | 9 | 10 | 15 | -------------------------------------------------------------------------------- /src/components/PackExplorer/PackExplorer.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 27 | -------------------------------------------------------------------------------- /src/components/PackExplorer/ProjectDisplay.vue: -------------------------------------------------------------------------------- 1 | 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 | Logo of bridge. v2 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 | 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 | 22 | 23 | 39 | -------------------------------------------------------------------------------- /src/components/Toolbar/Menu/Button.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 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 | 4 | 5 | 26 | -------------------------------------------------------------------------------- /src/components/UIElements/Logo.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | -------------------------------------------------------------------------------- /src/components/UIElements/SelectedStatus.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 36 | -------------------------------------------------------------------------------- /src/components/ViewFolders/ViewFolders.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 8 | 9 | 24 | -------------------------------------------------------------------------------- /src/components/Windows/LoadingWindow/LoadingWindow.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 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 | 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 | --------------------------------------------------------------------------------