├── .eslintrc.js ├── .gitattributes ├── .github └── workflows │ └── pages.yml ├── .gitignore ├── .prettierrc ├── README.md ├── package.json ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── index.html ├── manifest.json ├── mstile-150x150.png ├── safari-pinned-tab.svg └── site.webmanifest ├── src ├── App.css ├── App.tsx ├── CreateEditShortcut.css ├── CreateEditShortcut.tsx ├── DownloadButton.tsx ├── DownloadModal.tsx ├── DownloadStatusSwitcher.tsx ├── ErrorBoundary.tsx ├── FileManager.ts ├── FilePane.css ├── FilePane.tsx ├── Key.ts ├── Modal.tsx ├── ModalContainer.tsx ├── SearchActions.css ├── SearchActions.tsx ├── UploadShortcutModal.css ├── UploadShortcutModal.tsx ├── Uploader.ts ├── ace │ ├── mode-scpl.ts │ └── scpl.iro ├── data │ ├── ActionCategories.ts │ └── ShortcutMeta.ts ├── img │ ├── back.svg │ ├── bg.jpg │ ├── close.svg │ ├── delete.svg │ ├── delete_w.svg │ ├── down.svg │ ├── edit.svg │ ├── edit_w.svg │ ├── error.svg │ ├── error_grey.svg │ ├── error_w.svg │ ├── error_x.svg │ ├── file.png │ ├── file_active.png │ ├── file_error.png │ ├── file_warning.png │ ├── folder.png │ ├── folder_error.png │ ├── folder_warning.png │ ├── glyph_sheet.png │ ├── glyph_sheet_white.png │ ├── icon.png │ ├── import.png │ ├── magic.svg │ ├── magic_w.svg │ ├── menu.svg │ ├── new_file.png │ ├── new_folder.png │ ├── qr.svg │ ├── right.svg │ ├── search.svg │ ├── settings.svg │ ├── settings_w.svg │ ├── shortcut-file.png │ ├── shortcuts_grey.png │ ├── success.svg │ ├── upload.png │ └── var-sheet.png ├── index.css ├── index.tsx ├── logo.svg ├── react-app-env.d.ts ├── serviceWorker.ts └── testshortcut.json └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // http://eslint.org/docs/rules/ 3 | //"parser": "esprima", 4 | // "parser": "espree", 5 | parser: "@typescript-eslint/parser", 6 | /*"ecmaFeatures": { 7 | "binaryLiterals": true, // enable binary literals 8 | "blockBindings": true, // enable let and const (aka block bindings) 9 | "defaultParams": true, // enable default function parameters 10 | "forOf": true, // enable for-of loops 11 | "generators": true, // enable generators 12 | "objectLiteralComputedProperties": true, // enable computed object literal property names 13 | "objectLiteralDuplicateProperties": true, // enable duplicate object literal properties in strict mode 14 | "objectLiteralShorthandMethods": true, // enable object literal shorthand methods 15 | "objectLiteralShorthandProperties": true, // enable object literal shorthand properties 16 | "experimentalObjectRestSpread": true, // enable {...a} 17 | "octalLiterals": true, // enable octal literals 18 | "regexUFlag": true, // enable the regular expression u flag 19 | "regexYFlag": true, // enable the regular expression y flag 20 | "templateStrings": true, // enable template strings 21 | "unicodeCodePointEscapes": true, // enable code point escapes 22 | "jsx": true, // enable JSX 23 | "es6": true, 24 | "ecmascript6": true 25 | },*/ 26 | parserOptions: { 27 | ecmaVersion: 2018, 28 | sourceType: "module", 29 | ecmaFeatures: { 30 | jsx: true, 31 | modules: true 32 | }, 33 | jsx: true 34 | }, 35 | 36 | env: { 37 | browser: true, // browser global variables. 38 | node: true, // Node.js global variables and Node.js-specific rules. 39 | amd: false, // defines require() and define() as global variables as per the amd spec. 40 | mocha: false, // adds all of the Mocha testing global variables. 41 | jasmine: false, // adds all of the Jasmine testing global variables for version 1.3 and 2.0. 42 | phantomjs: false, // phantomjs global variables. 43 | jquery: false, // jquery global variables. 44 | prototypejs: false, // prototypejs global variables. 45 | shelljs: false, // shelljs global variables. 46 | es6: true 47 | }, 48 | 49 | globals: { 50 | // e.g. "angular": true 51 | }, 52 | 53 | plugins: [ 54 | // e.g. "react" (must run `npm install eslint-plugin-react` first) 55 | "@typescript-eslint" 56 | ], 57 | extends: ["eslint:recommended"], 58 | 59 | rules: { 60 | ////////// Possible Errors ////////// 61 | 62 | //"comma-dangle": 2, // disallow trailing commas in object literals 63 | "no-cond-assign": 0, // disallow assignment in conditional expressions 64 | "no-console": 1, // disallow use of console (off by default in the node environment) 65 | "no-constant-condition": 0, // disallow use of constant expressions in conditions 66 | "no-control-regex": 0, // disallow control characters in regular expressions 67 | "no-debugger": 0, // disallow use of debugger 68 | "no-dupe-keys": 0, // disallow duplicate keys when creating object literals 69 | "no-empty": 0, // disallow empty statements 70 | "no-empty-class": 0, // disallow the use of empty character classes in regular expressions 71 | "no-ex-assign": 0, // disallow assigning to the exception in a catch block 72 | "no-extra-boolean-cast": 0, // disallow double-negation boolean casts in a boolean context 73 | "no-extra-parens": 0, // disallow unnecessary parentheses (off by default) 74 | "no-extra-semi": 1, // disallow unnecessary semicolons 75 | "no-func-assign": 2, // disallow overwriting functions written as function declarations 76 | "no-inner-declarations": 0, // disallow function or variable declarations in nested blocks 77 | "no-invalid-regexp": 1, // disallow invalid regular expression strings in the RegExp constructor 78 | "no-irregular-whitespace": 1, // disallow irregular whitespace outside of strings and comments 79 | "no-negated-in-lhs": 2, // disallow negation of the left operand of an in expression 80 | "no-obj-calls": 0, // disallow the use of object properties of the global object (Math and JSON) as functions 81 | "no-regex-spaces": 0, // disallow multiple spaces in a regular expression literal 82 | "no-sparse-arrays": 0, // disallow sparse arrays 83 | "no-unreachable": 2, // disallow unreachable statements after a return, throw, continue, or break statement 84 | "use-isnan": 0, // disallow comparisons with the value NaN 85 | "valid-jsdoc": 0, // Ensure JSDoc comments are valid (off by default) 86 | "valid-typeof": 0, // Ensure that the results of typeof are compared against a valid string 87 | 88 | ////////// Best Practices ////////// 89 | 90 | "block-scoped-var": 0, // treat var statements as if they were block scoped (off by default) 91 | complexity: 0, // specify the maximum cyclomatic complexity allowed in a program (off by default) 92 | "consistent-return": 0, // require return statements to either always or never specify values 93 | curly: [1], // specify curly brace conventions for all control statements 94 | "default-case": 1, // require default case in switch statements (off by default) 95 | "dot-notation": [1, { allowPattern: "(^[A-Z]|^[a-z]+_[a-z_]+$)" }], 96 | eqeqeq: ["error", "always", { null: "never" }], // require the use of === and !== 97 | "guard-for-in": 0, // make sure for-in loops have an if statement (off by default) 98 | "no-alert": 0, // disallow the use of alert, confirm, and prompt 99 | "no-caller": 0, // disallow use of arguments.caller or arguments.callee 100 | "no-div-regex": 0, // disallow division operators explicitly at beginning of regular expression (off by default) 101 | "no-else-return": 2, // disallow else after a return in an if (off by default) 102 | "no-empty-label": 0, // disallow use of labels for anything other then loops and switches 103 | "no-eq-null": 2, // disallow comparisons to null without a type-checking operator (off by default) 104 | "no-eval": 2, // disallow use of eval() 105 | "no-extend-native": 0, // disallow adding to native types 106 | "no-extra-bind": 0, // disallow unnecessary function binding 107 | "no-fallthrough": 2, // disallow fallthrough of case statements 108 | "no-floating-decimal": 0, // disallow the use of leading or trailing decimal points in numeric literals (off by default) 109 | "no-implied-eval": 2, // disallow use of eval()-like methods 110 | "no-iterator": 0, // disallow usage of __iterator__ property 111 | "no-labels": 0, // disallow use of labeled statements 112 | "no-lone-blocks": 2, // disallow unnecessary nested blocks 113 | "no-loop-func": 0, // disallow creation of functions within loops 114 | "no-multi-spaces": 0, // disallow use of multiple spaces 115 | "no-multi-str": 0, // disallow use of multiline strings 116 | "no-native-reassign": 2, // disallow reassignments of native objects 117 | "no-new": 2, // disallow use of new operator when not part of the assignment or comparison 118 | "no-new-func": 2, // disallow use of new operator for Function object 119 | "no-new-wrappers": 2, // disallows creating new instances of String, Number, and Boolean 120 | "no-octal": 0, // disallow use of octal literals 121 | "no-octal-escape": 0, // disallow use of octal escape sequences in string literals, such as var foo = "Copyright \251"; 122 | "no-process-env": 0, // disallow use of process.env (off by default) 123 | "no-proto": 0, // disallow usage of __proto__ property 124 | "no-redeclare": 2, // disallow declaring the same variable more then once 125 | "no-return-assign": 0, // disallow use of assignment in return statement 126 | "no-script-url": 0, // disallow use of javascript: urls. 127 | "no-self-compare": 0, // disallow comparisons where both sides are exactly the same (off by default) 128 | "no-sequences": 0, // disallow use of comma operator 129 | "no-unused-expressions": 0, // disallow usage of expressions in statement position 130 | "no-void": 0, // disallow use of void operator (off by default) 131 | "no-warning-comments": 0, // disallow usage of configurable warning terms in comments, e.g. TODO or FIXME (off by default) 132 | "no-with": 0, // disallow use of the with statement 133 | radix: 2, // require use of the second argument for parseInt() (off by default) 134 | "vars-on-top": 0, // requires to declare all vars on top of their containing scope (off by default) 135 | "wrap-iife": 2, // require immediate function invocation to be wrapped in parentheses (off by default) 136 | yoda: 1, // require or disallow Yoda conditions 137 | 138 | // "max-len": 1, // there is no fixer for this 139 | 140 | ////////// Strict Mode ////////// 141 | 142 | "global-strict": 0, // (deprecated) require or disallow the "use strict" pragma in the global scope (off by default in the node environment) 143 | "no-extra-strict": 0, // (deprecated) disallow unnecessary use of "use strict"; when already in strict mode 144 | strict: 0, // controls location of Use Strict Directives 145 | 146 | ////////// Variables ////////// 147 | 148 | "no-catch-shadow": 2, // disallow the catch clause parameter name being the same as a variable in the outer scope (off by default in the node environment) 149 | "no-delete-var": 2, // disallow deletion of variables 150 | "no-label-var": 1, // disallow labels that share a name with a variable 151 | "no-shadow": 0, // disallow declaration of variables already declared in the outer scope 152 | "no-shadow-restricted-names": 2, // disallow shadowing of names such as arguments 153 | "no-undef": 2, // disallow use of undeclared variables unless mentioned in a /*global */ block 154 | "no-undef-init": 1, // disallow use of undefined when initializing variables 155 | "no-undefined": 0, // disallow use of undefined variable (off by default) 156 | "no-unused-vars": 0, // disallow declaration of variables that are not used in the code 157 | "no-use-before-define": 0, // disallow use of variables before they are defined 158 | 159 | ////////// Node.js ////////// 160 | 161 | "handle-callback-err": 1, // enforces error handling in callbacks (off by default) (on by default in the node environment) 162 | "no-mixed-requires": [1, { grouping: true, allowCall: true }], // disallow mixing regular variable and require declarations (off by default) (on by default in the node environment) 163 | "no-new-require": 0, // disallow use of new operator with the require function (off by default) (on by default in the node environment) 164 | "no-path-concat": 2, // disallow string concatenation with __dirname and __filename (off by default) (on by default in the node environment) 165 | "no-process-exit": 0, // disallow process.exit() (on by default in the node environment) 166 | "no-restricted-modules": 0, // restrict usage of specified node modules (off by default) 167 | "no-sync": 0, // disallow use of synchronous methods (off by default) 168 | 169 | ////////// Stylistic Issues ////////// 170 | 171 | //"brace-style": [1, "1tbs", {"allowSingleLine": true}], // enforce one true brace style (off by default) 172 | //"camelcase": 1, // require camel case names 173 | "comma-spacing": [1, { before: false, after: true }], // enforce spacing before and after comma 174 | "comma-style": 0, // enforce one true comma style (off by default) 175 | "consistent-this": 0, // enforces consistent naming when capturing the current execution context (off by default) 176 | "eol-last": 0, // enforce newline at the end of file, with no multiple empty lines 177 | "func-names": [1, "as-needed"], // require function expressions to have a name (off by default) 178 | "func-style": 0, // enforces use of function declarations or expressions (off by default) 179 | "key-spacing": [ 180 | 1, 181 | { beforeColon: false, afterColon: true, mode: "strict" } 182 | ], // enforces spacing between keys and values in object literal properties 183 | "max-nested-callbacks": 0, // specify the maximum depth callbacks can be nested (off by default) 184 | // "new-cap": 1, // require a capital letter for constructors // Disabled because it applies to modules as well -.- 185 | "new-cap": 0, 186 | // "new-parens": 2, 187 | "new-parens": 0, // disallow the omission of parentheses when invoking a constructor with no arguments 188 | "no-array-constructor": 2, // disallow use of the Array constructor 189 | "no-inline-comments": 0, // disallow comments inline after code (off by default) 190 | "no-lonely-if": 1, // disallow if as the only statement in an else block (off by default) 191 | "no-multiple-empty-lines": 0, // disallow multiple empty lines (off by default) 192 | "no-nested-ternary": 0, // disallow nested ternary expressions (off by default) 193 | "no-new-object": 1, // disallow use of the Object constructor 194 | "semi-spacing": [1, { before: false, after: true }], // disallow space before semicolon 195 | "no-spaced-func": 1, // disallow space between function identifier and application 196 | "no-ternary": 0, // disallow the use of ternary operators (off by default) 197 | "no-trailing-spaces": 0, // disallow trailing whitespace at the end of lines 198 | "no-underscore-dangle": 0, // disallow dangling underscores in identifiers 199 | "one-var": 0, // allow just one var statement per function (off by default) 200 | "operator-assignment": 2, // require assignment operator shorthand where possible or prohibit it entirely (off by default) 201 | "padded-blocks": [1, "never"], // enforce padding within blocks (off by default) 202 | "quote-props": [1, "as-needed"], // require quotes around object literal property names (off by default) 203 | quotes: [ 204 | 1, 205 | "double", 206 | { avoidEscape: true, allowTemplateLiterals: true } 207 | ], // specify whether double or single quotes should be used 208 | "prefer-template": 1, // prefer template strings 209 | semi: [1, "always"], // require or disallow use of semicolons instead of ASI 210 | "sort-vars": 0, // sort variables within the same declaration block (off by default) 211 | "space-before-function-paren": [1, "never"], // require a space after certain keywords (off by default) 212 | "space-before-blocks": [1, "always"], // require or disallow space before blocks (off by default) 213 | "space-in-parens": 0, // require or disallow spaces inside parentheses (off by default) 214 | "space-unary-ops": 0, // Require or disallow spaces before/after unary operators (words on by default, nonwords off by default) 215 | "prefer-const": 1, 216 | complexity: 1, 217 | "no-mixed-spaces-and-tabs": 0, 218 | 219 | ////////// ECMAScript 6 ////////// 220 | 221 | "no-var": 2, // require let or const instead of var (off by default) 222 | "generator-star-spacing": ["error", { before: true, after: false }] // enforce the position of the * in generator functions (off by default) 223 | } 224 | }; 225 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | docs/**/* linguist-generated=true 2 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | on: 3 | push: 4 | branches: 5 | - no-files 6 | jobs: 7 | build-and-deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 🛎️ 11 | uses: actions/checkout@v2.3.1 12 | with: 13 | fetch-depth: 1 14 | 15 | - name: Install and Build 🔧 16 | run: | 17 | yarn install 18 | yarn add sass 19 | env node_env=PRODUCTION yarn build 20 | - name: Deploy 🚀 21 | uses: JamesIves/github-pages-deploy-action@4.0.0 22 | with: 23 | branch: gh-pages # The branch the action should deploy to. 24 | folder: docs # The folder the action should deploy. 25 | clean: true # Automatically remove deleted files from the deploy branch 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | /docs 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # junk folder 27 | /junk 28 | package-lock.json 29 | 30 | 31 | yarn.lock -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 4, 4 | "useTabs": true, 5 | "semi": true, 6 | "singleQuote": false, 7 | "trailingComma": "none", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false, 10 | "arrowParens": "avoid", 11 | "proseWrap": "always", 12 | "overrides": [ 13 | { 14 | "files": ".prettierrc", 15 | "options": { "parser": "json" } 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scpl editor 2 | 3 | ![screenshot](https://i.imgur.com/hOXtMNh.png) 4 | 5 | Shortcuts are previewed using [shortcut-preview](https://github.com/xalien95/shortcut-preview). 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scpl-editor", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "https://scpl.dev/", 6 | "dependencies": { 7 | "browserfs": "^1.4.3", 8 | "pretty-bytes": "^5.1.0", 9 | "qrcode": "^1.3.3", 10 | "react": "^16.8.4", 11 | "react-ace": "^6.4.0", 12 | "react-dom": "^16.8.4", 13 | "react-dropzone": "^10.0.5", 14 | "react-helmet": "^5.2.0", 15 | "react-markdown": "^4.0.8", 16 | "react-portal": "^4.2.0", 17 | "react-scripts": "2.1.8", 18 | "scpl": "^1.9.3", 19 | "shortcut-preview": "pfgithub/shortcut-preview", 20 | "typescript": "3.3.3333", 21 | "yarn": "^1.17.3" 22 | }, 23 | "scripts": { 24 | "start": "react-scripts start", 25 | "build": "react-scripts build && rm -rf docs && mv build docs && echo \"editor.scpl.dev\" > docs/CNAME", 26 | "test": "react-scripts test", 27 | "eject": "react-scripts eject", 28 | "format": "prettier --write src/***.{css,tsx,ts,json} .prettierrc .eslintrc.js package.json" 29 | }, 30 | "eslintConfig": { 31 | "extends": "react-app" 32 | }, 33 | "browserslist": [ 34 | ">0.2%", 35 | "not dead", 36 | "not ie <= 11", 37 | "not op_mini all" 38 | ], 39 | "devDependencies": { 40 | "@types/jest": "24.0.11", 41 | "@types/node": "11.11.0", 42 | "@types/pretty-bytes": "^5.1.0", 43 | "@types/qrcode": "^1.3.3", 44 | "@types/react": "16.8.7", 45 | "@types/react-dom": "16.8.2", 46 | "@types/react-helmet": "^5.0.8", 47 | "@types/react-portal": "^4.0.2", 48 | "@typescript-eslint/eslint-plugin": "^1.4.2", 49 | "@typescript-eslint/parser": "^1.4.2", 50 | "onchange": "^5.2.0", 51 | "prettier": "^1.17.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #1e1f57 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 30 | ScPL Editor 31 | 32 | 33 | 34 | 35 |
36 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/public/mstile-150x150.png -------------------------------------------------------------------------------- /public/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 22 | 31 | 55 | 72 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ScPL Editor", 3 | "short_name": "ScPL Editor", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#1e1f57", 17 | "background_color": "#1e1f57", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { parse, PositionedError } from "scpl"; 3 | import "./App.css"; 4 | // import {Helmet} from "react-helmet"; 5 | 6 | import testshortcut from "./testshortcut.json"; 7 | 8 | import ace from "brace"; 9 | import "./ace/mode-scpl"; 10 | import "brace/theme/chrome"; 11 | import "brace/ext/language_tools"; 12 | import "brace/ext/searchbox"; 13 | import AceEditor from "react-ace"; 14 | 15 | import { FilePane } from "./FilePane"; 16 | import { SearchActions } from "./SearchActions"; 17 | import { DownloadModal } from "./DownloadModal"; 18 | import { keys } from "./Key"; 19 | import { UploadShortcutModal } from "./UploadShortcutModal"; 20 | import { ErrorBoundary } from "./ErrorBoundary"; 21 | 22 | import ShortcutPreview from "shortcut-preview"; 23 | 24 | let timeout: NodeJS.Timeout; 25 | 26 | const Range = ace.acequire("ace/range").Range; 27 | 28 | const langTools = ace.acequire("ace/ext/language_tools"); 29 | const findAndReplace = ace.acequire("ace/ext/searchbox"); 30 | 31 | const rhymeCompleter = { 32 | getCompletions: ( 33 | editor: ace.Editor, 34 | session: ace.IEditSession, 35 | pos: ace.Position, 36 | prefix: string, 37 | callback: ( 38 | thing: null, 39 | completions: { 40 | name: string; 41 | value: string; 42 | score: number; 43 | meta: string; 44 | }[] 45 | ) => void 46 | ) => { 47 | if (prefix.length === 0) { 48 | callback(null, []); 49 | return; 50 | } 51 | const variables = session.getValue().match(/(v|mv):([A-Za-z0-9]+)/g); 52 | console.log(variables); 53 | if (!variables) { 54 | callback(null, []); 55 | return; 56 | } 57 | callback( 58 | null, 59 | variables.map(v => ({ 60 | name: v, 61 | value: v, 62 | score: 25, 63 | meta: v 64 | })) 65 | ); 66 | } 67 | }; 68 | langTools.addCompleter(rhymeCompleter); 69 | 70 | const hotkey = 71 | window.navigator.platform === "MacIntel" || 72 | window.navigator.platform.includes("Win") 73 | ? "⌘" 74 | : "^"; 75 | 76 | class MaybeUpdate extends Component<{ shouldUpdate: boolean }, {}> { 77 | shouldComponentUpdate(nextProps: { shouldUpdate: boolean }) { 78 | return nextProps.shouldUpdate; 79 | } 80 | render() { 81 | return this.props.children; 82 | } 83 | } 84 | 85 | class App extends Component< 86 | {}, 87 | { 88 | fileValue: string; 89 | shortcutData: any; 90 | shortcutDownload: Buffer | undefined; 91 | annotations: Array; 92 | markers: Array<{ 93 | startRow: number; 94 | endRow: number; 95 | startCol: number; 96 | endCol: number; 97 | className: string; 98 | type: string; 99 | }>; 100 | errors: Array<{ 101 | startRow: number; 102 | endRow: number; 103 | startCol: number; 104 | endCol: number; 105 | message: string; 106 | }>; 107 | loading: boolean; 108 | took: { waitedFor: number; convertedIn: number }; 109 | mobileFilemenu: boolean; 110 | openDownload: boolean; 111 | showPreview: boolean; 112 | showPreviewFullscreen: boolean; 113 | showUploadShortcutModal: boolean; 114 | tabs: { filename: string; active: boolean }[]; 115 | files: { 116 | [filename: string]: string; 117 | }; 118 | } 119 | > { 120 | reactAceComponentRef: React.RefObject; 121 | constructor(props: Readonly<{}>) { 122 | super(props); 123 | this.state = { 124 | fileValue: "...", 125 | shortcutData: testshortcut, 126 | shortcutDownload: undefined, 127 | annotations: [], 128 | markers: [], 129 | loading: false, 130 | took: { waitedFor: 0, convertedIn: 0 }, 131 | mobileFilemenu: false, 132 | openDownload: false, 133 | showPreview: false, 134 | showPreviewFullscreen: false, 135 | showUploadShortcutModal: false, 136 | errors: [], 137 | tabs: [{ filename: "download.scpl", active: true }], 138 | files: { 139 | "download.scpl": `ShowResult "Hello ScPL" 140 | ChooseFromMenu "ScPL Editor" items=["Getting Started", "View Documentation"] 141 | Case "Getting Started" 142 | URL "https://docs.scpl.dev/gettingstarted" 143 | Case "View Documentation" 144 | URL "https://docs.scpl.dev/" 145 | End Menu 146 | OpenURLs`, 147 | "other.scpl": `ShowResult "ScPL"`, 148 | urlquery: 149 | new URLSearchParams(window.location.search).get("scpl") || 150 | "" 151 | } 152 | }; 153 | this.reactAceComponentRef = React.createRef(); 154 | } 155 | componentDidMount() {} 156 | componentWillMount() { 157 | const urlParams = new URLSearchParams(window.location.search); 158 | this.onChange( 159 | new URLSearchParams(window.location.search).get("scpl") || 160 | `ShowResult "Hello ScPL" 161 | ChooseFromMenu "ScPL Editor" items=["Getting Started", "View Documentation"] 162 | Case "Getting Started" 163 | URL "https://docs.scpl.dev/gettingstarted" 164 | Case "View Documentation" 165 | URL "https://docs.scpl.dev/" 166 | End Menu 167 | OpenURLs` 168 | ); 169 | } 170 | render() { 171 | return ( 172 |
{ 174 | if (keys.save(e)) { 175 | alert("save"); 176 | } 177 | if (keys.export(e)) { 178 | this.setState({ openDownload: true }); 179 | } 180 | }} 181 | > 182 |
183 |
Drop file anywhere to upload
184 |
185 | {this.state.openDownload ? ( 186 | { 188 | this.setState({ openDownload: false }); 189 | }} 190 | filename={ 191 | this.state.shortcutData._filename || 192 | "download.shortcut" 193 | } 194 | file={this.state.shortcutDownload} 195 | /> 196 | ) : null} 197 | {this.state.showUploadShortcutModal ? ( 198 | 200 | this.setState({ showUploadShortcutModal: false }) 201 | } 202 | onResult={result => { 203 | this.setState({ showUploadShortcutModal: false }); 204 | this.onChange(result); 205 | }} 206 | /> 207 | ) : null} 208 | 209 |
210 |
216 | this.setState({ 217 | mobileFilemenu: !this.state.mobileFilemenu 218 | }) 219 | } 220 | /> 221 |
222 |
ScPL Editor
223 | 257 |
258 | 357 |
358 |
359 | 390 |
391 |
392 |
393 | { 395 | console.log("insert", text); 396 | const reactAceComponent = this 397 | .reactAceComponentRef.current; 398 | const editor = (reactAceComponent as any) 399 | .editor as ace.Editor; 400 | editor.session.insert( 401 | editor.getCursorPosition(), 402 | text 403 | ); 404 | console.log( 405 | editor.session.getValue(), 406 | text, 407 | editor.getCursorPosition() 408 | ); 409 | this.setState({ 410 | fileValue: editor.session.getValue() 411 | }); 412 | }} 413 | /> 414 |
415 |
416 |
417 |
418 | {this.state.shortcutData && 419 | this.state.shortcutData[0].WFWorkflowActions 420 | .length}{" "} 421 | action 422 | {this.state.shortcutData && 423 | this.state.shortcutData[0].WFWorkflowActions 424 | .length === 1 425 | ? "" 426 | : "s"} 427 |
428 |
429 | 430 | 445 |
449 | this.setState({ 450 | openDownload: true 451 | }) 452 | } 453 | > 454 | Export 455 |
456 |
457 |
458 |
459 |
464 | this.onChange(file)} 508 | /> 509 |
510 |
511 |
512 | {this.state.errors.map(err => ( 513 |
514 |
515 | {err.message} 516 |
517 |
518 | 525 |
526 |
527 | ))} 528 |
529 |
530 | {this.state.tabs.length > 1 ? ( 531 |
532 | {this.state.tabs.map(tab => ( 533 |
538 |
539 | × 540 |
541 |
542 | {tab.filename} 543 |
544 |
545 | ))} 546 |
547 | ) : null} 548 |
549 | 563 |
564 |
578 |
579 | Converted in {this.state.took.convertedIn} ms. 580 |
581 | {this.state.showPreview && this.state.shortcutData ? ( 582 | ( 584 |
585 |

586 | A fatal error occured in 587 | shortcut-preview. The error is{" "} 588 | {err.toString()} 589 |

590 |
591 | )} 592 | > 593 | 595 | this.onActionSelect(data) 596 | } 597 | data={this.state.shortcutData} 598 | /> 599 |
600 | ) : null} 601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 | {!this.state.showPreview ? ( 620 |
621 |
622 |

623 | There are too many actions to render a 624 | preview automatically. 625 |

626 | 636 |
637 |
638 | ) : null} 639 | {!this.state.shortcutData ? ( 640 |
641 |
642 |

643 | Failed to render because of code error. 644 |

645 |
646 |
647 | ) : null} 648 |
649 |
650 |
651 | ); 652 | } 653 | onActionSelect(data: { type: "action" | "parameter"; actionData: any }) { 654 | if (data.actionData.SCPLData) { 655 | const scpldata = data.actionData.SCPLData; 656 | 657 | const reactAceComponent = this.reactAceComponentRef.current; 658 | if (!reactAceComponent) { 659 | return; 660 | } 661 | const editor = (reactAceComponent as any).editor as ace.Editor; 662 | // .Position.start|end 663 | const line = scpldata.Position.start[0]; 664 | const col = scpldata.Position.start[1] - 1; 665 | editor.gotoLine(line, col, true); 666 | editor.selection.setRange( 667 | new Range( 668 | scpldata.Position.start[0] - 1, 669 | scpldata.Position.start[1] - 1, 670 | scpldata.Position.end[0] - 1, 671 | scpldata.Position.end[1] - 1 672 | ), 673 | true 674 | ); 675 | } 676 | } 677 | getAce(): ace.Editor { 678 | const reactAceComponent = this.reactAceComponentRef.current; 679 | if (!reactAceComponent) { 680 | throw new Error("No reactacecomponent"); 681 | } 682 | const editor = (reactAceComponent as any).editor as ace.Editor; 683 | return editor; 684 | } 685 | jumpToError(d: { 686 | startCol: number; 687 | startRow: number; 688 | endCol: number; 689 | endRow: number; 690 | }) { 691 | const reactAceComponent = this.reactAceComponentRef.current; 692 | if (!reactAceComponent) { 693 | return; 694 | } 695 | const editor = (reactAceComponent as any).editor as ace.Editor; 696 | const line = d.startRow; 697 | const col = d.startCol - 1; 698 | editor.gotoLine(line, col, true); 699 | editor.selection.setRange( 700 | new Range( 701 | d.startRow - 1, 702 | d.startCol - 1, 703 | d.endRow - 1, 704 | d.endCol - 1 705 | ), 706 | true 707 | ); 708 | } 709 | onChange(text: string) { 710 | // Enable navigation prompt 711 | window.onbeforeunload = () => { 712 | return true; 713 | }; 714 | const willWaitFor = Math.max(this.state.took.convertedIn * 4, 100); 715 | if (!this.state.loading) { 716 | this.setState({ 717 | showPreview: this.state.took.convertedIn < 50, 718 | fileValue: text, 719 | loading: true 720 | }); 721 | } 722 | if (timeout) { 723 | clearTimeout(timeout); 724 | } 725 | timeout = setTimeout( 726 | () => this.onChangeLimited(text, willWaitFor), 727 | willWaitFor 728 | ); 729 | } 730 | onChangeLimited(text: string, waitedFor: number) { 731 | // parse 732 | let output: { 733 | shortcutjson: any; 734 | shortcutplist: Buffer; 735 | warnings: PositionedError[]; 736 | }; 737 | const annotationFromError = ( 738 | err: PositionedError, 739 | isWarning: boolean 740 | ) => { 741 | return { 742 | row: err.start[0] - 1, 743 | column: err.start[1] - 1, 744 | text: err.message, // Or the Json reply from the parser 745 | type: isWarning ? "warning" : "error" 746 | }; 747 | }; 748 | const markerFromError = (er: PositionedError, isWarning: boolean) => { 749 | return { 750 | startRow: er.start[0] - 1, 751 | endRow: er.end[0] - 1, 752 | startCol: er.start[1] - 1, 753 | endCol: er.end[1] - 1, 754 | className: `ace_active-line ${isWarning ? "warning" : "error"}`, 755 | type: "background" 756 | }; 757 | }; 758 | const errorBannerFromError = ( 759 | er: PositionedError, 760 | isWarning: boolean 761 | ) => { 762 | return { 763 | startRow: er.start[0], 764 | endRow: er.end[0], 765 | startCol: er.start[1], 766 | endCol: er.end[1], 767 | message: er.message, 768 | type: isWarning ? "warning" : "error" 769 | }; 770 | }; 771 | const startTime = new Date().getTime(); 772 | try { 773 | output = parse(text, { 774 | make: ["shortcutjson", "shortcutplist"], 775 | useWarnings: true 776 | }); 777 | } catch (er) { 778 | console.log(er.message); //eslint-disable-line no-console 779 | if (!(er instanceof PositionedError)) { 780 | throw er; 781 | } 782 | this.setState({ 783 | fileValue: text, 784 | loading: false, 785 | shortcutDownload: undefined, 786 | shortcutData: undefined, 787 | took: { 788 | waitedFor: waitedFor, 789 | convertedIn: new Date().getTime() - startTime 790 | }, 791 | annotations: [annotationFromError(er, false)], 792 | markers: [markerFromError(er, false)], 793 | errors: [errorBannerFromError(er, false)] 794 | }); 795 | return; 796 | } 797 | const { shortcutjson, shortcutplist } = output; 798 | this.setState({ 799 | fileValue: text, 800 | took: { 801 | waitedFor: waitedFor, 802 | convertedIn: new Date().getTime() - startTime 803 | }, 804 | loading: false, 805 | shortcutData: shortcutjson, 806 | annotations: [ 807 | ...output.warnings.map(warn => annotationFromError(warn, false)) 808 | ], 809 | markers: [ 810 | ...output.warnings.map(warn => markerFromError(warn, false)) 811 | ], 812 | errors: [ 813 | ...output.warnings.map(warn => 814 | errorBannerFromError(warn, false) 815 | ) 816 | ], 817 | shortcutDownload: shortcutplist 818 | }); 819 | } 820 | } 821 | 822 | export default App; 823 | -------------------------------------------------------------------------------- /src/CreateEditShortcut.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | import { 4 | GlyphName, 5 | objectGlyphs, 6 | peopleGlyphs, 7 | symbolGlyphs, 8 | ColorName, 9 | colors 10 | } from "./data/ShortcutMeta"; 11 | import { ModalContainer } from "./ModalContainer"; 12 | 13 | import "./CreateEditShortcut.css"; 14 | 15 | type CreateEditShortcutProps = { 16 | onCancel: () => void; 17 | onResult: (name: string, color: undefined, glyph: undefined) => void; 18 | }; 19 | 20 | export class CreateEditShortcut extends Component< 21 | CreateEditShortcutProps, 22 | { chosenGlyph: GlyphName; chosenColor: ColorName; mode: "color" | "glyph" } 23 | > { 24 | constructor(props: Readonly) { 25 | super(props); 26 | this.state = { 27 | chosenGlyph: "magicwand", 28 | chosenColor: "purple", 29 | mode: "color" 30 | }; 31 | } 32 | render() { 33 | return ( 34 | this.props.onCancel()}> 35 |
40 |
this.props.onCancel()} 44 | > 45 | Cancel 46 |
47 |

New ScPL File

48 |
49 |
50 |
54 |
58 |
59 |
60 |
61 | 67 |
72 | Name is required. 73 |
74 |
75 |
76 |
77 |
78 | 87 | 96 |
97 | 98 |
99 |
108 |
109 |
Objects
110 | 111 | {objectGlyphs.map(id => ( 112 |
113 | 122 | this.setState({ 123 | chosenGlyph: id 124 | }) 125 | } 126 | /> 127 |
129 | ))} 130 |
131 | 132 |
133 |
People
134 | 135 | {peopleGlyphs.map(id => ( 136 |
137 | 146 | this.setState({ 147 | chosenGlyph: id 148 | }) 149 | } 150 | /> 151 |
153 | ))} 154 |
155 | 156 |
157 |
Symbols
158 | 159 | {symbolGlyphs.map(id => ( 160 |
161 | 170 | this.setState({ 171 | chosenGlyph: id 172 | }) 173 | } 174 | /> 175 |
177 | ))} 178 |
179 |
180 |
181 | 182 |
190 |
191 | {colors.map(color => ( 192 |
193 | 201 | this.setState({ 202 | chosenColor: color 203 | }) 204 | } 205 | /> 206 |
208 | ))} 209 |
210 |
211 | 212 |
213 | Create 214 |
215 |
216 | 217 | ); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/DownloadButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | export class DownloadButton extends Component< 4 | { filename: string; file: Buffer | undefined }, 5 | {} 6 | > { 7 | // from https://github.com/axetroy/react-download 8 | render() { 9 | return ( 10 | this.onClick(e)} 14 | > 15 | {this.props.children} 16 | 17 | ); 18 | } 19 | fakeClick(obj: HTMLAnchorElement) { 20 | const ev = document.createEvent("MouseEvents"); 21 | ev.initMouseEvent( 22 | "click", 23 | true, 24 | false, 25 | window, 26 | 0, 27 | 0, 28 | 0, 29 | 0, 30 | 0, 31 | false, 32 | false, 33 | false, 34 | false, 35 | 0, 36 | null 37 | ); 38 | obj.dispatchEvent(ev); 39 | } 40 | exportRaw(name: string, data: Buffer) { 41 | const urlObject = window.URL || (window as any).webkitURL || window; 42 | const export_blob = new Blob([data]); 43 | 44 | if ("msSaveBlob" in navigator) { 45 | // Prefer msSaveBlob if available - Edge supports a[download] but 46 | // ignores the filename provided, using the blob UUID instead. 47 | // msSaveBlob will respect the provided filename 48 | navigator.msSaveBlob(export_blob, name); 49 | } else if ("download" in HTMLAnchorElement.prototype) { 50 | const save_link = document.createElementNS( 51 | "http://www.w3.org/1999/xhtml", 52 | "a" 53 | ) as HTMLAnchorElement; 54 | save_link.href = urlObject.createObjectURL(export_blob); 55 | save_link.download = name; 56 | this.fakeClick(save_link); 57 | } else { 58 | alert( 59 | "Downloading shortcuts is not available on this browser yet :(\nIt should be implemented within a few days from a few days from a while from now." 60 | ); 61 | } 62 | } 63 | onClick(_e: React.MouseEvent) { 64 | if (!this.props.file) { 65 | return; 66 | } 67 | this.exportRaw(this.props.filename, this.props.file); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/DownloadModal.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | import * as QRCode from "qrcode"; 4 | 5 | import { ModalContainer } from "./ModalContainer"; 6 | import { DownloadButton } from "./DownloadButton"; 7 | 8 | import prettyBytes from "pretty-bytes"; 9 | 10 | import shortcutDownloadPreviewIcon from "./img/shortcut-file.png"; 11 | import { uploadShortcut } from "./Uploader"; 12 | import { DownloadStatusSwitcher, UploadStatus } from "./DownloadStatusSwitcher"; 13 | 14 | import "./CreateEditShortcut.css"; 15 | 16 | export class ShortcutDownloadPreviewButton extends Component< 17 | { filename: string; filesize: number }, 18 | {} 19 | > { 20 | render() { 21 | return ( 22 |
23 | 24 |
{this.props.filename}
25 |
26 | {prettyBytes(this.props.filesize)} 27 |
28 |
29 | ); 30 | } 31 | } 32 | 33 | type DownloadModalProps = { 34 | onCancel: () => void; 35 | filename: string; 36 | file: Buffer | undefined; 37 | }; 38 | 39 | export class DownloadModal extends Component { 40 | constructor(props: Readonly) { 41 | super(props); 42 | this.state = { uploadStatus: "None" }; 43 | } 44 | componentWillReceiveProps(nextProps: Readonly) { 45 | if (nextProps.file !== this.props.file) { 46 | this.setState({ uploadStatus: "None" }); 47 | } 48 | } 49 | async uploadFile() { 50 | if (!this.props.file) { 51 | this.setState({ 52 | uploadStatus: "Error", 53 | uploadError: "No file. Make sure your shortcut has no errors." 54 | }); 55 | return; 56 | } 57 | this.setState({ 58 | uploadStatus: "Uploading" 59 | }); 60 | const uploadResult = await uploadShortcut( 61 | this.props.file, 62 | this.props.filename 63 | ); 64 | if (uploadResult.result === "error") { 65 | this.setState({ 66 | uploadStatus: "Error", 67 | uploadError: uploadResult.message 68 | }); 69 | return; 70 | } 71 | const qrcode = await QRCode.toDataURL(uploadResult.url); 72 | this.setState({ 73 | uploadStatus: "URL", 74 | uploadedURL: uploadResult.url, 75 | qrcode: qrcode 76 | }); 77 | } 78 | render() { 79 | return ( 80 | this.props.onCancel()}> 81 |
e.stopPropagation()} 88 | > 89 |

Export Shortcut File

90 | {this.props.file ? ( 91 |
92 |
93 | {"download" in HTMLAnchorElement.prototype ? ( 94 | 98 | 104 | 105 | ) : ( 106 | this.uploadFile()} 109 | detailsMsg={ 110 |
111 | To download on this browser, 112 | ScPL editor will upload your 113 | shortcut to{" "} 114 | 119 | file.io 120 | 121 | . 122 |
123 | } 124 | uploadAction="Create Download Link" 125 | > 126 | 133 | 139 | 140 |
141 | )} 142 |
143 |
or
144 |
145 | this.uploadFile()} 148 | detailsMsg={ 149 |
150 | QR codes must be 151 | scanned using{" "} 152 | 157 | This Shortcut 158 | 159 | {" "}due to new security 160 | restrictions in iOS 13.{" "} 161 | To generate a QR code, ScPL editor 162 | will upload your shortcut to{" "} 163 | 168 | file.io 169 | 170 | . 171 |
172 | } 173 | uploadAction="Generate QR Code" 174 | additionalButtonClasses="qr-btn" 175 | > 176 |
177 |

178 | Add to your library via QR Code: 179 |

180 |
181 |
190 |
191 |

192 | Open your Camera app and point it 193 | steady for 2-3 seconds at this QR 194 | Code. 195 |

196 |
197 | 198 |
199 |
200 | ) : ( 201 |
202 |

Your code seems to have some errors.

203 |
204 | )} 205 |
206 |
this.props.onCancel()} 210 | > 211 | Done 212 |
213 |
214 | 215 | ); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/DownloadStatusSwitcher.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | export type UploadStatus = 4 | | { 5 | uploadStatus: "None" | "Uploading"; 6 | } 7 | | { 8 | uploadStatus: "URL"; 9 | uploadedURL: string; 10 | qrcode: string; 11 | } 12 | | { 13 | uploadStatus: "Error"; 14 | uploadError: string; 15 | }; 16 | 17 | export class DownloadStatusSwitcher extends Component<{ 18 | status: UploadStatus; 19 | requestUpload: () => void; 20 | detailsMsg: React.ReactNode; 21 | uploadAction: string; 22 | additionalButtonClasses?: string; 23 | }> { 24 | render() { 25 | switch (this.props.status.uploadStatus) { 26 | case "None": 27 | return ( 28 |
29 |

30 | 37 |

{this.props.detailsMsg}

38 |
39 | ); 40 | case "URL": 41 | return this.props.children; 42 | case "Uploading": 43 | return ( 44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |

Uploading...

61 |
62 |
63 | ); 64 | case "Error": 65 | return ( 66 |
67 |

Error uploading: {this.props.status.uploadError}

68 |
69 | ); 70 | default: 71 | return ( 72 |
73 |

Something bad happened.

74 |
75 | ); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | type ErrorBoundaryProps = { 4 | errorDisplay: (error: Error) => React.ReactNode; 5 | }; 6 | export class ErrorBoundary extends Component< 7 | ErrorBoundaryProps, 8 | { error: Error | undefined } 9 | > { 10 | constructor(props: Readonly) { 11 | super(props); 12 | this.state = { error: undefined }; 13 | } 14 | componentDidCatch(error: Error) { 15 | this.setState({ error: error }); 16 | } 17 | render() { 18 | if (this.state.error) { 19 | console.log(this.state.error); 20 | return this.props.errorDisplay(this.state.error); 21 | } 22 | return this.props.children; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/FileManager.ts: -------------------------------------------------------------------------------- 1 | export class FileManager { 2 | constructor() {} 3 | listItems() { 4 | // returns a list of files and folders 5 | } 6 | createFolder() {} 7 | saveFile(path: string, data: string) { 8 | // saves the file at the path 9 | } 10 | loadFile(path: string, data: string) {} 11 | renameFile(path: string, newPath: string) {} 12 | } 13 | -------------------------------------------------------------------------------- /src/FilePane.css: -------------------------------------------------------------------------------- 1 | /* File pane */ 2 | .file-pane { 3 | width: 300px; 4 | background-color: #fff; 5 | border-right: 0.5px solid #c7c7cc; 6 | transition: 0.1s left ease-out; 7 | z-index: 100; 8 | padding-bottom: 20px; 9 | overflow-x: hidden; 10 | overflow-y: auto; 11 | height: stretch; 12 | } 13 | .file-pane input { 14 | width: 90%; 15 | margin-left: 15px; 16 | } 17 | .file-pane h2 { 18 | margin: 20px 15px; 19 | } 20 | /* Files Navigation */ 21 | .files-header { 22 | position: sticky; 23 | top: 0; 24 | background-color: #fafafa; 25 | height: 55px; 26 | border-bottom: 1px solid #ddd; 27 | margin-bottom: 10px; 28 | padding: 15px 0; 29 | box-shadow: 0px -5px 20px rgba(0, 0, 0, 0.15); 30 | } 31 | .file-btns { 32 | display: grid; 33 | grid-template-columns: 1fr 1fr 1fr; 34 | padding: 0 10px; 35 | } 36 | .file-btn { 37 | font-size: 15px; 38 | padding: 13px 0; 39 | width: 100%; 40 | width: stretch; 41 | padding-left: 30px; 42 | font-weight: normal; 43 | outline: none; 44 | } 45 | .upload-btn { 46 | background: #eee url(img/upload.png) center / 25px no-repeat; 47 | } 48 | .large-upload-btn { 49 | background: #eee url(img/upload.png) 22% center / 25px no-repeat; 50 | padding-left: 10%; 51 | } 52 | .new-btn { 53 | background: #eee url(img/new_file.png) center / 25px no-repeat; 54 | } 55 | .newf-btn { 56 | background: #eee url(img/new_folder.png) center / 25px no-repeat; 57 | } 58 | /* File list */ 59 | .file-list li { 60 | padding: 8px 10px; 61 | padding-left: 25px; 62 | cursor: pointer; 63 | user-select: none; 64 | transition: 0.3s background-color; 65 | } 66 | .file-list li:hover:not(.open-folder) { 67 | background-color: #e8f6fe !important; 68 | } 69 | .file-list li:active:not(.open-folder) { 70 | background-color: #daeffc !important; 71 | } 72 | .file-list li.open-folder > ul > li { 73 | width: 94%; 74 | } 75 | .file-list ul { 76 | list-style-type: none; 77 | padding: 0; 78 | margin: 0; 79 | } 80 | .file-list ul ul { 81 | border-left: 1px solid #ddd; 82 | margin: 5px 10px; 83 | } 84 | .list-item-file > div, 85 | .list-item-folder > div { 86 | padding-left: 27px; 87 | font-size: 14px; 88 | } 89 | .item-name { 90 | width: 70%; 91 | height: 18px; 92 | white-space: nowrap; 93 | overflow: hidden; 94 | text-overflow: ellipsis; 95 | display: inline-block; 96 | margin-top: 3px; 97 | } 98 | .item-name .load { 99 | display: none; 100 | } 101 | .file-list ul li.open-folder ul li > div > .item-name { 102 | width: 60%; 103 | } 104 | .file-list ul li.open-folder ul li.open-folder ul > li > div > .item-name { 105 | width: 50%; 106 | } 107 | /* File items */ 108 | .list-item-file > div { 109 | background: url(img/file.png) 2px center / 18px no-repeat; 110 | } 111 | .file-list li.list-item-file.active { 112 | background-color: #007aff20; 113 | font-weight: 500; 114 | } 115 | .file-list li.list-item-file.active:hover { 116 | background-color: #007aff10 !important; 117 | } 118 | .file-list li.list-item-file.active > div { 119 | background-image: url(img/file_active.png); 120 | } 121 | /* uploading state */ 122 | .list-item-file.uploading { 123 | background: none; 124 | pointer-events: none; 125 | user-select: none; 126 | color: #777; 127 | } 128 | .list-item-file.uploading .action-btns { 129 | opacity: 0.5; 130 | } 131 | .list-item-file.uploading > div { 132 | background: none; 133 | } 134 | .list-item-file.uploading .item-name .load { 135 | display: inline-block; 136 | position: absolute; 137 | margin-left: -25px; 138 | margin-top: 2px; 139 | width: 10px; 140 | height: 10px; 141 | } 142 | /* Folder items */ 143 | .list-item-folder > div { 144 | background: url(img/folder.png) left center / 20px no-repeat; 145 | } 146 | .file-list li.list-item-folder > ul { 147 | display: none; 148 | transform-origin: top center; 149 | transition: 0.1s left ease-out; 150 | transform: scale(1, 0); 151 | } 152 | .file-list li.list-item-folder { 153 | background: url(img/right.svg) 10px 15px / 10px no-repeat; 154 | } 155 | .file-list li.list-item-folder.open-folder { 156 | background: url(img/down.svg) 10px 15px / 10px no-repeat; 157 | padding-bottom: 0; 158 | } 159 | .file-list li.list-item-folder.open-folder > ul { 160 | display: block; 161 | animation: scaleIn 0.1s; 162 | transform: scale(1, 1); 163 | } 164 | .list-item-folder.open-folder > div { 165 | font-weight: 500; 166 | } 167 | @keyframes scaleIn { 168 | from { 169 | transform: scale(1, 0); 170 | } 171 | to { 172 | transform: scale(1, 1); 173 | } 174 | } 175 | /* Rename and delete btns */ 176 | .action-btns { 177 | float: right; 178 | margin-top: -3px; 179 | } 180 | .file-list ul ul ul .action-btns { 181 | margin-right: 3px; 182 | } 183 | .edit-btn, 184 | .delete-btn { 185 | background: url() center / 20px no-repeat; 186 | padding: 15px; 187 | float: right; 188 | border-radius: 3px; 189 | cursor: pointer; 190 | } 191 | .action-btns div:active { 192 | background-color: #8c99b3; 193 | } 194 | .edit-btn { 195 | background-image: url(img/edit.svg); 196 | } 197 | .edit-btn:active { 198 | background-image: url(img/edit_w.svg); 199 | } 200 | .delete-btn { 201 | background-image: url(img/delete.svg); 202 | margin-left: 3px; 203 | } 204 | .delete-btn:active { 205 | background-image: url(img/delete_w.svg); 206 | } 207 | -------------------------------------------------------------------------------- /src/FilePane.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Dropzone, { DropEvent } from "react-dropzone"; 3 | import { inverse } from "scpl"; 4 | 5 | import { CreateEditShortcut } from "./CreateEditShortcut"; 6 | 7 | import "./FilePane.css"; 8 | 9 | type FileData = { type: "file"; name: string }; 10 | type FolderData = { 11 | type: "folder"; 12 | name: string; 13 | files: (FileData | FolderData)[]; 14 | }; 15 | 16 | class ActionButtons extends Component<{}> { 17 | render() { 18 | return ( 19 |
20 |
{ 23 | e.stopPropagation(); 24 | }} 25 | /> 26 |
{ 29 | e.stopPropagation(); 30 | }} 31 | /> 32 |
33 | ); 34 | } 35 | } 36 | 37 | class FileComponent extends Component<{ 38 | data: FileData; 39 | }> { 40 | render() { 41 | return ( 42 |
  • { 45 | e.stopPropagation(); 46 | }} 47 | title={this.props.data.name} 48 | > 49 |
    50 |
    51 |
    52 | {this.props.data.name} 53 |
    54 | 55 |
    56 |
  • 57 | ); 58 | } 59 | } 60 | 61 | class FolderComponent extends Component< 62 | { 63 | data: FolderData; 64 | searchTerm: string; 65 | }, 66 | { 67 | expanded: boolean; 68 | } 69 | > { 70 | constructor( 71 | props: Readonly<{ 72 | data: FolderData; 73 | searchTerm: string; 74 | }> 75 | ) { 76 | super(props); 77 | this.state = { expanded: false }; 78 | } 79 | render() { 80 | return ( 81 |
  • { 86 | this.setState({ expanded: !this.state.expanded }); 87 | e.stopPropagation(); 88 | }} 89 | title={this.props.data.name} 90 | > 91 |
    92 |
    {this.props.data.name}
    93 | 94 |
    95 | 99 |
  • 100 | ); 101 | } 102 | } 103 | 104 | // sort: 105 | // a.order - b.order != 0 ? a.order - b.order 106 | // : a.name.localeCompare(b.name, undefined, {numeric: true}) 107 | 108 | class FileList extends Component<{ 109 | files: (FileData | FolderData)[]; 110 | searchTerm: string; 111 | }> { 112 | render() { 113 | return ( 114 |
      115 | {this.props.files.map(file => { 116 | if (file.type === "file") { 117 | if ( 118 | file.name 119 | .toLowerCase() 120 | .indexOf(this.props.searchTerm.toLowerCase()) > 121 | -1 122 | ) { 123 | return ( 124 | 125 | ); 126 | } 127 | return null; 128 | } 129 | return ( 130 | 135 | ); 136 | })} 137 |
    138 | ); 139 | } 140 | } 141 | 142 | export class FilePane extends Component< 143 | { 144 | onActiveFileChanged: (fileContents: string) => void; 145 | files: (FileData | FolderData)[]; 146 | }, 147 | { 148 | showFileModal: boolean; 149 | searchTerm: string; 150 | } 151 | > { 152 | constructor( 153 | props: Readonly<{ 154 | onActiveFileChanged: (fileContents: string) => void; 155 | files: (FileData | FolderData)[]; 156 | }> 157 | ) { 158 | super(props); 159 | this.state = { 160 | showFileModal: false, 161 | searchTerm: "" 162 | }; 163 | } 164 | onDrop(acceptedFiles: File[], _rejectedFiles: File[], _event: DropEvent) { 165 | const reader = new FileReader(); 166 | 167 | reader.onabort = () => alert("file reading was aborted"); 168 | reader.onerror = () => alert("file reading has failed"); 169 | reader.onload = () => { 170 | // Do whatever you want with the file contents 171 | const binaryStr = new Buffer(reader.result as ArrayBuffer); 172 | let inverted: string; 173 | try { 174 | inverted = inverse(binaryStr); 175 | } catch (e) { 176 | alert( 177 | `An unhandled error occured while trying to convert shortcut -> scpl. The error is: ${e.toString()}` 178 | ); 179 | throw new Error(e); 180 | } 181 | this.props.onActiveFileChanged(inverted); 182 | }; 183 | 184 | acceptedFiles.forEach(file => reader.readAsArrayBuffer(file)); 185 | } 186 | render() { 187 | return ( 188 |
    189 | 190 | {({ getRootProps, getInputProps }) => ( 191 |
    192 |
    193 |
    194 |
    199 | Upload Shortcut 200 | 201 | 202 |
    203 |
    204 |
    205 |
    206 | )} 207 |
    208 | 209 | {this.state.showFileModal ? ( 210 | this.setState({ showFileModal: false })} 212 | onResult={(name, color, glyph) => { 213 | this.setState({ showFileModal: false }); 214 | }} 215 | /> 216 | ) : null} 217 |
    218 | ); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/Key.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | type KeyNames = "save" | "closePanel" | "export"; 4 | 5 | export const keys: { 6 | [key in KeyNames]: (e: React.KeyboardEvent) => boolean 7 | } = { 8 | save: (e: React.KeyboardEvent) => { 9 | if ( 10 | e.nativeEvent.code === "KeyS" && 11 | (e.ctrlKey || e.metaKey) && 12 | !e.shiftKey && 13 | !e.altKey 14 | ) { 15 | e.preventDefault(); 16 | e.stopPropagation(); 17 | return true; 18 | } 19 | return false; 20 | }, 21 | closePanel: (e: React.KeyboardEvent) => { 22 | if ( 23 | e.nativeEvent.code === "Escape" && 24 | !(e.ctrlKey || e.metaKey) && 25 | !e.shiftKey && 26 | !e.altKey 27 | ) { 28 | e.preventDefault(); 29 | e.stopPropagation(); 30 | return true; 31 | } 32 | return false; 33 | }, 34 | export: (e: React.KeyboardEvent) => { 35 | if ( 36 | e.nativeEvent.code === "KeyS" && 37 | (e.ctrlKey || e.metaKey) && 38 | e.shiftKey && 39 | !e.altKey 40 | ) { 41 | e.preventDefault(); 42 | e.stopPropagation(); 43 | return true; 44 | } 45 | return false; 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /src/Modal.tsx: -------------------------------------------------------------------------------- 1 | export const _ = class {}; 2 | 3 | /* 4 |
    5 |
    9 | Cancel 10 |
    11 |

    Import from iCloud

    12 |
    iCloud URL
    13 | 19 |
    20 | Import Shortcut 21 |
    22 |
    23 | 24 |
    25 |
    29 | Cancel 30 |
    31 |

    Create New Folder

    32 |
    New Folder Name
    33 | 39 |
    40 | Create Folder 41 |
    42 |
    43 | 44 |
    45 |
    49 | Cancel 50 |
    51 |

    Rename File

    52 | 58 |
    59 | Change Name 60 |
    61 |
    62 | 63 |
    64 |
    68 | Cancel 69 |
    70 |

    Rename Folder

    71 | 77 |
    78 | Change Name 79 |
    80 |
    81 | 82 | 83 | */ 84 | -------------------------------------------------------------------------------- /src/ModalContainer.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Portal } from "react-portal"; 3 | 4 | import { keys } from "./Key"; 5 | 6 | export class ModalContainer extends Component<{ onCancel: () => void }> { 7 | render() { 8 | return ( 9 | 10 |
    this.props.onCancel()} 16 | onKeyDown={e => { 17 | if (keys.closePanel(e)) { 18 | this.props.onCancel(); 19 | } 20 | }} 21 | > 22 |
    e.stopPropagation()}> 23 | {this.props.children} 24 |
    25 |
    26 |
    27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/SearchActions.css: -------------------------------------------------------------------------------- 1 | /* Search actions */ 2 | .search-container { 3 | padding-top: 5px; 4 | } 5 | .search-container input { 6 | width: 75%; 7 | } 8 | .close-search-btn { 9 | padding: 5px; 10 | font-size: 15px; 11 | text-align: center; 12 | display: inline-block; 13 | margin-left: 10px; 14 | color: #007aff; 15 | cursor: pointer; 16 | } 17 | .close-mobile-search-btn { 18 | float: right; 19 | position: sticky; 20 | top: 0; 21 | padding: 10px 20px; 22 | font-size: 30px; 23 | text-align: center; 24 | display: inline-block; 25 | background-color: #fafafa; 26 | color: #007aff; 27 | cursor: pointer; 28 | display: none; 29 | border-bottom-left-radius: 10px; 30 | border-left: 0.5px solid #c7c7cc50; 31 | border-bottom: 0.5px solid #c7c7cc50; 32 | } 33 | .close-search-btn a { 34 | text-decoration: none !important; 35 | } 36 | .search-action-results { 37 | position: absolute; 38 | opacity: 0; 39 | pointer-events: none; 40 | cursor: pointer; 41 | z-index: 2000; 42 | margin: 0 auto; 43 | top: 50px; 44 | right: 0; 45 | left: 0; 46 | width: 500px; 47 | max-height: 500px; 48 | overflow-y: scroll; 49 | background-color: #fff; 50 | border-radius: 8px; 51 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); 52 | transition: 0.3s opacity; 53 | } 54 | .search-action-results.search-action-results-visible { 55 | opacity: 1; 56 | pointer-events: all; 57 | } 58 | .action-item { 59 | padding: 15px 20px; 60 | padding-top: 18px; 61 | transition: 0.3s background-color; 62 | } 63 | .action-item-unknown .action-item-title:before { 64 | padding: 10px; 65 | width: auto; 66 | height: auto; 67 | background: url(img/shortcuts_grey.png) center / 30px no-repeat; 68 | } 69 | .action-item-title:before { 70 | content: ' '; 71 | padding: 3px 11px; 72 | margin-right: 8px; 73 | background: center / contain no-repeat; 74 | } 75 | .action-item:hover { 76 | background-color: #fafafa; 77 | } 78 | .action-item:active { 79 | background-color: #007aff20; 80 | } 81 | .search-action-results .action-item:not(:last-child) { 82 | border-bottom: 0.5px solid #eee; 83 | } 84 | .action-item-title { 85 | font-weight: bold; 86 | display: inline-block; 87 | } 88 | .action-item-code { 89 | display: inline-block; 90 | margin: 0 10px; 91 | background-color: rgba(150, 150, 150, 0.2); 92 | border: 1px solid #ddd; 93 | color: #444; 94 | padding: 3px 8px; 95 | border-radius: 3px; 96 | font-size: 13px; 97 | font-family: monospace; 98 | font-weight: normal; 99 | } 100 | .action-item-description { 101 | font-size: 14px; 102 | color: #555; 103 | margin: 8px 0; 104 | } 105 | .action-item-url { 106 | font-size: 14px; 107 | } 108 | .action-item-usage { 109 | margin: 10px 0; 110 | padding: 10px; 111 | background: rgba(235, 235, 235, 0.67); 112 | border: 1px solid #ddd; 113 | border-radius: 5px; 114 | color: #1000a2; 115 | box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.15); 116 | font-family: monospace; 117 | } 118 | /* Icons */ 119 | .action-item-photos div:before { 120 | background-image: url(data:image/png;base64,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); 121 | } 122 | .action-item-gear div:before { 123 | background-image: url(data:image/png;base64,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); 124 | } 125 | .action-item-calendar div:before { 126 | background: url(img/var-sheet.png) 0px / cover no-repeat; 127 | background-position: -23px 0px !important; 128 | } 129 | .action-item-files div:before { 130 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADoAAAA6CAYAAADhu0ooAAAAAXNSR0IArs4c6QAABHRJREFUaAXtm99rFFcUx7/n7uxOsokoTVqNiRpR6INv0taqVdJiSvH3Uwv+AZZiGws+tKVK6Zt9KNgohhRUxEqKIvqgUBpEWhUUW4pCVUrAgiaNNbFKrWZjMrfnrM7uaLLZmdm72dmwA8u9e+ecc8/nnjszd2bOEPJsHU268XEKrdrBCk1YyOKzofECEZJa66o86kZ3E9GQ1ngIwl023EcaPaRwNm6j+4Nb1DtRZ5RrZ/tM/boexacMtVZDx3LJRaGdQKMMf5Ji2Nl2my6M59MY0PaF2nbu4Rserc0MOGb/eEai0sbAmj3+Vs3A1rYeSnn9egZkT6OuG03hBE/JN7xC5VbnKX4uZmPjh7006Pqu3MqRRTrBkMfLHVJ4hGFkCKc6Z+uky5cB7e/H1yywwt1R/qVekkrhK5cjPXV31+tXRjUu8lhkwF2B8i7JiRGWfDRAv6TBHGD71IOUEGn1hA0guU6mHuFPbrTKO3q5vKcRuxrNangI66YupMBrSxgtXhC8mmss/LTPXAws/QyoXwTUvPTM1WqM+t9XNLreGtNc/AZmVE+XdaE6E8j3fgDmvUl5Ib0dKD5I1ncB05u9rcWrC6OcjJrCdrHscz7I1cRRzGV7fith0xmg4bVcEkbbmxQv9aaHNSnTtZAtMY2w8QjfJSwtxEp+XWFUvBiuzi86vkSyPlw0vdYStQz7PdC43NtquE6wFa+GMsskw+Z9m4vXEDbwMTtnpW+VQILMWBuZlVA8SVh/GJjbEojBt3BkQMVjq5qw7js+ixfhEhQp0DRsFWHtIaC51XewfAlGDjQNazPsQWD+O74YfAlFElQ8jyUIaw4AC1b74sgrFFnQNGycsHq/mUtPSUCdEWDgms4bBRFQFqFlpy/RCYUm9dYs+WLWl8P8LENZ/mCzWuFrkxrR2gbC4i1ZZyWyfn9ZrXA12lXnhB7WrQPhloAP/uKn0HeCO1zILd6kTl0XTSJb2+D+C1KGjgkmdeoGQTItWwE1PaKltleJaKkjYLr/SkRNj2ip7VUiWuoImO6/ElHTI1pqe5WIljoCpvuvRNT0iJbaXkERHRkKf38YFLzQvhTn5DwM2qkrf++GWyt++U9P+D6Y8YHiN96Pwpr441hYzeB6148G18loaKTkjff9TEPAyq97gKtdnEjHmYjF2hzOC7q8T+O3jvA9CCPtqtc/sacFvbCbNgeoexnwPs4M79YTTRm7/24Dg9e47C/QGtHPlqSCss2CQP+9CcgvqpswyhvvS1F10JhfzKjIxklOuXCMGY2cIXKsBE6ptl66xb51R84/cw51b+mjm+6CYcfUjGo66XG7jFka9ONBusSVveYGMRqWhEkyOzOgUpk1C9tAdD4aLprwgi7aNj5xLblTF+/+TsOWjQ2Spu3uLNdSGKwqrHm/L7u8zYAKlOSg0wys4sh2SiJ+uYGmfRbfmcGbTy8cOd/7Pf0c5AteobwtCb7RhiaHv8P5kT8H+dL35yDPA7U36iYMY1WUP/CJxXFaLiHP++79/z+rqj3sk2nOmQAAAABJRU5ErkJggg==); 131 | } 132 | .action-item-music div:before { 133 | background-image: url(data:image/png;base64,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); 134 | } 135 | .action-item-sharing div:before { 136 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADoAAAA6CAYAAADhu0ooAAAAAXNSR0IArs4c6QAAA8FJREFUaAXtm8tPE0Ecx38zfdCHD2w9CPSgRhP/AA0a5YaPBEy8ecdgNBg8GKMHjXrTgweJUZGL8aDxJAe4QNQDkkjwakKUBBNK4SAFjJQutTP+Ztit26UrpXRrd7tzmcfOzO/3me/s7Gx3SmCDEHz6oyniCZ/McmghAAcogUbgEMFmISAQ2KB5eS9zSGOHKbSbZBwSHGDSQ2AkmV0eXrm8e+ZfxtD3wiHSlzpax+EmAGmnAJ7CtaqjlAFkAfiAQuB+sjP0qZBX60F7vtU1BWKPsPFFBFx/vVAvVVKGPnP0+flMOn4Vug8qerfyQJoe/4wyn68fp+cJfSW7pXFaf6SZzLmZKzvmNd9xANRw94uf+X1v7Q4paCSD3zfY2JsIaXg50D0N+x5ipkW74IC4mUH9A41DTt2GZ8uHgdAxHIkcuFah3PGxBgoLCoeJJK6ZFgecwgw4a569FP68BkbJrUpAHm+k8PKMH960+eFQJG95sARZMiGb6JyI52Q9DX/HQq8l1tROBeSL034Iqlbm0xzOD65ariyq+nuRLe+lERI8W2lIwR4NkIooK9gkY5bQI5VS8vUEPtfV8GGa/YXdZe00FowIDAc04+WO9dNVQF4fyeRMdAytwnsNth3vWQthBSPF1SiWs25R4pUBUpjJ4DbmggorzVooqmD0Mgo7qUUr/WiCQVu/Al8XChvQYGPbCUwtFa5TjrGXjPikCZajM7M+zCC1+gLWSkhph0EdxZs0t03SjDstRsZtlu+EqmXQStokxDuLf9+O9Yl35Y2DFX3qrbqK6kfDLF2sWmbt9eXF9LUZ1fV9i3TNKOqCGqW3e95V1O4KGv13FTWOiN3zrqJ2V9Dov6uocUTsnq8ZRUt6TduKusVs3rfSv1nbmlHUBTWbAnYtdxW1q3JmfruKmo2MXctdRe2qnJnftaMofvpOmY2CU8qR8RfFX3ZXnAJkykFBoZTBkmkFh1wQjF78PBnHG3V/KUxb+URQir1S20hG/M48WWoHdmknGL0ezsbx1FjHZpz+X++Um/FRX1cw0uRqekAeJdNfcVBasPFsepCudEfjeKJ62EFs+SjIluiKTssNA8+y205UdY2JybOAEnSuKzzOgT/JHwr75wSTONkpSCSoSMzNTl3DZXhUpB0Sxigs3tBY8s5r1cZRc0QXZ9BnlXgrJntRXeuOcmnDXOZY9blXMOjP0wszeYrq7cq/gzByB8tO4YGk3BTX16mWtLqQDimU3yv+7yAG74M987FIINRazX/w4ZnUO/EIMbiel/0D9F46ra4okMoAAAAASUVORK5CYII=); 137 | } 138 | .action-item-text div:before { 139 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADoAAAA6CAYAAADhu0ooAAAAAXNSR0IArs4c6QAAA+NJREFUaAXtm8lPFEEUxr+CYdFBUVBxTTTiwRjFBQIHvRj04hpv6t0rBzV60Cg3jPFgTIj6BxiPHrgoehKNRDRyMEZF0eCCYUkgYECHab/X4wyzEaeaGma65CWV7q6pevV+9aq6urrfKPxDnFdYgynsY7E9TNVMq+GggseFTKVMcykTbOwnFIZ5/MbUw/QYhWhX2/GV5zOKmukX5wUaCHSevx9kKpypXJ7kT9GONnZAi9qFZ+lsSgF13qMEo7jOwqcImvJ7OiV5k6doMXAbi9GkNmEy3q4EEOcNKjGOeyywO76QD887EMRRtRlDUdtjoM5rFGMCD9knMhdtkE5C7FW1nNOUghjRBK5ZBClY9Zx4V6J8rkedl6glZCfTNHi0hJ+PCmHC1qud6IqAhXHBOkhxkDhO2CjKXSdD+MTzgGRYKCGSrRfiQ4SzFVL8FhDGArq3zkIvJiKRUeaoPNbZLtXi0bW2UwqjeLTcelAyCugC60EVSuRuK9utzKXqDLDqMvczwczrmCgZ5g7tezPQ36KvzUGZcro4gnVkWz9QVKVTw1zZELeh3ZWe9EWejHSqDt/VKW227Cza1veomB5Yzu1ADoZuiKPJo3h7IgoNsDlJ/hH9oesftgRL50ETusOCC/05Wsg3nUuO8GZUZg5/8h0wet+cvjSa9EE38t3Zoiy8Vuo9CQzfSWOimSz9OVq8zkzLyVqKsqT3bzv6Hu09DqxoMj90B24koxu99vbAYNSEuVGmP3Tnxi7jrcyDGu/SHCv8bzyqf9etOAFUnc3B7mUc+HHV81qrf9et4a4lsCw3AzE0yI03t4geRH/ojj310IyhKrNoW9+jqohvmer4zsjgs24m/TA1xo/6z/k95XcmpVPK6IOmqPBHhv7Q9QdXipXzoCld4vMM/XVUbkbBesO7l15g8m1Wu1IfdAPf6y49ZtYoh+/QPxwGRtrM6o3Tpj9Hgw1x1Q2dKoZSyJKVRRGPSnhK5t9f+rjpXsmAMpPr6ORHYPBm9jAVxmQd5XMVA6nsliEZuiN2M7p0IwWMw/liPSgZxaMSCmq79IhH+aRsuZBROd0M1gjhMz8H6y81fugfCZMrZkCVqnHnaLsfbPZoY7vair6IFwtwkUM47FFR/laLBD26sYAuqNrhztPW/LXYs2WtEtkptafnZSlO8/qJZ5X5V1HCcs9FzYqBqi34xfBsfg9ER/RHHx8l1PxANPpaOGKgcuHGoJejkfP1FpNeWI4oyLWIzWI7GeLj6cWsWEx9so3u30GAS8zfn/dLT+RG+oC2Nmf8d5AU4Mg628h8+fpbzZR/f/ApxiNZQpJtj7/+Aycd2EjTXPDBAAAAAElFTkSuQmCC); 140 | } 141 | .action-item-web div:before { 142 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADoAAAA6CAYAAADhu0ooAAAAAXNSR0IArs4c6QAABbdJREFUaAXlm01sG1UQx+etP5tKjVQjUhIXKgQcoHAitEXJLRQORUKCcOFGgAMSQgoHEDJgJIPgQBFCqgQ0Ry5ECA6pBC2cWvpBOEHLAQoq5EMJaiI1tImT2Pt4f4uJn9f77N312sTLSKvdfV87v515M2/Xa0FNJFO4OpAQ6YdUs2Eh6Q5Joj8m5G6SoocEpZt0D7daUpGEXC1LsSxIzktBl9UFTm/K4qml3E1zjS4mTJV9hdWDCSFfKUs6YgmKmdpth3JbUjkmaGpTincWcz3n3XSqB33h11Tfzf0fKLjn1FZf7zbKNilTwFJtHy/+Nf8ifXjnuq5WLcjbK5kBGftSNRjSG3Xh8Zk5UX6MXt21xLpbfED5S8ls2fpCnXc7JJCG+qV1gvLzPcy3Bbonvu89aYlhruj2vZp1B/bEe99ljorr7i3cuL8k6IKi3gLnBt28t4nsuKQDM7mdP1TAVJjORQ0SBgIT2HAskCeTYscVVRhHQdREWbW0Idf2xdOUelTBRRISRoMBwWiVhRiMmhWdPGC0sKxzVkTtHIzKsjIbNbB6Hpm1LBK99RXRKgEjgtCO7YB16LYYjQ3G6ZG7ap8fZq5JmvyxRMenS7RSlIFUtUmkxMBbN4L1DnTJ+k670oKOHknWATpbAnJ8aoO++qXsrPJ0rubofyeAnHwq1RQSGqLt8SdS9OR9wTJhsF4h3BuGvKeveq9htfw3m3T2jzLNKpeFO4/eG6uBg/W53o8a1av46dViWzfIS4s2HTxWpM/UfAQk5JwChrti02V8OKGfejruOGi2V9D559OkWxKQo5+uG4MN4LGxOAMWlzfadxx0RT33I5KyNIPkdpM/VYMQPMKvdHyOYh4+PFGsRFooizkZNG34ge04KCvnnHdcbtrv7/NvRX2sjoBiXu5X0fXufyPsuT9tgst6tSRc9enBagBCX7/SVlCkh/GheCVNuCmGAHP09OZWlHVrwxF6r7pZLBNqleRX2haMkAKwGACsSZD8T46ljQsGhtQjNFKOHoFNYzvL22JRJHXnCgZuyi6nwyMCn1Wu7BQ3SPQf+7w2pzr7mc5DB3VCAvD9M5v0yfdVdwPE+FBCWdtyzZ+wILwB7VgA2SjXcjvTPtRFvRsklGNLmpTQy9sBifFDmaO4884FNywZBiTmYyuW5JvYsuu6zaUwIf3mWwZz7luyqB9ItP1aRdj8SLJm7kGhZx+IV+rQhgWWDAsSYwaeo26QpoDh1hZpAoI5qQOiLGxIjBnYdSceT3p+AnnwVov0hI8L6ykG5yxYQGALWwK5LnKkrqjJkqwsXn8cVgv5RokeFkbQaQck9AjkuphrvFpB4MEDM/ZeBG6KvoeUlSE/q/x4UW38sO1ljCBtArkuQ+KCWAx4hWQFYT2eo1zW7n0g19WVurjo3ZLItc4Vjz5WO49bBvWiHEddvAKBNwAWj26dlECguqviLV0jYUjd3VHWq+XMRv3DqgsEqr9ERgR2Pqmwcm6QiNB4leJn/cvjtbIPBOpMAVjMY2OrwS0B7/dtXysgzfoGSi8YFCCA8yrNcq3XcYK2s0jK1SCdeZmmz1fTOHD1MJ5ATOM3K1cfWV0XA4XrV0mITLPGpnrMw2fUr2CjysLOZR4A8X6n0zmzTlcpl8Qthb9/s4R1e11lhApsaf+ugpGYjRCTAUXM4jucy4bayBSD0YpJOR0ZIgMIGK210sYUPiUztOn6YrCt0cYJazmfmRVSnup6IgMA2JZzmZnKysi2xWtRtCqYYiRyuAcV0IXXd05LSccMN6Vri8GELzu3QHGwULrykpqp33UtlUNx9dX5hYXStZe5uPah8H/xqTnQ1TfocwvzIyWbPsKH+Hw3umUPnaE7GPTv6aF/rUU1IvwdxCL7DbUOPqwmcmUua9Xb6rASSKU8aZP1pve/gzgQdueXsslEakQVb9s/+GzI9W+RQhyq15z+A1nLlExSxvoCAAAAAElFTkSuQmCC); 143 | } 144 | .action-item-contacts div:before { 145 | background-image: url(data:image/png;base64,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); 146 | } 147 | .action-item-location div:before { 148 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADoAAAA6CAYAAADhu0ooAAAAAXNSR0IArs4c6QAABJhJREFUaAXdm1+IFVUcx79z3VaxVtfU0lTUhYzEIlvRTaVEEwtN88mCeoggpVBDeomlvAkhpaAhlVQPooLSUxAaqaiQq7v4sOqDhVZqpZZ/SLr90W3d6fu9d7c7d3bn7ty5c+7MnR+cO+fM+c2Z32d+M+f85sy5FvqTZnscbMyHhdnc3k/10Ux3M93FdAdTJeVfnuxPpt+ZLtGms7TpCLf78a71czFDLM/KZruJjaRZP58p5akXj4oumrGfwGkCt/ZlUm/QlfZA1OEDKr/C1Lu+r1bis8+mKZ8gg9XYYt1ymlUI8qY9nGhfUGG2U6kK80d4Nz6L9db1HtvzoGl7MDpwkBUzeiqrfNuGWsxF2vpbHPlnrwMbWU4KpNhm0HEblJHkPNpsT6erj7GcB89WV/1PF7lm8hZuy4HZWJdASHkpRVe+o4yF3Dh5PrtTe5InNinHy6MLmZJ2yzrdpcdzYYr3cJI6ICegM98kTzY49yQyb2OSQMclEq4QapRA6wv3JbJUL9A7E4lWCFUn0NrCffEqjR3K98HyLaypiRdWzppBtOrFqcCqx4Ap9wLXGa02fghcuBHc2liBjq4DXmsClk8HRgzOQw1nvnFMAkAFsZreW/Ywn6MBecCeXMdtoOVCTynYNjKPDmDvsORBAs4EHp9Q3Pjdp4DfNIFShlQcdMhA4OVpwEp6cOIwf5ZvPupPr5hWxUAbOJ2mzuWlRkCwfuXwOaD9kl9tbz3joE9MBF7n7bmYt2lK4XWJsuGbEg/wUDcCqg7lOXYsev4evc/jzD52n74CfHXGh6IPlVBBRzLGWsGh4VW+D43iUFGu6Nm0Na8XgoQC+hAH9VX03guPABrsw5BrDBJ2ngijpVwbgc3S8/b0JD5/s4B5DZyqCPD8FcPY2gb8o3n5kCQQ6NLJwPoFwAMjQrLC1czNTuAjgoYpJYMOHQR8/jxQo9cBQ7KjHbicCbfxks3N3AK+Pgvc1tcOA6LOJ4wAwW1ayR7toiGLtrub8VeewEjo3BvFdfd9D2hYCVtK9mg5BpzXx75+ZFNLPwoBqysK2p+N8qQ8akJiBfo+w72wAgT3xYoNqHrZXSfd5oVXjg2oxk29YJuSWIAqAlIkZFJiAaqYVrGtSYkcVOOyqSHFeeEiB/3yO+Dbq06TzOQjB91sKEBwX65IQTUXpDmhSkikoCaCd6+LJtAOr0qT+xUgaL62QtIp0L8qdLLsaU5czp3trQNmAwQXU0avafp043Mq2XV4gOJT24DJ9wCHfgxwcPBDbgj0FybOvlZG9Gmh3M8LASz9VbfuDwEOrK5DLJzRgiPDUWYsrkmrPLqHiYFYYsVGJ/amulcu70ssJict8J71kzwqf67lr6F5vewZovrRokexdS+N4+pH5rdGZY3B836slZ1qP+dR5TJYw98kdUytXG8jpqzkQbUGvRaLuPd4d101b46T5Rmuvv4/vM2DCittXaPCHA45n7JUjT2xbP4syyAWh3h/A8v9HWQddZ9k8tZzNBZhVoAHaOXb/v8O4ra29x98xlClnmkIUx+LZdwNhFrWPOEfTIrPLxLM9x98/gMUcPpQ6NVISQAAAABJRU5ErkJggg==); 149 | } 150 | -------------------------------------------------------------------------------- /src/SearchActions.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import ReactMarkdown from "react-markdown"; 3 | 4 | import { allActions, getActionFromID } from "scpl"; 5 | 6 | import { keys } from "./Key"; 7 | import { categories } from "./data/ActionCategories"; 8 | 9 | import "./SearchActions.css"; 10 | 11 | const actionsByName = allActions.sort((a, b) => { 12 | if (a.name > b.name) { 13 | return 1; 14 | } 15 | if (a.name < b.name) { 16 | return -1; 17 | } 18 | return 0; 19 | }); 20 | 21 | class ActionData extends Component<{ 22 | onSelect: (text: string) => void; 23 | actionID: string; 24 | canShowDetail: boolean; 25 | }> { 26 | render() { 27 | const action = getActionFromID(this.props.actionID); 28 | if (!action) { 29 | return null; 30 | } 31 | const usage = action 32 | .genDocsUsage() 33 | .replace(/^```\s+(.+?)\s+```$/, "$1"); 34 | return ( 35 |
    { 42 | e.stopPropagation(); 43 | this.props.onSelect(usage); 44 | }} 45 | > 46 |
    47 | {action.name} 48 |
    {action.shortName}
    49 |
    50 |
    51 | {this.props.canShowDetail ? ( 52 | 53 | ) : ( 54 | ( 55 | action._data.Description || { 56 | DescriptionSummary: "No description" 57 | } 58 | ).DescriptionSummary 59 | )} 60 |
    61 |
    {usage}
    62 | 70 | See futher documentation 71 | 72 |
    73 | ); 74 | } 75 | } 76 | 77 | type SearchActionsProps = { insertText: (text: string) => void }; 78 | export class SearchActions extends Component< 79 | SearchActionsProps, 80 | { searchTerm: string | undefined } 81 | > { 82 | input: HTMLInputElement | null; 83 | constructor(props: Readonly) { 84 | super(props); 85 | this.state = { searchTerm: undefined }; 86 | this.input = null; 87 | } 88 | searchChanged(value: string | undefined) { 89 | const searchTerm = value; 90 | this.setState({ searchTerm: searchTerm }); 91 | } 92 | render() { 93 | return ( 94 |
    e.stopPropagation()}> 95 | (this.input = c)} 99 | onKeyDown={e => { 100 | if (keys.closePanel(e)) { 101 | if (this.input) { 102 | this.input.blur(); 103 | } 104 | this.searchChanged(undefined); 105 | } 106 | }} 107 | onKeyUp={e => this.searchChanged(e.currentTarget.value)} 108 | onFocus={e => this.searchChanged(e.currentTarget.value)} 109 | onBlur={e => 110 | setTimeout(() => this.searchChanged(undefined), 300) 111 | } 112 | /> 113 | {this.state.searchTerm !== undefined ? ( 114 |
    this.searchChanged(undefined)} 117 | > 118 | Cancel 119 |
    120 | ) : null} 121 |
    128 |
    this.searchChanged(undefined)} 131 | > 132 | × 133 |
    134 | {actionsByName 135 | .filter( 136 | action => 137 | action.name 138 | .toLowerCase() 139 | .replace(/[^A-Za-z0-9]/g, "") 140 | .indexOf( 141 | ( 142 | this.state.searchTerm || "kjhgvb" 143 | ).replace(/[^A-Za-z0-9]/g, "") 144 | ) > -1 145 | ) 146 | .map((action, i, v) => ( 147 | { 150 | this.props.insertText(`\n${text}`); 151 | this.searchChanged(undefined); 152 | }} 153 | canShowDetail={v.length === 1} 154 | key={action.id} 155 | /> 156 | ))} 157 |
    158 |
    159 | ); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/UploadShortcutModal.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/UploadShortcutModal.css -------------------------------------------------------------------------------- /src/UploadShortcutModal.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { inverse } from "scpl"; 3 | 4 | import { ModalContainer } from "./ModalContainer"; 5 | 6 | import { DownloadStatusSwitcher, UploadStatus } from "./DownloadStatusSwitcher"; 7 | import { downloadShortcut } from "./Uploader"; 8 | 9 | import "./UploadShortcutModal.css"; 10 | 11 | type UploadShortcutProps = { 12 | onCancel: () => void; 13 | onResult: (filecont: string) => void; 14 | }; 15 | 16 | export class UploadShortcutModal extends Component< 17 | UploadShortcutProps, 18 | { uploadStatus: UploadStatus; shortcutID: string } 19 | > { 20 | constructor(props: Readonly) { 21 | super(props); 22 | this.state = { 23 | uploadStatus: { uploadStatus: "None" }, 24 | shortcutID: "" 25 | }; 26 | } 27 | async convertShortcut(data: ArrayBuffer) { 28 | const binaryStr = new Buffer(data); 29 | let inverted: string; 30 | try { 31 | inverted = inverse(binaryStr); 32 | } catch (e) { 33 | this.setState({ 34 | uploadStatus: { 35 | uploadStatus: "Error", 36 | uploadError: `An unhandled error occured while trying to convert shortcut -> scpl. The error is: ${e.toString()}` 37 | } 38 | }); 39 | throw new Error(e); 40 | } 41 | this.props.onResult(inverted); 42 | } 43 | async downloadShortcut() { 44 | if (!this.state.shortcutID) { 45 | this.setState({ 46 | uploadStatus: { 47 | uploadStatus: "Error", 48 | uploadError: "No ID." 49 | } 50 | }); 51 | return; 52 | } 53 | this.setState({ 54 | uploadStatus: { uploadStatus: "Uploading" } 55 | }); 56 | const uploadResult = await downloadShortcut(this.state.shortcutID); 57 | if (uploadResult.result === "error") { 58 | this.setState({ 59 | uploadStatus: { 60 | uploadStatus: "Error", 61 | uploadError: uploadResult.message 62 | } 63 | }); 64 | return; 65 | } 66 | // done 67 | await this.convertShortcut(uploadResult.shortcut); 68 | } 69 | render() { 70 | return ( 71 | this.props.onCancel()}> 72 |
    77 |
    this.props.onCancel()} 81 | > 82 | Cancel 83 |
    84 |

    Import Shortcut

    85 | { 89 | const shortcutID = e.currentTarget.value.match( 90 | /[a-z0-9]{32}/ 91 | ); 92 | if (!shortcutID) { 93 | this.setState({ shortcutID: "" }); 94 | return; 95 | } 96 | this.setState({ shortcutID: shortcutID[0] }); 97 | }} 98 | /> 99 |

    {this.state.shortcutID}

    100 | this.downloadShortcut()} 103 | detailsMsg={ 104 |
    105 | To convert the imported shortcut, ScPL Editor will upload your shortcut 106 | to{" "} 107 | 112 | shortcutsweb.app 113 | 114 | . 115 |
    116 | } 117 | uploadAction="Import" 118 | additionalButtonClasses="large-btn stretch-btn" 119 | /> 120 | 121 |
    122 |
    123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Uploader.ts: -------------------------------------------------------------------------------- 1 | type UploadResult = 2 | | { 3 | success: true; 4 | key: string; 5 | link: string; 6 | expiry: string; 7 | } 8 | | { success: false; error: number; message: string }; 9 | 10 | export function uploadShortcut( 11 | shortcut: Buffer, 12 | filename: string, 13 | onProgress?: (percent: number) => void 14 | ): Promise< 15 | { result: "success"; url: string } | { result: "error"; message: string } 16 | > { 17 | return new Promise(resolve => { 18 | const formdata = new FormData(); 19 | formdata.append( 20 | "file", 21 | new Blob([shortcut], { type: "application/x-octet-stream" }), 22 | filename 23 | ); 24 | 25 | const xhttp = new XMLHttpRequest(); 26 | xhttp.open("POST", "https://file.io", true); 27 | 28 | xhttp.onprogress = e => { 29 | if (e.lengthComputable) { 30 | onProgress && onProgress(e.loaded / e.total); 31 | } else { 32 | onProgress && onProgress(0.1); 33 | } 34 | }; 35 | xhttp.onerror = () => { 36 | resolve({ 37 | result: "error", 38 | message: 39 | "An error occured. This may be because you do not have internet access or because the file.io servers were unreachable." 40 | }); 41 | }; 42 | xhttp.onreadystatechange = () => { 43 | if (xhttp.readyState === 4) { 44 | onProgress && onProgress(1); 45 | // Typical action to be performed when the document is ready: 46 | if (xhttp.response) { 47 | const result: UploadResult = JSON.parse(xhttp.response); 48 | if (!result.success) { 49 | resolve({ 50 | result: "error", 51 | message: `The file could not be uploaded. Error: ${ 52 | result.message 53 | }` 54 | }); 55 | } else { 56 | resolve({ result: "success", url: result.link }); 57 | } 58 | } 59 | } 60 | }; 61 | 62 | xhttp.send(formdata); 63 | }); 64 | } 65 | 66 | export function downloadShortcut( 67 | id: string, 68 | onProgress?: (percent: number) => void 69 | ): Promise< 70 | | { result: "success"; shortcut: ArrayBuffer } 71 | | { result: "error"; message: string } 72 | > { 73 | return new Promise(resolve => { 74 | const xhttp = new XMLHttpRequest(); 75 | xhttp.open( 76 | "GET", 77 | `https://shortcutsweb.app/inspectshortcut?id=${encodeURIComponent( 78 | id 79 | )}`, 80 | true 81 | ); 82 | xhttp.responseType = "arraybuffer"; 83 | 84 | xhttp.onprogress = e => { 85 | if (e.lengthComputable) { 86 | onProgress && onProgress(e.loaded / e.total); 87 | } else { 88 | onProgress && onProgress(0.1); 89 | } 90 | }; 91 | xhttp.onerror = () => { 92 | resolve({ 93 | result: "error", 94 | message: 95 | "An error occured. This may be because you do not have internet access or because the shortcutsweb.app servers are unreachable." 96 | }); 97 | }; 98 | xhttp.onreadystatechange = () => { 99 | if (xhttp.readyState === 4) { 100 | onProgress && onProgress(1); 101 | // Typical action to be performed when the document is ready: 102 | if (xhttp.response) { 103 | const result: ArrayBuffer = xhttp.response; 104 | if (xhttp.status === 200) { 105 | return resolve({ result: "success", shortcut: result }); 106 | } 107 | return resolve({ 108 | result: "error", 109 | message: `There was an error getting your shortcut data. Maybe you entered the wrong URL? Additional information: Error ${ 110 | xhttp.status 111 | }: ${xhttp.statusText}: ${Buffer.from(result).toString( 112 | "utf-8" 113 | )}` 114 | }); 115 | } 116 | } 117 | }; 118 | 119 | xhttp.send(); 120 | }); 121 | } 122 | -------------------------------------------------------------------------------- /src/ace/mode-scpl.ts: -------------------------------------------------------------------------------- 1 | /*eslint-disable func-names*/ 2 | 3 | import ace from "brace"; 4 | 5 | // @ts-ignore 6 | ace.define("ace/mode/scpl_highlight_rules", function( 7 | require: any, 8 | exports: any, 9 | _module: any 10 | ) { 11 | "use strict"; 12 | const oop = require("../lib/oop"); 13 | const TextHighlightRules = require("./text_highlight_rules") 14 | .TextHighlightRules; 15 | /* --------------------- START ----------------------------- */ 16 | const ScplHighlightRules = function() { 17 | // @ts-ignore 18 | this.$rules = { 19 | start: [ 20 | { 21 | token: "punctuation", 22 | regex: '(\\")', 23 | push: "string__1" 24 | }, 25 | { 26 | token: "punctuation", 27 | regex: "(\\')", 28 | push: "string__2" 29 | }, 30 | { 31 | token: "punctuation", 32 | regex: "(^\\s*\\|\\s*)", 33 | push: "barlist__1" 34 | }, 35 | { 36 | token: "variable", 37 | regex: "(([A-Za-z0-9@._]+):([A-Za-z0-9@._]+))" 38 | }, 39 | { 40 | token: "support.type", 41 | regex: "(([A-Za-z0-9@._]+)=)" 42 | }, 43 | { 44 | token: "support.constant", 45 | regex: "(@(([A-Za-z0-9@._]+))?)" 46 | }, 47 | { 48 | token: "keyword", 49 | regex: 50 | "([Ii][Ff]|[Rr][Ee][Pp][Ee][Aa][Tt][Ww][Ii][Tt][Hh][Ee][Aa][Cc][Hh]|[Cc][Hh][Oo][Oo][Ss][Ee][Ff][Rr][Oo][Mm][Mm][Ee][Nn][Uu]|[Rr][Ee][Pp][Ee][Aa][Tt]|[Oo][Tt][Hh][Ee][Rr][Ww][Ii][Ss][Ee]|[Ee][Nn][Dd]|[Ff][Ll][Oo][Ww]|[Ee][Ll][Ss][Ee]|[Cc][Aa][Ss][Ee])" 51 | }, 52 | { 53 | token: "entity.name.function", 54 | regex: "([A-Za-z0-9@._]+)" 55 | }, 56 | { 57 | token: "comment", 58 | regex: "(/\\*)", 59 | push: "multi_line_comment__1" 60 | }, 61 | { 62 | token: "comment", 63 | regex: "(--\\[)", 64 | push: "multi_line_comment__2" 65 | }, 66 | { 67 | token: "comment", 68 | regex: "(--.*)" 69 | }, 70 | { 71 | token: "comment", 72 | regex: "(#.*)" 73 | }, 74 | { 75 | token: "comment", 76 | regex: "(//.*)" 77 | }, 78 | { 79 | token: "punctuation", 80 | regex: "(\\()", 81 | push: "action__1" 82 | }, 83 | { 84 | token: "punctuation", 85 | regex: "(\\[)", 86 | push: "action__2" 87 | }, 88 | { 89 | token: "punctuation", 90 | regex: "({)", 91 | push: "action__3" 92 | }, 93 | { 94 | token: "punctuation", 95 | regex: '(["\\:,;^\\->=])' 96 | }, 97 | { 98 | token: "invalid", 99 | regex: "(\\?\\?.+?\\?\\?)" 100 | }, 101 | { 102 | token: "invalid", 103 | regex: "([^\\s])" 104 | }, 105 | { 106 | defaultToken: "text" 107 | } 108 | ], 109 | action__1: [ 110 | { 111 | token: "punctuation", 112 | regex: "(\\))", 113 | next: "pop" 114 | }, 115 | { 116 | token: "punctuation", 117 | regex: '(\\")', 118 | push: "string__1" 119 | }, 120 | { 121 | token: "punctuation", 122 | regex: "(\\')", 123 | push: "string__2" 124 | }, 125 | { 126 | token: "punctuation", 127 | regex: "(^\\s*\\|\\s*)", 128 | push: "barlist__1" 129 | }, 130 | { 131 | token: "variable", 132 | regex: "(([A-Za-z0-9@._]+):([A-Za-z0-9@._]+))" 133 | }, 134 | { 135 | token: "support.type", 136 | regex: "(([A-Za-z0-9@._]+)=)" 137 | }, 138 | { 139 | token: "support.constant", 140 | regex: "(@(([A-Za-z0-9@._]+))?)" 141 | }, 142 | { 143 | token: "keyword", 144 | regex: 145 | "([Ii][Ff]|[Rr][Ee][Pp][Ee][Aa][Tt][Ww][Ii][Tt][Hh][Ee][Aa][Cc][Hh]|[Cc][Hh][Oo][Oo][Ss][Ee][Ff][Rr][Oo][Mm][Mm][Ee][Nn][Uu]|[Rr][Ee][Pp][Ee][Aa][Tt]|[Oo][Tt][Hh][Ee][Rr][Ww][Ii][Ss][Ee]|[Ee][Nn][Dd]|[Ff][Ll][Oo][Ww]|[Ee][Ll][Ss][Ee]|[Cc][Aa][Ss][Ee])" 146 | }, 147 | { 148 | token: "entity.name.function", 149 | regex: "([A-Za-z0-9@._]+)" 150 | }, 151 | { 152 | token: "comment", 153 | regex: "(/\\*)", 154 | push: "multi_line_comment__1" 155 | }, 156 | { 157 | token: "comment", 158 | regex: "(--\\[)", 159 | push: "multi_line_comment__2" 160 | }, 161 | { 162 | token: "comment", 163 | regex: "(--.*)" 164 | }, 165 | { 166 | token: "comment", 167 | regex: "(#.*)" 168 | }, 169 | { 170 | token: "comment", 171 | regex: "(//.*)" 172 | }, 173 | { 174 | token: "punctuation", 175 | regex: "(\\()", 176 | push: "action__1" 177 | }, 178 | { 179 | token: "punctuation", 180 | regex: "(\\[)", 181 | push: "action__2" 182 | }, 183 | { 184 | token: "punctuation", 185 | regex: "({)", 186 | push: "action__3" 187 | }, 188 | { 189 | token: "punctuation", 190 | regex: '(["\\:,;^\\->=])' 191 | }, 192 | { 193 | token: "invalid", 194 | regex: "(\\?\\?.+?\\?\\?)" 195 | }, 196 | { 197 | token: "invalid", 198 | regex: "([^\\s])" 199 | }, 200 | { 201 | defaultToken: "text" 202 | } 203 | ], 204 | action__2: [ 205 | { 206 | token: "punctuation", 207 | regex: "(\\])", 208 | next: "pop" 209 | }, 210 | { 211 | token: "punctuation", 212 | regex: '(\\")', 213 | push: "string__1" 214 | }, 215 | { 216 | token: "punctuation", 217 | regex: "(\\')", 218 | push: "string__2" 219 | }, 220 | { 221 | token: "punctuation", 222 | regex: "(^\\s*\\|\\s*)", 223 | push: "barlist__1" 224 | }, 225 | { 226 | token: "variable", 227 | regex: "(([A-Za-z0-9@._]+):([A-Za-z0-9@._]+))" 228 | }, 229 | { 230 | token: "support.type", 231 | regex: "(([A-Za-z0-9@._]+)=)" 232 | }, 233 | { 234 | token: "support.constant", 235 | regex: "(@(([A-Za-z0-9@._]+))?)" 236 | }, 237 | { 238 | token: "keyword", 239 | regex: 240 | "([Ii][Ff]|[Rr][Ee][Pp][Ee][Aa][Tt][Ww][Ii][Tt][Hh][Ee][Aa][Cc][Hh]|[Cc][Hh][Oo][Oo][Ss][Ee][Ff][Rr][Oo][Mm][Mm][Ee][Nn][Uu]|[Rr][Ee][Pp][Ee][Aa][Tt]|[Oo][Tt][Hh][Ee][Rr][Ww][Ii][Ss][Ee]|[Ee][Nn][Dd]|[Ff][Ll][Oo][Ww]|[Ee][Ll][Ss][Ee]|[Cc][Aa][Ss][Ee])" 241 | }, 242 | { 243 | token: "entity.name.function", 244 | regex: "([A-Za-z0-9@._]+)" 245 | }, 246 | { 247 | token: "comment", 248 | regex: "(/\\*)", 249 | push: "multi_line_comment__1" 250 | }, 251 | { 252 | token: "comment", 253 | regex: "(--\\[)", 254 | push: "multi_line_comment__2" 255 | }, 256 | { 257 | token: "comment", 258 | regex: "(--.*)" 259 | }, 260 | { 261 | token: "comment", 262 | regex: "(#.*)" 263 | }, 264 | { 265 | token: "comment", 266 | regex: "(//.*)" 267 | }, 268 | { 269 | token: "punctuation", 270 | regex: "(\\()", 271 | push: "action__1" 272 | }, 273 | { 274 | token: "punctuation", 275 | regex: "(\\[)", 276 | push: "action__2" 277 | }, 278 | { 279 | token: "punctuation", 280 | regex: "({)", 281 | push: "action__3" 282 | }, 283 | { 284 | token: "punctuation", 285 | regex: '(["\\:,;^\\->=])' 286 | }, 287 | { 288 | token: "invalid", 289 | regex: "(\\?\\?.+?\\?\\?)" 290 | }, 291 | { 292 | token: "invalid", 293 | regex: "([^\\s])" 294 | }, 295 | { 296 | defaultToken: "text" 297 | } 298 | ], 299 | action__3: [ 300 | { 301 | token: "punctuation", 302 | regex: "(})", 303 | next: "pop" 304 | }, 305 | { 306 | token: "punctuation", 307 | regex: '(\\")', 308 | push: "string__1" 309 | }, 310 | { 311 | token: "punctuation", 312 | regex: "(\\')", 313 | push: "string__2" 314 | }, 315 | { 316 | token: "punctuation", 317 | regex: "(^\\s*\\|\\s*)", 318 | push: "barlist__1" 319 | }, 320 | { 321 | token: "variable", 322 | regex: "(([A-Za-z0-9@._]+):([A-Za-z0-9@._]+))" 323 | }, 324 | { 325 | token: "support.type", 326 | regex: "(([A-Za-z0-9@._]+)=)" 327 | }, 328 | { 329 | token: "support.constant", 330 | regex: "(@(([A-Za-z0-9@._]+))?)" 331 | }, 332 | { 333 | token: "keyword", 334 | regex: 335 | "([Ii][Ff]|[Rr][Ee][Pp][Ee][Aa][Tt][Ww][Ii][Tt][Hh][Ee][Aa][Cc][Hh]|[Cc][Hh][Oo][Oo][Ss][Ee][Ff][Rr][Oo][Mm][Mm][Ee][Nn][Uu]|[Rr][Ee][Pp][Ee][Aa][Tt]|[Oo][Tt][Hh][Ee][Rr][Ww][Ii][Ss][Ee]|[Ee][Nn][Dd]|[Ff][Ll][Oo][Ww]|[Ee][Ll][Ss][Ee]|[Cc][Aa][Ss][Ee])" 336 | }, 337 | { 338 | token: "entity.name.function", 339 | regex: "([A-Za-z0-9@._]+)" 340 | }, 341 | { 342 | token: "comment", 343 | regex: "(/\\*)", 344 | push: "multi_line_comment__1" 345 | }, 346 | { 347 | token: "comment", 348 | regex: "(--\\[)", 349 | push: "multi_line_comment__2" 350 | }, 351 | { 352 | token: "comment", 353 | regex: "(--.*)" 354 | }, 355 | { 356 | token: "comment", 357 | regex: "(#.*)" 358 | }, 359 | { 360 | token: "comment", 361 | regex: "(//.*)" 362 | }, 363 | { 364 | token: "punctuation", 365 | regex: "(\\()", 366 | push: "action__1" 367 | }, 368 | { 369 | token: "punctuation", 370 | regex: "(\\[)", 371 | push: "action__2" 372 | }, 373 | { 374 | token: "punctuation", 375 | regex: "({)", 376 | push: "action__3" 377 | }, 378 | { 379 | token: "punctuation", 380 | regex: '(["\\:,;^\\->=])' 381 | }, 382 | { 383 | token: "invalid", 384 | regex: "(\\?\\?.+?\\?\\?)" 385 | }, 386 | { 387 | token: "invalid", 388 | regex: "([^\\s])" 389 | }, 390 | { 391 | defaultToken: "text" 392 | } 393 | ], 394 | barlist__1: [ 395 | { 396 | token: "punctuation", 397 | regex: "($)", 398 | next: "pop" 399 | }, 400 | { 401 | token: "escape", 402 | regex: "(\\\\[\"'\\\\n])" 403 | }, 404 | { 405 | token: "escape", 406 | regex: "(\\\\\\()", 407 | push: "escape__1" 408 | }, 409 | { 410 | token: "support.constant", 411 | regex: "([^\\\\]+)" 412 | }, 413 | { 414 | defaultToken: "text" 415 | } 416 | ], 417 | escape__1: [ 418 | { 419 | token: "escape", 420 | regex: "(\\))", 421 | next: "pop" 422 | }, 423 | { 424 | token: "punctuation", 425 | regex: '(\\")', 426 | push: "string__1" 427 | }, 428 | { 429 | token: "punctuation", 430 | regex: "(\\')", 431 | push: "string__2" 432 | }, 433 | { 434 | token: "punctuation", 435 | regex: "(^\\s*\\|\\s*)", 436 | push: "barlist__1" 437 | }, 438 | { 439 | token: "variable", 440 | regex: "(([A-Za-z0-9@._]+):([A-Za-z0-9@._]+))" 441 | }, 442 | { 443 | token: "support.type", 444 | regex: "(([A-Za-z0-9@._]+)=)" 445 | }, 446 | { 447 | token: "support.constant", 448 | regex: "(@(([A-Za-z0-9@._]+))?)" 449 | }, 450 | { 451 | token: "keyword", 452 | regex: 453 | "([Ii][Ff]|[Rr][Ee][Pp][Ee][Aa][Tt][Ww][Ii][Tt][Hh][Ee][Aa][Cc][Hh]|[Cc][Hh][Oo][Oo][Ss][Ee][Ff][Rr][Oo][Mm][Mm][Ee][Nn][Uu]|[Rr][Ee][Pp][Ee][Aa][Tt]|[Oo][Tt][Hh][Ee][Rr][Ww][Ii][Ss][Ee]|[Ee][Nn][Dd]|[Ff][Ll][Oo][Ww]|[Ee][Ll][Ss][Ee]|[Cc][Aa][Ss][Ee])" 454 | }, 455 | { 456 | token: "entity.name.function", 457 | regex: "([A-Za-z0-9@._]+)" 458 | }, 459 | { 460 | token: "comment", 461 | regex: "(/\\*)", 462 | push: "multi_line_comment__1" 463 | }, 464 | { 465 | token: "comment", 466 | regex: "(--\\[)", 467 | push: "multi_line_comment__2" 468 | }, 469 | { 470 | token: "comment", 471 | regex: "(--.*)" 472 | }, 473 | { 474 | token: "comment", 475 | regex: "(#.*)" 476 | }, 477 | { 478 | token: "comment", 479 | regex: "(//.*)" 480 | }, 481 | { 482 | token: "punctuation", 483 | regex: "(\\()", 484 | push: "action__1" 485 | }, 486 | { 487 | token: "punctuation", 488 | regex: "(\\[)", 489 | push: "action__2" 490 | }, 491 | { 492 | token: "punctuation", 493 | regex: "({)", 494 | push: "action__3" 495 | }, 496 | { 497 | token: "punctuation", 498 | regex: '(["\\:,;^\\->=])' 499 | }, 500 | { 501 | token: "invalid", 502 | regex: "(\\?\\?.+?\\?\\?)" 503 | }, 504 | { 505 | token: "invalid", 506 | regex: "([^\\s])" 507 | }, 508 | { 509 | defaultToken: "text" 510 | } 511 | ], 512 | multi_line_comment__1: [ 513 | { 514 | token: "comment", 515 | regex: "(\\*/)", 516 | next: "pop" 517 | }, 518 | { 519 | defaultToken: "comment" 520 | } 521 | ], 522 | multi_line_comment__2: [ 523 | { 524 | token: "comment", 525 | regex: "(--\\])", 526 | next: "pop" 527 | }, 528 | { 529 | defaultToken: "comment" 530 | } 531 | ], 532 | string__1: [ 533 | { 534 | token: "punctuation", 535 | regex: '(\\")', 536 | next: "pop" 537 | }, 538 | { 539 | token: "escape", 540 | regex: "(\\\\[\"'\\\\n])" 541 | }, 542 | { 543 | token: "escape", 544 | regex: "(\\\\\\()", 545 | push: "escape__1" 546 | }, 547 | { 548 | token: "support.constant", 549 | regex: '([^"\\\\]+)' 550 | }, 551 | { 552 | defaultToken: "text" 553 | } 554 | ], 555 | string__2: [ 556 | { 557 | token: "punctuation", 558 | regex: "(\\')", 559 | next: "pop" 560 | }, 561 | { 562 | token: "escape", 563 | regex: "(\\\\[\"'\\\\n])" 564 | }, 565 | { 566 | token: "escape", 567 | regex: "(\\\\\\()", 568 | push: "escape__1" 569 | }, 570 | { 571 | token: "support.constant", 572 | regex: "([^'\\\\]+)" 573 | }, 574 | { 575 | defaultToken: "text" 576 | } 577 | ] 578 | }; 579 | // @ts-ignore 580 | this.normalizeRules(); 581 | }; 582 | /* ------------------------ END ------------------------------ */ 583 | oop.inherits(ScplHighlightRules, TextHighlightRules); 584 | exports.ScplHighlightRules = ScplHighlightRules; 585 | }); 586 | 587 | // @ts-ignore 588 | ace.define("ace/mode/scpl", ["ace/mode/scpl_highlight_rules"], function( 589 | // @ts-ignore 590 | require, 591 | // @ts-ignore 592 | exports, 593 | // @ts-ignore 594 | _module 595 | ) { 596 | const oop = require("ace/lib/oop"); 597 | const TextMode = require("ace/mode/text").Mode; 598 | const ExampleHighlightRules = require("ace/mode/scpl_highlight_rules") 599 | .ScplHighlightRules; 600 | 601 | const Mode = function() { 602 | // @ts-ignore 603 | this.HighlightRules = ExampleHighlightRules; 604 | }; 605 | oop.inherits(Mode, TextMode); 606 | 607 | (function() { 608 | // Extra logic goes here. (see below) 609 | }.call(Mode.prototype)); 610 | 611 | exports.Mode = Mode; 612 | }); 613 | // (function() { 614 | // // @ts-ignore 615 | // ace.require(["ace/mode/scpl"], function(m) { 616 | // if (typeof module === "object" && typeof exports === "object" && module) { 617 | // module.exports = m; 618 | // } 619 | // }); 620 | // }()); 621 | -------------------------------------------------------------------------------- /src/ace/scpl.iro: -------------------------------------------------------------------------------- 1 | ################################################################# 2 | ## Iro 3 | ################################################################ 4 | ## 5 | ## * Press Ctrl + '+'/'-' To Zoom in 6 | ## * Press Ctrl + S to save and recalculate... 7 | ## * Documents are saved to web storage. 8 | ## * Only one save slot supported. 9 | ## * Matches cannot span lines. 10 | ## * Unicode chars must be defined in \u0000 to \uffff format. 11 | ## * All matches must be contained by a single group ( ... ) 12 | ## * Look behinds not permitted, (?<= or (?=]) 181 | styles [] = .punctuation; 182 | } 183 | : pattern { 184 | regex \= (\?\?.+?\?\?) 185 | styles [] = .illegal; 186 | } 187 | : pattern { 188 | regex \= ([^\s]) 189 | styles [] = .illegal; 190 | } 191 | } 192 | string : context { 193 | : inline_push { 194 | regex \= (\") 195 | styles [] = .punctuation; 196 | : pop { 197 | regex \= (\") 198 | styles [] = .punctuation; 199 | } 200 | : include "escape"; 201 | : pattern{ 202 | regex \= ([^"\\]+) 203 | styles [] = .text; 204 | } 205 | } 206 | : inline_push { 207 | regex \= (\') 208 | styles [] = .punctuation; 209 | : pop { 210 | regex \= (\') 211 | styles [] = .punctuation; 212 | } 213 | : include "escape"; 214 | : pattern{ 215 | regex \= ([^'\\]+) 216 | styles [] = .text; 217 | } 218 | } 219 | } 220 | barlist : context { 221 | : inline_push { 222 | regex \= (^\s*\|\s*) 223 | styles [] = .punctuation; 224 | : pop { 225 | regex \= ($) 226 | styles [] = .punctuation; 227 | } 228 | : include "escape"; 229 | : pattern{ 230 | regex \= ([^\\]+) 231 | styles [] = .text; 232 | } 233 | } 234 | } 235 | escape : context { 236 | : pattern { 237 | regex \= (\\["'\\n]) 238 | styles [] = .escape; 239 | } 240 | : inline_push { 241 | regex \= (\\\() 242 | styles [] = .escape; 243 | : pop { 244 | regex \= (\)) 245 | styles [] = .escape; 246 | } 247 | : include "action"; 248 | } 249 | } 250 | 251 | 252 | multi_line_comment : context { 253 | description = multiline 254 | : inline_push { 255 | regex \= (/\*) 256 | styles [] = .comment; 257 | default_style = .comment 258 | : pop { 259 | regex \= (\*/) 260 | styles [] = .comment; 261 | } 262 | } 263 | : inline_push { 264 | regex \= (--\[) 265 | styles [] = .comment; 266 | default_style = .comment 267 | : pop { 268 | regex \= (--\]) 269 | styles [] = .comment; 270 | } 271 | } 272 | } 273 | 274 | inline_comment : context { 275 | : pattern { 276 | regex \= (--.*) 277 | styles [] = .comment; 278 | } 279 | : pattern { 280 | regex \= (#.*) 281 | styles [] = .comment; 282 | } 283 | : pattern { 284 | regex \= (//.*) 285 | styles [] = .comment; 286 | } 287 | } 288 | 289 | } 290 | -------------------------------------------------------------------------------- /src/data/ActionCategories.ts: -------------------------------------------------------------------------------- 1 | export const categories: {[key: string]: string | undefined} = { 2 | "Photos & Video": "action-item-photos", 3 | "Scripting": "action-item-gear", 4 | "Calendar": "action-item-calendar", 5 | "Documents": "action-item-files", 6 | "Music": "action-item-music", 7 | "Sharing": "action-item-sharing", 8 | "Text": "action-item-text", 9 | "Web": "action-item-web", 10 | "Contacts": "action-item-contacts", 11 | "Location": "action-item-location" 12 | }; 13 | -------------------------------------------------------------------------------- /src/data/ShortcutMeta.ts: -------------------------------------------------------------------------------- 1 | export type GlyphName = 2 | | "car" 3 | | "amb" 4 | | "house" 5 | | "cart" 6 | | "forkknife" 7 | | "sun" 8 | | "cloud" 9 | | "tree" 10 | | "footprints" 11 | | "compass" 12 | | "photo" 13 | | "bus" 14 | | "plane" 15 | | "hospital" 16 | | "purse" 17 | | "gaspump" 18 | | "moon" 19 | | "rain" 20 | | "flower" 21 | | "signs" 22 | | "earth" 23 | | "film" 24 | | "motorcycle" 25 | | "boat" 26 | | "city" 27 | | "stand" 28 | | "temp" 29 | | "snow" 30 | | "umbrella" 31 | | "fire" 32 | | "binoculars" 33 | | "mountain" 34 | | "filmfull" 35 | | "camera" 36 | | "videomarker" 37 | | "calendar" 38 | | "comment" 39 | | "paperairplane" 40 | | "creditcard" 41 | | "smartphone" 42 | | "emptykeyboard" 43 | | "printer" 44 | | "database" 45 | | "cube" 46 | | "videocamera" 47 | | "playbutton-one" 48 | | "message" 49 | | "letter" 50 | | "suitcase" 51 | | "watch" 52 | | "laptop" 53 | | "calculator" 54 | | "harddrive" 55 | | "servers" 56 | | "television" 57 | | "microphone" 58 | | "clipboard" 59 | | "messages" 60 | | "openletter" 61 | | "folder" 62 | | "phone" 63 | | "keyboard" 64 | | "stats" 65 | | "serverset" 66 | | "inbox" 67 | | "controller" 68 | | "puzzle" 69 | | "speaker" 70 | | "bookmark" 71 | | "mask" 72 | | "dice" 73 | | "soccer" 74 | | "lifesaver" 75 | | "chess" 76 | | "stopwatch" 77 | | "platter" 78 | | "trophy" 79 | | "headphones" 80 | | "books" 81 | | "emptyglasses" 82 | | "ticket" 83 | | "baseball" 84 | | "tennisball" 85 | | "telescope" 86 | | "clock" 87 | | "volume" 88 | | "heart" 89 | | "lightbulb" 90 | | "musicnote" 91 | | "book" 92 | | "glasses" 93 | | "masks" 94 | | "basketball" 95 | | "football" 96 | | "microscope" 97 | | "alarmclock" 98 | | "bell" 99 | | "star" 100 | | "lightning" 101 | | "flag" 102 | | "hourglass" 103 | | "battery" 104 | | "paintbrush" 105 | | "scissors" 106 | | "colorpicker" 107 | | "hammerwrench" 108 | | "screwdriver" 109 | | "trashcan" 110 | | "soupbowl" 111 | | "fish" 112 | | "tag" 113 | | "locked" 114 | | "magicwand" 115 | | "pencil" 116 | | "magnify" 117 | | "tool" 118 | | "gears" 119 | | "hand" 120 | | "teardrop" 121 | | "apple" 122 | | "cake" 123 | | "key" 124 | | "unlocked" 125 | | "magicstar" 126 | | "paperclip" 127 | | "link" 128 | | "wrench" 129 | | "hammer" 130 | | "privacy" 131 | | "cup" 132 | | "carrot" 133 | | "bottle" 134 | | "wineglass" 135 | | "oven" 136 | | "showerhead" 137 | | "pillbottle" 138 | | "scope" 139 | | "beaker" 140 | | "pawprint" 141 | | "gift" 142 | | "stairs" 143 | | "hanger" 144 | | "shirt" 145 | | "pill" 146 | | "bandaid" 147 | | "needle" 148 | | "cat" 149 | | "like" 150 | | "alien" 151 | | "rocket" 152 | | "laundry" 153 | | "bath" 154 | | "pills" 155 | | "inhaler" 156 | | "atom" 157 | | "dog" 158 | | "cap" 159 | | "bed" 160 | | "girlbaby" 161 | | "mansymbol" 162 | | "user" 163 | | "accessibility" 164 | | "dance" 165 | | "snowboard" 166 | | "activity" 167 | | "boybaby" 168 | | "womansymbol" 169 | | "users" 170 | | "podium" 171 | | "gym" 172 | | "swim" 173 | | "sprint" 174 | | "person" 175 | | "handicap" 176 | | "group" 177 | | "handraised" 178 | | "hike" 179 | | "hiking" 180 | | "cane" 181 | | "alert" 182 | | "bookmarkthis" 183 | | "stopfilled" 184 | | "left" 185 | | "up" 186 | | "play" 187 | | "stop" 188 | | "checked" 189 | | "moneysign" 190 | | "yensign" 191 | | "info" 192 | | "shareleft" 193 | | "barcode" 194 | | "frame" 195 | | "right" 196 | | "down" 197 | | "prev" 198 | | "next" 199 | | "plus" 200 | | "eurosign" 201 | | "bitcoinsign" 202 | | "smile" 203 | | "shareright" 204 | | "qrcode" 205 | | "sizes" 206 | | "download" 207 | | "upload" 208 | | "power" 209 | | "help" 210 | | "xfilled" 211 | | "pounds" 212 | | "pi" 213 | | "cssfile" 214 | | "money" 215 | | "yen" 216 | | "filefilled" 217 | | "list" 218 | | "more" 219 | | "share" 220 | | "spinner" 221 | | "target" 222 | | "location" 223 | | "crop" 224 | | "move" 225 | | "euro" 226 | | "bitcoin" 227 | | "file" 228 | | "document" 229 | | "listitems" 230 | | "infinite" 231 | | "loading" 232 | | "podcasts" 233 | | "mapmarker" 234 | | "exit" 235 | | "repeat" 236 | | "poundsign" 237 | | "asterisk" 238 | | "filedoc" 239 | | "fourgrid" 240 | | "sixgrid" 241 | | "recycle" 242 | | "playvideo" 243 | | "bigtarget" 244 | | "squarep" 245 | | "resize" 246 | | "sync" 247 | | "playsolo" 248 | | "rss" 249 | | "quotes" 250 | | "text" 251 | | "shuffle" 252 | | "signal" 253 | | "peace" 254 | | "cloudservice" 255 | | "settings" 256 | | "wifi" 257 | | "nuclear"; 258 | 259 | export const objectGlyphs: GlyphName[] = [ 260 | "car", 261 | "amb", 262 | "house", 263 | "cart", 264 | "forkknife", 265 | "sun", 266 | "cloud", 267 | "tree", 268 | "footprints", 269 | "compass", 270 | "photo", 271 | "bus", 272 | "plane", 273 | "hospital", 274 | "purse", 275 | "gaspump", 276 | "moon", 277 | "rain", 278 | "flower", 279 | "signs", 280 | "earth", 281 | "film", 282 | "motorcycle", 283 | "boat", 284 | "city", 285 | "stand", 286 | "temp", 287 | "snow", 288 | "umbrella", 289 | "fire", 290 | "binoculars", 291 | "mountain", 292 | "filmfull", 293 | "camera", 294 | "videomarker", 295 | "calendar", 296 | "comment", 297 | "paperairplane", 298 | "creditcard", 299 | "smartphone", 300 | "emptykeyboard", 301 | "printer", 302 | "database", 303 | "cube", 304 | "videocamera", 305 | "playbutton-one", 306 | "message", 307 | "letter", 308 | "suitcase", 309 | "watch", 310 | "laptop", 311 | "calculator", 312 | "harddrive", 313 | "servers", 314 | "television", 315 | "microphone", 316 | "clipboard", 317 | "messages", 318 | "openletter", 319 | "folder", 320 | "phone", 321 | "keyboard", 322 | "stats", 323 | "serverset", 324 | "inbox", 325 | "controller", 326 | "puzzle", 327 | "speaker", 328 | "bookmark", 329 | "mask", 330 | "dice", 331 | "soccer", 332 | "lifesaver", 333 | "chess", 334 | "stopwatch", 335 | "platter", 336 | "trophy", 337 | "headphones", 338 | "books", 339 | "emptyglasses", 340 | "ticket", 341 | "baseball", 342 | "tennisball", 343 | "telescope", 344 | "clock", 345 | "volume", 346 | "heart", 347 | "lightbulb", 348 | "musicnote", 349 | "book", 350 | "glasses", 351 | "masks", 352 | "basketball", 353 | "football", 354 | "microscope", 355 | "alarmclock", 356 | "bell", 357 | "star", 358 | "lightning", 359 | "flag", 360 | "hourglass", 361 | "battery", 362 | "paintbrush", 363 | "scissors", 364 | "colorpicker", 365 | "hammerwrench", 366 | "screwdriver", 367 | "trashcan", 368 | "soupbowl", 369 | "fish", 370 | "tag", 371 | "locked", 372 | "magicwand", 373 | "pencil", 374 | "magnify", 375 | "tool", 376 | "gears", 377 | "hand", 378 | "teardrop", 379 | "apple", 380 | "cake", 381 | "key", 382 | "unlocked", 383 | "magicstar", 384 | "paperclip", 385 | "link", 386 | "wrench", 387 | "hammer", 388 | "privacy", 389 | "cup", 390 | "carrot", 391 | "bottle", 392 | "wineglass", 393 | "oven", 394 | "showerhead", 395 | "pillbottle", 396 | "scope", 397 | "beaker", 398 | "pawprint", 399 | "gift", 400 | "stairs", 401 | "hanger", 402 | "shirt", 403 | "pill", 404 | "bandaid", 405 | "needle", 406 | "cat", 407 | "like", 408 | "alien", 409 | "rocket", 410 | "laundry", 411 | "bath", 412 | "pills", 413 | "inhaler", 414 | "atom", 415 | "dog", 416 | "cap", 417 | "bed" 418 | ]; 419 | 420 | export const peopleGlyphs: GlyphName[] = [ 421 | "girlbaby", 422 | "mansymbol", 423 | "user", 424 | "accessibility", 425 | "dance", 426 | "snowboard", 427 | "activity", 428 | "boybaby", 429 | "womansymbol", 430 | "users", 431 | "podium", 432 | "gym", 433 | "swim", 434 | "sprint", 435 | "person", 436 | "handicap", 437 | "group", 438 | "handraised", 439 | "hike", 440 | "hiking", 441 | "cane" 442 | ]; 443 | 444 | export const symbolGlyphs: GlyphName[] = [ 445 | "alert", 446 | "bookmarkthis", 447 | "stopfilled", 448 | "left", 449 | "up", 450 | "play", 451 | "stop", 452 | "checked", 453 | "moneysign", 454 | "yensign", 455 | "info", 456 | "shareleft", 457 | "barcode", 458 | "frame", 459 | "right", 460 | "down", 461 | "prev", 462 | "next", 463 | "plus", 464 | "eurosign", 465 | "bitcoinsign", 466 | "smile", 467 | "shareright", 468 | "qrcode", 469 | "sizes", 470 | "download", 471 | "upload", 472 | "power", 473 | "help", 474 | "xfilled", 475 | "pounds", 476 | "pi", 477 | "cssfile", 478 | "money", 479 | "yen", 480 | "filefilled", 481 | "list", 482 | "more", 483 | "share", 484 | "spinner", 485 | "target", 486 | "location", 487 | "crop", 488 | "move", 489 | "euro", 490 | "bitcoin", 491 | "file", 492 | "document", 493 | "listitems", 494 | "infinite", 495 | "loading", 496 | "podcasts", 497 | "mapmarker", 498 | "exit", 499 | "repeat", 500 | "poundsign", 501 | "asterisk", 502 | "filedoc", 503 | "fourgrid", 504 | "sixgrid", 505 | "recycle", 506 | "playvideo", 507 | "bigtarget", 508 | "squarep", 509 | "resize", 510 | "sync", 511 | "playsolo", 512 | "rss", 513 | "quotes", 514 | "text", 515 | "shuffle", 516 | "signal", 517 | "peace", 518 | "cloudservice", 519 | "settings", 520 | "wifi", 521 | "nuclear" 522 | ]; 523 | 524 | export type ColorName = 525 | | "red" 526 | | "darkorange" 527 | | "orange" 528 | | "yellow" 529 | | "green" 530 | | "seagreen" 531 | | "lightblue" 532 | | "blue" 533 | | "darkblue" 534 | | "darkpurple" 535 | | "purple" 536 | | "pink" 537 | | "black" 538 | | "brown" 539 | | "grey"; 540 | 541 | export const colors: ColorName[] = [ 542 | "red", 543 | "darkorange", 544 | "orange", 545 | "yellow", 546 | "green", 547 | "seagreen", 548 | "lightblue", 549 | "blue", 550 | "darkblue", 551 | "darkpurple", 552 | "purple", 553 | "pink", 554 | "black", 555 | "brown", 556 | "grey" 557 | ]; 558 | -------------------------------------------------------------------------------- /src/img/back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 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 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/img/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/img/bg.jpg -------------------------------------------------------------------------------- /src/img/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/img/delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/img/delete_w.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/img/down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 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/img/edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/img/edit_w.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/img/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /src/img/error_grey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /src/img/error_w.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /src/img/error_x.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /src/img/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/img/file.png -------------------------------------------------------------------------------- /src/img/file_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/img/file_active.png -------------------------------------------------------------------------------- /src/img/file_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/img/file_error.png -------------------------------------------------------------------------------- /src/img/file_warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/img/file_warning.png -------------------------------------------------------------------------------- /src/img/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/img/folder.png -------------------------------------------------------------------------------- /src/img/folder_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/img/folder_error.png -------------------------------------------------------------------------------- /src/img/folder_warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/img/folder_warning.png -------------------------------------------------------------------------------- /src/img/glyph_sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/img/glyph_sheet.png -------------------------------------------------------------------------------- /src/img/glyph_sheet_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/img/glyph_sheet_white.png -------------------------------------------------------------------------------- /src/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/img/icon.png -------------------------------------------------------------------------------- /src/img/import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/img/import.png -------------------------------------------------------------------------------- /src/img/magic.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 22 | 31 | 55 | 72 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/img/magic_w.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 22 | 31 | 55 | 72 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/img/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/img/new_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/img/new_file.png -------------------------------------------------------------------------------- /src/img/new_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/img/new_folder.png -------------------------------------------------------------------------------- /src/img/right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 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/img/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/img/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/img/settings_w.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/img/shortcut-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/img/shortcut-file.png -------------------------------------------------------------------------------- /src/img/shortcuts_grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/img/shortcuts_grey.png -------------------------------------------------------------------------------- /src/img/success.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/img/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/img/upload.png -------------------------------------------------------------------------------- /src/img/var-sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfgithub/scpl-editor/9e3d5e13a65d723362e1f76c300ea4b4bf621617/src/img/var-sheet.png -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import * as serviceWorker from "./serviceWorker"; 6 | 7 | ReactDOM.render(, document.getElementById("root")); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.register(); 13 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === "localhost" || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === "[::1]" || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | type Config = { 24 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 25 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 26 | }; 27 | 28 | export function register(config?: Config) { 29 | if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { 30 | // The URL constructor is available in all browsers that support SW. 31 | const publicUrl = new URL( 32 | (process as { env: { [key: string]: string } }).env.PUBLIC_URL, 33 | window.location.href 34 | ); 35 | if (publicUrl.origin !== window.location.origin) { 36 | // Our service worker won't work if PUBLIC_URL is on a different origin 37 | // from what our page is served on. This might happen if a CDN is used to 38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 39 | return; 40 | } 41 | 42 | window.addEventListener("load", () => { 43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 44 | 45 | if (isLocalhost) { 46 | // This is running on localhost. Let's check if a service worker still exists or not. 47 | checkValidServiceWorker(swUrl, config); 48 | 49 | // Add some additional logging to localhost, pointing developers to the 50 | // service worker/PWA documentation. 51 | navigator.serviceWorker.ready.then(() => { 52 | console.log( 53 | "This web app is being served cache-first by a service " + 54 | "worker. To learn more, visit https://bit.ly/CRA-PWA" 55 | ); 56 | }); 57 | } else { 58 | // Is not localhost. Just register service worker 59 | registerValidSW(swUrl, config); 60 | } 61 | }); 62 | } 63 | } 64 | 65 | function registerValidSW(swUrl: string, config?: Config) { 66 | navigator.serviceWorker 67 | .register(swUrl) 68 | .then(registration => { 69 | registration.onupdatefound = () => { 70 | const installingWorker = registration.installing; 71 | if (installingWorker == null) { 72 | return; 73 | } 74 | installingWorker.onstatechange = () => { 75 | if (installingWorker.state === "installed") { 76 | if (navigator.serviceWorker.controller) { 77 | // At this point, the updated precached content has been fetched, 78 | // but the previous service worker will still serve the older 79 | // content until all client tabs are closed. 80 | console.log( 81 | "New content is available and will be used when all " + 82 | "tabs for this page are closed. See https://bit.ly/CRA-PWA." 83 | ); 84 | 85 | // Execute callback 86 | if (config && config.onUpdate) { 87 | config.onUpdate(registration); 88 | } 89 | } else { 90 | // At this point, everything has been precached. 91 | // It's the perfect time to display a 92 | // "Content is cached for offline use." message. 93 | console.log("Content is cached for offline use."); 94 | 95 | // Execute callback 96 | if (config && config.onSuccess) { 97 | config.onSuccess(registration); 98 | } 99 | } 100 | } 101 | }; 102 | }; 103 | }) 104 | .catch(error => { 105 | console.error("Error during service worker registration:", error); 106 | }); 107 | } 108 | 109 | function checkValidServiceWorker(swUrl: string, config?: Config) { 110 | // Check if the service worker can be found. If it can't reload the page. 111 | fetch(swUrl) 112 | .then(response => { 113 | // Ensure service worker exists, and that we really are getting a JS file. 114 | const contentType = response.headers.get("content-type"); 115 | if ( 116 | response.status === 404 || 117 | (contentType != null && 118 | contentType.indexOf("javascript") === -1) 119 | ) { 120 | // No service worker found. Probably a different app. Reload the page. 121 | navigator.serviceWorker.ready.then(registration => { 122 | registration.unregister().then(() => { 123 | window.location.reload(); 124 | }); 125 | }); 126 | } else { 127 | // Service worker found. Proceed as normal. 128 | registerValidSW(swUrl, config); 129 | } 130 | }) 131 | .catch(() => { 132 | console.log( 133 | "No internet connection found. App is running in offline mode." 134 | ); 135 | }); 136 | } 137 | 138 | export function unregister() { 139 | if ("serviceWorker" in navigator) { 140 | navigator.serviceWorker.ready.then(registration => { 141 | registration.unregister(); 142 | }); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/testshortcut.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "WFWorkflowClientVersion": "754", 4 | "WFWorkflowClientRelese": "2.1.2", 5 | "WFWorkflowMinimumClientVersion": 411, 6 | "WFWorkflowIcon": { 7 | "WFWorkflowIconStartColor": 2071128575, 8 | "WFWorkflowIconImageData": { 9 | "type": "Buffer", 10 | "data": [] 11 | }, 12 | "WFWorkflowIconGlyphNumber": 59511 13 | }, 14 | "WFWorkflowTypes": ["NCWidget", "WatchKit"], 15 | "WFWorkflowInputContentItemClasses": [ 16 | "WFAppStoreAppContentItem", 17 | "WFArticleContentItem", 18 | "WFContactContentItem", 19 | "WFDateContentItem", 20 | "WFEmailAddressContentItem", 21 | "WFGenericFileContentItem", 22 | "WFImageContentItem", 23 | "WFiTunesProductContentItem", 24 | "WFLocationContentItem", 25 | "WFDCMapsLinkContentItem", 26 | "WFAVAssetContentItem", 27 | "WFPDFContentItem", 28 | "WFPhoneNumberContentItem", 29 | "WFRichTextContentItem", 30 | "WFSafariWebPageContentItem", 31 | "WFStringContentItem", 32 | "WFURLContentItem" 33 | ], 34 | "WFWorkflowActions": [] 35 | } 36 | ] 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "preserve" 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | --------------------------------------------------------------------------------