├── .github ├── ISSUE_TEMPLATE │ └── bug_report.yml └── workflows │ └── build.yml ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── README.md ├── banner.png ├── package.json ├── renderer.d.ts ├── 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 │ └── main.rs └── tauri.conf.json ├── tsconfig.json ├── ui ├── assets │ ├── OpenSans.ttf │ ├── add.png │ ├── editor.png │ ├── element.png │ ├── glue.wasm │ ├── home-btn.png │ ├── home │ │ ├── cogwheel.png │ │ ├── dashboard.png │ │ ├── folder.png │ │ ├── marketplace.png │ │ └── user.png │ ├── logo.png │ ├── marketplace.png │ ├── mul transparent.png │ ├── mul.png │ ├── run-app.png │ └── script.png ├── babel.config.json ├── index.html ├── src │ ├── BaseComponentProps.ts │ ├── DarkMode.scss │ ├── Error.scss │ ├── ErrorBoundary.tsx │ ├── components │ │ ├── Dialog.scss │ │ ├── Dialog.tsx │ │ ├── Separator.tsx │ │ ├── TabbedSidebar │ │ │ ├── TabbedSidebar.scss │ │ │ └── index.tsx │ │ ├── Tabs.scss │ │ └── Tabs.tsx │ ├── index.tsx │ ├── libs │ │ ├── configFiles.tsx │ │ ├── elements │ │ │ └── builtin.tsx │ │ ├── getAsset.ts │ │ ├── github │ │ │ └── index.tsx │ │ ├── internal.ts │ │ ├── logic │ │ │ └── index.ts │ │ ├── newMainLuaFile.ts │ │ ├── plugin │ │ │ └── index.ts │ │ ├── pocketbase.ts │ │ ├── project.tsx │ │ ├── projectBinary.ts │ │ ├── settings │ │ │ ├── defaultSettings.ts │ │ │ ├── index.ts │ │ │ └── settingsType.ts │ │ ├── tauriFS.ts │ │ ├── theme │ │ │ ├── example.json │ │ │ └── index.ts │ │ └── utils │ │ │ ├── addSpaceAtCapitals.ts │ │ │ └── capitalizeFirstLetter.ts │ ├── main.scss │ ├── plugins │ │ ├── test │ │ │ ├── index.js │ │ │ └── proton.config.js │ │ └── test2 │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ └── proton.config.js │ └── screens │ │ ├── editor │ │ ├── Console.tsx │ │ ├── Editor.scss │ │ ├── Editor.tsx │ │ ├── Hierarchy.tsx │ │ ├── Inspector.tsx │ │ ├── Preview.tsx │ │ ├── TextEditor.scss │ │ ├── TextEditor.tsx │ │ ├── TextEditorFileList.tsx │ │ └── Tools.tsx │ │ └── home │ │ ├── Home.scss │ │ ├── Home.tsx │ │ ├── Sidebar.tsx │ │ ├── dashboard │ │ ├── dashboard.scss │ │ └── index.tsx │ │ ├── marketplace │ │ ├── index.tsx │ │ └── marketplace.scss │ │ ├── settings │ │ ├── ThemeSettingPanel.tsx │ │ ├── index.tsx │ │ └── settings.scss │ │ └── user │ │ ├── index.tsx │ │ └── user.scss └── vite.config.js ├── update_versions.py └── yarn.lock /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | assignees: 6 | - TechStudent10 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thanks for taking the time to fill out this bug report! 12 | - type: textarea 13 | id: what-happened 14 | attributes: 15 | label: What happened? 16 | description: Also, what did you expect to happen? 17 | placeholder: X happened but I expected Y to happen. 18 | validations: 19 | required: true 20 | - type: input 21 | id: version 22 | attributes: 23 | label: Version 24 | description: What version of Proton are you running? 25 | validations: 26 | required: true 27 | - type: checkboxes 28 | id: duplicates 29 | attributes: 30 | label: Duplicate Issues 31 | description: By submitting this issue, you agree that you have searched the Issues tab and have found no issues relating to your problem OR the solutions provided don't work for you. 32 | options: 33 | - label: There are no duplicate issues/the solutions provided don't work for me. 34 | required: true -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Release App 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | release: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | platform: [macos-latest, ubuntu-20.04, windows-latest] 14 | runs-on: ${{ matrix.platform }} 15 | steps: 16 | - name: Checkout repository 17 | uses: actions/checkout@v3 18 | 19 | - name: Install dependencies (ubuntu only) 20 | if: matrix.platform == 'ubuntu-20.04' 21 | # You can remove libayatana-appindicator3-dev if you don't use the system tray feature. 22 | run: | 23 | sudo apt-get update 24 | sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev 25 | 26 | - name: Rust setup 27 | uses: dtolnay/rust-toolchain@stable 28 | 29 | - name: Rust cache 30 | uses: swatinem/rust-cache@v2 31 | with: 32 | workspaces: './src-tauri -> target' 33 | 34 | - name: Sync node version and setup cache 35 | uses: actions/setup-node@v3 36 | with: 37 | node-version: 'lts/*' 38 | cache: 'yarn' # Set this to npm, yarn or pnpm. 39 | 40 | - name: Install app dependencies and build web 41 | # Remove `&& yarn build` if you build your frontend in `beforeBuildCommand` 42 | run: yarn # Change this to npm, yarn or pnpm. 43 | 44 | - name: Build the app 45 | uses: tauri-apps/tauri-action@v0 46 | 47 | env: 48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 49 | with: 50 | tagName: ${{ github.ref_name }} # This only works if your workflow triggers on new tags. 51 | releaseName: 'Proton Designer v__VERSION__' # tauri-action replaces \_\_VERSION\_\_ with the app version. 52 | releaseBody: "See the assets to download and install this version. Join the Discord Server: https://discord.gg/xvACqqYDer" 53 | releaseDraft: true 54 | prerelease: false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs/*.log 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | lerna-debug.log* 7 | 8 | # Diagnostic reports (https://nodejs.org/api/report.html) 9 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | *.lcov 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (https://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # TypeScript v1 declaration files 44 | typings/ 45 | 46 | # TypeScript cache 47 | *.tsbuildinfo 48 | 49 | # Optional npm cache directory 50 | .npm 51 | 52 | # Optional eslint cache 53 | .eslintcache 54 | 55 | # Microbundle cache 56 | .rpt2_cache/ 57 | .rts2_cache_cjs/ 58 | .rts2_cache_es/ 59 | .rts2_cache_umd/ 60 | 61 | # Optional REPL history 62 | .node_repl_history 63 | 64 | # Output of 'npm pack' 65 | *.tgz 66 | 67 | # Yarn Integrity file 68 | .yarn-integrity 69 | 70 | # dotenv environment variables file 71 | .env 72 | .env.test 73 | 74 | # parcel-bundler cache (https://parceljs.org/) 75 | .cache 76 | 77 | # Next.js build output 78 | .next 79 | 80 | # Nuxt.js build / generate output 81 | .nuxt 82 | dist 83 | 84 | # Gatsby files 85 | .cache/ 86 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 87 | # https://nextjs.org/blog/next-9-1#public-directory-support 88 | # public 89 | 90 | # vuepress build output 91 | .vuepress/dist 92 | 93 | # Serverless directories 94 | .serverless/ 95 | 96 | # FuseBox cache 97 | .fusebox/ 98 | 99 | # DynamoDB Local files 100 | .dynamodb/ 101 | 102 | # TernJS port file 103 | .tern-port 104 | 105 | # Other things 106 | ts-build/ 107 | user_data/ 108 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Debug Main Process", 8 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", 9 | "program": "${workspaceRoot}/index.js", 10 | "runtimeArgs": [ 11 | ".", 12 | // this args for attaching render process 13 | "--remote-debugging-port=9222" 14 | ], 15 | "windows": { 16 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" 17 | } 18 | }, 19 | { 20 | "type": "chrome", 21 | "request": "attach", 22 | "name": "Attach to Render Process", 23 | "port": 9222, 24 | "webRoot": "${workspaceRoot}/ui" 25 | } 26 | ], 27 | "compounds": [ 28 | { 29 | "name": "Debug Main and Renderer", 30 | "configurations": ["Debug Main Process", "Attach to Render Process"] 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "2.0.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Electron: Main", 11 | "preLaunchTask": "webpack", 12 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", 13 | "program": "${workspaceFolder}/main.js", 14 | "runtimeArgs": [ 15 | ".", 16 | "--enable-logging", 17 | "--remote-debugging-port=9223", 18 | ] 19 | }, 20 | { 21 | "name": "Electron: Renderer", 22 | "type": "chrome", 23 | "request": "attach", 24 | "port": 9223, 25 | "webRoot": "${workspaceFolder}", 26 | "timeout": 30000 27 | } 28 | ], 29 | "compounds": [ 30 | { 31 | "name": "Electron: All", 32 | "configurations": [ 33 | "Electron: Main", 34 | "Electron: Renderer" 35 | ] 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 TechStudent10 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Proton Banner](banner.png) 2 | 3 | # Proton Designer 4 | App designer, builder and prototyper. 5 | 6 | > CAUTION: THIS IS PRE-RELEASE SOFTWARE. I DO NOT GUARANTEE THAT THIS WILL WORK UNDER ALL CIRCUMSTANCES. USE AT YOUR OWN RISK. 7 | 8 | Releases can be found in the Releases tab. 9 | 10 | --- 11 | 12 | ## Developing and Building 13 | 14 | ### Installing Dependencies 15 | 16 | 1. Clone the repo and change directory to it 17 | ```bash 18 | git clone https://github.com/ProtonDesigner/core 19 | cd core 20 | ``` 21 | 22 | 2. Install the dependencies 23 | ```bash 24 | yarn 25 | ``` 26 | 27 | ### Running 28 | 29 | 1. Run the application 30 | ```bash 31 | yarn dev 32 | ``` 33 | 34 | ## Building 35 | 36 | 1. Run this command. It should put the executable in the `dist/` folder 37 | ```bash 38 | yarn build 39 | ``` 40 | 41 | --- 42 | 43 | For support, showcasing, and ideas, join the Discord server: https://discord.gg/xvACqqYDer 44 | 45 | --- 46 | 47 | More to come 👀 48 | -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/banner.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proton", 3 | "version": "0.4.0", 4 | "description": "The all-in-one app builder for Windows, Mac and Linux.", 5 | "repository": "https://github.com/ProtonDesigner/rewrite", 6 | "author": "TechStudent10", 7 | "license": "MIT", 8 | "type": "module", 9 | "devDependencies": { 10 | "@tauri-apps/cli": "^1.5.6", 11 | "@types/http-proxy": "^1.17.9", 12 | "@types/node": "^18.11.18", 13 | "@types/react": "^18.0.26", 14 | "@types/react-dom": "^18.0.10", 15 | "@types/sync-fetch": "^0.4.0", 16 | "@types/uuid": "^9.0.0", 17 | "@vitejs/plugin-react": "^3.1.0", 18 | "ts-loader": "^9.4.2", 19 | "ts-node": "^10.9.1", 20 | "tsc": "^2.0.4", 21 | "typescript": "^4.9.4", 22 | "vite": "^4.5.3", 23 | "vite-plugin-html": "^3.2.0", 24 | "vite-plugin-wasm": "^3.2.1" 25 | }, 26 | "dependencies": { 27 | "@monaco-editor/react": "^4.4.6", 28 | "@neodrag/react": "^1.0.1", 29 | "@tauri-apps/api": "^1.2.0", 30 | "@techstudent10/plugin": "^1.0.3", 31 | "cross-fetch": "^3.1.5", 32 | "eventsource": "^2.0.2", 33 | "monaco-editor": "^0.34.1", 34 | "path-browserify": "^1.0.1", 35 | "pocketbase": "^0.9.0", 36 | "react": "^18.2.0", 37 | "react-dom": "^18.2.0", 38 | "react-router-dom": "^6.4.2", 39 | "react-zoom-pan-pinch": "^2.6.1", 40 | "sass": "^1.55.0", 41 | "sync-fetch": "^0.4.2", 42 | "unique-names-generator": "^4.7.1", 43 | "uuid": "^9.0.0", 44 | "wasmoon": "^1.14.1" 45 | }, 46 | "scripts": { 47 | "electron": "electron .", 48 | "watch:www": "cd ui && vite serve", 49 | "build:www": "cd ui && vite build", 50 | "build:app": "yarn tauri build", 51 | "build": "yarn build:app", 52 | "dev": "yarn tauri dev" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /renderer.d.ts: -------------------------------------------------------------------------------- 1 | export interface IElectronAPI { 2 | getSettings: () => {}, 3 | getPlugins: () => Promise, 4 | getPlugin: (pluginName: string) => any, 5 | logInfo: (message: string) => void, 6 | logWarn: (message: string) => void, 7 | logErr: (message: string) => void, 8 | // Local script stuff 9 | // createTempDir: (name: string, cb: () => {}) => {}, 10 | // watchForChanges: (scriptDir: string, cb: () => {}) => {} 11 | } 12 | 13 | declare global { 14 | interface Window { 15 | electronAPI: IElectronAPI, 16 | } 17 | } 18 | 19 | declare module '*!text' { 20 | var _: string; 21 | export default _; 22 | } 23 | -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proton-designer" 3 | version = "0.4.0" 4 | description = "Proton Designer is an application builder." 5 | authors = [ "TechStudent10",] 6 | license = "MIT" 7 | repository = "https://github.com/ProtonDesigner/core" 8 | default-run = "proton-designer" 9 | edition = "2021" 10 | rust-version = "1.59" 11 | 12 | [dependencies] 13 | serde_json = "1.0" 14 | home = "0.5.4" 15 | 16 | [features] 17 | default = [ "custom-protocol",] 18 | custom-protocol = [ "tauri/custom-protocol",] 19 | 20 | [dependencies.serde] 21 | version = "1.0" 22 | features = [ "derive",] 23 | 24 | [dependencies.tauri] 25 | version = "1.2.5" 26 | features = [ "shell-open", "dialog-save", "fs-create-dir", "fs-exists", "fs-remove-file", "fs-rename-file", "fs-write-file",] 27 | 28 | [build-dependencies.tauri-build] 29 | version = "1.2.1" 30 | features = [] 31 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | 5 | // use tauri_build::{try_build, Attributes, WindowsAttributes}; 6 | 7 | // fn main() { 8 | // let win_att = WindowsAttributes::new().sdk_dir("C:/Program Files (x86)/Windows Kits/10/bin/10.0.22621.0/x64/"); 9 | // if let Err(error) = try_build(Attributes::new().windows_attributes(win_att)) { 10 | // panic!("error found during tauri-build: {}", error); 11 | // } 12 | // } -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | all(not(debug_assertions), target_os = "windows"), 3 | windows_subsystem = "windows" 4 | )] 5 | 6 | use std::fs; 7 | use std::path::Path; 8 | use std::env; 9 | use home; 10 | 11 | // Some basic FS stuff (because Tauri limits it in the frontend) 12 | fn get_current_working_dir() -> String { 13 | let res = env::current_dir(); 14 | match res { 15 | Ok(path) => path.into_os_string().into_string().unwrap(), 16 | Err(_) => "FAILED".to_string() 17 | } 18 | } 19 | 20 | #[tauri::command] 21 | fn cwd() -> String { 22 | return get_current_working_dir(); 23 | } 24 | 25 | #[tauri::command] 26 | fn list_dir(directory: String) -> Vec { 27 | let path = Path::new(&get_current_working_dir()).join("..").join(directory); 28 | println!("{}", path.display()); 29 | let paths = fs::read_dir(path).unwrap(); 30 | let names = paths.map(|entry| { 31 | let entry = entry.unwrap(); 32 | 33 | let entry_path = entry.path(); 34 | 35 | let file_name = entry_path.file_name().unwrap(); 36 | 37 | let file_name_as_str = file_name.to_str().unwrap(); 38 | 39 | let file_name_as_string = String::from(file_name_as_str); 40 | 41 | file_name_as_string 42 | }).collect::>(); 43 | names 44 | } 45 | 46 | #[tauri::command] 47 | fn read_file(file_path: String) -> String { 48 | let path = Path::new(&get_current_working_dir()).join("..").join(file_path); 49 | let data = fs::read_to_string(path).expect("Unable to read file"); 50 | data 51 | } 52 | 53 | #[tauri::command] 54 | fn write_file(file_path: String, data: String) -> Result<(), ()> { 55 | fs::write(file_path, data).expect("Unable to write to file"); 56 | Ok(()) 57 | } 58 | 59 | #[tauri::command] 60 | fn mkdir(directory_path: String) -> Result<(), ()> { 61 | fs::create_dir_all(directory_path).expect("Unable to create directory"); 62 | 63 | Ok(()) 64 | } 65 | 66 | #[tauri::command] 67 | fn exists(path: String) -> bool { 68 | return Path::new(&path).exists() 69 | } 70 | 71 | #[tauri::command] 72 | fn rmdir(directory_path: String) -> Result<(), ()> { 73 | fs::remove_dir_all(directory_path).expect("Unable to delete directory"); 74 | 75 | Ok(()) 76 | } 77 | 78 | #[tauri::command] 79 | fn rmfile(path: String) -> Result<(), ()> { 80 | fs::remove_file(path).expect("Unable to delete file"); 81 | 82 | Ok(()) 83 | } 84 | 85 | // General utility stuff 86 | #[tauri::command] 87 | fn get_home_dir() -> String { 88 | return String::from(home::home_dir().unwrap().to_string_lossy()); 89 | } 90 | 91 | fn main() { 92 | tauri::Builder::default() 93 | .invoke_handler(tauri::generate_handler![ 94 | list_dir, 95 | read_file, 96 | write_file, 97 | mkdir, 98 | cwd, 99 | exists, 100 | get_home_dir, 101 | rmdir, 102 | rmfile 103 | ]) 104 | .run(tauri::generate_context!()) 105 | .expect("error while running tauri application"); 106 | } 107 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../node_modules/@tauri-apps/cli/schema.json", 3 | "build": { 4 | "beforeBuildCommand": "yarn build:www", 5 | "beforeDevCommand": "yarn watch:www", 6 | "devPath": "http://localhost:5173", 7 | "distDir": "../ui/dist" 8 | }, 9 | "package": { 10 | "productName": "Proton Designer", 11 | "version": "0.4.0" 12 | }, 13 | "tauri": { 14 | "allowlist": { 15 | "all": false, 16 | "dialog": { 17 | "all": false, 18 | "ask": false, 19 | "confirm": false, 20 | "message": false, 21 | "open": false, 22 | "save": true 23 | }, 24 | "fs": { 25 | "all": false, 26 | "readFile": false, 27 | "writeFile": true, 28 | "readDir": false, 29 | "copyFile": false, 30 | "createDir": true, 31 | "removeDir": false, 32 | "removeFile": true, 33 | "renameFile": true, 34 | "exists": true, 35 | "scope": [ 36 | "$LOG/*" 37 | ] 38 | }, 39 | "shell": { 40 | "all": false, 41 | "execute": false, 42 | "open": true, 43 | "scope": [], 44 | "sidecar": false 45 | } 46 | }, 47 | "bundle": { 48 | "active": true, 49 | "category": "DeveloperTool", 50 | "copyright": "", 51 | "deb": { 52 | "depends": [] 53 | }, 54 | "externalBin": [], 55 | "icon": [ 56 | "icons/32x32.png", 57 | "icons/128x128.png", 58 | "icons/128x128@2x.png", 59 | "icons/icon.icns", 60 | "icons/icon.ico" 61 | ], 62 | "identifier": "com.techstudent10.proton", 63 | "longDescription": "", 64 | "macOS": { 65 | "entitlements": null, 66 | "exceptionDomain": "", 67 | "frameworks": [], 68 | "providerShortName": null, 69 | "signingIdentity": null 70 | }, 71 | "resources": [], 72 | "shortDescription": "", 73 | "targets": "all", 74 | "windows": { 75 | "certificateThumbprint": null, 76 | "digestAlgorithm": "sha256", 77 | "timestampUrl": "" 78 | } 79 | }, 80 | "security": { 81 | "csp": null 82 | }, 83 | "updater": { 84 | "active": false 85 | }, 86 | "windows": [ 87 | { 88 | "fullscreen": false, 89 | "height": 600, 90 | "resizable": true, 91 | "title": "Proton Designer", 92 | "width": 800 93 | } 94 | ] 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "esnext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | "jsx": "react", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "ESNext", /* Specify what module code is generated. */ 29 | "rootDir": "./", /* Specify the root folder within your source files. */ 30 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | "baseUrl": "./ui/src", /* Specify the base directory to resolve non-relative module names. */ 32 | "paths": { 33 | "@components/*": ["components/*"], 34 | "@libs/*": ["libs/*"], 35 | "@styles/*": ["styles/*"], 36 | "@root/*": ["*"] 37 | }, /* Specify a set of entries that re-map imports to additional lookup locations. */ 38 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 39 | "typeRoots": [ 40 | "node_modules/@types" 41 | ], /* Specify multiple folders that act like './node_modules/@types'. */ 42 | // "types": [ 43 | // "http-proxy" 44 | // ], /* Specify type package names to be included without being referenced in a source file. */ 45 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 46 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 47 | // "resolveJsonModule": true, /* Enable importing .json files. */ 48 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 49 | 50 | /* JavaScript Support */ 51 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 52 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 53 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 54 | 55 | /* Emit */ 56 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 57 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 58 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 59 | "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 60 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 61 | "outDir": "./ts-build", /* Specify an output folder for all emitted files. */ 62 | // "removeComments": true, /* Disable emitting comments. */ 63 | // "noEmit": true, /* Disable emitting files from a compilation. */ 64 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 65 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 66 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 67 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 68 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 69 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 70 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 71 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 72 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 73 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 74 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 75 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 76 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 77 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 78 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 79 | 80 | /* Interop Constraints */ 81 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 82 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 83 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 84 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 85 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 86 | 87 | /* Type Checking */ 88 | "strict": true, /* Enable all strict type-checking options. */ 89 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 90 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 91 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 92 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 93 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 94 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 95 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 96 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 97 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 98 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 99 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 100 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 101 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 102 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 103 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 104 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 105 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 106 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 107 | 108 | /* Completeness */ 109 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 110 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /ui/assets/OpenSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/ui/assets/OpenSans.ttf -------------------------------------------------------------------------------- /ui/assets/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/ui/assets/add.png -------------------------------------------------------------------------------- /ui/assets/editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/ui/assets/editor.png -------------------------------------------------------------------------------- /ui/assets/element.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/ui/assets/element.png -------------------------------------------------------------------------------- /ui/assets/glue.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/ui/assets/glue.wasm -------------------------------------------------------------------------------- /ui/assets/home-btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/ui/assets/home-btn.png -------------------------------------------------------------------------------- /ui/assets/home/cogwheel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/ui/assets/home/cogwheel.png -------------------------------------------------------------------------------- /ui/assets/home/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/ui/assets/home/dashboard.png -------------------------------------------------------------------------------- /ui/assets/home/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/ui/assets/home/folder.png -------------------------------------------------------------------------------- /ui/assets/home/marketplace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/ui/assets/home/marketplace.png -------------------------------------------------------------------------------- /ui/assets/home/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/ui/assets/home/user.png -------------------------------------------------------------------------------- /ui/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/ui/assets/logo.png -------------------------------------------------------------------------------- /ui/assets/marketplace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/ui/assets/marketplace.png -------------------------------------------------------------------------------- /ui/assets/mul transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/ui/assets/mul transparent.png -------------------------------------------------------------------------------- /ui/assets/mul.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/ui/assets/mul.png -------------------------------------------------------------------------------- /ui/assets/run-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/ui/assets/run-app.png -------------------------------------------------------------------------------- /ui/assets/script.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtonDesigner/core/ed0d96044a524ff58a60ed10b989bfc549e77537/ui/assets/script.png -------------------------------------------------------------------------------- /ui/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "10" 8 | } 9 | } 10 | ], 11 | "@babel/preset-react" 12 | ], 13 | "plugins": ["@babel/plugin-proposal-class-properties"] 14 | } -------------------------------------------------------------------------------- /ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Proton Designer 8 | 18 | 19 | 20 |
21 | 22 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ui/src/BaseComponentProps.ts: -------------------------------------------------------------------------------- 1 | import PluginManager from "./libs/plugin" 2 | import SettingsManager from "@libs/settings" 3 | 4 | export default interface BaseComponentProps { 5 | className?: string 6 | state: any 7 | setState(newState: Object): void 8 | setPage(newState: number): void 9 | pluginManager: PluginManager 10 | dialogUtils: { 11 | createDialog: Function, 12 | setDialog: Function, 13 | showDialog: Function, 14 | hideDialog: Function 15 | } 16 | settingsManager: SettingsManager 17 | } -------------------------------------------------------------------------------- /ui/src/DarkMode.scss: -------------------------------------------------------------------------------- 1 | .dark { 2 | background-color: hsl(0, 0%, 11%); 3 | color: white; 4 | 5 | // Home page 6 | &.home { 7 | .sidebar { 8 | background-color: hsl(0, 0%, 29%); 9 | } 10 | 11 | button:not(.primary):not(.danger):not(.tab__button button) { 12 | color: white; 13 | border-color: white; 14 | 15 | &:hover { 16 | color: black; 17 | background-color: white; 18 | } 19 | } 20 | 21 | .tab__button button { 22 | color: white; 23 | } 24 | 25 | } 26 | 27 | // Settings page 28 | 29 | } -------------------------------------------------------------------------------- /ui/src/Error.scss: -------------------------------------------------------------------------------- 1 | .error { 2 | text-align: center; 3 | margin-top: 25vh; 4 | height: 1vh; 5 | 6 | pre { 7 | background-color: rgb(153, 153, 153); 8 | font-family: monospace; 9 | overflow: scroll; 10 | 11 | text-align: left; 12 | } 13 | 14 | // overflow: scroll; 15 | } -------------------------------------------------------------------------------- /ui/src/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import "./Error.scss" 3 | 4 | type Props = { 5 | children: React.ReactNode 6 | } 7 | type State = { 8 | hasError: boolean, 9 | error: null | Error 10 | } 11 | 12 | class ErrorBoundary extends React.Component { 13 | state: { 14 | hasError: boolean, 15 | error: null | Error, 16 | errorInfo: null | React.ErrorInfo 17 | } = { 18 | hasError: false, 19 | error: null, 20 | errorInfo: null 21 | } 22 | 23 | static getDerivedStateFromError(error: Error) { 24 | return { 25 | hasError: true, 26 | error 27 | } 28 | } 29 | 30 | componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void { 31 | console.log(error, errorInfo) 32 | this.state.errorInfo = errorInfo 33 | } 34 | 35 | render() { 36 | if (this.state.hasError) { 37 | return
38 |

An error occurred!

39 |
40 |                     
41 |                         {this.state.error?.stack}
42 |                     
43 |                 
44 | 45 | 46 |
47 | } 48 | return this.props.children 49 | } 50 | } 51 | 52 | export default ErrorBoundary 53 | -------------------------------------------------------------------------------- /ui/src/components/Dialog.scss: -------------------------------------------------------------------------------- 1 | .dialog { 2 | position: absolute; 3 | top: 50%; 4 | left: 50%; 5 | transform: translate(-50%, -100%); 6 | border-radius: 0.7rem; 7 | padding: 15px; 8 | box-shadow: 0px 0px 45px 0px rgba(0,0,0,0.75); 9 | z-index: 0; 10 | background-color: white; 11 | 12 | opacity: 0; 13 | &.show { 14 | opacity: 1; 15 | z-index: 10; 16 | transform: translate(-50%, -50%); 17 | } 18 | img.close__btn { 19 | $size: 30px; 20 | width: $size; 21 | height: $size; 22 | cursor: pointer; 23 | position: absolute; 24 | left: 100%; 25 | padding: 5px; 26 | margin-left: 10px; 27 | border-radius: 50rem; 28 | } 29 | .top { 30 | display: flex; 31 | align-items: center; 32 | gap: 10px; 33 | flex-wrap: wrap; 34 | } 35 | 36 | transition: all ease-in 0.5s; 37 | } 38 | 39 | .screen { 40 | position: absolute; 41 | top: 0; 42 | left: 0; 43 | width: 100vw; 44 | height: 100vh; 45 | background-color: rgba(0, 0, 0, 0.7); 46 | z-index: -9; 47 | 48 | opacity: 0; 49 | &.show { 50 | z-index: 9; 51 | opacity: 1; 52 | } 53 | transition: all ease-in-out 0.5s; 54 | } -------------------------------------------------------------------------------- /ui/src/components/Dialog.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, ReactNode } from "react" 2 | import "./Dialog.scss" 3 | 4 | import getAsset from "@libs/getAsset" 5 | 6 | interface DialogProps { 7 | style?: object 8 | children: any 9 | title?: string 10 | show: boolean 11 | setShow: (value: boolean) => void 12 | className?: string 13 | } 14 | 15 | export default function Dialog(props: DialogProps) { 16 | return <> 17 |
props.setShow(false)} /> 18 |
19 |
20 | {props.title &&

{props.title}

} 21 | props.setShow(false)} /> 22 |
23 | {props.children} 24 |
25 | 26 | } 27 | -------------------------------------------------------------------------------- /ui/src/components/Separator.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from "react"; 2 | 3 | interface SeparatorProps { 4 | color?: string 5 | width?: string 6 | thickness?: string 7 | } 8 | 9 | export default function Separator(props: SeparatorProps) { 10 | return
15 | } 16 | -------------------------------------------------------------------------------- /ui/src/components/TabbedSidebar/TabbedSidebar.scss: -------------------------------------------------------------------------------- 1 | .sidebar__tabbed { 2 | display: flex; 3 | .tabs { 4 | display: flex; 5 | flex-direction: column; 6 | 7 | .tab__button { 8 | button { 9 | padding: 10px; 10 | background-color: inherit; 11 | border: none; 12 | outline: none; 13 | cursor: pointer; 14 | } 15 | 16 | .line { 17 | height: 100%; 18 | width: 5px; 19 | background-color: none; 20 | } 21 | 22 | &:hover { 23 | .line { 24 | background-color: grey; 25 | } 26 | } 27 | 28 | &.active { 29 | .line { 30 | background-color: black; 31 | } 32 | } 33 | 34 | display: flex; 35 | justify-content: space-between; 36 | } 37 | } 38 | 39 | .content { 40 | margin-left: 10px; 41 | margin-top: 30px; 42 | } 43 | } -------------------------------------------------------------------------------- /ui/src/components/TabbedSidebar/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { RefObject, useEffect, useRef, useState } from "react" 2 | import "./TabbedSidebar.scss" 3 | 4 | export interface Tab { 5 | title: string 6 | content: JSX.Element 7 | } 8 | 9 | interface Props { 10 | tabs: (ref: RefObject, state: ReturnType>) => Tab[] 11 | title?: string 12 | } 13 | 14 | function TabButton(props: { 15 | onClick: (e: React.MouseEvent) => any 16 | isActive: boolean 17 | text: string 18 | }) { 19 | const { onClick, isActive, text } = props 20 | return
21 | 24 |
25 |
26 | } 27 | 28 | function TabbedSidebar(props: Props) { 29 | const [currentTab, setCurrentTab] = useState(0) 30 | const [tabs, setTabs] = useState([]) 31 | 32 | const inputRef = useRef() as RefObject 33 | const inputState = useState("") 34 | 35 | let { tabs: _tabs, title } = props 36 | let loaded = false 37 | 38 | useEffect(() => { 39 | if (!loaded) { 40 | const tabs = _tabs(inputRef, inputState) 41 | console.log(tabs) 42 | setTabs(tabs) 43 | loaded = true 44 | } 45 | }, []) 46 | 47 | const ContentComponent = tabs.length === 0 ? <> : tabs[currentTab].content 48 | 49 | return
50 |
51 |

{title}

52 | {tabs && tabs.map((tab, index) => { 53 | return { 54 | setCurrentTab(index) 55 | }} /> 56 | })} 57 |
58 |
59 | { tabs && ContentComponent } 60 |
61 |
62 | } 63 | 64 | export default TabbedSidebar 65 | -------------------------------------------------------------------------------- /ui/src/components/Tabs.scss: -------------------------------------------------------------------------------- 1 | $item-active: darkgrey; 2 | $tools-height: 50px; 3 | 4 | $item-padding: 5px; 5 | 6 | $bar-height: 35px; 7 | 8 | .tabs { 9 | .bar { 10 | display: flex; 11 | gap: 10px; 12 | height: $bar-height; 13 | background-color: hsl(0, 0%, 23%); 14 | 15 | .item { 16 | padding: $item-padding; 17 | 18 | background-color: lightgrey; 19 | border: black 1px solid; 20 | border-bottom: none; 21 | 22 | &.active { 23 | background-color: $item-active; 24 | } 25 | } 26 | } 27 | .content { 28 | background-color: $item-active; 29 | height: calc(100vh - $bar-height); 30 | 31 | &.editor { 32 | height: calc(100vh - $bar-height - $tools-height); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /ui/src/components/Tabs.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import "./Tabs.scss" 3 | 4 | export interface Tab { 5 | title: string 6 | content?: JSX.Element 7 | onClick?: Function 8 | } 9 | 10 | interface TabsProps { 11 | tabs: Array 12 | currentIndex?: number 13 | setCurrentIndex?: (index: number) => void 14 | isEditor?: boolean 15 | className?: string 16 | onChange?: (index: number) => void 17 | } 18 | 19 | export default function Tabs(props: TabsProps) { 20 | let currentIndex: number; 21 | let setCurrentIndex: ((index: number) => void); 22 | if (props.currentIndex) { 23 | currentIndex = props.currentIndex; 24 | setCurrentIndex = props.setCurrentIndex || useState(0)[1] 25 | } else { 26 | [currentIndex, setCurrentIndex] = useState(0) 27 | } 28 | 29 | return
30 |
31 | {props.tabs.map((tab: Tab, index: number) => { 32 | return
{ 35 | if (tab.content) { 36 | setCurrentIndex(index) 37 | } else if (tab.onClick) { 38 | tab.onClick(e) 39 | } else { 40 | throw `Tab must have either \`content\` or \`onClick\`. Error occurred on tab ${tab.title}.` 41 | } 42 | 43 | props.onChange && props.onChange(index) 44 | }} 45 | key={tab.title} 46 | > 47 | {tab.title} 48 |
49 | })} 50 |
51 |
52 | {props.tabs.length > 0 && props.tabs[currentIndex]?.content} 53 |
54 |
55 | } 56 | -------------------------------------------------------------------------------- /ui/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, FC } from 'react'; 2 | import { createRoot } from "react-dom/client"; 3 | 4 | import Home from "./screens/home/Home" 5 | import Editor from "./screens/editor/Editor" 6 | import PluginManager from './libs/plugin'; 7 | import Dialog from './components/Dialog'; 8 | 9 | import { open } from "@tauri-apps/api/shell" 10 | import SettingsManager, { SettingsManagerOptions, SettingsType } from "@libs/settings" 11 | import { initConfigDirs } from '@libs/configFiles'; 12 | 13 | import "./DarkMode.scss" 14 | 15 | // Opening links in the default browser 16 | Object.values(document.getElementsByTagName("a")).map(anchorElement => { 17 | if (anchorElement.target == "_blank") { 18 | anchorElement.onclick = (e) => { 19 | open( 20 | anchorElement.href 21 | ) 22 | } 23 | } 24 | }) 25 | 26 | initConfigDirs() 27 | 28 | declare global { 29 | interface Window { 30 | customNamespace: { 31 | DEBUG: boolean 32 | } 33 | } 34 | } 35 | 36 | window.customNamespace = window.customNamespace || {} 37 | window.customNamespace.DEBUG = window.location.href === "http://localhost:5173/" 38 | 39 | import ThemeLoader from '@libs/theme'; 40 | 41 | // invoke("greet", { name: "world "}).then(response => { 42 | // console.log(response) 43 | // }) 44 | 45 | // invoke("list_dir", { directory: "C:\\" }).then((response) => { 46 | // const data = response as string[] 47 | // console.log(data) 48 | // }) 49 | 50 | import "./main.scss" 51 | import ErrorBoundary from './ErrorBoundary'; 52 | 53 | const pluginManager = new PluginManager() 54 | const settingsManager = new SettingsManager({}) 55 | const themeLoader = new ThemeLoader() 56 | 57 | const pages = [ 58 | Home, 59 | () => {return <>}, 60 | Editor 61 | ] 62 | 63 | themeLoader.loadTheme() 64 | 65 | function App(props: any) { 66 | const [state, setState] = useState({}) 67 | const [currentPage, setPage] = useState(0) 68 | 69 | const [dialogTitle, setDialogTitle] = useState("") 70 | const [dialogContents, setDialogContents] = useState(<>) 71 | const [dialogShow, setDialogShow] = useState(false) 72 | 73 | const setNewPage = (page: number) => { 74 | setPage(page) 75 | } 76 | 77 | let Screen = pages[currentPage] 78 | 79 | function createDialog(title: string, contents: JSX.Element) { 80 | console.log(title, contents) 81 | const newDialog = { 82 | title: title, 83 | contents: contents, 84 | show: false 85 | } 86 | return newDialog 87 | } 88 | 89 | // the `dialog` parameter is just the return type of `createDialog` 90 | function setDialog(dialog: ReturnType) { 91 | setDialogTitle(dialog.title) 92 | setDialogContents(dialog.contents) 93 | setDialogShow(dialog.show) 94 | } 95 | 96 | function showDialog() { 97 | setDialogShow(true) 98 | } 99 | 100 | function hideDialog() { 101 | setDialogShow(false) 102 | } 103 | 104 | return <> 105 | 106 | {dialogContents} 107 | 108 | 109 | 123 | 124 | } 125 | 126 | async function log(infoLevel: "info" | "warn" | "error" | "err", message: string) { 127 | } 128 | 129 | const node = document.getElementById("app"); 130 | const root = createRoot(node!); 131 | 132 | root.render( 133 | 134 | 135 | 136 | 137 | 138 | ) -------------------------------------------------------------------------------- /ui/src/libs/configFiles.tsx: -------------------------------------------------------------------------------- 1 | import TauriFS from "@libs/tauriFS"; 2 | import * as path from "path" 3 | 4 | async function initConfigDirs() { 5 | const baseHomeDir = path.join(await TauriFS.getHomeDir(), ".proton") 6 | if (!await TauriFS.exists(baseHomeDir)) { 7 | console.log("dir does not exist") 8 | TauriFS.mkdir(baseHomeDir) 9 | TauriFS.mkdir(path.join(baseHomeDir, "plugins")) 10 | TauriFS.mkdir(path.join(baseHomeDir, "themes")) 11 | } 12 | } 13 | 14 | export { 15 | initConfigDirs 16 | } 17 | -------------------------------------------------------------------------------- /ui/src/libs/elements/builtin.tsx: -------------------------------------------------------------------------------- 1 | // import { TextElement } from "./TextElement"; 2 | // import { ImageElement } from "./ImageElement"; 3 | // import { MusicElement } from "./MusicElement"; 4 | // import { ButtonElement } from "./ButtonElement"; 5 | 6 | import React from "react"; 7 | // import { ElementProperty, ProjectElement } from "@libs/project" 8 | 9 | let ELEMENT_LIST: {[key: string]: any}; 10 | 11 | import("@libs/project").then(({ ProjectElement, ElementProperty }) => { 12 | class TextElement extends ProjectElement { 13 | initialize() { 14 | this.name = "Text" 15 | this.elementID = "text" 16 | this.properties.addProperty(new ElementProperty("text", "Hi!")) 17 | } 18 | 19 | render(): any { 20 | return
21 | {this.properties.getProp("text")} 22 |
23 | } 24 | } 25 | 26 | class ImageElement extends ProjectElement { 27 | initialize(): void { 28 | this.name = "Image" 29 | this.elementID = "image" 30 | this.properties.addProperty(new ElementProperty("URL", "")) 31 | this.properties.addProperty(new ElementProperty("Alt", "")) 32 | this.properties.addProperty(new ElementProperty("Width", 100)) 33 | this.properties.addProperty(new ElementProperty("Height", 100)) 34 | } 35 | 36 | render(): JSX.Element; 37 | render(): any { 38 | return ( 39 | {this.properties.getProp("Alt")} 45 | ) 46 | } 47 | } 48 | 49 | class MusicElement extends ProjectElement { 50 | audio: HTMLAudioElement 51 | isPlaying: boolean 52 | 53 | constructor() { 54 | super() 55 | this.audio = new Audio(this.properties.getProp("URL")) 56 | this.isPlaying = false 57 | } 58 | 59 | initialize(): void { 60 | this.name = "Audio" 61 | this.elementID = "audio" 62 | 63 | let properties: Array = [ 64 | new ElementProperty("URL", "") 65 | ] 66 | properties.map(property => { 67 | this.properties.addProperty(property) 68 | }) 69 | } 70 | 71 | render(): JSX.Element 72 | render(): any { 73 | return <> 74 | } 75 | 76 | onRun() { 77 | this.audio.src = this.properties.getProp("URL") 78 | 79 | if (this.isPlaying) { 80 | this.audio.pause() 81 | this.isPlaying = false 82 | } else { 83 | this.audio.play() 84 | this.isPlaying = true 85 | } 86 | } 87 | } 88 | 89 | class ButtonElement extends ProjectElement { 90 | initialize(): void { 91 | this.name = "Button" 92 | this.elementID = "button" 93 | 94 | const props: Array = [ 95 | new ElementProperty("Text", ""), 96 | new ElementProperty("Lua Function", "") 97 | ] 98 | props.forEach((property) => { 99 | this.properties.addProperty(property) 100 | }) 101 | } 102 | 103 | render(): JSX.Element; 104 | render(): any { 105 | return ( 106 | 116 | ) 117 | } 118 | } 119 | 120 | ELEMENT_LIST = { 121 | "text": TextElement, 122 | "image": ImageElement, 123 | "audio": MusicElement, 124 | "button": ButtonElement 125 | } 126 | }) 127 | 128 | export { 129 | ELEMENT_LIST 130 | } 131 | -------------------------------------------------------------------------------- /ui/src/libs/getAsset.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path" 2 | 3 | export default function getAsset(assetDir: string, assetName: string) { 4 | const baseAssetPath = path.join(".", "assets", assetDir) 5 | return `/${assetName}` 6 | } 7 | -------------------------------------------------------------------------------- /ui/src/libs/github/index.tsx: -------------------------------------------------------------------------------- 1 | import fetch from "sync-fetch" 2 | 3 | namespace GitHubUtils { 4 | export const GH_API_URL = "https://api.github.com" 5 | 6 | export function convertToRawLink(options: { 7 | username: string 8 | repo: string, 9 | branch: string, 10 | file_path: string 11 | }) { 12 | const { username, repo, branch, file_path } = options 13 | 14 | return `https://raw.githubusercontent.com/${username}/${repo}/${branch}/${file_path}` 15 | } 16 | 17 | export function getDirectory(options: { 18 | user: string, 19 | repo: string, 20 | path: string 21 | }): Array | {[key: string]: any} { 22 | const { user, repo, path } = options 23 | 24 | return fetch(`${GH_API_URL}/repos/${user}/${repo}/contents/${path}`).json() 25 | } 26 | 27 | export function getFile(options: { 28 | username: string, 29 | repo: string, 30 | file_path: string, 31 | branch: string 32 | }) { 33 | const { username, repo, branch, file_path } = options 34 | 35 | const rawGHLink = convertToRawLink(options) 36 | return fetch(rawGHLink).text() 37 | } 38 | } 39 | 40 | export default GitHubUtils 41 | -------------------------------------------------------------------------------- /ui/src/libs/internal.ts: -------------------------------------------------------------------------------- 1 | export * from "./project" 2 | // export * from "./elements/TextElement" 3 | export * from "./elements/builtin" 4 | // export * from "./newMainLuaFile" -------------------------------------------------------------------------------- /ui/src/libs/logic/index.ts: -------------------------------------------------------------------------------- 1 | import pb from "../pocketbase" 2 | 3 | import * as libs from "../internal" 4 | 5 | import PluginManager from "../plugin" 6 | 7 | interface EditorLogic { 8 | project: libs.Project 9 | } 10 | 11 | interface Logic { 12 | [key: string]: any 13 | name: string 14 | } 15 | 16 | class Logic { 17 | constructor(pluginManager: PluginManager) { 18 | this.pluginManager = pluginManager 19 | } 20 | 21 | invoke(command: string, args?: {[key: string]: any} | null) { 22 | const func = this[command] 23 | let results: { 24 | [key: string]: any 25 | } = {} 26 | if (func) { 27 | let _args: any = args ? args : {} 28 | results['main'] = func(_args) 29 | } 30 | let pluginResults = this.pluginManager.invoke(command, args) 31 | let new_results: any = {} 32 | Object.keys(results).map(key => { 33 | const result = results[key] 34 | new_results[key] = result 35 | }) 36 | Object.keys(pluginResults).map(key => { 37 | const result = pluginResults[key] 38 | new_results[key] = result 39 | }) 40 | return new_results 41 | } 42 | } 43 | 44 | class EditorLogic extends Logic { 45 | constructor(pluginManager: PluginManager, project: libs.Project) { 46 | super(pluginManager) 47 | this.name = "editor" 48 | this.project = project 49 | 50 | this.saveProject = this.saveProject.bind(this) 51 | this.deleteElement = this.deleteElement.bind(this) 52 | this.addElement = this.addElement.bind(this) 53 | this.saveScreens = this.saveScreens.bind(this) 54 | } 55 | 56 | saveProject() { 57 | // console.log(this) 58 | const { project } = this 59 | 60 | const serializedProject: { [key: string]: any } = {...project.serialize()} 61 | const screens = this.saveScreens() 62 | serializedProject["screens"] = screens 63 | 64 | pb.collection('projects').update(project.id, serializedProject, { 65 | "$autoCancel": false 66 | }) 67 | } 68 | 69 | saveScreens() { 70 | const { project } = this 71 | 72 | const screens = project.screens 73 | let screenIDs: Array = [] 74 | 75 | Object.keys(screens).map(key => { 76 | const screen = screens[key] 77 | const serializedScreen = screen.serialize() 78 | 79 | let screenExists: boolean = screen.pb_id !== "" 80 | 81 | if (screenExists) { 82 | pb.collection("screens").update(screen.pb_id, serializedScreen, { 83 | "$autoCancel": false 84 | }) 85 | } else { 86 | pb.collection("screens").create(serializedScreen).then(data => { 87 | screen.pb_id = data.id 88 | console.log(data.id) 89 | }) 90 | setTimeout(() => {}, 150) 91 | } 92 | 93 | screenIDs.push(screen.pb_id) 94 | }) 95 | 96 | return screenIDs 97 | } 98 | 99 | addElement({ element, screenID }: { 100 | element: libs.ProjectElement, 101 | screenID: string 102 | }) { 103 | const { project } = this 104 | 105 | project.screens[screenID].addElement(element) 106 | } 107 | 108 | deleteElement({ elementUID, screenID }: { 109 | elementUID: string, 110 | screenID: string 111 | }) { 112 | const { project } = this 113 | 114 | project.screens[screenID].deleteElement(elementUID) 115 | this.saveProject() 116 | } 117 | } 118 | class HomeLogic extends Logic { 119 | constructor(pluginManager: PluginManager) { 120 | super(pluginManager) 121 | this.name = "home" 122 | } 123 | } 124 | 125 | export { 126 | EditorLogic, 127 | HomeLogic 128 | } 129 | -------------------------------------------------------------------------------- /ui/src/libs/newMainLuaFile.ts: -------------------------------------------------------------------------------- 1 | import { ProjectScript } from "@libs/project" 2 | 3 | function newMainLuaFile() { 4 | return new ProjectScript("main.lua", "function main()\nend") 5 | } 6 | 7 | export { newMainLuaFile } 8 | -------------------------------------------------------------------------------- /ui/src/libs/plugin/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import { Project } from '@libs/project'; 3 | import { Project as ProtonProject } from '@techstudent10/plugin/lib/projectTypes'; 4 | import ProtonPlugin from "@techstudent10/plugin" 5 | import { readTextFile, readDir, BaseDirectory } from "@tauri-apps/api/fs" 6 | 7 | class PluginManager { 8 | // TODO: move this directory to the Electron side 9 | // TODO: set this dynamically 10 | PLUGIN_PATH = "C:/Users/moham/projs/ProtonDesigner/rewrite/ui/src/plugins" 11 | 12 | PLUGINS: { 13 | [key: string]: any 14 | } = [] 15 | 16 | constructor(options?: { 17 | path?: string 18 | }) { 19 | if (options?.path) { 20 | this.PLUGIN_PATH = options.path 21 | } 22 | 23 | const getPlugins = async () => { 24 | await readDir("plugins", { dir: BaseDirectory.AppLocalData, recursive: true }).then(plugin => { 25 | console.log(plugin) 26 | }) 27 | } 28 | 29 | // window.electronAPI.getPlugins().then((data: Array) => { 30 | // this.load(data) 31 | // }) 32 | } 33 | 34 | load(pluginFiles: Array) { 35 | window.electronAPI.logInfo("Loading plugins...") 36 | pluginFiles.map(filename => { 37 | // console.log("a", filename) 38 | try { 39 | const pluginObj = window.electronAPI.getPlugin(filename) 40 | 41 | const plugin = pluginObj.plugin 42 | const pluginConfig = pluginObj.pluginConfig 43 | // console.log(plugin, "\n", pluginConfig) 44 | // const newPlugin = new ProtonPlugin() 45 | // Object.assign(newPlugin, plugin) 46 | // // console.log(newPlugin, plugin) 47 | // this.PLUGINS[filename] = {plugin: newPlugin, pluginConfig} 48 | // window.electronAPI.logInfo(`Loaded Plugin ${filename}`) 49 | } catch (err) { 50 | const errMsg = `Error occurred while loading plugin ${filename}` 51 | console.error(errMsg) 52 | window.electronAPI.logErr(errMsg) 53 | console.error(err) 54 | } 55 | }) 56 | 57 | window.electronAPI.logInfo("Plugins loaded") 58 | console.log("Plugins finished loading") 59 | } 60 | 61 | setProject(project: Project) { 62 | // console.log(this.PLUGINS) 63 | Object.keys(this.PLUGINS).map((key: any) => { 64 | this.PLUGINS[key].plugin.project = project 65 | }) 66 | } 67 | 68 | invoke(command: string, namespace?: string, args?: {[key: string]: any} | null) { 69 | let results: { 70 | [key: string]: any 71 | } = {} 72 | namespace = namespace || "editor" 73 | Object.keys(this.PLUGINS).map(key => { 74 | const plugin = this.PLUGINS[key].plugin 75 | const func = plugin[`${namespace}_${command}`] 76 | // console.log(func) 77 | // console.log(`${namespace}_${command}`) 78 | let result; 79 | if (func) { 80 | // console.log("executing!") 81 | let _args: any = args ? args : {} 82 | // console.log(plugin) 83 | result = func(_args) 84 | // console.log(result) 85 | } 86 | results[key] = result 87 | }) 88 | return results 89 | } 90 | } 91 | 92 | export default PluginManager 93 | -------------------------------------------------------------------------------- /ui/src/libs/pocketbase.ts: -------------------------------------------------------------------------------- 1 | import Pocketbase from "pocketbase" 2 | 3 | const pb = new Pocketbase("https://proton-db.pockethost.io") 4 | export default pb -------------------------------------------------------------------------------- /ui/src/libs/project.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { v4 as uuidV4 } from 'uuid'; 3 | import { LuaEngine } from 'wasmoon'; 4 | 5 | import { newMainLuaFile } from './newMainLuaFile'; 6 | // import { ELEMENT_LIST } from "./elements/builtin" 7 | import pb from './pocketbase'; 8 | 9 | interface Project { 10 | screens: { 11 | [key: string]: ProjectScreen 12 | } 13 | name: string 14 | id: string 15 | scripts: { 16 | [key: string]: any 17 | } 18 | } 19 | 20 | interface ProjectElement { 21 | uid: string 22 | properties: ElementProperties 23 | name: string 24 | render(): any, 25 | elementID: string 26 | lua: LuaEngine 27 | } 28 | 29 | interface ElementProperty { 30 | value: any 31 | name: string 32 | uid: string 33 | editable: boolean 34 | id: string 35 | } 36 | 37 | interface ElementProperties { 38 | properties: { 39 | [key: string]: ElementProperty 40 | } 41 | } 42 | 43 | interface ProjectScript { 44 | name: string 45 | contents: string 46 | uid: string 47 | } 48 | 49 | import { ELEMENT_LIST } from "@libs/internal" 50 | 51 | class Project { 52 | constructor(name?: string) { 53 | this.screens = {} 54 | this.name = name || "" 55 | this.id = uuidV4() 56 | this.scripts = {} 57 | } 58 | 59 | addScreen(screen: ProjectScreen) { 60 | this.screens[screen.uid] = screen 61 | } 62 | 63 | deleteScreen(screenUID: string) { 64 | delete this.screens[screenUID] 65 | } 66 | 67 | addScript(script: ProjectScript) { 68 | this.scripts[script.uid] = script 69 | } 70 | 71 | updateScript(scriptUID: string, newValue: string) { 72 | this.scripts[scriptUID].updateContents(newValue) 73 | } 74 | 75 | deleteScript(scriptUID: string) { 76 | delete this.scripts[scriptUID] 77 | } 78 | 79 | getScripts() { 80 | return this.scripts 81 | } 82 | 83 | getScript(scriptName: string) { 84 | const scripts = this.getScripts() 85 | 86 | let scriptContents: string = ""; 87 | 88 | Object.keys(scripts).map(key => { 89 | const script: ProjectScript = scripts[key] 90 | if (!scriptContents) { 91 | if (scriptName == script.name) { 92 | scriptContents = script.contents 93 | } 94 | } 95 | }) 96 | 97 | return scriptContents 98 | } 99 | 100 | serialize() { 101 | // Serialize scripts 102 | let serializedScripts: { 103 | [key: string]: {} 104 | } = {} 105 | 106 | Object.keys(this.scripts).map(key => { 107 | const script: ProjectScript = this.scripts[key] 108 | // console.log(script) 109 | serializedScripts[script.uid] = script?.serialize() 110 | }) 111 | 112 | return { 113 | name: this.name, 114 | scripts: serializedScripts 115 | } 116 | } 117 | 118 | load(json: any, screensCallback: Function) { 119 | const screens: Array = json.screens 120 | console.log(json) 121 | const name: string = json.name 122 | 123 | this.name = name 124 | this.id = json.id 125 | let mainExists = false 126 | Object.keys(json.scripts).map(key => { 127 | const script = json.scripts[key] 128 | if (script.name == "main.lua") { 129 | mainExists = true 130 | } 131 | const newScript = new ProjectScript() 132 | newScript.load(script) 133 | this.addScript(newScript) 134 | }) 135 | 136 | if (!mainExists) { 137 | this.addScript(newMainLuaFile()) 138 | } 139 | 140 | if (screens.length === 0) { 141 | const newScreen = new ProjectScreen("Screen 1") 142 | if (json.elements) { 143 | Object.keys(json.elements).forEach(key => { 144 | const element = json.elements[key] 145 | 146 | const ElementConstructor = ELEMENT_LIST[element.id] 147 | const newElement: ProjectElement = new ElementConstructor() 148 | newElement.load(element) 149 | newScreen.addElement(element) 150 | }) 151 | } 152 | pb.collection("screens").create(newScreen.serialize()).then(createdScreen => { 153 | newScreen.load(createdScreen) 154 | this.addScreen(newScreen) 155 | pb.collection("projects").update(this.id, { 156 | screens: [ 157 | createdScreen.id 158 | ] 159 | }) 160 | console.log(createdScreen) 161 | }) 162 | } else { 163 | const screenObj: { [key: string]: any } = {} 164 | const screensFinished = new Event("screens-finished") 165 | screens.map((screenID, index) => { 166 | pb.collection("screens").getOne(screenID).then(screen => { 167 | console.log(index, screens.length) 168 | screenObj[screen.uid] = screen 169 | if (index == (screens.length - 1)) { 170 | document.dispatchEvent(screensFinished) 171 | console.log("ending loop!") 172 | } 173 | }) 174 | }) 175 | document.addEventListener("screens-finished", (e) => { 176 | console.log("sr") 177 | Object.keys(screenObj).map(key => { 178 | const screen = screenObj[key] 179 | const newScreen = new ProjectScreen() 180 | newScreen.load(screen) 181 | console.log(screen) 182 | this.addScreen(newScreen) 183 | }) 184 | screensCallback() 185 | }) 186 | } 187 | 188 | console.log(this.screens) 189 | } 190 | } 191 | 192 | class ProjectElement { 193 | constructor() { 194 | this.uid = uuidV4() 195 | this.name = "Element" 196 | this.elementID = "generic" 197 | this.properties = new ElementProperties() 198 | 199 | this.properties.addProperty(new ElementProperty("x", 0)) 200 | this.properties.addProperty(new ElementProperty("y", 0)) 201 | this.initialize() 202 | this.render = this.render.bind(this) 203 | } 204 | 205 | serialize() { 206 | return { 207 | uid: this.uid, 208 | properties: this.properties.getAllProps(), 209 | elementID: this.elementID, 210 | name: this.name 211 | } 212 | } 213 | 214 | render() { 215 | return
216 | Here you put your custom component logic! 217 |
218 | } 219 | 220 | onRun() {} 221 | onStop() {} 222 | 223 | getLua(functionName: string) { 224 | const isMain = functionName === "main" 225 | 226 | if (isMain) { 227 | return 228 | } 229 | 230 | const func = this.lua.global.get(functionName) 231 | 232 | if (typeof func !== "function") { 233 | return 234 | } 235 | 236 | return func 237 | } 238 | 239 | initialize() { 240 | console.log(`Component "${this.uid}" initialized!`) 241 | } 242 | 243 | load(json: any) { 244 | this.uid = json.uid 245 | this.name = json.name 246 | this.elementID = json.elementID 247 | this.properties.loadProperties(json.properties) 248 | } 249 | } 250 | 251 | class ElementProperties { 252 | constructor() { 253 | this.properties = {}; 254 | } 255 | 256 | addProperty(property: ElementProperty) { 257 | this.properties[property.id] = property 258 | } 259 | 260 | updateProperty(id: string, value: any) { 261 | this.properties[id].changeValue(value) 262 | } 263 | 264 | deleteProperty(id: string) { 265 | delete this.properties[id] 266 | } 267 | 268 | getProp(id: string) { 269 | let property = this.properties[id] 270 | return property.value 271 | } 272 | 273 | getAllProps() { 274 | let properties: { 275 | [key: string]: any 276 | } = {} 277 | Object.keys(this.properties).map((property_key: string) => { 278 | const property = this.properties[property_key] 279 | properties[property_key] = property.value 280 | }) 281 | return properties 282 | } 283 | 284 | loadProperties(json: { 285 | [key: string]: any 286 | }) { 287 | Object.keys(json).map(key => { 288 | const property = json[key] 289 | this.updateProperty(key, property) 290 | }) 291 | } 292 | } 293 | 294 | class ElementProperty { 295 | constructor(name: string, value: any, id?: string) { 296 | this.value = value 297 | this.name = name 298 | this.uid = uuidV4() 299 | this.editable = true 300 | 301 | this.id = id || this.name 302 | } 303 | 304 | changeValue(newValue: any) { 305 | this.value = newValue 306 | } 307 | 308 | load(json: any) { 309 | this.value = json.value 310 | this.name = json.name 311 | this.uid = json.uid 312 | this.editable = json.editable 313 | } 314 | 315 | serialize() { 316 | return { 317 | name: this.name, 318 | value: this.value, 319 | uid: this.uid, 320 | editable: this.editable 321 | } 322 | } 323 | } 324 | 325 | class ProjectScript { 326 | constructor(name?: string, initialContents?: string) { 327 | this.name = name || "script.lua" 328 | this.contents = initialContents || "" 329 | this.uid = uuidV4() 330 | } 331 | 332 | updateContents(newContents: string) { 333 | this.contents = newContents 334 | } 335 | 336 | load(json: any) { 337 | this.name = json.name 338 | this.contents = json.contents 339 | this.uid = json.uid 340 | } 341 | 342 | serialize() { 343 | return { 344 | name: this.name, 345 | contents: this.contents, 346 | uid: this.uid 347 | } 348 | } 349 | } 350 | 351 | class ProjectScreen { 352 | uid: string 353 | name: string 354 | elements: {[key: string]: ProjectElement} 355 | pb_id: string 356 | 357 | constructor(name?: string, initialElements?: any) { 358 | this.name = name || "" 359 | this.elements = initialElements || {} 360 | this.uid = uuidV4() 361 | this.pb_id = "" 362 | } 363 | 364 | addElement(element: ProjectElement) { 365 | this.elements[element.uid] = element 366 | } 367 | 368 | deleteElement(elementUID: string) { 369 | delete this.elements[elementUID] 370 | } 371 | 372 | load(json: {[key: string]: any}) { 373 | this.name = json.name 374 | this.uid = json.uid 375 | this.pb_id = json.id 376 | 377 | Object.keys(json.elements).map(key => { 378 | const element = json.elements[key] 379 | const elementID = element.elementID 380 | // const builtinElements = require("./elements/builtin") 381 | // const ELEMENT_LIST = builtinElements.default 382 | const ElementConstructor = ELEMENT_LIST[elementID] 383 | const newElement: ProjectElement = new ElementConstructor() 384 | newElement.load(element) 385 | 386 | this.addElement(newElement) 387 | }) 388 | } 389 | 390 | serialize() { 391 | let elements: {[key: string]: any} = {}; 392 | 393 | Object.keys(this.elements).map(key => { 394 | const element = this.elements[key] 395 | elements[element.uid] = element.serialize() 396 | }) 397 | 398 | const data: { 399 | [key: string]: any 400 | } = { 401 | id: this.pb_id, 402 | uid: this.uid, 403 | name: this.name, 404 | elements: elements 405 | } 406 | 407 | if (this.pb_id == "") { 408 | delete data.id 409 | } 410 | 411 | return data 412 | } 413 | } 414 | 415 | export { 416 | ProjectElement, 417 | ProjectScreen, 418 | ElementProperty, 419 | ElementProperties, 420 | ProjectScript, 421 | Project 422 | } 423 | -------------------------------------------------------------------------------- /ui/src/libs/projectBinary.ts: -------------------------------------------------------------------------------- 1 | import { Project } from "./internal" 2 | 3 | interface BinaryOptions {} 4 | 5 | const project = new Project() 6 | type SerializeReturnType = ReturnType 7 | 8 | class ProjectBinary { 9 | options: BinaryOptions 10 | 11 | constructor(options?: BinaryOptions) { 12 | this.options = options || {} 13 | } 14 | 15 | serialize(project: Project) { 16 | const projectObject = project.serialize() 17 | const projectString = JSON.stringify(projectObject) 18 | let output = "" 19 | for (var i = 0; i < projectString.length; i++) { 20 | output += projectString[i].charCodeAt(0).toString(2) + " " 21 | } 22 | return output 23 | } 24 | 25 | deserialize(binary: string): T { 26 | let newBinary = binary.split(" ") 27 | let jsonStr = "" 28 | for (let i = 0; i < newBinary.length; i++) { 29 | jsonStr += String.fromCharCode(parseInt(newBinary[i], 2)) 30 | } 31 | return JSON.parse(jsonStr) 32 | } 33 | } 34 | 35 | const test = new ProjectBinary({}) 36 | 37 | console.log(test.serialize(project)) 38 | console.log(test.deserialize(test.serialize(project))) 39 | 40 | export default ProjectBinary 41 | export type { SerializeReturnType } 42 | export { 43 | BinaryOptions 44 | } 45 | -------------------------------------------------------------------------------- /ui/src/libs/settings/defaultSettings.ts: -------------------------------------------------------------------------------- 1 | import {SettingsInterface} from "@libs/settings/settingsType"; 2 | 3 | const defaultSettings: SettingsInterface = { 4 | personalization: { 5 | darkMode: false, 6 | option: [ 7 | { 8 | value: "hi", 9 | selected: false 10 | }, { 11 | value: "bye", 12 | selected: true 13 | } 14 | ] 15 | } 16 | } 17 | 18 | export default defaultSettings 19 | -------------------------------------------------------------------------------- /ui/src/libs/settings/index.ts: -------------------------------------------------------------------------------- 1 | import type {SettingsInterface} from "./settingsType.js" 2 | import defaultSettings from "./defaultSettings" 3 | 4 | interface SettingsManagerOptions { 5 | isDev?: boolean 6 | storageKey?: null | string 7 | } 8 | 9 | class SettingsManager { 10 | options: SettingsManagerOptions 11 | settings: SettingsInterface 12 | constructor(options: SettingsManagerOptions) { 13 | this.options = options 14 | 15 | this.settings = {} 16 | this.loadSettingsFromLocalStorage() 17 | } 18 | 19 | private loadSettingsFromLocalStorage(): void { 20 | if (localStorage.getItem("settings")) { 21 | this.settings = this.validateSettings(localStorage.getItem("settings")) 22 | } else { 23 | this.initializeSettings() 24 | } 25 | } 26 | 27 | private validateSettings(_settings: ReturnType) { 28 | const settings = JSON.parse(_settings || "{}") as {[key: string]: any} 29 | 30 | Object.keys(defaultSettings).map(key => { 31 | const value = defaultSettings[key] 32 | if (!Object.keys(settings).includes(key)) { 33 | settings[key] = value 34 | } 35 | }) 36 | 37 | Object.keys(settings).map(key => { 38 | if (!Object.keys(defaultSettings).includes(key)) { 39 | delete settings[key] 40 | } 41 | }) 42 | 43 | return settings 44 | } 45 | 46 | saveSettings() { 47 | localStorage.setItem("settings", JSON.stringify(this.validateSettings(JSON.stringify(this.settings)))) 48 | console.log("Saved settings!") 49 | } 50 | 51 | private initializeSettings(): void { 52 | this.settings = defaultSettings 53 | this.saveSettings() 54 | } 55 | } 56 | 57 | export default SettingsManager 58 | export { 59 | SettingsManagerOptions, 60 | SettingsInterface as SettingsType 61 | } 62 | -------------------------------------------------------------------------------- /ui/src/libs/settings/settingsType.ts: -------------------------------------------------------------------------------- 1 | interface SettingsInterface { 2 | personalization?: { 3 | darkMode: boolean, 4 | option: [ 5 | { 6 | value: "hi", 7 | selected: boolean 8 | }, { 9 | value: "bye", 10 | selected: boolean 11 | } 12 | ] 13 | } 14 | [key: string]: any 15 | } 16 | 17 | export type {SettingsInterface} 18 | -------------------------------------------------------------------------------- /ui/src/libs/tauriFS.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api"; 2 | 3 | namespace TauriFS { 4 | export async function listDir(directory: string): Promise { 5 | return await invoke("list_dir", { directory, isDebug: false }) 6 | } 7 | 8 | export async function readFile(filePath: string): Promise { 9 | return await invoke("read_file", { filePath }) 10 | } 11 | 12 | export async function writeFile(filePath: string, data: string) { 13 | await invoke("write_file", { filePath, data }) 14 | } 15 | 16 | export async function mkdir(directoryPath: string) { 17 | await invoke("mkdir", { directoryPath }) 18 | } 19 | 20 | export async function cwd(): Promise { 21 | return await invoke("cwd") 22 | } 23 | 24 | export async function exists(path: string): Promise { 25 | return await invoke("exists", { path }) 26 | } 27 | 28 | export async function getHomeDir(): Promise { 29 | return await invoke("get_home_dir") 30 | } 31 | 32 | export async function rmdir(directoryPath: string): Promise { 33 | return await invoke("rmdir", { directoryPath }) 34 | } 35 | 36 | export async function rmfile(path: string): Promise { 37 | return await invoke("rmfile", { path }) 38 | } 39 | } 40 | 41 | export default TauriFS 42 | -------------------------------------------------------------------------------- /ui/src/libs/theme/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "dashboard": { 3 | "sidebar": { 4 | "background-color": "rgb(255, 255, 255)", 5 | "button": "rgb(255, 255, 255)", 6 | "button:hover": "rgb(255, 255, 255)" 7 | }, 8 | "background-color": "rgb(255, 255, 255)" 9 | }, 10 | "editor": { 11 | "background-color": "rgb(255, 255, 255)" 12 | }, 13 | "etc": "etc..." 14 | } -------------------------------------------------------------------------------- /ui/src/libs/theme/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path" 2 | 3 | import TauriFS from "@libs/tauriFS" 4 | import { v4 as uuidV4 } from "uuid" 5 | 6 | class ThemeLoader { 7 | INIT_CONSTANT = "style.scss" 8 | 9 | theme_names: Array 10 | 11 | constructor() { 12 | this.theme_names = [] 13 | } 14 | 15 | async getThemes(): Promise { 16 | return await TauriFS.listDir(path.join(await TauriFS.getHomeDir(), ".proton", "themes")) 17 | } 18 | 19 | async setDefaultTheme(themeName: string): Promise { 20 | localStorage.setItem("defaultTheme", themeName) 21 | } 22 | 23 | async getDefaultTheme(): Promise { 24 | return localStorage.getItem("defaultTheme") || "" 25 | } 26 | 27 | async loadTheme(): Promise { 28 | return await this.loadThemeWithName(await this.getDefaultTheme()) 29 | } 30 | 31 | async deleteTheme(themeName: string): Promise { 32 | await TauriFS.rmdir(`${await TauriFS.getHomeDir()}/.proton/themes/${themeName}`) 33 | } 34 | 35 | async loadThemeWithName(themeName: string): Promise { 36 | const themes = await this.getThemes() 37 | const THEME_PATH = path.join(await TauriFS.getHomeDir(), ".proton", "themes") 38 | 39 | if (themeName == "") return false 40 | if (!themes.includes(themeName)) return false 41 | 42 | let themeLoaded = false 43 | this.theme_names.map(_themeName => { 44 | if (themeLoaded) return 45 | if (themeName == _themeName) themeLoaded = true 46 | }) 47 | 48 | if (themeLoaded) return false 49 | 50 | const styleContents = await TauriFS.readFile(path.join(THEME_PATH, themeName, "style.css")) 51 | 52 | const styleTag = document.createElement("style") 53 | styleTag.innerText = styleContents 54 | 55 | this.theme_names.push(themeName) 56 | 57 | document.head.appendChild(styleTag) 58 | 59 | return true 60 | } 61 | } 62 | 63 | export default ThemeLoader 64 | -------------------------------------------------------------------------------- /ui/src/libs/utils/addSpaceAtCapitals.ts: -------------------------------------------------------------------------------- 1 | export default function addSpaceAtCapitals(s: string) { 2 | return s.replace(/([A-Z]+)/g, ' $1').trim() 3 | } 4 | -------------------------------------------------------------------------------- /ui/src/libs/utils/capitalizeFirstLetter.ts: -------------------------------------------------------------------------------- 1 | function capitalizeFirstLetter(string: string) { 2 | return string.charAt(0).toUpperCase() + string.slice(1); 3 | } 4 | 5 | export default capitalizeFirstLetter -------------------------------------------------------------------------------- /ui/src/main.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Open Sans"; 3 | src: url("/OpenSans.ttf"); 4 | } 5 | 6 | * { 7 | margin: 0; 8 | padding: 0; 9 | 10 | font-family: "Open Sans"; 11 | user-select: none; 12 | } 13 | 14 | // .app { 15 | 16 | // } 17 | -------------------------------------------------------------------------------- /ui/src/plugins/test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function getAugmentedNamespace(n) { 4 | if (n.__esModule) return n; 5 | var f = n.default; 6 | if (typeof f == "function") { 7 | var a = function a () { 8 | if (this instanceof a) { 9 | var args = [null]; 10 | args.push.apply(args, arguments); 11 | var Ctor = Function.bind.apply(f, args); 12 | return new Ctor(); 13 | } 14 | return f.apply(this, arguments); 15 | }; 16 | a.prototype = f.prototype; 17 | } else a = {}; 18 | Object.defineProperty(a, '__esModule', {value: true}); 19 | Object.keys(n).forEach(function (k) { 20 | var d = Object.getOwnPropertyDescriptor(n, k); 21 | Object.defineProperty(a, k, d.get ? d : { 22 | enumerable: true, 23 | get: function () { 24 | return n[k]; 25 | } 26 | }); 27 | }); 28 | return a; 29 | } 30 | 31 | var lib = {}; 32 | 33 | Object.defineProperty(lib, "__esModule", { value: true }); 34 | var ProtonPlugin$1 = /** @class */ (function () { 35 | function ProtonPlugin(project) { 36 | this.project = project; 37 | } 38 | ProtonPlugin.prototype.invoke = function (command, args) { 39 | var func = this[command]; 40 | if (func) { 41 | var _args = args ? args : {}; 42 | func.apply(void 0, _args); 43 | } 44 | }; 45 | ProtonPlugin.prototype.editor_saveProject = function () { }; 46 | ProtonPlugin.prototype.editor_addElement = function (_a) { 47 | _a.element; 48 | }; 49 | ProtonPlugin.prototype.editor_deleteElement = function (_a) { 50 | _a.elementUID; 51 | }; 52 | return ProtonPlugin; 53 | }()); 54 | lib.default = ProtonPlugin$1; 55 | 56 | module.exports = { 57 | default: (msg) => { 58 | console.log(msg); 59 | } 60 | }; 61 | 62 | var test$1 = /*#__PURE__*/Object.freeze({ 63 | __proto__: null 64 | }); 65 | 66 | var require$$1 = /*@__PURE__*/getAugmentedNamespace(test$1); 67 | 68 | module.exports = { 69 | color: "red", 70 | settings: [ 71 | { 72 | id: "color", 73 | type: "string", 74 | required: true, 75 | defaultValue: "blue" 76 | } 77 | ] 78 | }; 79 | 80 | const plugin = lib; 81 | const { default: test } = require$$1; 82 | 83 | 84 | const ProtonPlugin = plugin.default; 85 | 86 | class MyPlugin extends ProtonPlugin { 87 | editor_addElement() { 88 | test("yooo"); 89 | } 90 | } 91 | 92 | var src = { 93 | MyPlugin 94 | }; 95 | 96 | module.exports = src; 97 | -------------------------------------------------------------------------------- /ui/src/plugins/test/proton.config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | settings: [ 3 | { 4 | id: "color", 5 | type: "string", 6 | required: true, 7 | defaultValue: "blue" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /ui/src/plugins/test2/index.js: -------------------------------------------------------------------------------- 1 | const plugin = require("@techstudent10/plugin") 2 | 3 | const ProtonPlugin = plugin.default 4 | 5 | class MyPlugin extends ProtonPlugin { 6 | editor_saveProject() { 7 | console.log("hello world from plugin num 2!") 8 | } 9 | } 10 | 11 | module.exports = MyPlugin 12 | 13 | -------------------------------------------------------------------------------- /ui/src/plugins/test2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-folder", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT" 6 | } 7 | -------------------------------------------------------------------------------- /ui/src/plugins/test2/proton.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | settings: [ 3 | { 4 | id: "color", 5 | type: "string", 6 | required: true, 7 | defaultValue: "red" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /ui/src/screens/editor/Console.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, RefObject, useEffect } from 'react'; 2 | 3 | interface ConsoleProps { 4 | messages: any 5 | setMessages: any 6 | currentElementUID: string 7 | } 8 | 9 | interface ConsoleMessageProps { 10 | message: string 11 | type: string 12 | } 13 | 14 | function ConsoleMessage(props: ConsoleMessageProps) { 15 | const compRef = useRef() as RefObject 16 | 17 | useEffect(() => { 18 | compRef.current?.scrollIntoView() 19 | }) 20 | 21 | return
22 | {props.message} 23 |
24 | } 25 | 26 | export default function Console(props: ConsoleProps) { 27 | return
28 | {props.messages && Object.keys(props.messages).map(key => { 29 | const msg = props.messages[key]; 30 | return 31 | })} 32 |
33 | } 34 | -------------------------------------------------------------------------------- /ui/src/screens/editor/Editor.scss: -------------------------------------------------------------------------------- 1 | $tools-height: 50px; 2 | $hierarchy-width: 150px; 3 | $inspector-width: 250px; 4 | $console-height: 130px; 5 | 6 | .editor { 7 | .tools, .hierarchy, .inspector { 8 | // Positioning 9 | position: absolute; 10 | top: 0; 11 | } 12 | 13 | .tools, .hierarchy { 14 | left: 0; 15 | } 16 | 17 | .inspector { 18 | right: 0; 19 | } 20 | 21 | .hierarchy, .inspector { 22 | height: calc(100vh - $tools-height); 23 | margin-top: $tools-height; 24 | } 25 | 26 | .tools { 27 | width: 100%; 28 | height: $tools-height; 29 | background-color: grey; 30 | 31 | display: flex; 32 | justify-content: flex-start; 33 | gap: 5px; 34 | 35 | .toolButton { 36 | width: $tools-height; 37 | height: $tools-height; 38 | 39 | img { 40 | width: inherit; 41 | height: inherit; 42 | } 43 | } 44 | } 45 | 46 | .hierarchy { 47 | background-color: darkgrey; 48 | width: $hierarchy-width; 49 | 50 | .item { 51 | display: flex; 52 | align-items: center; 53 | gap: 10px; 54 | padding: 5px; 55 | cursor: pointer; 56 | 57 | img { 58 | --size: 30px; 59 | width: var(--size); 60 | height: var(--size); 61 | } 62 | 63 | &:hover, &.active { 64 | background-color: hsl(0, 0%, 35%) 65 | } 66 | } 67 | } 68 | 69 | .inspector { 70 | background-color: lightgrey; 71 | width: $inspector-width; 72 | 73 | h2 { 74 | padding: 2px; 75 | text-align: center; 76 | } 77 | } 78 | 79 | .tabs__component { 80 | position: absolute; 81 | top: $tools-height; 82 | left: $hierarchy-width; 83 | width: calc(100vw - $hierarchy-width); 84 | height: calc(100vh - $tools-height - $console-height); 85 | & > .content { 86 | background-color: white; 87 | height: 100%; 88 | border-left: 1px solid black; 89 | border-bottom: 1px solid black; 90 | border-right: 1px solid black; 91 | & > .preview { 92 | position: absolute; 93 | 94 | top: 50%; 95 | left: 50%; 96 | transform: translate(-50%, -50%); 97 | 98 | border-radius: 0.5rem; 99 | border: 5px solid black; 100 | 101 | // top: $tools-height; 102 | // left: $hierarchy-width; 103 | // height: calc(100vh - $tools-height); 104 | // width: calc(100vw - $inspector-width - $hierarchy-width); 105 | 106 | &.full { 107 | width: calc(100vw - $hierarchy-width) 108 | } 109 | 110 | &.desktop { 111 | width: 50rem; 112 | height: 22rem; 113 | } 114 | 115 | .element__container { 116 | width: fit-content; 117 | } 118 | } 119 | } 120 | } 121 | 122 | .element__parent { 123 | width: 350px; 124 | .element__list { 125 | width: 100%; 126 | 127 | .elements__item { 128 | display: flex; 129 | align-items: center; 130 | justify-content: space-around; 131 | padding: 5px; 132 | background-color: hsl(0, 0%, 95%); 133 | 134 | button { 135 | padding: 5px; 136 | border-radius: 0.4rem; 137 | cursor: pointer; 138 | } 139 | 140 | border-radius: 0.7rem; 141 | } 142 | } 143 | } 144 | 145 | .console { 146 | width: calc(100vw - $hierarchy-width); 147 | height: $console-height; 148 | 149 | position: absolute; 150 | bottom: 0; 151 | left: $hierarchy-width; 152 | background-color: hsl(0, 0%, 70%); 153 | 154 | overflow-y: scroll; 155 | border-top: 3px solid hsl(0, 0%, 75%); 156 | 157 | &.dextend { 158 | width: calc(100vw - $hierarchy-width - $inspector-width); 159 | } 160 | 161 | // Custom scrollbar courtesy of https://www.w3schools.com/howto/howto_css_custom_scrollbar.aspok 162 | /* width */ 163 | &::-webkit-scrollbar { 164 | width: 10px; 165 | } 166 | 167 | /* Track */ 168 | &::-webkit-scrollbar-track { 169 | background: #f1f1f1; 170 | } 171 | 172 | /* Handle */ 173 | &::-webkit-scrollbar-thumb { 174 | background: #888; 175 | } 176 | 177 | /* Handle on hover */ 178 | &::-webkit-scrollbar-thumb:hover { 179 | background: #555; 180 | } 181 | 182 | .console__message { 183 | padding: 8px; 184 | overflow-wrap: break-word; 185 | 186 | &.info { 187 | background-color: hsl(0, 0%, 30%); 188 | border-top: 2px solid hsl(0, 0%, 25%); 189 | border-bottom: 2px solid hsl(0, 0%, 25%); 190 | } 191 | 192 | &.warn { 193 | background-color: hsl(55, 100%, 30%); 194 | border-top: 2px solid hsl(55, 100%, 25%); 195 | border-bottom: 2px solid hsl(55, 100%, 25%); 196 | } 197 | 198 | &.error { 199 | background-color: hsl(0, 100%, 30%); 200 | border-top: 2px solid hsl(0, 100%, 25%); 201 | border-bottom: 2px solid hsl(0, 100%, 25%); 202 | } 203 | } 204 | } 205 | } -------------------------------------------------------------------------------- /ui/src/screens/editor/Editor.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react" 2 | import BaseComponentProps from "@root/BaseComponentProps"; 3 | import "./Editor.scss" 4 | import Hierarchy from "./Hierarchy"; 5 | import Inspector from "./Inspector"; 6 | import Tools from "./Tools"; 7 | import { ProjectElement, Project, ProjectScreen } from "@libs/internal" 8 | import { ELEMENT_LIST } from "@libs/elements/builtin" 9 | import Dialog from "@components/Dialog"; 10 | import Preview from "./Preview"; 11 | import Console from "./Console"; 12 | import Tabs, { Tab } from "@components/Tabs"; 13 | 14 | import * as path from "path" 15 | import TextEditor from "./TextEditor"; 16 | 17 | import { LuaFactory } from "wasmoon" 18 | 19 | import { EditorLogic } from "@libs/logic"; 20 | 21 | // // @ts-expect-error 22 | // import * as luaGlueWasm from "wasmoon/dist/glue.wasm" 23 | 24 | const factory = new LuaFactory("/glue.wasm") 25 | const lua = await factory.createEngine() 26 | 27 | interface EditorProps extends BaseComponentProps {} 28 | 29 | function Editor(props: EditorProps) { 30 | const [loaded, setLoaded] = useState(false) 31 | const [showElementDialog, setElementDialog] = useState(false) 32 | const [currentElementUID, setCurrentElementUID] = useState(null) 33 | const [render, forceRerender] = useState(0) 34 | const [consoleMessages, setConsoleMessages] = useState([]) 35 | const [currentPage, setCurrentPage] = useState(0) 36 | const [project, setProject] = useState(new Project()); 37 | const [isRunning, setIsRunning] = useState(false) 38 | const [currentScreen, setCurrentScreen] = useState("") 39 | 40 | const logic = new EditorLogic(props.pluginManager, project) 41 | 42 | useEffect(() => { 43 | logic.project = project 44 | }, [project]) 45 | 46 | function consoleLog(message: any, type: string) { 47 | const newConsoleMessages = consoleMessages 48 | newConsoleMessages.push({ 49 | message, 50 | type 51 | }) 52 | setConsoleMessages([...newConsoleMessages]) 53 | } 54 | 55 | function saveProject() { 56 | return logic.invoke("saveProject") 57 | } 58 | 59 | function addElement(element: ProjectElement) { 60 | return logic.invoke("addElement", {element, screenID: currentScreen}) 61 | } 62 | 63 | // async function runLua() { 64 | // // Expose logging functions to lua 65 | // lua.global.set("infoToConsole", (msg: any) => consoleLog(msg, "info")) 66 | // lua.global.set("warnToConsole", (msg: any) => consoleLog(msg, "warn")) 67 | // lua.global.set("errToConsole", (msg: any) => consoleLog(msg, "error")) 68 | 69 | // // Expose elements to Lua 70 | // lua.global.set("getElement", (name: string) => { 71 | // const elements = project.elements 72 | // let element: any = null 73 | // Object.keys(elements).map(key => { 74 | // const _element: ProjectElement = elements[key] 75 | // if (!element) { 76 | // if (_element.name == name) { 77 | // element = _element.serialize() 78 | // } 79 | // } 80 | // }) 81 | // return element 82 | // }) 83 | 84 | // Object.keys(project.elements).map(key => { 85 | // const doStuff = async () => { 86 | // const element = project.elements[key] 87 | // try { 88 | // element.lua = lua 89 | // await element.onRun() 90 | // } catch (err: any) { 91 | // consoleLog(`An exception occurred in ${element.name} while attempting to start:`, "error") 92 | // err.split("\n").map((line: string) => { 93 | // consoleLog(line, "error") 94 | // }) 95 | // } 96 | // } 97 | // doStuff() 98 | // }) 99 | 100 | // // Get and execute the main function 101 | // await lua.doStringSync(project.getScript("main.lua")) 102 | // const mainFunction = lua.global.get("main") 103 | // const output = mainFunction() 104 | 105 | // // lua.global.close() 106 | // setIsRunning(false) 107 | 108 | // Object.keys(project.elements).map(key => { 109 | // const doStuff = async () => { 110 | // const element = project.elements[key] 111 | // try { 112 | // await element.onStop() 113 | // } catch (err: any) { 114 | // consoleLog(`An exception occurred in ${element.name} while stopping:`, "error") 115 | // err.split("\n").map((line: string) => { 116 | // consoleLog(line, "error") 117 | // }) 118 | // } 119 | // } 120 | // doStuff() 121 | // }) 122 | // } 123 | 124 | useEffect(() => { 125 | async function getData() { 126 | const newProject = new Project() 127 | newProject.load(props.state.project, () => { 128 | props.pluginManager.setProject(newProject) 129 | setCurrentScreen(Object.values(newProject.screens)[0].uid) 130 | setLoaded(true) 131 | }) 132 | setProject(newProject) 133 | // console.log(props.state.project) 134 | 135 | // runLua().catch((reason) => { 136 | // console.warn("Unable to execute Lua Script") 137 | // console.warn(reason) 138 | // }) 139 | } 140 | if (!loaded) { 141 | getData() 142 | } 143 | }, []) 144 | 145 | function createTabs() { 146 | let tabs: Array = [] 147 | 148 | Object.keys(project.screens).map(key => { 149 | const screen = project.screens[key] 150 | 151 | tabs.push({ 152 | title: screen.name, 153 | content: 161 | }) 162 | }) 163 | 164 | function CreateScreenDialog(props: any) { 165 | const [screenName, setScreenName] = useState(`Screen ${Object.keys(project.screens).length + 1}`) 166 | 167 | return <> 168 | 169 | { 170 | setScreenName(e.target.value) 171 | }} /> 172 | 180 | 181 | } 182 | 183 | tabs.push({ 184 | title: "+", 185 | onClick: () => { 186 | props.dialogUtils.setDialog( 187 | props.dialogUtils.createDialog( 188 | "Create Screen", 189 | 190 | ) 191 | ) 192 | props.dialogUtils.showDialog() 193 | } 194 | }) 195 | 196 | return tabs 197 | } 198 | 199 | // useEffect(() => { 200 | // if (isRunning) runLua() 201 | // }, [isRunning]) 202 | 203 | return
204 | props.setPage(0)} 210 | /> 211 | {loaded ? (currentPage == 0 ? <> 212 | { 213 | setCurrentElementUID(null) 214 | setCurrentScreen(Object.values(project.screens)[index].uid) 215 | }} /> 216 | 217 |
218 | {Object.keys(ELEMENT_LIST).map(element_id => { 219 | let Element: ProjectElement = new ELEMENT_LIST[element_id] 220 | return <> 221 |
222 |
223 |

{Element.name}

224 | 249 |
250 | 251 | })} 252 |
253 |
254 | 255 | 263 | 271 | 272 | : ) : "Loading..."} 276 |
277 | } 278 | 279 | export default Editor; 280 | -------------------------------------------------------------------------------- /ui/src/screens/editor/Hierarchy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import getAsset from '@libs/getAsset'; 3 | import { Project } from "@libs/project" 4 | 5 | interface HierarchyProps { 6 | project: Project 7 | setElementDialog?: any 8 | currentElementUID: string 9 | setCurrentElementUID: any 10 | saveProject: any 11 | currentScreen: string 12 | } 13 | interface HierarchyItemProps { 14 | name: string 15 | icon: JSX.Element 16 | onClick?: (e: any) => void 17 | active: boolean 18 | key: any 19 | } 20 | 21 | function HierarchyItem(props: HierarchyItemProps) { 22 | return
23 | {props.icon} 24 |

{props.name}

25 |
26 | } 27 | 28 | export default function Hierarchy(props: HierarchyProps) { 29 | return
30 | {/* Icon} /> */} 31 | {props.project.screens[props.currentScreen] && Object.keys(props.project.screens[props.currentScreen].elements).map(key => { 32 | let element = props.project.screens[props.currentScreen].elements[key]; 33 | return } 38 | onClick={(e) => { 39 | if (element.uid === props.currentElementUID) { 40 | props.setCurrentElementUID(null) 41 | } else { 42 | props.setCurrentElementUID(element.uid) 43 | } 44 | }} 45 | /> 46 | })} 47 | 48 |
49 | } 50 | -------------------------------------------------------------------------------- /ui/src/screens/editor/Inspector.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { ProjectElement, Project } from '@libs/project'; 3 | 4 | interface InspectorProps { 5 | currentElementUID: string 6 | setCurrentElement: any 7 | project: Project 8 | forceRerender: any 9 | saveProject: any 10 | currentScreen: string 11 | } 12 | 13 | function capitalizeFirstLetter(string: string) 14 | { 15 | return string.charAt(0).toUpperCase() + string.slice(1); 16 | } 17 | 18 | export default function Inspector(props: InspectorProps) { 19 | if (!props.currentElementUID) return <> 20 | 21 | const currentElement: ProjectElement = props.project.screens[props.currentScreen].elements[props.currentElementUID] 22 | const properties = currentElement.properties.getAllProps() 23 | 24 | const [render, forceRerender] = useState(0) 25 | 26 | return
27 |

{currentElement.name}

28 | {Object.keys(properties).map(key => { 29 | const property = properties[key] 30 | 31 | if (key == "x" || key == "y") return <> 32 | 33 | return
34 | {capitalizeFirstLetter(key)}: { 38 | const newValue = e.target.value 39 | currentElement.properties.updateProperty(key, newValue) 40 | forceRerender(render+1) 41 | props.forceRerender(Math.random()) 42 | props.saveProject() 43 | }} 44 | /> 45 |
46 | })} 47 | 53 |
54 | } 55 | -------------------------------------------------------------------------------- /ui/src/screens/editor/Preview.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, RefObject, useEffect } from "react"; 2 | import { ProjectElement, Project } from "@libs/project"; 3 | import { useDraggable, DragOptions } from "@neodrag/react"; 4 | import { ProjectScreen } from "@libs/project"; 5 | 6 | interface PreviewProps { 7 | project: Project 8 | currentElementUID: any 9 | type: string 10 | saveProject: () => any 11 | consoleLog: (message: string, type: string) => any 12 | screen: ProjectScreen 13 | } 14 | 15 | interface ContainerProps { 16 | element: ProjectElement 17 | children: any 18 | parent: RefObject 19 | saveProject: () => any 20 | key: any 21 | } 22 | 23 | function ComponentContainer(props: ContainerProps) { 24 | const nodeRef = useRef(null) as RefObject 25 | 26 | // console.log(props.element.properties.getProp("x")) 27 | 28 | const options: DragOptions = { 29 | bounds: ".preview", 30 | position: { 31 | x: props.element.properties.getProp("x"), 32 | y: props.element.properties.getProp("y") 33 | } 34 | } 35 | 36 | const { isDragging, dragState } = useDraggable(nodeRef, options) 37 | 38 | useEffect(() => { 39 | // console.log(isDragging, dragState) 40 | 41 | const offsetX = dragState?.offsetX 42 | const offsetY = dragState?.offsetY 43 | 44 | props.element.properties.updateProperty("x", offsetX && offsetX < 0 ? 0 : offsetX) 45 | props.element.properties.updateProperty("y", offsetY && offsetY < 0 ? 0 : offsetY) 46 | 47 | props.saveProject() 48 | }, [isDragging, dragState]) 49 | 50 | return
{props.children}
51 | } 52 | 53 | export default function Preview(props: PreviewProps) { 54 | // console.log(props.project) 55 | 56 | const previewRef = useRef() as RefObject 57 | 58 | const elements = props.screen.elements 59 | 60 | return
61 | {elements && Object.keys(elements).map((element_index) => { 62 | let element: ProjectElement = elements[element_index] 63 | let Component = element.render 64 | return 70 | 71 | 72 | })} 73 |
74 | } 75 | -------------------------------------------------------------------------------- /ui/src/screens/editor/TextEditor.scss: -------------------------------------------------------------------------------- 1 | @use "./Editor"; 2 | 3 | $tabs-height: 46.38px; // Don't ask why this is so specific 4 | $file-list-width: 250px; 5 | 6 | .text__editor { 7 | margin-top: Editor.$tools-height; 8 | 9 | .tab__container { 10 | .editor-panel { 11 | margin: 0; 12 | padding: 0; 13 | 14 | height: calc(100vh - Editor.$tools-height - $tabs-height - 2px); 15 | } 16 | 17 | margin-left: $file-list-width; 18 | width: calc(100vw - $file-list-width); 19 | } 20 | 21 | .editor__list { 22 | position: absolute; 23 | width: $file-list-width; 24 | background-color: hsl(0, 0%, 34%); 25 | 26 | height: calc(100vh - Editor.$tools-height); 27 | 28 | .file { 29 | padding: 5px; 30 | display: flex; 31 | justify-content: space-between; 32 | cursor: pointer; 33 | 34 | img { 35 | width: 22px; 36 | height: 22px; 37 | display: none; 38 | } 39 | &:hover { 40 | background-color: darkgrey; 41 | 42 | img { 43 | display: block; 44 | } 45 | } 46 | } 47 | } 48 | 49 | .new__file { 50 | input, button { 51 | outline: 0; 52 | border: 0; 53 | padding: 5px; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ui/src/screens/editor/TextEditor.tsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useState } from "react"; 2 | import { Project, ProjectScript } from "@libs/project"; 3 | import './TextEditor.scss'; 4 | import Editor, { loader } from "@monaco-editor/react" 5 | 6 | import * as monaco from "monaco-editor" 7 | loader.config({ monaco }) 8 | 9 | import Tabs, { Tab } from '@components/Tabs'; 10 | 11 | import TextEditorFileList from "./TextEditorFileList"; 12 | 13 | interface TextEditorProps { 14 | project: Project 15 | rerender: any 16 | saveProject: any 17 | } 18 | 19 | export default function TextEditor(props: TextEditorProps) { 20 | const [scripts, setScripts] = useState< 21 | { 22 | [key: string]: any 23 | } 24 | >(props.project.serialize().scripts) 25 | 26 | const [selectedTab, setSelectedTab] = useState(0) 27 | 28 | const [tabs, setTabs] = useState>([]) 29 | 30 | return
31 | 41 |
42 | 48 |
49 |
50 | } 51 | -------------------------------------------------------------------------------- /ui/src/screens/editor/TextEditorFileList.tsx: -------------------------------------------------------------------------------- 1 | import Editor from "@monaco-editor/react"; 2 | import React, { FC, useState } from "react"; 3 | import Dialog from "@components/Dialog"; 4 | import { Project, ProjectScript } from "@libs/project"; 5 | 6 | import { Tab } from "@components/Tabs"; 7 | import getAsset from "@libs/getAsset"; 8 | 9 | function createNewTab( 10 | title: string, 11 | script: ProjectScript, 12 | project: Project, 13 | saveProject: () => void 14 | ): Tab { 15 | const newTab: Tab = { 16 | title: title, 17 | content: { 23 | project.updateScript(script.uid, value || script.contents) 24 | saveProject() 25 | }} 26 | theme="vs-dark" 27 | /> 28 | } 29 | return newTab 30 | } 31 | 32 | interface TextEditorFileListProps { 33 | scripts: { 34 | [key: string]: any 35 | } 36 | setScripts: (value: {[key: string]: any}) => void 37 | tabs: Array 38 | setTabs: (newState: Array) => any 39 | setSelectedTab: (newState: number) => any 40 | project: Project 41 | saveProject: any 42 | rerender: (value: number) => void 43 | } 44 | 45 | function EditorFile(props: { 46 | script: { 47 | [key: string]: any 48 | } 49 | tabs: Array 50 | setTabs: (newState: Array) => any 51 | setSelectedTab: (newState: number) => any 52 | project: Project 53 | saveProject: any 54 | }) { 55 | const script: ProjectScript = new ProjectScript() 56 | script.load(props.script) 57 | return
{ 58 | // Check if tab is open 59 | // If tab is open, switch to tab 60 | // Else create a new tab 61 | 62 | let tabExists = false 63 | let tabIndex = 0 64 | 65 | props.tabs.map((tab, index) => { 66 | if (tab.title == script.name) { 67 | tabExists = true 68 | tabIndex = index 69 | } 70 | }) 71 | 72 | if (tabExists) { 73 | props.setSelectedTab(tabIndex) 74 | } else { 75 | const newTab: Tab = createNewTab( 76 | script.name, 77 | script, 78 | props.project, 79 | props.saveProject 80 | ) 81 | let newTabs = [...props.tabs] 82 | newTabs.push(newTab) 83 | props.setTabs(newTabs) 84 | props.setSelectedTab(props.tabs.length) 85 | } 86 | }}> 87 |

{props.script.name}

88 | { 90 | e.preventDefault() 91 | 92 | props.project.deleteScript(script.uid) 93 | let newTabs = [...props.tabs] 94 | let tabFound = false; 95 | newTabs.map((tab, index) => { 96 | if (tabFound) return 97 | if (tab.title == props.script.name) { 98 | delete newTabs[index] 99 | tabFound = true 100 | } 101 | }) 102 | 103 | props.setTabs(newTabs) 104 | props.setSelectedTab(props.tabs.length - 1) 105 | // TODO: loop over tabs 106 | // Then delete the one that fits all the criteria 107 | // Above 108 | // (let's get this thing released tomorrow!) 109 | }} 110 | src={getAsset(".", "mul transparent.png")} 111 | /> 112 |
113 | } 114 | 115 | function createNewScript(name: string, project: Project) { 116 | if (!name.endsWith(".lua")) { 117 | return "Filename must end with .lua" 118 | } 119 | 120 | const newScript = new ProjectScript(name) 121 | project.addScript(newScript) 122 | return newScript 123 | } 124 | 125 | function CreateNewFileDialog(props: { 126 | setShow: (show: boolean) => void 127 | rerender: (value: number) => void 128 | project: Project 129 | saveProject: () => void 130 | setTabs: (value: any) => void 131 | scripts: { 132 | [key: string]: any 133 | } 134 | setScripts: (value: {[key: string]: any}) => void 135 | tabs: Array 136 | }) { 137 | const [filename, setFilename] = useState("") 138 | return
139 |

Please enter the name of the file below

140 | { 145 | setFilename(e.target.value) 146 | }} 147 | /> 148 | 175 |
176 | } 177 | 178 | function TextEditorFileList(props: TextEditorFileListProps) { 179 | const [showFileDialog, setShowFileDialog] = useState(false); 180 | const [, sidebarReload] = useState(0); 181 | 182 | return <> 183 |
184 | {props.scripts && Object.keys(props.scripts).map(key => { 185 | const script: { 186 | [key: string]: any 187 | } = props.scripts[key] 188 | return 197 | })} 198 | 199 |
{ 200 | setShowFileDialog(true) 201 | }}> 202 | Add New File 203 |
204 |
205 | 206 | 216 | 217 | 218 | } 219 | 220 | export default TextEditorFileList 221 | -------------------------------------------------------------------------------- /ui/src/screens/editor/Tools.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as path from 'path'; 3 | 4 | const baseAssetPath = path.join(".", "assets") 5 | 6 | export function getAsset(assetName: string) { 7 | return `/${assetName}` 8 | } 9 | 10 | interface ToolsProps { 11 | currentPage: number 12 | setCurrentPage: (newPage: number) => void 13 | setRunning: (newState: boolean) => void 14 | running: boolean 15 | goBackHome: () => any 16 | } 17 | 18 | interface ToolButtonProps { 19 | children: any 20 | onClick: () => void 21 | } 22 | 23 | function ToolButton(props: ToolButtonProps) { 24 | return
27 | {props.children} 28 |
29 | } 30 | 31 | export default function Tools(props: ToolsProps) { 32 | return
33 | props.goBackHome()}> 34 | 35 | 36 | props.setCurrentPage(0)}> 37 | 38 | 39 | props.setCurrentPage(1)}> 40 | 41 | 42 | { 43 | props.setRunning(!props.running) 44 | }}> 45 | 46 | 47 |
48 | } 49 | -------------------------------------------------------------------------------- /ui/src/screens/home/Home.scss: -------------------------------------------------------------------------------- 1 | $sidebar-width: 150px; 2 | $page-padding: 10px; 3 | 4 | .home { 5 | .sidebar { 6 | width: $sidebar-width; 7 | height: 100vh; 8 | background-color: darkgrey; 9 | position: relative; 10 | .item { 11 | display: flex; 12 | align-items: center; 13 | width: calc(100% - 10px); 14 | padding: 5px; 15 | 16 | p { 17 | padding-left: 5px; 18 | } 19 | 20 | .icon { 21 | width: 30px; 22 | height: 30px; 23 | 24 | img { 25 | width: 100%; 26 | height: 100%; 27 | border-radius: inherit; 28 | } 29 | } 30 | 31 | &:hover { 32 | background-color: hsl(0, 0%, 50%); 33 | } 34 | 35 | cursor: pointer; 36 | } 37 | } 38 | 39 | .page { 40 | position: absolute; 41 | left: $sidebar-width + $page-padding; 42 | top: $page-padding; 43 | } 44 | } -------------------------------------------------------------------------------- /ui/src/screens/home/Home.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, FC } from 'react' 2 | import "./Home.scss" 3 | 4 | import Sidebar, { SidebarItem, getAsset } from "./Sidebar" 5 | import Separator from '@components/Separator' 6 | import Dashboard from './dashboard' 7 | import User from './user' 8 | import Settings from './settings' 9 | 10 | import pb from '@libs/pocketbase' 11 | 12 | import BaseComponentProps from '@root/BaseComponentProps' 13 | import Marketplace from './marketplace' 14 | 15 | interface HomeProps extends BaseComponentProps {} 16 | 17 | export default function Home(props: HomeProps) { 18 | const [currentPage, setPage] = useState(1) 19 | const [currentUser, setCurrentUser] = useState(pb.authStore.model) 20 | 21 | console.log(currentPage) 22 | useEffect(() => { 23 | pb.authStore.onChange((auth) => { 24 | console.log("auth changed", auth) 25 | setCurrentUser(pb.authStore.model) 26 | }) 27 | }) 28 | 29 | const pages = [ 30 | User, 31 | Dashboard, 32 | Settings, 33 | Marketplace 34 | ] 35 | 36 | let Component = pages[currentPage] 37 | 38 | let component_props = {...props} 39 | delete component_props["className"] 40 | 41 | return ( 42 |
43 | 44 | } 46 | onClick={() => setPage(0)} 47 | rounded 48 | > 49 | {currentUser ? currentUser.username : "Sign In"} 50 | 51 | 52 | } 54 | onClick={() => setPage(1)} 55 | > 56 | Dashboard 57 | 58 | } 60 | onClick={() => setPage(3)} 61 | > 62 | Marketplace 63 | 64 | {/* }> 65 | Projects 66 | */} 67 | } style={{ 68 | position: "absolute", 69 | bottom: "0" 70 | }} onClick={() => setPage(2)}> 71 | Settings 72 | 73 | 74 |
75 | 83 |
84 |
85 | ) 86 | } -------------------------------------------------------------------------------- /ui/src/screens/home/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import * as path from "path" 4 | 5 | const baseAssetPath = path.join(".", "assets", "home") 6 | 7 | export function getAsset(assetName: string) { 8 | return `/home/${assetName}` 9 | } 10 | 11 | interface SidebarItemProps { 12 | onClick?: any 13 | style?: Object 14 | rounded?: boolean 15 | icon?: any 16 | children?: any 17 | } 18 | 19 | interface SidebarProps { 20 | children?: any 21 | } 22 | 23 | export function SidebarItem(props: SidebarItemProps) { 24 | return
25 |
{props.icon}
28 |

{props.children}

29 |
30 | } 31 | 32 | export default function Sidebar(props: SidebarProps) { 33 | return
34 | {props.children} 35 |
36 | } -------------------------------------------------------------------------------- /ui/src/screens/home/dashboard/dashboard.scss: -------------------------------------------------------------------------------- 1 | @use "../Home"; 2 | 3 | .dialog__buttons { 4 | padding: 5px; 5 | outline: none; 6 | border: none; 7 | display: flex; 8 | justify-content: flex-end; 9 | gap: 5px; 10 | 11 | button { 12 | padding: 10px; 13 | outline: none; 14 | border: none; 15 | cursor: pointer; 16 | 17 | &.danger { 18 | border: hsl(0, 100%, 35%) 2px solid; 19 | color: hsl(0, 100%, 25%); 20 | 21 | &:hover { 22 | background-color: hsl(0, 100%, 35%); 23 | color: white; 24 | } 25 | } 26 | 27 | &.primary { 28 | border: hsl(180, 100%, 35%) 2px solid; 29 | color: hsl(180, 100%, 25%); 30 | 31 | &:hover { 32 | background-color: hsl(180, 100%, 35%); 33 | color: white; 34 | } 35 | } 36 | 37 | transition: all ease-in-out 0.2s; 38 | } 39 | } 40 | 41 | .dashboard { 42 | width: calc(100vw - Home.$sidebar-width - Home.$page-padding); 43 | 44 | .buttons { 45 | display: flex; 46 | justify-content: space-between; 47 | } 48 | 49 | button { 50 | border: black 2px solid; 51 | color: black; 52 | 53 | &:hover { 54 | background-color: black; 55 | color: white; 56 | } 57 | 58 | &.danger { 59 | border: hsl(0, 100%, 35%) 2px solid; 60 | color: hsl(0, 100%, 25%); 61 | 62 | &:hover { 63 | background-color: hsl(0, 100%, 35%); 64 | color: white; 65 | } 66 | } 67 | 68 | &.primary { 69 | border: hsl(180, 100%, 35%) 2px solid; 70 | color: hsl(180, 100%, 25%); 71 | 72 | &:hover { 73 | background-color: hsl(180, 100%, 35%); 74 | color: white; 75 | } 76 | } 77 | 78 | background-color: inherit; 79 | outline: 0; 80 | padding: 5px; 81 | border-radius: 0.7rem; 82 | cursor: pointer; 83 | 84 | transition: all ease-in-out 0.2s; 85 | } 86 | 87 | .project__dialog { 88 | position: absolute; 89 | } 90 | 91 | .projects { 92 | display: flex; 93 | flex-wrap: wrap; 94 | .project__card { 95 | padding: 15px; 96 | box-shadow: 0px 0px 40px 5px rgba(0,0,0,0.20); 97 | width: 125px; 98 | border-radius: 0.7rem; 99 | 100 | text-align: left; 101 | margin-right: 10px; 102 | margin-bottom: 10px; 103 | 104 | h4 { 105 | padding: 2px; 106 | text-overflow: ellipsis; 107 | overflow: hidden; 108 | white-space: nowrap; 109 | } 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /ui/src/screens/home/dashboard/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, FC } from 'react'; 2 | import "./dashboard.scss" 3 | 4 | import PocketBase from "pocketbase" 5 | import { 6 | Project, ProjectScreen 7 | } from '@libs/project'; 8 | 9 | import Dialog from '@components/Dialog'; 10 | 11 | import { newMainLuaFile } from "@libs/newMainLuaFile" 12 | 13 | import PluginManager from "@libs/plugin"; 14 | import Separator from '@components/Separator'; 15 | 16 | interface DashboardProps { 17 | pb: PocketBase 18 | currentUser: any 19 | setCurrentUser(newState: any): any 20 | setCurrentPage(newState: any): any 21 | setState(newState: any): any 22 | state: object 23 | pluginManager: PluginManager 24 | dialogUtils: { 25 | createDialog: Function, 26 | setDialog: Function, 27 | showDialog: Function, 28 | hideDialog: Function 29 | } 30 | } 31 | 32 | interface ProjectCard { 33 | projectName: string 34 | onOpen(e: any): any 35 | onDelete(e: any): any 36 | } 37 | 38 | function ProjectCard(props: ProjectCard) { 39 | return
40 |

{props.projectName}

41 |
42 | 43 | 44 |
45 |
46 | } 47 | 48 | export default function Dashboard(props: DashboardProps) { 49 | // WHY DID WE HAVE TO GO TO C++ LAND :(((( https://stackoverflow.com/a/60696264/20616402 50 | const [projects, setProjects] = useState([]) 51 | 52 | async function getData() { 53 | try { 54 | const resultList = await props.pb.collection("projects").getFullList(100, { 55 | filter: `user = "${props.currentUser.id}"` 56 | }) 57 | // console.log(resultList) 58 | setProjects(resultList) 59 | } catch (e) { 60 | console.log(e) 61 | if (window.customNamespace.DEBUG) {} else { 62 | setProjects([ 63 | {} 64 | ]); 65 | } 66 | } 67 | } 68 | 69 | useEffect(() => { 70 | if (!props.currentUser) return () => {}; 71 | 72 | getData() 73 | }, []) 74 | 75 | const createProject = async (projectName: string) => { 76 | const project = new Project(projectName) 77 | project.addScript(newMainLuaFile()) 78 | 79 | const newScreen = new ProjectScreen("Screen 1") 80 | project.addScreen(newScreen) 81 | let data: any = project.serialize() 82 | data["user"] = props.currentUser.id 83 | await props.pb.collection("screens").create(newScreen.serialize()).then(screen_data => { 84 | data["screens"] = [ 85 | screen_data.id 86 | ] 87 | }) 88 | try { 89 | const createdProject = await props.pb.collection("projects").create(data) 90 | props.setState({ 91 | ...props.state, 92 | project: createdProject 93 | }) 94 | props.setCurrentPage(2) 95 | } catch (e) { 96 | console.log(e) 97 | } 98 | } 99 | 100 | const DialogContent = () => { 101 | const [projectName, setProjectName] = useState("") 102 | return <> 103 |

Enter your project name then click Create

104 |
105 | 106 |
107 | { 108 | const value = e.target.value 109 | setProjectName(value) 110 | }} placeholder="Project Name" /> 111 |
112 |
113 | 114 |
115 | 116 | } 117 | 118 | return
119 |

Dashboard

120 | {props.currentUser ? <> 121 | 126 | 127 |

Projects

128 |
129 | {JSON.stringify(projects) !== JSON.stringify([]) ? projects.map(project => { 130 | return { 131 | props.setState({ 132 | ...props.state, 133 | project: project 134 | }) 135 | props.setCurrentPage(2) 136 | }} onDelete={(e) => { 137 | props.dialogUtils.setDialog( 138 | props.dialogUtils.createDialog( 139 | `Are you sure you want to delete project "${project.name}"?`, 140 | <> 141 | If you click yes, yes if you click that button that says yes, you are agreeing that we can delete your project from our servers. 142 |
143 |
144 | Are you sure you want to continue? 145 |
146 |
147 |
148 |
149 |
150 | 153 | 159 |
160 | 161 | ) 162 | ) 163 | props.dialogUtils.showDialog() 164 | }} key={Math.random()} /> 165 | }) :

Loading...

} 166 |
167 | : "Please login to create a project." 168 | } 169 |
170 | } 171 | -------------------------------------------------------------------------------- /ui/src/screens/home/marketplace/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import "./marketplace.scss" 3 | 4 | import PocketBase from "pocketbase" 5 | import PluginManager from "@libs/plugin"; 6 | 7 | import GitHubUtils from "@libs/github"; 8 | import TauriFS from "@libs/tauriFS"; 9 | import ThemeLoader from "@libs/theme"; 10 | 11 | interface MarketplaceProps { 12 | pb: PocketBase 13 | currentUser: any 14 | setCurrentUser(newState: any): any 15 | setCurrentPage(newState: any): any 16 | setState(newState: any): any 17 | state: object 18 | pluginManager: PluginManager 19 | dialogUtils: { 20 | createDialog: Function, 21 | setDialog: Function, 22 | showDialog: Function, 23 | hideDialog: Function 24 | } 25 | } 26 | 27 | const themeLoader = new ThemeLoader() 28 | 29 | interface InfoJSONSchema { 30 | name: string 31 | author: string 32 | } 33 | 34 | interface ThemeInterface { 35 | aboutMD: string 36 | name: string 37 | styleCSSPath: string 38 | infoJSON: InfoJSONSchema 39 | } 40 | 41 | function MarketplaceItem(props: { 42 | theme: ThemeInterface 43 | }) { 44 | const [themeInstalled, setThemeInstalled] = useState(false) 45 | 46 | useEffect(() => { 47 | (async () => { 48 | const themes = await themeLoader.getThemes() 49 | if (themes.includes(`${theme.infoJSON.author}.${theme.infoJSON.name}`)) { 50 | setThemeInstalled(true) 51 | } 52 | })() 53 | }, []) 54 | 55 | if (themeInstalled) return <> 56 | 57 | const { theme } = props 58 | return
59 |
60 | {theme.infoJSON.name} 61 |
62 | By {theme.infoJSON.author} 63 |
64 |
65 | 86 |
87 | } 88 | 89 | function Marketplace(props: MarketplaceProps) { 90 | const [themes, setThemes] = useState([]) 91 | 92 | useEffect(() => { 93 | if (themes.length !== 0) return 94 | const themeFolderNames = (GitHubUtils.getDirectory({ 95 | user: "ProtonDesigner", 96 | repo: "themes", 97 | path: "themes" 98 | })) as any[] 99 | 100 | let fetchedThemes: Array = [] 101 | themeFolderNames.forEach(theme => { 102 | const dir = (GitHubUtils.getDirectory({ 103 | user: "ProtonDesigner", 104 | repo: "themes", 105 | path: theme.path 106 | })) as any[] 107 | 108 | let aboutMD = "" 109 | const name = theme.name 110 | let styleCSSPath = "" 111 | let infoJSON: InfoJSONSchema = { 112 | name: "You'll never see this", 113 | author: "Tech10 (developer)" 114 | } 115 | 116 | dir.map(file => { 117 | let filename: string = file.name 118 | const readFile = GitHubUtils.getFile({ 119 | username: "ProtonDesigner", 120 | repo: "themes", 121 | file_path: file.path, 122 | branch: "develop" 123 | }) 124 | if (filename.toLowerCase() == "about.md") { 125 | aboutMD = readFile 126 | } else if (filename.toLowerCase() == "info.json") { 127 | infoJSON = JSON.parse(readFile) 128 | } else if (filename.toLowerCase() == "style.css") { 129 | styleCSSPath = file.path 130 | } 131 | }) 132 | 133 | fetchedThemes.push({ 134 | aboutMD, 135 | infoJSON, 136 | name, 137 | styleCSSPath 138 | }) 139 | }) 140 | 141 | console.log(fetchedThemes) 142 | setThemes(fetchedThemes) 143 | }, []) 144 | 145 | return
146 |

Marketplace

147 |

The Marketplace is the tab where users can share their own custom made themes for Proton! Check some out below.

148 |

149 | 150 | Theme Index (open in default browser) 151 | 152 |

153 |
154 | {themes && themes.map(theme => { 155 | return 156 | })} 157 |
158 |
159 | } 160 | 161 | export default Marketplace 162 | -------------------------------------------------------------------------------- /ui/src/screens/home/marketplace/marketplace.scss: -------------------------------------------------------------------------------- 1 | .marketplace { 2 | .themes { 3 | display: flex; 4 | flex-wrap: wrap; 5 | .marketplace__item { 6 | padding: 20px; 7 | box-shadow: 0px 0px 40px 5px rgba(0,0,0,0.20); 8 | 9 | width: fit-content; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /ui/src/screens/home/settings/ThemeSettingPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react" 2 | import SettingsManager from "@libs/settings" 3 | import ThemeLoader from "@libs/theme" 4 | 5 | const themeLoader = new ThemeLoader() 6 | 7 | function ThemeSettingPanel(props: {}) { 8 | const [themes, setThemes] = useState([]) 9 | 10 | function getSetThemes() { 11 | themeLoader.getThemes().then(themes => { 12 | setThemes(themes) 13 | }) 14 | } 15 | 16 | useEffect(() => { 17 | getSetThemes() 18 | }, []) 19 | 20 | 21 | if (themes.length == 0) return <>You have no themes installed! Go to the Marketplace to get some themes :) 22 | 23 | return
24 | {themes && themes.map(themeName => <> 25 |

{themeName}

26 |
27 | 33 | {" "} 34 | 48 |
49 |
50 | )} 51 |
52 |
53 | 59 |
60 | } 61 | 62 | export default ThemeSettingPanel 63 | -------------------------------------------------------------------------------- /ui/src/screens/home/settings/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { RefObject, useRef, useState } from "react" 2 | import "./settings.scss" 3 | import PocketBase from "pocketbase" 4 | 5 | import PluginManager from "@libs/plugin" 6 | import TabbedSidebar, { Tab } from "@components/TabbedSidebar" 7 | 8 | import SettingsManager from "@libs/settings" 9 | import capitalizeFirstLetter from "@libs/utils/capitalizeFirstLetter" 10 | import addSpaceAtCapitals from "@libs/utils/addSpaceAtCapitals" 11 | import ThemeSettingPanel from "./ThemeSettingPanel" 12 | 13 | interface SettingsProps { 14 | pb: PocketBase 15 | currentUser: any 16 | setCurrentUser(newState: any): any 17 | setCurrentPage(newState: any): any 18 | setState(newState: any): any 19 | state: object 20 | pluginManager: PluginManager 21 | dialogUtils: { 22 | createDialog: Function, 23 | setDialog: Function, 24 | showDialog: Function, 25 | hideDialog: Function 26 | } 27 | } 28 | 29 | const settingsManager = new SettingsManager({}) 30 | 31 | export default function Settings(props: SettingsProps) { 32 | function generateTabs(_object: { [key: string]: any }, level: number, inputRef: RefObject, inputState: ReturnType>) { 33 | let tabs: Array = [{ 34 | title: "Welcome", 35 | content: <> 36 | Welcome to Proton Designer! 37 | 38 | }] 39 | 40 | Object.keys(_object).map(objKey => { 41 | const value = _object[objKey] 42 | const [inpState, setInpState] = inputState 43 | 44 | console.log(objKey, value) 45 | 46 | if (typeof value == "object") { 47 | console.log(value) 48 | let content = <> 49 | function getContent(value: any, key: string) { 50 | const valueType = typeof value 51 | 52 | if (valueType == "boolean") { 53 | return 61 | } else if (valueType == "string") { 62 | return { 63 | setInpState(e.target.value) 64 | 65 | settingsManager.settings[objKey][key] = e.target.value 66 | settingsManager.saveSettings() 67 | }} /> 68 | } 69 | } 70 | tabs.push({ 71 | title: capitalizeFirstLetter(objKey), 72 | content: <>{Object.keys(value).map((key) => { 73 | const settingValue = value[key] 74 | setInpState(settingsManager.settings[objKey][key]) 75 | return <> 76 |

You must restart the app to apply changes.

77 |
78 | {addSpaceAtCapitals(capitalizeFirstLetter(key))}: {getContent(settingValue, key)} 79 | 80 | })} 81 | }) 82 | } else { 83 | tabs.push({ 84 | title: capitalizeFirstLetter(objKey), 85 | content: <>{value} 86 | }) 87 | } 88 | }) 89 | 90 | const additionalTabs: { 91 | [key: string]: (props: any) => JSX.Element 92 | } = { 93 | themes: ThemeSettingPanel 94 | } 95 | Object.keys(additionalTabs).map(key => { 96 | const SettingContent = additionalTabs[key] 97 | tabs.push({ 98 | title: capitalizeFirstLetter(key), 99 | content: 100 | }) 101 | }) 102 | 103 | return tabs 104 | } 105 | 106 | return
107 | generateTabs(settingsManager.settings, 0, ref, state)} /> 108 |
109 | } 110 | -------------------------------------------------------------------------------- /ui/src/screens/home/settings/settings.scss: -------------------------------------------------------------------------------- 1 | @use "../Home"; 2 | @use "../../../components/Tabs"; 3 | 4 | .settings { 5 | div.tabs { 6 | .tab__button { 7 | background-color: inherit; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /ui/src/screens/home/user/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, FC } from 'react'; 2 | import "./user.scss" 3 | 4 | import PocketBase, { ClientResponseError } from "pocketbase" 5 | 6 | import capitalizeFirstLetter from '@libs/utils/capitalizeFirstLetter'; 7 | 8 | async function login(pb: PocketBase, username: string, password: string): Promise<{[key: string]: any} | null> { 9 | let _return: {[key: string]: any} | null = null 10 | try { 11 | await pb.collection("users").authWithPassword(username, password) 12 | } catch (err: any) { 13 | _return = err.data 14 | } 15 | return _return 16 | }; 17 | async function signup( 18 | pb: PocketBase, 19 | username: string, 20 | password: string, 21 | passwordConfirm: string 22 | ): Promise<{[key: string]: any} | null> { 23 | const data = { 24 | username, 25 | password, 26 | passwordConfirm: passwordConfirm 27 | } 28 | let _return: {[key: string]: any} | null = null; 29 | try { 30 | const createdUser = await pb.collection("users").create(data) 31 | } catch (err: any) { 32 | _return = err.data 33 | } 34 | if (_return) { 35 | return _return 36 | } 37 | await login(pb, username, password) 38 | return null 39 | }; 40 | const logout = (pb: PocketBase) => { 41 | pb.authStore.clear() 42 | }; 43 | 44 | interface UserProps { 45 | pb: PocketBase 46 | currentUser: any 47 | setCurrentUser(newState: any): any 48 | dialogUtils: { 49 | createDialog: Function, 50 | setDialog: Function, 51 | showDialog: Function, 52 | hideDialog: Function 53 | } 54 | } 55 | 56 | function Login(props: any) { 57 | const [username, setUsername] = useState("") 58 | const [password, setPassword] = useState("") 59 | const [error, setError] = useState(null) 60 | 61 | return
62 |

Sign in to access your account and all your projects in the cloud

63 |
64 | setUsername(e.target.value)} 68 | placeholder="Username" 69 | /> 70 |
71 |
72 | setPassword(e.target.value)} 76 | placeholder="Password" 77 | /> 78 | {error ? <>
79 |
{error}
: <> 80 |
81 |
} 82 | 91 |

Don't have an account? Click props.setLoginPage(false)}>Here

92 |
93 | } 94 | 95 | function Signup(props: any) { 96 | const [username, setUsername] = useState("") 97 | const [password, setPassword] = useState("") 98 | const [passwordConfirm, setPasswordConfirm] = useState("") 99 | const [email, setEmail] = useState("") 100 | const [error, setError] = useState(null) 101 | 102 | return
103 |

Sign Up for saving projects in the cloud

104 |
105 | 106 | setUsername(e.target.value)} 110 | placeholder="Username" 111 | /> 112 |
113 |
114 | 115 | setEmail(e.target.value)} 119 | placeholder="Email" 120 | /> 121 |
122 |
123 | 124 | setPassword(e.target.value)} 128 | placeholder="Password" 129 | /> 130 |
131 |
132 | 133 | setPasswordConfirm(e.target.value)} 137 | placeholder="Confirm Password" 138 | /> 139 | {error ? <>
140 |
{error}
: <> 141 |
142 |
} 143 | 150 |

Already have an account? Click props.setLoginPage(true)}>Here

151 |
152 | } 153 | 154 | function LoggedInUser(props: any) { 155 | return
156 |

Hello, {props.currentUser.username}

157 |

Logged in as {props.currentUser.username}

158 | 159 |
160 | } 161 | 162 | export default function User(props: UserProps) { 163 | const [loginPage, setLoginPage] = useState(true) 164 | 165 | return
166 | {props.currentUser ? : 167 |
e.preventDefault()}> 168 | {loginPage ? : } 169 | 170 | } 171 |
172 | } 173 | -------------------------------------------------------------------------------- /ui/src/screens/home/user/user.scss: -------------------------------------------------------------------------------- 1 | .user__page { 2 | // .logged__in { 3 | 4 | // } 5 | button { 6 | border: 1px solid hsl(0, 0%, 40%); 7 | outline: none; 8 | background-color: transparent; 9 | padding: 4px; 10 | border-radius: 0.5rem; 11 | } 12 | input { 13 | outline: 0; 14 | border: 0; 15 | background-color: grey; 16 | border-radius: 0.5rem; 17 | padding: 5px; 18 | color: white; 19 | 20 | &::placeholder { 21 | color: white; 22 | } 23 | } 24 | .error { 25 | color: red; 26 | } 27 | .login__page, .signup__page { 28 | text-align: center; 29 | margin: 0.5rem; 30 | } 31 | a { 32 | color: hsl(240, 100%, 35%); 33 | cursor: pointer; 34 | } 35 | } -------------------------------------------------------------------------------- /ui/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite" 2 | import react from "@vitejs/plugin-react" 3 | import path from "path-browserify" 4 | import wasm from "vite-plugin-wasm" 5 | 6 | export default defineConfig({ 7 | root: ".", 8 | clearScreen: false, 9 | server: { 10 | strictPort: true 11 | }, 12 | envPrefix: ["VITE_", "TAURI_"], 13 | build: { 14 | outDir: "./dist", 15 | target: "esnext", 16 | minify: !process.env.TAURI_DEBUG ? "esbuild" : false, 17 | sourcemap: !process.env.TAURI_DEBUG, 18 | commonjsOptions: { 19 | transformMixedEsModules: true 20 | } 21 | }, 22 | plugins: [ 23 | react({ 24 | include: "**/*.{jsx,tsx}" 25 | }), 26 | wasm() 27 | ], 28 | publicDir: "assets", 29 | resolve: { 30 | alias: { 31 | path: "path-browserify", 32 | "@components": path.resolve(__dirname, "/src/components"), 33 | "@libs": path.resolve(__dirname, "/src/libs"), 34 | "@screens": path.resolve(__dirname, "/src/screens"), 35 | "@root": path.resolve(__dirname, "/src") 36 | } 37 | } 38 | }) 39 | -------------------------------------------------------------------------------- /update_versions.py: -------------------------------------------------------------------------------- 1 | # You will need to install the toml library: 2 | # pip install toml 3 | 4 | import json 5 | import toml 6 | import os 7 | import argparse 8 | 9 | PACKAGE_JSON = "package.json" 10 | TAURI_CONF_JSON = os.path.join("src-tauri", "tauri.conf.json") 11 | CARGO_TOML = os.path.join("src-tauri", "Cargo.toml") 12 | 13 | JSON_INDENTATION_LEVEL = 2 14 | 15 | # Update package.json 16 | def updatePackageJson(version: str): 17 | with open(PACKAGE_JSON, "r+") as f: 18 | f.seek(0) 19 | package_json = json.load(f) 20 | package_json["version"] = version 21 | f.seek(0) 22 | json.dump(package_json, f, indent=JSON_INDENTATION_LEVEL) 23 | f.truncate() 24 | f.write("\n") 25 | 26 | # Update tauri.conf.json 27 | def updateTauriJson(version: str): 28 | with open(TAURI_CONF_JSON, "r+") as f: 29 | f.seek(0) 30 | tauri_json = json.load(f) 31 | tauri_json["package"]["version"] = version 32 | f.seek(0) 33 | json.dump(tauri_json, f, indent=JSON_INDENTATION_LEVEL) 34 | f.truncate() 35 | f.write("\n") 36 | 37 | # Update Cargo.toml 38 | def updateCargoToml(version: str): 39 | with open(CARGO_TOML, "r+") as f: 40 | f.seek(0) 41 | cargo_toml = toml.load(f) 42 | cargo_toml["package"]["version"] = version 43 | f.seek(0) 44 | toml.dump(cargo_toml, f) 45 | f.truncate() 46 | 47 | if __name__ == "__main__": 48 | parser = argparse.ArgumentParser( 49 | prog="ProtonVersionUpdater", 50 | description="Utility for updating versions in all files (because it was annoying to do so manually)", 51 | epilog="The classic cycle of a programmer" 52 | ) 53 | parser.add_argument("version") 54 | 55 | args = parser.parse_args() 56 | updatePackageJson(args.version) 57 | updateTauriJson(args.version) 58 | updateCargoToml(args.version) 59 | --------------------------------------------------------------------------------