├── .eslintrc.cjs ├── .eslintrc.json ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── extensions.json ├── README.md ├── elec ├── index.ts └── shared │ ├── exec.ts │ ├── key-events.ts │ ├── readJSON.ts │ ├── sleep.ts │ └── uriFromPath.ts ├── index.html ├── package.json ├── pnpm-lock.yaml ├── public ├── favicon.ico └── static │ └── assets │ ├── css.svg │ ├── down.svg │ ├── folder-open.svg │ ├── folder.svg │ ├── html.svg │ ├── jpg.svg │ ├── js.svg │ ├── json.svg │ ├── png.svg │ ├── svg.svg │ ├── ts.svg │ └── xml.svg ├── script ├── build.js └── rollup.config.js ├── src ├── App.svelte ├── assets │ ├── css.svg │ ├── down.svg │ ├── jpg.svg │ ├── js.svg │ ├── json.svg │ ├── png.svg │ ├── svelte.png │ ├── svg.svg │ ├── ts.svg │ ├── warp.png │ ├── warpwhite.png │ └── xml.svg ├── components │ ├── CodeMirror.svelte │ ├── Editor.svelte │ ├── Monaco.svelte │ ├── Terminal.svelte │ ├── canvas │ │ ├── EditorInput.svelte │ │ ├── LayoutEditor.svelte │ │ ├── MobileFrame.svelte │ │ ├── NestedContent.svelte │ │ ├── StyleEditor.svelte │ │ ├── UICanvas.svelte │ │ ├── UIPallete.svelte │ │ └── stores │ │ │ ├── layout.ts │ │ │ ├── selected.ts │ │ │ └── style.ts │ ├── fileExplorer │ │ ├── Explorer.svelte │ │ ├── File.svelte │ │ └── Folder.svelte │ ├── tabs │ │ ├── OnlyTabs.svelte │ │ ├── RemovableTabs.svelte │ │ └── Tabs.svelte │ ├── warp │ │ └── widgets │ │ │ ├── Button.svelte │ │ │ ├── Container.svelte │ │ │ ├── Generic.svelte │ │ │ ├── Image.svelte │ │ │ ├── Label.svelte │ │ │ ├── ScrollContainer.svelte │ │ │ ├── TextBox.svelte │ │ │ ├── TextInput.svelte │ │ │ ├── VideoPlayer.svelte │ │ │ └── index.ts │ └── xterm.css ├── main.ts ├── modules │ └── warp │ │ ├── canvasActions │ │ └── index.ts │ │ └── codeMap │ │ └── index.ts ├── stores │ ├── activeDirectory.ts │ ├── activeFile.ts │ ├── directoryStore.ts │ ├── layout.ts │ ├── selected.ts │ └── style.ts ├── views │ ├── AppBuilder.svelte │ └── Launcher.svelte └── vite-env.d.ts ├── svelte.config.js ├── tsconfig.json ├── vite.config.js └── warp.png /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: [ 7 | '*.cjs', 8 | 'rollup.config.js', 9 | 'dist/', 10 | 'argo/', 11 | 'global.d.ts', 12 | 'public/static/assets/azure-media-plugin.js', 13 | '**/worker/*.js', 14 | '**/pnpm-lock.yaml' 15 | ], 16 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 17 | settings: { 18 | 'svelte3/typescript': () => require('typescript') 19 | }, 20 | parserOptions: { 21 | sourceType: 'module', 22 | ecmaVersion: 2019 23 | }, 24 | env: { 25 | browser: true, 26 | es2017: true, 27 | node: true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 7 | "ignorePatterns": ["public/*", "static/*"], 8 | "parser": "@typescript-eslint/parser", 9 | "parserOptions": { 10 | "ecmaVersion": 12, 11 | "sourceType": "module" 12 | }, 13 | "plugins": ["@typescript-eslint"], 14 | "rules": { 15 | "indent": ["error", 4], 16 | "linebreak-style": ["error", "unix"], 17 | "quotes": ["error", "single"], 18 | "semi": ["error", "never"], 19 | "import/no-named-as-default": 0 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: 'CodeQL' 13 | 14 | on: 15 | push: 16 | branches: [main] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [main] 20 | schedule: 21 | - cron: '15 13 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: ['javascript'] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | .DS_Store 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # TypeScript v1 declaration files 46 | typings/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | 76 | # parcel-bundler cache (https://parceljs.org/) 77 | .cache 78 | 79 | # Next.js build output 80 | .next 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | dist 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | # vuepress build output 93 | .vuepress/dist 94 | 95 | # Serverless directories 96 | .serverless/ 97 | 98 | # FuseBox cache 99 | .fusebox/ 100 | 101 | # DynamoDB Local files 102 | .dynamodb/ 103 | 104 | # TernJS port file 105 | .tern-port 106 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .svelte-kit/** 2 | public/static/** 3 | public/build/** 4 | dist/** 5 | node_modules/** 6 | worker-site/** 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "svelteStrictMode": true, 5 | "trailingComma": "none", 6 | "semi": false, 7 | "printWidth": 100 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["svelte.svelte-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Warp Code IDE 2 | 3 | An electron based Svelte app builder that allows you to visually create applications, whilst generating clean Svelte code, using Vite for the UI build step (Svelte based) and Rollup for the Electron components (Typescript). 4 | 5 | Capacitor is used to build and deploy to mobile applications. 6 | 7 | It will also support standard web and native desktop applications down the line. 8 | 9 | Note: This is in very early pre-alpha and serves as proof of concept in it's current state. Here be dragons, etc etc. 10 | 11 | Active contributors very welcome. 12 | 13 | ## What does it look like (for now)? 14 | 15 | ![alt text](https://github.com/patrickjquinn/project-warp/blob/main/warp.png?raw=true) 16 | 17 | ## Recommended IDE Setup 18 | 19 | [VSCode](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). 20 | 21 | ## Setting up Warp on your machine 22 | 23 | ``` 24 | npn i -g pnpm 25 | pnpm i 26 | pnpm dev:all 27 | ``` 28 | 29 | ## Troubleshooting 30 | 31 | If the 'dev:all' command failed to execute because of node-pty. 32 | 33 | Run: 34 | 35 | ``` 36 | pnpm rebuild 37 | ``` 38 | 39 | And then: 40 | 41 | ``` 42 | $(npm bin)/electron-rebuild 43 | ``` 44 | 45 | On .nix machines (Mac, Linux, BSD) 46 | 47 | And: 48 | 49 | ``` 50 | .\node_modules\.bin\electron-rebuild.cmd 51 | ``` 52 | 53 | On NT machines (Windows, ReactOS, Wine). 54 | 55 | ## Trying it out. 56 | 57 | Once you have the service running, create a new project with the onscreen option to do so. It will download and initialize the project from another template repo. By default it will open the README file but if you navigate to `src/pages/index.svelte` the editor will appear and allow you interact with the visual canvas. 58 | 59 | Now in the top options bar, click the 'Run+Build' tab and then 'Run Web'. This will start the poject's Vite server in dev mode and changes made in code or on the canvas will reflect in real time. 60 | -------------------------------------------------------------------------------- /elec/index.ts: -------------------------------------------------------------------------------- 1 | import 'v8-compile-cache' 2 | import { join } from 'path' 3 | import { app, BrowserWindow, ipcMain, dialog, Menu, shell } from 'electron' 4 | import is_dev from 'electron-is-dev' 5 | import dotenv from 'dotenv' 6 | import Store from 'electron-store' 7 | import pty from 'node-pty' 8 | import defaultShell from 'default-shell' 9 | import os from 'os' 10 | import fs from 'fs' 11 | import util from 'util' 12 | import directoryTree from 'directory-tree' 13 | import { exec } from './shared/exec' 14 | import { sleep } from './shared/sleep' 15 | import readJSON from './shared/readJSON' 16 | import * as path from 'path' 17 | import degit from 'degit' 18 | // import chokidar from 'chokidar' 19 | 20 | const readFile = util.promisify(fs.readFile) 21 | const store = new Store() 22 | const darkBackgroundColor = 'black' 23 | 24 | let ptyProcess 25 | let watcher 26 | let projectDir = `` 27 | let launcherWindow: BrowserWindow 28 | let win: BrowserWindow 29 | 30 | const userData = app.getPath('userData') 31 | const recent = readJSON(path.join(userData, 'recent.json')) || [] 32 | 33 | ipcMain.on('store:set', async (e, args) => { 34 | store.set(args.key, args.value) 35 | }) 36 | ipcMain.handle('store:get', async (e, args) => { 37 | const value = await store.get(args) 38 | return value 39 | }) 40 | ipcMain.on('store:delete', async (e, args) => { 41 | store.delete(args) 42 | }) 43 | 44 | ipcMain.on('request-proj-struct', (e) => { 45 | e.sender.send('send-proj-struct', buildTree(projectDir)) 46 | }) 47 | 48 | function buildTree(rootPath: string) { 49 | return directoryTree(rootPath) 50 | } 51 | 52 | dotenv.config({ path: join(__dirname, '../../.env') }) 53 | 54 | class createWin { 55 | constructor() { 56 | win = new BrowserWindow({ 57 | width: 800, 58 | height: 600, 59 | title: 'Warp Code', 60 | titleBarStyle: 'hidden', 61 | webPreferences: { 62 | nodeIntegration: true, 63 | contextIsolation: false 64 | }, 65 | show: false, 66 | backgroundColor: '#2f4f4f' 67 | }) 68 | 69 | win.maximize() 70 | win.show() 71 | 72 | const URL = is_dev 73 | ? 'http://localhost:5173' 74 | : `file://${join(__dirname, '../../dist/index.html')}` 75 | 76 | win.loadURL(URL) 77 | 78 | win.on('ready-to-show', () => { 79 | watcher = fs.watch(projectDir, { recursive: true }, (eventType, filename) => { 80 | if ( 81 | !filename.includes('node_modules') && 82 | !filename.includes('_tmp_') && 83 | !filename.includes('pnpm-lock') && 84 | !filename.includes('.routify') && 85 | !filename.includes('.DS_Store') 86 | ) { 87 | console.log(eventType) 88 | win.webContents.send('send-proj-struct', buildTree(projectDir)) 89 | console.log(filename) 90 | } 91 | }) 92 | }) 93 | 94 | win.on('focus', () => { 95 | win.webContents.send('focus') 96 | }) 97 | 98 | win.on('blur', () => { 99 | win.webContents.send('blur') 100 | }) 101 | 102 | ptyProcess = pty.spawn(defaultShell, [], { 103 | name: 'xterm-color', 104 | cols: 80, 105 | rows: 24, 106 | cwd: projectDir, 107 | env: process.env 108 | }) 109 | 110 | ptyProcess.on('data', (data) => { 111 | win.webContents.send('terminal-incData', data) 112 | }) 113 | 114 | ipcMain.on('terminal-into', (event, data) => { 115 | ptyProcess.write(data) 116 | }) 117 | } 118 | } 119 | 120 | function launch() { 121 | const warpspace = `${os.homedir()}/warpspace` 122 | if (!fs.existsSync(warpspace)) { 123 | fs.mkdirSync(warpspace) 124 | } 125 | 126 | launcherWindow = new BrowserWindow({ 127 | width: 800, 128 | height: 600, 129 | minWidth: 600, 130 | titleBarStyle: 'hidden', 131 | webPreferences: { 132 | nodeIntegration: true, 133 | contextIsolation: false 134 | }, 135 | show: false, 136 | backgroundColor: darkBackgroundColor 137 | }) 138 | 139 | launcherWindow.loadURL('http://localhost:5173/#/launch') 140 | 141 | launcherWindow.once('ready-to-show', () => { 142 | launcherWindow.show() 143 | }) 144 | } 145 | 146 | async function readFileAt(path) { 147 | return readFile(path, 'utf8') 148 | } 149 | 150 | app.whenReady().then(() => { 151 | launch() 152 | }) 153 | 154 | app.on('window-all-closed', () => { 155 | if (process.platform !== 'darwin') { 156 | app.quit() 157 | } 158 | }) 159 | 160 | app.on('activate', () => { 161 | if (BrowserWindow.getAllWindows().length === 0) { 162 | new createWin() 163 | } 164 | }) 165 | 166 | function pushToRecents(dir) { 167 | if (!fs.existsSync(dir)) return 168 | const index = recent.indexOf(dir) 169 | if (index !== -1) recent.splice(index, 1) 170 | recent.unshift(dir) 171 | while (recent.length > 5) recent.pop() 172 | fs.writeFileSync(path.join(userData, 'recent.json'), JSON.stringify(recent)) 173 | } 174 | 175 | function openProject(dir) { 176 | pushToRecents(dir) 177 | projectDir = dir 178 | console.log(`Project Dir = ${projectDir}`) 179 | launcherWindow.close() 180 | createApplicationMenu() 181 | new createWin() 182 | 183 | // watcher = chokidar.watch(projectDir, { 184 | // usePolling: true, 185 | // ignored: (path) => path.includes('node_modules') 186 | // }).on('all', (event, path) => { 187 | // console.log(event) 188 | // if (!path.includes('node_modules') && !path.includes('_tmp_') 189 | // && !path.includes('pnpm-lock') && !path.includes('.routify') && !path.includes('.DS_Store')) { 190 | // win.webContents.send('send-proj-struct', buildTree(projectDir)) 191 | // } 192 | // }) 193 | } 194 | 195 | function existingProjectDialog(dir) { 196 | const focusedWindow: BrowserWindow | null = BrowserWindow.getFocusedWindow() 197 | if (dir) { 198 | openProject(dir) 199 | } else { 200 | if (!focusedWindow) return 201 | dialog 202 | .showOpenDialog(focusedWindow, { 203 | title: 'Open project', 204 | buttonLabel: 'Open project', 205 | properties: ['openDirectory'], 206 | defaultPath: `${os.homedir()}/warpspace` 207 | }) 208 | .then((result) => { 209 | if (result.canceled || !result.filePaths || result.filePaths.length === 0) return 210 | setTimeout(() => { 211 | openProject(result.filePaths[0]) 212 | }, 0) 213 | }) 214 | .catch((err) => { 215 | console.log(err) 216 | }) 217 | } 218 | } 219 | 220 | function createProjectDialog(event) { 221 | const focusedWindow: BrowserWindow | null = BrowserWindow.getFocusedWindow() 222 | if (!focusedWindow) return 223 | 224 | dialog 225 | .showOpenDialog(focusedWindow, { 226 | title: 'Create project', 227 | buttonLabel: 'Create project', 228 | properties: ['openDirectory', 'createDirectory'], 229 | defaultPath: `${os.homedir()}/warpspace` 230 | }) 231 | .then(async (results) => { 232 | if (results.canceled || !results.filePaths || results.filePaths.length === 0) return 233 | 234 | const [filename] = results.filePaths 235 | 236 | if (event) event.sender.send('status', `cloning repo to ${path.basename(filename)}...`) 237 | 238 | try { 239 | const emitter = degit('patrickjquinn/warp-project-template') 240 | await emitter.clone(filename) 241 | 242 | if (event) event.sender.send('status', `installing dependencies...`) 243 | 244 | // install dependencies 245 | await exec(`npm i -g pnpm`, { cwd: filename }) 246 | await exec(`pnpm install`, { cwd: filename }) 247 | 248 | if (event) event.sender.send('status', `opening project...`) 249 | openProject(filename) 250 | } catch (err) { 251 | if (event) event.sender.send('status', `an error occured: ${err}`) 252 | } 253 | }) 254 | } 255 | 256 | // Quit when all windows are closed. 257 | app.on('window-all-closed', function () { 258 | // On OS X it is common for applications and their menu bar 259 | // to stay active until the user quits explicitly with Cmd + Q 260 | if (process.platform !== 'darwin') { 261 | app.quit() 262 | } 263 | }) 264 | 265 | ipcMain.on('term-resize', async (event, size, term) => { 266 | try { 267 | ptyProcess.resize( 268 | Math.max(size ? size.cols : term.cols, 1), 269 | Math.max(size ? size.rows : term.rows, 1) 270 | ) 271 | } catch (err) { 272 | console.log(err) 273 | } 274 | }) 275 | 276 | ipcMain.on('save-file', async (event, fileDetails) => { 277 | try { 278 | await fs.promises.writeFile(fileDetails.path, fileDetails.contents) 279 | event.sender.send('file-saved') 280 | } catch (err) { 281 | event.sender.send('file-saved') 282 | } 283 | }) 284 | 285 | ipcMain.on('create-new-project', (event) => { 286 | createProjectDialog(event) 287 | }) 288 | 289 | ipcMain.on('open-existing-project', (event, dir) => { 290 | existingProjectDialog(dir) 291 | }) 292 | 293 | ipcMain.on('open-file', async (e, filePath) => { 294 | if (filePath) { 295 | try { 296 | const fileContents = await readFileAt(filePath) 297 | e.sender.send('file-opened', filePath, fileContents.toString()) 298 | e.sender.send('file-sent', fileContents) 299 | } catch (err) { 300 | e.sender.send('file-sent', `And error occured opening ${filePath}`) 301 | } 302 | } 303 | }) 304 | 305 | ipcMain.on('open-readme', async (e) => { 306 | try { 307 | const filePath = `${projectDir}/README.md` 308 | if (fs.existsSync(filePath)) { 309 | const fileContents = await readFileAt(filePath) 310 | e.sender.send('file-opened', filePath, fileContents.toString()) 311 | e.sender.send('file-sent', fileContents) 312 | } 313 | } catch (err) { 314 | console.log(err) 315 | } 316 | }) 317 | 318 | ipcMain.on('read-recents', async (event) => { 319 | try { 320 | const recents = await getRecents() 321 | event.sender.send('recents-sent', recents) 322 | } catch (err) { 323 | console.log(err) 324 | event.sender.send('recents-sent', null) 325 | } 326 | }) 327 | 328 | const getRecents = async () => { 329 | const userData = app.getPath('userData') 330 | const file = path.join(userData, 'recent.json') 331 | return fs.existsSync(file) ? JSON.parse(await fs.promises.readFile(file, 'utf-8')) : null 332 | } 333 | 334 | const saveCurrentFile = async () => { 335 | console.log('not yet done.') 336 | } 337 | 338 | const openMenuProject = async () => { 339 | existingProjectDialog(null) 340 | } 341 | 342 | const createMenuProject = async () => { 343 | createProjectDialog(null) 344 | } 345 | 346 | const quitMenuProject = async () => { 347 | ptyProcess.kill('9') 348 | watcher.close() 349 | win.close() 350 | launch() 351 | } 352 | 353 | const openProjectInBrowser = async () => { 354 | await sleep(1500) 355 | return shell.openExternal('http://localhost:5001') 356 | } 357 | 358 | const buildAndRunProject = async () => { 359 | return exec('pnpm dev:start', { cwd: projectDir }) 360 | } 361 | 362 | const runMenuWeb = async () => { 363 | const promises = [buildAndRunProject(), openProjectInBrowser()] 364 | return Promise.all(promises) 365 | } 366 | 367 | const runMenuAndroid = async () => { 368 | console.log('running android...') 369 | } 370 | 371 | const runMenuIOS = async () => { 372 | console.log('running iOS...') 373 | } 374 | 375 | const buildMenuProject = async () => { 376 | console.log('building project...') 377 | } 378 | 379 | const createApplicationMenu = () => { 380 | const template: Array> = [ 381 | { 382 | label: 'File', 383 | submenu: [ 384 | { 385 | label: 'Create Project', 386 | accelerator: 'CommandOrControl+P', 387 | click() { 388 | createMenuProject() 389 | } 390 | }, 391 | { 392 | label: 'New File', 393 | accelerator: 'CommandOrControl+N', 394 | click() { 395 | console.log('new file') 396 | } 397 | }, 398 | { 399 | label: 'Open File', 400 | accelerator: 'CommandOrControl+O', 401 | click() { 402 | console.log('not yet done.') 403 | } 404 | }, 405 | { 406 | label: 'Save File', 407 | accelerator: 'CommandOrControl+S', 408 | enabled: true, 409 | click() { 410 | saveCurrentFile() 411 | } 412 | }, 413 | { 414 | label: 'Open Project', 415 | accelerator: 'CommandOrControl+O', 416 | click() { 417 | openMenuProject() 418 | } 419 | }, 420 | { 421 | label: 'Close Project', 422 | accelerator: 'CommandOrControl+Q+P', 423 | enabled: true, 424 | click() { 425 | quitMenuProject() 426 | } 427 | } 428 | ] 429 | }, 430 | { 431 | label: 'Edit', 432 | submenu: [ 433 | { 434 | label: 'Undo', 435 | accelerator: 'CommandOrControl+Z', 436 | role: 'undo' 437 | }, 438 | { 439 | label: 'Redo', 440 | accelerator: 'Shift+CommandOrControl+Z', 441 | role: 'redo' 442 | }, 443 | { type: 'separator' }, 444 | { 445 | label: 'Cut', 446 | accelerator: 'CommandOrControl+X', 447 | role: 'cut' 448 | }, 449 | { 450 | label: 'Copy', 451 | accelerator: 'CommandOrControl+C', 452 | role: 'copy' 453 | }, 454 | { 455 | label: 'Paste', 456 | accelerator: 'CommandOrControl+V', 457 | role: 'paste' 458 | }, 459 | { 460 | label: 'Select All', 461 | accelerator: 'CommandOrControl+A', 462 | role: 'selectall' 463 | } 464 | ] 465 | }, 466 | { 467 | label: 'Run + Build', 468 | submenu: [ 469 | { 470 | label: 'Run Web', 471 | accelerator: 'CommandOrControl+R+W', 472 | click: async () => { 473 | await runMenuWeb() 474 | } 475 | }, 476 | { 477 | label: 'Run Android', 478 | accelerator: 'CommandOrControl+R+A', 479 | click() { 480 | runMenuAndroid() 481 | } 482 | }, 483 | { 484 | label: 'Run IOS', 485 | accelerator: 'CommandOrControl+R+I', 486 | click() { 487 | runMenuIOS() 488 | } 489 | }, 490 | { 491 | label: 'Build', 492 | accelerator: 'CommandOrControl+B', 493 | click() { 494 | buildMenuProject() 495 | } 496 | } 497 | ] 498 | }, 499 | { 500 | label: 'Editor', 501 | submenu: [ 502 | { 503 | label: 'Increase Font Size', 504 | accelerator: 'CommandOrControl+]', 505 | click() { 506 | // ipcMain.increaseFontSize(); 507 | } 508 | }, 509 | { 510 | label: 'Decrease Font Size', 511 | accelerator: 'CommandOrControl+[', 512 | click() { 513 | // ipcMain.decreaseFontSize(); 514 | } 515 | } 516 | ] 517 | }, 518 | { 519 | label: 'Window', 520 | submenu: [ 521 | { 522 | label: 'Minimize', 523 | accelerator: 'CommandOrControl+M', 524 | role: 'minimize' 525 | }, 526 | { 527 | label: 'Close', 528 | accelerator: 'CommandOrControl+W', 529 | role: 'close' 530 | } 531 | ] 532 | }, 533 | { 534 | label: 'Help', 535 | role: 'help', 536 | submenu: [ 537 | { 538 | label: 'Visit Warp Website', 539 | click: async () => { 540 | await shell.openExternal('https://warpcode.io/') 541 | } 542 | }, 543 | { 544 | label: 'Toggle Debug Tools', 545 | click(item, focusedWindow) { 546 | if (focusedWindow) focusedWindow.webContents.toggleDevTools() 547 | } 548 | } 549 | ] 550 | } 551 | ] 552 | 553 | if (process.platform === 'darwin') { 554 | const name = 'Warp' 555 | template.unshift({ 556 | label: name, 557 | submenu: [ 558 | { 559 | label: `About ${name}`, 560 | role: 'about' 561 | }, 562 | { type: 'separator' }, 563 | { 564 | label: 'Services', 565 | role: 'services', 566 | submenu: [] 567 | }, 568 | { type: 'separator' }, 569 | { 570 | label: `Hide ${name}`, 571 | accelerator: 'Command+H', 572 | role: 'hide' 573 | }, 574 | { 575 | label: 'Hide Others', 576 | accelerator: 'Command+Alt+H', 577 | role: 'hideothers' 578 | }, 579 | { 580 | label: 'Show All', 581 | role: 'unhide' 582 | }, 583 | { type: 'separator' }, 584 | { 585 | label: `Quit ${name}`, 586 | accelerator: 'Command+Q', 587 | click() { 588 | app.quit() 589 | } 590 | } 591 | ] 592 | }) 593 | 594 | const windowMenu = template.find((item) => item.label === 'Window') 595 | if (!windowMenu) return 596 | windowMenu.role = 'window' 597 | } 598 | return Menu.setApplicationMenu(Menu.buildFromTemplate(template)) 599 | } 600 | -------------------------------------------------------------------------------- /elec/shared/exec.ts: -------------------------------------------------------------------------------- 1 | import * as child_process from 'child_process' 2 | import util from 'util' 3 | 4 | const exec_async = util.promisify(child_process.exec) 5 | 6 | export const exec = (cmd, opts) => { 7 | return exec_async(cmd, opts) 8 | } 9 | -------------------------------------------------------------------------------- /elec/shared/key-events.ts: -------------------------------------------------------------------------------- 1 | export function createKeyEvent(which) { 2 | return function (node, callback) { 3 | function handleKeydown(event) { 4 | if (event.which === which) callback(event) 5 | } 6 | 7 | node.addEventListener('keydown', handleKeydown) 8 | 9 | return { 10 | destroy() { 11 | node.removeEventListener('keydown', handleKeydown) 12 | } 13 | } 14 | } 15 | } 16 | 17 | export const enter = createKeyEvent(13) 18 | export const escape = createKeyEvent(27) 19 | -------------------------------------------------------------------------------- /elec/shared/readJSON.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | 3 | export default function readJSON(file) { 4 | return fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf-8')) : null 5 | } 6 | -------------------------------------------------------------------------------- /elec/shared/sleep.ts: -------------------------------------------------------------------------------- 1 | export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)) 2 | -------------------------------------------------------------------------------- /elec/shared/uriFromPath.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | 3 | export default function uriFromPath(_path) { 4 | let pathName = path.resolve(_path).replace(/\\/g, '/') 5 | if (pathName.length > 0 && pathName.charAt(0) !== '/') { 6 | pathName = '/' + pathName 7 | } 8 | return encodeURI('file://' + pathName) 9 | } 10 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Warp Code 10 | 28 | 29 | 30 |
31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.1", 3 | "type": "module", 4 | "main": "dist/elec/build.cjs", 5 | "scripts": { 6 | "dev": "vite", 7 | "dev:all": "concurrently -n=svelte,elec -c=green,blue \"pnpm dev\" \"pnpm dev:elec\"", 8 | "dev:elec": "node script/build --env=development --watch", 9 | "build": "vite build", 10 | "serve": "vite preview", 11 | "check": "svelte-check --tsconfig ./tsconfig.json", 12 | "lint": "prettier --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", 13 | "lint:fix": "pnpm format && eslint --fix --ignore-path .gitignore .", 14 | "format": "prettier --write --plugin-search-dir=. ." 15 | }, 16 | "devDependencies": { 17 | "@rollup/plugin-alias": "^4.0.3", 18 | "@rollup/plugin-commonjs": "^24.0.1", 19 | "@rollup/plugin-json": "^6.0.0", 20 | "@rollup/plugin-node-resolve": "^15.0.1", 21 | "@sveltejs/vite-plugin-svelte": "^2.0.2", 22 | "@tsconfig/svelte": "^3.0.0", 23 | "@types/node": "^18.11.18", 24 | "@typescript-eslint/eslint-plugin": "^5.48.2", 25 | "@typescript-eslint/parser": "^5.48.2", 26 | "chalk": "^5.2.0", 27 | "codemirror": "^6.0.1", 28 | "concurrently": "^7.6.0", 29 | "css2json": "^1.1.1", 30 | "cssbeautify": "^0.3.1", 31 | "cssjson": "^2.1.3", 32 | "default-shell": "^2.2.0", 33 | "degit": "^2.8.4", 34 | "dotenv": "^16.0.3", 35 | "electron": "^22.0.3", 36 | "electron-builder": "^23.6.0", 37 | "electron-connect": "^0.6.3", 38 | "electron-rebuild": "^3.2.9", 39 | "esbuild": "^0.16.17", 40 | "eslint": "^8.32.0", 41 | "eslint-config-prettier": "^8.6.0", 42 | "eslint-plugin-svelte3": "^4.0.0", 43 | "himalaya": "^1.1.0", 44 | "html2json": "^1.0.2", 45 | "json-to-css": "^0.1.0", 46 | "minimist": "^1.2.7", 47 | "monaco-editor": "^0.34.1", 48 | "moveable": "^0.42.2", 49 | "ora": "^6.1.2", 50 | "prettier": "^2.8.3", 51 | "prettier-plugin-svelte": "^2.9.0", 52 | "pretty-ms": "^8.0.0", 53 | "rollup": "^3.10.1", 54 | "rollup-plugin-esbuild": "^5.0.0", 55 | "svelte": "^3.55.1", 56 | "svelte-awesome": "^3.0.1", 57 | "svelte-check": "^3.0.2", 58 | "svelte-dnd-action": "^0.9.22", 59 | "svelte-preprocess": "^5.0.1", 60 | "svelte-spa-router": "^3.3.0", 61 | "svelte-split-pane": "^0.1.2", 62 | "svelte-touch-to-mouse": "^1.0.0", 63 | "svelte-tree": "^0.7.0", 64 | "svelte-watch-resize": "^1.0.3", 65 | "tiny-glob": "^0.2.9", 66 | "tslib": "^2.4.1", 67 | "typescript": "^4.9.4", 68 | "v8-compile-cache": "^2.3.0", 69 | "vite": "^4.0.4", 70 | "vite-plugin-commonjs-externals": "^0.1.1", 71 | "wait-on": "^7.0.1" 72 | }, 73 | "dependencies": { 74 | "chokidar": "^3.5.3", 75 | "css-validator": "^0.11.0", 76 | "directory-tree": "^3.5.1", 77 | "electron-is-dev": "^2.0.0", 78 | "electron-store": "^8.1.0", 79 | "node-pty": "^0.10.1", 80 | "simply-beautiful": "^0.2.14", 81 | "xterm": "^5.1.0", 82 | "xterm-addon-fit": "^0.7.0", 83 | "xterm-webfont": "^2.0.0" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickjquinn/project-warpcode/a387b02149ae0f8835d5e0acea9e893ccfdc999f/public/favicon.ico -------------------------------------------------------------------------------- /public/static/assets/css.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/static/assets/down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/static/assets/folder-open.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/static/assets/folder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/static/assets/html.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/static/assets/jpg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/static/assets/js.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/static/assets/json.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/static/assets/png.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/static/assets/svg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/static/assets/ts.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/static/assets/xml.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /script/build.js: -------------------------------------------------------------------------------- 1 | import * as rollup from 'rollup' 2 | import chalk from 'chalk' 3 | import waitOn from 'wait-on' 4 | import electronConnect from 'electron-connect' 5 | import * as rollupConfig from './rollup.config.js' 6 | import net from 'net' 7 | import { URL } from 'url' 8 | 9 | const electron = electronConnect.server.create({ stopOnClose: true }) 10 | const options = rollupConfig.default 11 | const opt = options('development') 12 | const TAG = '[script/build.js]' 13 | const resource = `http://localhost:${5173}/index.html` 14 | 15 | const watchFunc = function () { 16 | const watcher = rollup.watch(opt) 17 | watcher.on('change', (filename) => { 18 | const log = chalk.green(`change -- ${filename}`) 19 | console.log(TAG, log) 20 | }) 21 | watcher.on('event', (ev) => { 22 | if (ev.code === 'END') { 23 | electron.electronState === 'init' ? electron.start() : electron.restart() 24 | } else if (ev.code === 'ERROR') { 25 | console.log(ev.error) 26 | } 27 | }) 28 | } 29 | 30 | waitOn( 31 | { 32 | resources: [resource], 33 | timeout: 5000 34 | }, 35 | (err) => { 36 | if (err) { 37 | const { port, hostname } = new URL(resource) 38 | const serverSocket = net.connect(port || 80, hostname, () => { 39 | watchFunc() 40 | }) 41 | serverSocket.on('error', (err) => { 42 | console.log(err) 43 | process.exit(1) 44 | }) 45 | } else { 46 | watchFunc() 47 | } 48 | } 49 | ) 50 | -------------------------------------------------------------------------------- /script/rollup.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { nodeResolve } from '@rollup/plugin-node-resolve' 3 | import commonjs from '@rollup/plugin-commonjs' 4 | import esbuild from 'rollup-plugin-esbuild' 5 | import alias from '@rollup/plugin-alias' 6 | import json from '@rollup/plugin-json' 7 | import { dirname } from 'path' 8 | import { fileURLToPath } from 'url' 9 | 10 | const __dirname = dirname(fileURLToPath(import.meta.url)) 11 | 12 | export default (env = 'production') => { 13 | return { 14 | input: path.join(__dirname, '../elec/index.ts'), 15 | output: { 16 | file: path.join(__dirname, '../dist/elec/build.cjs'), 17 | format: 'cjs', 18 | name: 'ElectronMainBundle', 19 | sourcemap: true 20 | }, 21 | plugins: [ 22 | nodeResolve({ jsnext: true, preferBuiltins: true, browser: true }), 23 | commonjs(), 24 | json(), 25 | esbuild({ 26 | // All options are optional 27 | include: /\.[jt]sx?$/, // default, inferred from `loaders` option 28 | exclude: /node_modules/, // default 29 | // watch: process.argv.includes('--watch'), // rollup 中有配置 30 | sourceMap: false, // default 31 | minify: process.env.NODE_ENV === 'production', 32 | target: 'esnext', // default, or 'es20XX', 'esnext' 33 | jsxFactory: 'React.createElement', 34 | jsxFragment: 'React.Fragment', 35 | define: { 36 | __VERSION__: '"x.y.z"' 37 | }, 38 | loaders: { 39 | // Add .json files support 40 | // require @rollup/plugin-commonjs 41 | '.json': 'json', 42 | // Enable JSX in .js files too 43 | '.js': 'jsx' 44 | } 45 | }), 46 | alias({ 47 | entries: [{ find: '@main', replacement: path.join(__dirname, '../src/main') }] 48 | }) 49 | ], 50 | external: [ 51 | 'crypto', 52 | 'assert', 53 | 'fs', 54 | 'util', 55 | 'os', 56 | 'events', 57 | 'child_process', 58 | 'http', 59 | 'https', 60 | 'path', 61 | 'electron', 62 | 'node-pty', 63 | 'chokidar' 64 | ] 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/App.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | 14 |
15 | 16 | 26 | -------------------------------------------------------------------------------- /src/assets/css.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 37 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/assets/down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/assets/jpg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/assets/js.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/json.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 11 | 20 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/assets/png.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/assets/svelte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickjquinn/project-warpcode/a387b02149ae0f8835d5e0acea9e893ccfdc999f/src/assets/svelte.png -------------------------------------------------------------------------------- /src/assets/svg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/assets/ts.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/warp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickjquinn/project-warpcode/a387b02149ae0f8835d5e0acea9e893ccfdc999f/src/assets/warp.png -------------------------------------------------------------------------------- /src/assets/warpwhite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickjquinn/project-warpcode/a387b02149ae0f8835d5e0acea9e893ccfdc999f/src/assets/warpwhite.png -------------------------------------------------------------------------------- /src/assets/xml.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/components/CodeMirror.svelte: -------------------------------------------------------------------------------- 1 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/components/Editor.svelte: -------------------------------------------------------------------------------- 1 | 164 | 165 | 181 | 182 | {#if $openTabs.length > 0} 183 |
184 | 185 |
186 | {/if} 187 | 188 | 243 | -------------------------------------------------------------------------------- /src/components/Monaco.svelte: -------------------------------------------------------------------------------- 1 | 104 | 105 |
106 | 107 | 114 | -------------------------------------------------------------------------------- /src/components/Terminal.svelte: -------------------------------------------------------------------------------- 1 | 70 | 71 |
72 | 73 | 74 | 75 | 91 | -------------------------------------------------------------------------------- /src/components/canvas/EditorInput.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 | 13 |
14 | 15 | 32 | -------------------------------------------------------------------------------- /src/components/canvas/LayoutEditor.svelte: -------------------------------------------------------------------------------- 1 | 38 | 39 |
40 | {#if style} 41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | {#if localSelect?.meta?.container} 49 | 50 | {/if} 51 | 52 | {/if} 53 |
54 | 55 | 61 | -------------------------------------------------------------------------------- /src/components/canvas/MobileFrame.svelte: -------------------------------------------------------------------------------- 1 | 3 | 4 |
5 |
6 |
7 |
8 |
9 | 10 |
11 |
12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 | 20 |
21 |
22 |
23 |
24 | 25 | 172 | -------------------------------------------------------------------------------- /src/components/canvas/NestedContent.svelte: -------------------------------------------------------------------------------- 1 | 266 | 267 | 268 | 269 |
278 | {#each items.filter((item) => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item (item.id)} 279 |
280 |
289 | {#if item.widget === 'container'} 290 | {#if item.hasOwnProperty('items')} 291 | 298 | {:else} 299 | {item.value ?? ''} 302 | {/if} 303 | {:else if item.widget == 'label'} 304 | 307 | {:else if item.widget == 'scrollContainer'} 308 | {item.value ?? ''} 311 | {:else if item.widget == 'button'} 312 | 315 | {:else if item.widget == 'videoPlayer'} 316 | 321 | {:else if item.widget == 'image'} 322 | 323 | {:else} 324 | 330 | {/if} 331 |
332 | {/each} 333 |
334 | 335 | 354 | -------------------------------------------------------------------------------- /src/components/canvas/StyleEditor.svelte: -------------------------------------------------------------------------------- 1 | 58 | 59 |
60 | {#if style} 61 |
62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | {/if} 71 |
72 | 73 | 79 | -------------------------------------------------------------------------------- /src/components/canvas/UICanvas.svelte: -------------------------------------------------------------------------------- 1 | 260 | 261 | 262 | 263 |
264 |
265 | 266 |
267 |
268 |
276 | {#each items.filter((item) => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item (item.id)} 277 |
278 |
287 | {#if item.widget === 'container'} 288 | {#if item.hasOwnProperty('items')} 289 | 296 | {:else} 297 | {item.value ?? ''} 300 | {/if} 301 | {:else if item.widget == 'label'} 302 | 307 | {:else if item.widget == 'scrollContainer'} 308 | {item.value ?? ''} 311 | {:else if item.widget == 'button'} 312 | 317 | {:else if item.widget == 'videoPlayer'} 318 | 323 | {:else if item.widget == 'image'} 324 | 329 | {:else} 330 | 336 | {/if} 337 |
338 | {/each} 339 |
340 |
341 |
342 |
343 |
344 |
345 | 346 | 407 | -------------------------------------------------------------------------------- /src/components/canvas/UIPallete.svelte: -------------------------------------------------------------------------------- 1 | 171 | 172 |
178 | {#each items as item (item.id)} 179 |
180 | 181 | {item.name} 182 |
183 | {/each} 184 |
185 | 186 | 204 | -------------------------------------------------------------------------------- /src/components/canvas/stores/layout.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store' 2 | 3 | export const activeLayout = writable({}) 4 | -------------------------------------------------------------------------------- /src/components/canvas/stores/selected.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store' 2 | 3 | const selected = writable({}) 4 | 5 | export default selected 6 | -------------------------------------------------------------------------------- /src/components/canvas/stores/style.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store' 2 | 3 | export const activeStyle = writable({ 4 | color: 'black', 5 | 'background-color': 'white', 6 | 'line-height': '0.2rem' 7 | }) 8 | -------------------------------------------------------------------------------- /src/components/fileExplorer/Explorer.svelte: -------------------------------------------------------------------------------- 1 | 32 | 33 |
34 |
35 | {#if files} 36 | 43 | {/if} 44 |
45 |
46 | 47 | 59 | -------------------------------------------------------------------------------- /src/components/fileExplorer/File.svelte: -------------------------------------------------------------------------------- 1 | 63 | 64 | {name} 65 | 66 | 75 | -------------------------------------------------------------------------------- /src/components/fileExplorer/Folder.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 | {name} 23 | 24 | {#if expanded} 25 |
    26 | {#each children as file} 27 |
  • 28 | {#if file.type === 'directory' || file.children} 29 | 30 | {:else} 31 | 32 | {/if} 33 |
  • 34 | {/each} 35 |
36 | {:else if root && children?.length > 0} 37 |
    38 | {#each children as file} 39 |
  • 40 | {#if file.type === 'directory' || file.children} 41 | 42 | {:else} 43 | 44 | {/if} 45 |
  • 46 | {/each} 47 |
48 | {/if} 49 | 50 | 75 | -------------------------------------------------------------------------------- /src/components/tabs/OnlyTabs.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 |
    21 | {#each items as item} 22 |
  • 23 | {item.label} 24 |
  • 25 | {/each} 26 | {#if add} 27 |
  • 28 | + 29 |
  • 30 | {/if} 31 |
32 | 33 | 67 | -------------------------------------------------------------------------------- /src/components/tabs/RemovableTabs.svelte: -------------------------------------------------------------------------------- 1 | 35 | 36 |
    37 | {#each items as item} 38 |
  • 39 | {#if activeTabValue === item.value} 40 | {item.label} 42 | x 44 | {:else} 45 | {item.label} 46 | {/if} 47 |
  • 48 | {/each} 49 | {#if add} 50 |
  • 51 | + 52 |
  • 53 | {/if} 54 |
55 | 56 | 90 | -------------------------------------------------------------------------------- /src/components/tabs/Tabs.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    9 | {#each items as item} 10 |
  • 11 | 12 | {item.label} 13 |
  • 14 | {/each} 15 |
16 | {#each items as item} 17 | {#if activeTabValue == item.value} 18 |
19 | {#if items.props} 20 | 21 | {:else} 22 | 23 | {/if} 24 |
25 | {/if} 26 | {/each} 27 | 28 | 68 | -------------------------------------------------------------------------------- /src/components/warp/widgets/Button.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 31 | -------------------------------------------------------------------------------- /src/components/warp/widgets/Container.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 | 25 | 35 | -------------------------------------------------------------------------------- /src/components/warp/widgets/Generic.svelte: -------------------------------------------------------------------------------- 1 | 26 | 27 | {@html formatted} 28 | -------------------------------------------------------------------------------- /src/components/warp/widgets/Image.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 | img 25 | 26 | 35 | -------------------------------------------------------------------------------- /src/components/warp/widgets/Label.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 |

25 | 26 | 31 | -------------------------------------------------------------------------------- /src/components/warp/widgets/ScrollContainer.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 | 25 | 35 | -------------------------------------------------------------------------------- /src/components/warp/widgets/TextBox.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 |

25 |
26 | 27 | 35 | -------------------------------------------------------------------------------- /src/components/warp/widgets/TextInput.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 33 | -------------------------------------------------------------------------------- /src/components/warp/widgets/VideoPlayer.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 | 34 | 35 | 41 | -------------------------------------------------------------------------------- /src/components/warp/widgets/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Container } from './Container.svelte' 2 | export { default as Label } from './Label.svelte' 3 | export { default as ScrollContainer } from './ScrollContainer.svelte' 4 | export { default as Button } from './Button.svelte' 5 | export { default as TextInput } from './TextInput.svelte' 6 | export { default as TextBox } from './TextBox.svelte' 7 | export { default as VideoPlayer } from './VideoPlayer.svelte' 8 | export { default as Image } from './Image.svelte' 9 | export { default as Generic } from './Generic.svelte' 10 | -------------------------------------------------------------------------------- /src/components/xterm.css: -------------------------------------------------------------------------------- 1 | .xterm { 2 | position: relative; 3 | user-select: none; 4 | -ms-user-select: none; 5 | -webkit-user-select: none; 6 | } 7 | 8 | .xterm.focus, 9 | .xterm:focus { 10 | outline: none; 11 | } 12 | 13 | .xterm .xterm-helpers { 14 | position: absolute; 15 | top: 0; 16 | /** 17 | * The z-index of the helpers must be higher than the canvases in order for 18 | * IMEs to appear on top. 19 | */ 20 | z-index: 5; 21 | } 22 | 23 | .xterm .xterm-helper-textarea { 24 | padding: 0; 25 | border: 0; 26 | margin: 0; 27 | /* Move textarea out of the screen to the far left, so that the cursor is not visible */ 28 | position: absolute; 29 | opacity: 0; 30 | left: -9999em; 31 | top: 0; 32 | width: 0; 33 | height: 0; 34 | z-index: -5; 35 | /** Prevent wrapping so the IME appears against the textarea at the correct position */ 36 | white-space: nowrap; 37 | overflow: hidden; 38 | resize: none; 39 | } 40 | 41 | .xterm .composition-view { 42 | /* TODO: Composition position got messed up somewhere */ 43 | background: #000; 44 | color: #fff; 45 | display: none; 46 | position: absolute; 47 | white-space: nowrap; 48 | z-index: 1; 49 | } 50 | 51 | .xterm .composition-view.active { 52 | display: block; 53 | } 54 | 55 | .xterm .xterm-viewport { 56 | /* On OS X this is required in order for the scroll bar to appear fully opaque */ 57 | background-color: #000; 58 | overflow-y: scroll; 59 | cursor: default; 60 | position: absolute; 61 | right: 0; 62 | left: 0; 63 | top: 0; 64 | bottom: 0; 65 | } 66 | 67 | .xterm .xterm-screen { 68 | position: relative; 69 | } 70 | 71 | .xterm .xterm-screen canvas { 72 | position: absolute; 73 | left: 0; 74 | top: 0; 75 | } 76 | 77 | .xterm .xterm-scroll-area { 78 | visibility: hidden; 79 | } 80 | 81 | .xterm-char-measure-element { 82 | display: inline-block; 83 | visibility: hidden; 84 | position: absolute; 85 | top: 0; 86 | left: -9999em; 87 | line-height: normal; 88 | } 89 | 90 | .xterm { 91 | cursor: text; 92 | } 93 | 94 | .xterm.enable-mouse-events { 95 | /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ 96 | cursor: default; 97 | } 98 | 99 | .xterm.xterm-cursor-pointer { 100 | cursor: pointer; 101 | } 102 | 103 | .xterm.column-select.focus { 104 | /* Column selection mode */ 105 | cursor: crosshair; 106 | } 107 | 108 | .xterm .xterm-accessibility, 109 | .xterm .xterm-message { 110 | position: absolute; 111 | left: 0; 112 | top: 0; 113 | bottom: 0; 114 | right: 0; 115 | z-index: 10; 116 | color: transparent; 117 | } 118 | 119 | .xterm .live-region { 120 | position: absolute; 121 | left: -9999px; 122 | width: 1px; 123 | height: 1px; 124 | overflow: hidden; 125 | } 126 | 127 | .xterm-dim { 128 | opacity: 0.5; 129 | } 130 | 131 | .xterm-underline { 132 | text-decoration: underline; 133 | } 134 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import App from './App.svelte' 2 | 3 | const app = new App({ 4 | target: document.getElementById('app') 5 | }) 6 | 7 | export default app 8 | -------------------------------------------------------------------------------- /src/modules/warp/canvasActions/index.ts: -------------------------------------------------------------------------------- 1 | import Moveable from 'moveable' 2 | 3 | class CanvasActions { 4 | canvas: HTMLElement 5 | constructor(canvas: HTMLElement | undefined) { 6 | if (!canvas) throw new Error('Please provide a valid canvas element') 7 | this.canvas = canvas 8 | } 9 | 10 | public makeItemResizable(items: Array | HTMLElement): void { 11 | const moveable = new Moveable(this.canvas, { 12 | target: items, 13 | resizable: true, 14 | keepRatio: false, 15 | throttleResize: 1, 16 | edge: true, 17 | zoom: 0.5, 18 | origin: true 19 | }) 20 | 21 | const frame = { 22 | translate: [0, 0] 23 | } 24 | moveable 25 | .on('resizeStart', (e) => { 26 | e.setOrigin(['%', '%']) 27 | e.dragStart && e.dragStart.set(frame.translate) 28 | }) 29 | .on('resize', (e) => { 30 | const beforeTranslate = e.drag.beforeTranslate 31 | 32 | frame.translate = beforeTranslate 33 | e.target.style.width = `${e.width}px` 34 | e.target.style.height = `${e.height}px` 35 | e.target.style.transform = `translate(${beforeTranslate[0]}px, ${beforeTranslate[1]}px)` 36 | }) 37 | } 38 | } 39 | 40 | export default CanvasActions 41 | -------------------------------------------------------------------------------- /src/modules/warp/codeMap/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-prototype-builtins */ 2 | import { parse } from 'himalaya' 3 | import cssbeautify from 'cssbeautify' 4 | import * as Css from 'json-to-css' 5 | // import { validate } from 'csstree-validator' 6 | import css2json from 'css2json' 7 | 8 | const validateCss = window.require('css-validator') 9 | const beautify = window.require('simply-beautiful') 10 | 11 | const linting_options = { 12 | indent_size: 4, 13 | space_before_conditional: true, 14 | jslint_happy: true, 15 | max_char: 0 16 | } 17 | 18 | const stockWidgets = ['image', 'button', 'image', 'label', 'textBox', 'textInput', 'videoPlayer'] 19 | const importRE = /import(?:["'\s]*([\w*{}\n, ]+)from\s*)?["'\s]*([@\w/_-]+)["'\s].*/g 20 | 21 | export class CodeMap { 22 | lang: string 23 | constructor(lang: string) { 24 | this.lang = lang 25 | } 26 | 27 | public validateCssString(cssString: string): boolean { 28 | const validateCSS = validateCss(cssString) 29 | if (validateCSS === false) { 30 | return false 31 | } 32 | return true 33 | } 34 | 35 | public mapToCode(canvas: Record, oldCode: string): string { 36 | const items: Array> = canvas.items as Array> 37 | let scriptItems = `` 38 | let mainItems = `` 39 | let cssItems = `` 40 | const scriptTag: string = this.removeWarpImports(oldCode) 41 | const styleTag: string = this.removeWarpStyles(oldCode, items) 42 | // if (oldCode.includes('')) { 43 | // scriptTag = oldCode.split('')[1].split(' 103 | ${scriptItems.trim()} 104 | ${scriptTag.trim()} 105 | 106 | 107 |
108 | ${mainItems.trim()} 109 |
110 | 111 | 115 | `, 116 | linting_options 117 | ) 118 | } 119 | 120 | public convertCSSJSONtoInline(css: Record, id: string): string { 121 | let styled = '' 122 | let isGlobalStyle = false 123 | if (!id.includes('placeholder')) { 124 | if (css.hasOwnProperty(`:global(${id})`)) { 125 | isGlobalStyle = true 126 | } 127 | const mapped = this.convertJSONToCSS(css) 128 | 129 | if (mapped) { 130 | const validateCSS = validateCss(mapped) 131 | if (!validateCSS) { 132 | console.log(`Invalid CSS on widget ${id} : ${validateCSS}`) 133 | return `` 134 | } 135 | let styleSplit 136 | if (isGlobalStyle) { 137 | styleSplit = mapped.split(`:global(#${id})`) 138 | } else { 139 | styleSplit = mapped.split(id) 140 | } 141 | 142 | if (styleSplit?.length > 0 && styleSplit[1]) { 143 | const splitValue = styleSplit[1].trim() 144 | if (splitValue?.includes('{') && splitValue?.includes('}')) { 145 | styled = styleSplit[1].replace('{', '').replace('}', '').trim() 146 | } 147 | } 148 | } 149 | } 150 | return styled 151 | } 152 | 153 | public convertCodeToCanvas(code: string): Array { 154 | const json = parse(code) 155 | 156 | let canvas = [] 157 | const cssItems = [] 158 | 159 | for (const item of json) { 160 | if (item.tagName === 'main') { 161 | for (const inner of item.children) { 162 | if (inner.type === 'element') { 163 | const mappedElement = this.transformCodeToCanvas(inner) 164 | if (mappedElement) { 165 | canvas.push(mappedElement) 166 | } 167 | } 168 | } 169 | } else if (item.tagName === 'style') { 170 | for (const style of item.children) { 171 | if (style.type === 'text') { 172 | const formatted = this.convertCSSToJSON(style.content) 173 | 174 | if (formatted) { 175 | cssItems.push(formatted) 176 | } 177 | } 178 | } 179 | } 180 | } 181 | 182 | canvas = this.mapStylesToAllCanvasItems(canvas, cssItems) 183 | 184 | return canvas 185 | } 186 | 187 | private removeWarpStyles(code: string, canvas: Array>): string { 188 | let styleTag = `` 189 | if (code.includes('')) { 190 | styleTag = code.split('')[1].split('') || code.includes('') 209 | ) { 210 | scriptTag = code.split('')[1].split(' 0 && cssItems?.length > 0) { 223 | for (let i = 0; i < canvas.length; i++) { 224 | const item = canvas[i] 225 | const id = `#${item.widget}${item.id}` 226 | 227 | const styleItem = this.fetchRelevantCSSTag(id, cssItems) 228 | if (styleItem) { 229 | canvas[i].style = styleItem 230 | } 231 | 232 | const subCanvasItems: Array> = item.items 233 | 234 | if (subCanvasItems?.length > 0) { 235 | canvas[i].items = this.mapStylesToAllCanvasItems(subCanvasItems, cssItems) 236 | } 237 | } 238 | } 239 | return canvas 240 | } 241 | 242 | private static capFirstLetter(input: string): string { 243 | return input.charAt(0).toUpperCase() + input.slice(1) 244 | } 245 | 246 | private extractStyleAndCodeify(item) { 247 | const widgetStyle: Record = item.style as Record 248 | let contentType: string = item.contentsType as string 249 | let cssItems = `` 250 | 251 | const formattetStyle = {} 252 | 253 | for (const [key, value] of Object.entries(widgetStyle)) { 254 | if (widgetStyle.hasOwnProperty(key)) { 255 | formattetStyle[key] = value 256 | } 257 | } 258 | 259 | if (!contentType) { 260 | contentType = 'slot' 261 | } 262 | 263 | cssItems = cssItems + this.convertJSONToCSS(formattetStyle) 264 | 265 | if (contentType === 'slot') { 266 | const subItems: Array> = item.items as Array< 267 | Record 268 | > | null 269 | if (subItems?.length > 0) { 270 | for (const subStyle of subItems) { 271 | cssItems = cssItems + this.extractStyleAndCodeify(subStyle) 272 | } 273 | } 274 | } 275 | 276 | return cssItems 277 | } 278 | 279 | private convertAttributesToInline(attributes) { 280 | let stringAttributes = `` 281 | 282 | if (attributes?.length > 0) { 283 | for (const attribute of attributes) { 284 | if (attribute?.key) { 285 | stringAttributes += `${attribute.key}="${attribute.value}" ` 286 | } 287 | } 288 | } 289 | 290 | return stringAttributes 291 | } 292 | 293 | private transformTemplateToWidget({ type, widget, value, id, item }): string { 294 | const defaultWidgets = [ 295 | 'Label', 296 | 'Container', 297 | 'ScrollContainer', 298 | 'TextBox', 299 | 'VideoPlayer', 300 | 'TextInput', 301 | 'Image', 302 | 'Button' 303 | ] 304 | 305 | const attributes = this.convertAttributesToInline(item.attributes) 306 | 307 | if (type === 'slot') { 308 | if (item.items?.length > 0) { 309 | let innerItem = '\n' 310 | for (const inner of item.items) { 311 | const widgetType: string = inner.widget as string 312 | const widgetID: string = inner.id as string 313 | let widgetValue: string = inner.value as string 314 | let contentType: string = inner.contentsType as string 315 | 316 | if (!widgetValue) { 317 | widgetValue = '' 318 | } 319 | if (!contentType) { 320 | contentType = 'slot' 321 | } 322 | const transformedInner = this.transformTemplateToWidget({ 323 | type: contentType, 324 | widget: widgetType, 325 | value: widgetValue, 326 | id: widgetID, 327 | item: inner 328 | }) 329 | innerItem += `${transformedInner}\n` 330 | } 331 | if (defaultWidgets.includes(CodeMap.capFirstLetter(widget))) { 332 | return `<${CodeMap.capFirstLetter(widget)} id="${ 333 | widget + id 334 | }" ${attributes}>${innerItem}` 335 | } 336 | return `<${widget} id="${widget + id}" ${attributes}>${innerItem}` 337 | } 338 | if (defaultWidgets.includes(CodeMap.capFirstLetter(widget))) { 339 | return `<${CodeMap.capFirstLetter(widget)} id="${ 340 | widget + id 341 | }" ${attributes}>${value}` 342 | } 343 | return `<${widget} id="${widget + id}" ${attributes}>${value}` 344 | } 345 | if (defaultWidgets.includes(CodeMap.capFirstLetter(widget))) { 346 | return `<${CodeMap.capFirstLetter(widget)} ${type}="${value}" id="${ 347 | widget + id 348 | }" ${attributes}/>` 349 | } 350 | return `<${widget} ${type}="${value}" id="${widget + id}" ${attributes}/>` 351 | } 352 | 353 | private fetchRelevantCSSTag(id: string, css: Array>) { 354 | const obj = {} 355 | for (const style of css) { 356 | if (style.hasOwnProperty(`:global(${id})`)) { 357 | obj[`:global(${id})`] = style[`:global(${id})`] 358 | return obj 359 | } else if (style.hasOwnProperty(id)) { 360 | obj[id] = style[id] 361 | return obj 362 | } 363 | } 364 | return null 365 | } 366 | 367 | private transformCodeToCanvas(item) { 368 | let id: number 369 | const widget = item.tagName 370 | let contentsType = '' 371 | let value = '' 372 | const attrs: unknown[] = [] 373 | 374 | // Come back to this, this is a blind stab of a fix. 375 | if (item.attributes?.length == 0) return null 376 | 377 | for (const obj of item.attributes) { 378 | if (obj.key === 'src' || obj.key === 'value') { 379 | contentsType = obj.key 380 | value = obj.value 381 | } else if (obj.key === 'id') { 382 | id = parseInt(obj.value.split(item.tagName)[1]) 383 | } else { 384 | if (obj?.key) { 385 | const otherObj = {} 386 | otherObj[obj.key] = obj.value 387 | attrs.push(obj) 388 | } 389 | } 390 | } 391 | 392 | if (contentsType.length === 0) { 393 | contentsType = 'slot' 394 | } 395 | 396 | if (value.length === 0 && item.children.length > 0) { 397 | for (const child of item.children) { 398 | value += child['content'] as string 399 | } 400 | } 401 | 402 | if (item.tagName.toLowerCase() === 'container' && item.children?.length > 0) { 403 | const items = [] 404 | for (const child of item.children) { 405 | if (child.type === 'element') { 406 | const mapped = this.transformCodeToCanvas(child) 407 | if (mapped) { 408 | items.push(mapped) 409 | } 410 | } 411 | } 412 | return { 413 | id, 414 | name: widget, 415 | widget, 416 | value, 417 | contentsType, 418 | items, 419 | attributes: attrs 420 | } 421 | } 422 | 423 | return { 424 | id, 425 | name: widget, 426 | widget, 427 | value, 428 | contentsType, 429 | attributes: attrs 430 | } 431 | } 432 | 433 | private convertCSSToJSON(css: string) { 434 | css = css.replaceAll('\t', '').trim() 435 | 436 | if (css.length > 0) { 437 | const beautified = cssbeautify(css, { 438 | autosemicolon: true 439 | }) 440 | 441 | return css2json(beautified) 442 | } 443 | 444 | return null 445 | } 446 | 447 | public convertJSONToCSS(json: Record): string { 448 | if (json) { 449 | const r = Css.of(json) 450 | 451 | const beautified = cssbeautify(r, { 452 | autosemicolon: true 453 | }) 454 | 455 | return beautify.css(beautified, linting_options) 456 | } 457 | return `` 458 | } 459 | } 460 | -------------------------------------------------------------------------------- /src/stores/activeDirectory.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store' 2 | 3 | const activeDirectory = writable({}) 4 | 5 | export default activeDirectory 6 | -------------------------------------------------------------------------------- /src/stores/activeFile.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store' 2 | 3 | const activeFile = writable({}) 4 | 5 | export default activeFile 6 | -------------------------------------------------------------------------------- /src/stores/directoryStore.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store' 2 | 3 | const DirectoryData = writable({ 4 | mainDir: '', 5 | fileTree: [], 6 | openFilePath: '', 7 | fileRead: false, 8 | stateObj: {}, 9 | activeFile: '', 10 | rename: false, 11 | deleteFile: false, 12 | activeDir: '', 13 | createFile: false, 14 | createFolder: false, 15 | createMainFile: false, 16 | createMainFolder: false, 17 | reload: false, 18 | activeFolder: '' 19 | }) 20 | 21 | const openTabs = writable([]) 22 | 23 | export { DirectoryData, openTabs } 24 | -------------------------------------------------------------------------------- /src/stores/layout.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store' 2 | 3 | export const activeLayout = writable({}) 4 | -------------------------------------------------------------------------------- /src/stores/selected.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store' 2 | 3 | const selected = writable({}) 4 | 5 | export default selected 6 | -------------------------------------------------------------------------------- /src/stores/style.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store' 2 | 3 | export const activeStyle = writable({ 4 | color: 'black', 5 | 'background-color': 'white', 6 | 'line-height': '0.2rem' 7 | }) 8 | -------------------------------------------------------------------------------- /src/views/AppBuilder.svelte: -------------------------------------------------------------------------------- 1 | 178 | 179 |
180 | 181 |
182 |
183 |
184 | 185 | 186 | 187 | 188 | 189 |
190 |
191 |
192 | 201 | 202 | 203 | 210 | 211 | 212 | 213 | 214 | 220 | 221 | 222 | 223 | 224 | 225 |
226 | 227 | 228 |
229 |
230 |
231 |
232 |
233 |
234 | 235 | 236 | {#if shouldShowCanvas} 237 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | {#if activeEditorTab === 1} 252 | 253 | {:else if activeEditorTab === 2} 254 | 255 | {:else} 256 | 257 | {/if} 258 | 259 | 260 | {/if} 261 | 262 |
263 |
264 |
265 |
266 | 267 | 323 | -------------------------------------------------------------------------------- /src/views/Launcher.svelte: -------------------------------------------------------------------------------- 1 | 41 | 42 |
43 |

Warp Code

44 | 45 | 66 |
67 | 68 | 152 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | /// 3 | /// 4 | 5 | declare type DndEvent = import('svelte-dnd-action').DndEvent 6 | declare namespace svelte.JSX { 7 | interface HTMLAttributes { 8 | onconsider?: (event: CustomEvent & { target: EventTarget & T }) => void 9 | onfinalize?: (event: CustomEvent & { target: EventTarget & T }) => void 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import sveltePreprocess from 'svelte-preprocess' 2 | 3 | export default { 4 | // Consult https://github.com/sveltejs/svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: sveltePreprocess() 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ESNEXT", 5 | "lib": ["ESNEXT", "dom"], 6 | "module": "ESNEXT", 7 | "moduleResolution": "node", 8 | "importHelpers": true, 9 | "esModuleInterop": true, 10 | "sourceMap": true, 11 | "baseUrl": "./", 12 | "strict": false, 13 | "types": ["vite/client", "node"], 14 | "paths": { 15 | "@/*": ["src/*", "elec/*"], 16 | "root/*": ["/*"] 17 | }, 18 | "allowSyntheticDefaultImports": true 19 | }, 20 | "include": ["src/**/*"], 21 | "exclude": ["node_modules/*", "public/*", "dist/*"] 22 | } 23 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { svelte } from '@sveltejs/vite-plugin-svelte' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [svelte()] 7 | }) 8 | -------------------------------------------------------------------------------- /warp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickjquinn/project-warpcode/a387b02149ae0f8835d5e0acea9e893ccfdc999f/warp.png --------------------------------------------------------------------------------