├── src ├── utils │ ├── utools_helper.js │ ├── uuid.js │ ├── aowua.js │ ├── html.js │ ├── translate.js │ └── file.js ├── components │ ├── ComponentClass.js │ ├── Button.js │ ├── Loading.js │ └── Overlay.js ├── workers │ ├── wasm_worker.js │ └── module_loader.js ├── in_workers │ ├── worker_loader.js │ └── wasm_in_worker.js ├── main.js └── pages │ └── MainFrame.js ├── README.md ├── public ├── core.wasm ├── README.md ├── img │ └── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── android-chrome-maskable-192x192.png │ │ └── android-chrome-maskable-512x512.png ├── index.css ├── index.html ├── plugin.json └── manifast.webmanifest ├── assembly ├── tsconfig.json └── index.ts ├── index.js ├── asconfig.json ├── .eslintrc.js ├── webpack.config.js ├── LICENSE ├── package.json └── .gitignore /src/utils/utools_helper.js: -------------------------------------------------------------------------------- 1 | export default window["utools"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aowua Translator 2 | 3 | ## 本工具仅为娱乐向的编码工具,不能对抗破解与检测,不要用于敏感数据的加密!! 4 | 5 | -------------------------------------------------------------------------------- /public/core.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Icy-Rime/AowuaTranslator/HEAD/public/core.wasm -------------------------------------------------------------------------------- /assembly/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "assemblyscript/std/assembly.json", 3 | "include": [ 4 | "./**/*.ts" 5 | ] 6 | } -------------------------------------------------------------------------------- /public/README.md: -------------------------------------------------------------------------------- 1 | # 兽音译者 2 | 3 | 兽音译者utools版 4 | 5 | 本工具仅为娱乐向的编码工具,不能对抗破解与检测,不要用于敏感数据的加密!! 6 | 7 | ## 使用方法 8 | 使用命令 “嗷呜” 打开该工具即可。 9 | -------------------------------------------------------------------------------- /src/components/ComponentClass.js: -------------------------------------------------------------------------------- 1 | 2 | export class ComponentClass { 3 | constructor () { 4 | this.element = undefined; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Icy-Rime/AowuaTranslator/HEAD/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Icy-Rime/AowuaTranslator/HEAD/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-maskable-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Icy-Rime/AowuaTranslator/HEAD/public/img/icons/android-chrome-maskable-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-maskable-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Icy-Rime/AowuaTranslator/HEAD/public/img/icons/android-chrome-maskable-512x512.png -------------------------------------------------------------------------------- /public/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | html, body, .content { 7 | width: 100%; 8 | height: 100%; 9 | } 10 | *:focus{ 11 | outline: 0; 12 | } 13 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const loader = require('@assemblyscript/loader') 3 | const imports = { /* imports go here */ } 4 | const wasmModule = loader.instantiateSync(fs.readFileSync(__dirname + '/build/optimized.wasm'), imports) 5 | module.exports = wasmModule.exports 6 | -------------------------------------------------------------------------------- /asconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": { 3 | "debug": { 4 | "binaryFile": "public/core.wasm", 5 | "textFile": "public/core.wat", 6 | "sourceMap": "public/core.wasm.map", 7 | "debug": true 8 | }, 9 | "release": { 10 | "binaryFile": "public/core.wasm", 11 | "optimize": true 12 | } 13 | }, 14 | "options": {} 15 | } -------------------------------------------------------------------------------- /src/utils/uuid.js: -------------------------------------------------------------------------------- 1 | export function generateUUID () { 2 | let d = new Date().getTime(); 3 | const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { 4 | const r = (d + Math.random() * 16) % 16 | 0; 5 | d = Math.floor(d / 16); 6 | return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16); 7 | }); 8 | return uuid; 9 | } 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | }, 6 | extends: [ 7 | 'eslint:recommended', 8 | ], 9 | parserOptions: { 10 | ecmaVersion: 12, 11 | sourceType: 'module', 12 | }, 13 | rules: { 14 | "semi": ["warn", "always",], 15 | "comma-style": ["warn", "last"], 16 | "comma-spacing": ["warn", { "before": false, "after": true }], 17 | "comma-dangle": ["warn", "always-multiline"], 18 | "quotes": ["warn", "double"], 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | Your browser does not support javascript. 14 |
15 | 16 | -------------------------------------------------------------------------------- /public/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginName": "兽音译者", 3 | "description": "兽音译者 utools 版本", 4 | "author": "Dreagonmon", 5 | "main": "index.html", 6 | "version": "0.0.1", 7 | "logo": "img/icons/android-chrome-512x512.png", 8 | "development": { 9 | "main": "http://127.0.0.1:12680/index.html" 10 | }, 11 | "features": [ 12 | { 13 | "code": "aowu", 14 | "explain": "嗷呜", 15 | "cmds":[ 16 | "嗷呜" 17 | ] 18 | } 19 | ], 20 | "pluginSetting": { 21 | "single": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/utils/aowua.js: -------------------------------------------------------------------------------- 1 | import { call } from "../in_workers/wasm_in_worker"; 2 | 3 | export const toRoar = async (text, code) => { 4 | let roars = await call("textToRoar", text, code); 5 | roars = code.charAt(3) + code.charAt(1) + code.charAt(0) + roars + code.charAt(2); 6 | return roars; 7 | }; 8 | 9 | export const fromRoar = async (roars) => { 10 | roars = roars.trim() 11 | let code = roars.charAt(2) + roars.charAt(1) + roars.charAt(roars.length - 1) + roars.charAt(0); 12 | roars = roars.substring(3, roars.length-1); 13 | return await call("textFromRoar", roars, code); 14 | }; -------------------------------------------------------------------------------- /src/workers/wasm_worker.js: -------------------------------------------------------------------------------- 1 | import { loadModule } from "./module_loader"; 2 | 3 | /** @type {Promise} */ 4 | const load = loadModule("core.wasm"); 5 | 6 | self.addEventListener("message", async (e) => { 7 | let wasm = await load; 8 | try { 9 | var data = e.data; 10 | self.postMessage({ 11 | function: data.function, 12 | uuid: data.uuid, 13 | error: null, 14 | return: wasm[data.function](...data.params), 15 | }); 16 | } catch (err) { 17 | console.error(err); 18 | self.postMessage({ 19 | function: data.function, 20 | uuid: data.uuid, 21 | error: err.message || true, 22 | return: null, 23 | }); 24 | } 25 | }, false); -------------------------------------------------------------------------------- /src/in_workers/worker_loader.js: -------------------------------------------------------------------------------- 1 | const __workerInfo = { 2 | inited: {}, 3 | loading: {}, 4 | worker: {}, 5 | }; 6 | 7 | const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); 8 | 9 | /** 10 | * load worker only once 11 | * @param {string} path 12 | * @returns {Promise} 13 | */ 14 | export async function loadWorker (path) { 15 | while (__workerInfo.loading[path]) { 16 | await sleep(50); 17 | } 18 | if (__workerInfo.inited[path]) { 19 | return __workerInfo.worker[path]; 20 | } 21 | __workerInfo.loading[path] = true; 22 | const worker = new Worker(path); 23 | __workerInfo.worker[path] = worker; 24 | __workerInfo.inited[path] = true; 25 | __workerInfo.loading[path] = false; 26 | return worker; 27 | } 28 | -------------------------------------------------------------------------------- /src/components/Button.js: -------------------------------------------------------------------------------- 1 | import { ComponentClass } from "./ComponentClass"; 2 | import { e } from "../utils/html"; 3 | 4 | export class Button extends ComponentClass { 5 | /** 6 | * Create Button 7 | * @returns {ComponentClass} component instance 8 | */ 9 | constructor (label = "button", onClick = undefined) { 10 | super(); 11 | this.element = e("button", { 12 | style: { 13 | padding: "8px", 14 | borderRadius: "4px", 15 | borderWidth: "0", 16 | color: "#FFF", 17 | backgroundColor: "#08E", 18 | cursor: "pointer", 19 | }, 20 | }, label); 21 | this.element.onclick = onClick; 22 | } 23 | } 24 | 25 | export function button (label = "button", onClick = undefined) { 26 | return (new Button(label, onClick)).element; 27 | } 28 | -------------------------------------------------------------------------------- /public/manifast.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Aowua Translator", 3 | "short_name": "Roar!", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#000000", 7 | "theme_color": "#4DBA87", 8 | "icons": [ 9 | { 10 | "src": "./img/icons/android-chrome-192x192.png", 11 | "sizes": "192x192", 12 | "type": "image/png" 13 | }, 14 | { 15 | "src": "./img/icons/android-chrome-512x512.png", 16 | "sizes": "512x512", 17 | "type": "image/png" 18 | }, 19 | { 20 | "src": "./img/icons/android-chrome-maskable-192x192.png", 21 | "sizes": "192x192", 22 | "type": "image/png", 23 | "purpose": "maskable" 24 | }, 25 | { 26 | "src": "./img/icons/android-chrome-maskable-512x512.png", 27 | "sizes": "512x512", 28 | "type": "image/png", 29 | "purpose": "maskable" 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /src/in_workers/wasm_in_worker.js: -------------------------------------------------------------------------------- 1 | import { loadWorker } from "./worker_loader"; 2 | 3 | /** @type {Promise} */ 4 | const load = loadWorker("wasm_worker.js"); 5 | let globalUUID = 0; 6 | export const call = async (funName, ...params) => { 7 | const worker = await load; 8 | const uuid = globalUUID++; 9 | return new Promise((resolve, reject) => { 10 | const listener = (event) => { 11 | const data = event.data; 12 | if (data.function === funName && data.uuid === uuid){ 13 | worker.removeEventListener("message", listener); 14 | if (data.error === null) { 15 | resolve(data.return); 16 | } else { 17 | reject(data.error); 18 | } 19 | } 20 | }; 21 | worker.addEventListener("message", listener); 22 | const data = { 23 | function: funName, 24 | uuid, 25 | params, 26 | }; 27 | worker.postMessage(data); 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /src/workers/module_loader.js: -------------------------------------------------------------------------------- 1 | import { AsBind } from "as-bind"; 2 | 3 | const __moduleInfo = { 4 | inited: {}, 5 | loading: {}, 6 | modules: {}, 7 | }; 8 | 9 | const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); 10 | 11 | /** 12 | * load wasm only once 13 | * @param {string} path 14 | * @returns {Promise} 15 | */ 16 | export async function loadModule (path) { 17 | while (__moduleInfo.loading[path]) { 18 | await sleep(50); 19 | } 20 | if (__moduleInfo.inited[path]) { 21 | return __moduleInfo.modules[path].exports; 22 | } 23 | __moduleInfo.loading[path] = true; 24 | const wasm = await AsBind.instantiate(fetch(path), { 25 | index: { 26 | consoleLog: message => { 27 | console.log(message); 28 | }, 29 | }, 30 | }); 31 | __moduleInfo.modules[path] = wasm; 32 | __moduleInfo.inited[path] = true; 33 | __moduleInfo.loading[path] = false; 34 | return wasm.exports; 35 | } 36 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import MainFrame from "./pages/MainFrame"; 3 | import { getText } from "./utils/translate"; 4 | // entry function 5 | async function main () { 6 | // registerSW(); 7 | document.title = getText("aowua_translator"); 8 | setPageContent(MainFrame); 9 | } 10 | 11 | // register function 12 | window.addEventListener("load", main); 13 | 14 | /** 15 | * change page content 16 | * @param {HTMLElement} contentElement 17 | */ 18 | const setPageContent = (contentElement) => { 19 | const body = document.querySelector("#app"); 20 | body.innerHTML = ""; // clear 21 | body.append(contentElement); 22 | }; 23 | 24 | /* eslint-disable-next-line no-unused-vars */ 25 | const registerSW = () => { 26 | // Check that service workers are supported 27 | if ("serviceWorker" in navigator) { 28 | navigator.serviceWorker.register("/service-worker.js").then((res) => { 29 | console.log("sw enabled.", res); 30 | }); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 3 | const {GenerateSW} = require("workbox-webpack-plugin"); 4 | 5 | module.exports = { 6 | // mode: "production", 7 | mode: process.env.NODE_ENV === 'development' ? 'development' : 'production', 8 | entry: { 9 | wasm_worker: "./src/workers/wasm_worker.js", 10 | main: "./src/main.js", 11 | }, 12 | output: { 13 | filename: "[name].js", 14 | path: path.resolve(__dirname, "dist"), 15 | }, 16 | devtool: "source-map", 17 | devServer: { 18 | contentBase: "./dist", 19 | port: 12680, 20 | noInfo: true, 21 | }, 22 | plugins: [ 23 | new CopyWebpackPlugin({ 24 | patterns: [ 25 | { 26 | from: "public", 27 | globOptions: { 28 | ignore: ["**/*.wat"], 29 | }, 30 | }, 31 | ], 32 | }), 33 | new GenerateSW({ 34 | inlineWorkboxRuntime: true, 35 | skipWaiting: true, 36 | clientsClaim: true, 37 | offlineGoogleAnalytics: false, 38 | swDest: "service-worker.js", 39 | }), 40 | ], 41 | }; 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dreagonmon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aowua-translator", 3 | "version": "0.1.0", 4 | "description": "Aowua Translator", 5 | "private": true, 6 | "scripts": { 7 | "build": "webpack --config webpack.config.js", 8 | "dev": "webpack-dev-server --open", 9 | "asbuild:debug": "asc ./node_modules/as-bind/lib/assembly/as-bind.ts assembly/index.ts --sourceMap --debug --target debug --exportRuntime", 10 | "asbuild:release": "asc ./node_modules/as-bind/lib/assembly/as-bind.ts assembly/index.ts --target release --exportRuntime", 11 | "asbuild": "npm run asbuild:release", 12 | "lint": "eslint ./src --fix" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/Icy-Rime/AowuaTranslator" 17 | }, 18 | "author": "dreagonmon", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "assemblyscript": "^0.18.26", 22 | "copy-webpack-plugin": "^6.0.1", 23 | "eslint": "^7.24.0", 24 | "eslint-plugin-import": "^2.22.1", 25 | "eslint-plugin-node": "^11.1.0", 26 | "eslint-plugin-promise": "^4.3.1", 27 | "webpack": "^4.43.0", 28 | "webpack-cli": "^3.3.11", 29 | "webpack-dev-server": "^3.11.0", 30 | "workbox-webpack-plugin": "^6.1.5" 31 | }, 32 | "dependencies": { 33 | "@assemblyscript/loader": "^0.18.26", 34 | "as-bind": "^0.6.1", 35 | "htm": "^3.0.4" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | .env.test 60 | 61 | # parcel-bundler cache (https://parceljs.org/) 62 | .cache 63 | 64 | # next.js build output 65 | .next 66 | 67 | # nuxt.js build output 68 | .nuxt 69 | 70 | # vuepress build output 71 | .vuepress/dist 72 | 73 | # Serverless directories 74 | .serverless/ 75 | 76 | # FuseBox cache 77 | .fusebox/ 78 | 79 | # DynamoDB Local files 80 | .dynamodb/ 81 | 82 | .vscode/ 83 | dist/ 84 | build/ 85 | public/*.wat 86 | public/*.map -------------------------------------------------------------------------------- /src/utils/html.js: -------------------------------------------------------------------------------- 1 | import htm from "htm"; 2 | import { ComponentClass } from "../components/ComponentClass"; 3 | export const html = htm.bind(e); 4 | /** 5 | * Create HTMLElement 6 | * @param {string} tagName tag name 7 | * @param {HTMLElement} attr attribute 8 | * @param {Array | Array} children children 9 | * @returns {HTMLElement} html element 10 | */ 11 | export function e (tagName, attr, ...children) { 12 | let elem; 13 | if (typeof (tagName) === "string") { elem = document.createElement(tagName); } else if (tagName instanceof ComponentClass) { elem = tagName.element; } else if (tagName instanceof HTMLElement) { elem = tagName; } 14 | for (const k in attr) { 15 | elem[k] = attr[k]; 16 | if (k === "style") { 17 | if (typeof(attr.style) === "string") { 18 | elem.style.cssText = attr.style; 19 | } else { 20 | for (const sk in attr.style) { 21 | elem.style[sk] = attr.style[sk]; 22 | } 23 | } 24 | } 25 | } 26 | children.forEach((child) => { 27 | if (child instanceof HTMLElement || typeof (child) === "string") { 28 | elem.append(child); 29 | } 30 | }); 31 | return elem; 32 | } 33 | 34 | /** 35 | * Create HTMLElement Factory 36 | * @param {string} tagName tag name 37 | * @returns {(attr:HTMLElement, ...children: Array | Array) => HTMLElement} function to create html element 38 | */ 39 | export function factory (tagName) { 40 | return e.bind(null, tagName); 41 | } 42 | 43 | /** 44 | * assignstyle to element 45 | * @param {HTMLElement} element 46 | * @param {object} style 47 | */ 48 | export function style (element, style) { 49 | for (const sk in style) { 50 | element.style[sk] = style[sk]; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/utils/translate.js: -------------------------------------------------------------------------------- 1 | const LOCAL_STORAGE_KEY_LOCAL = "config.local"; 2 | export const LOCAL_EN = "en"; 3 | export const LOCAL_ZH = "zh-CN"; 4 | // DICT 5 | const dict = { 6 | ["component_overlay_confirm"]: { 7 | default: "Confirm", 8 | "zh-CN": "确认", 9 | }, 10 | ["component_overlay_cancel"]: { 11 | default: "Cancel", 12 | "zh-CN": "取消", 13 | }, 14 | ["aowua_translator"]: { 15 | default: "Aowua Translator", 16 | "zh-CN": "兽音译者", 17 | }, 18 | ["original_text"]: { 19 | default: "Original text", 20 | "zh-CN": "原始文本", 21 | }, 22 | ["encoded_text"]: { 23 | default: "Encoded text", 24 | "zh-CN": "编码后的文本", 25 | }, 26 | ["encode"]: { 27 | default: "Encode ⇊⇊", 28 | "zh-CN": "编码 ⇊⇊", 29 | }, 30 | ["decode"]: { 31 | default: "⇈⇈ Decode", 32 | "zh-CN": "⇈⇈ 解码", 33 | }, 34 | ["roar_code"]: { 35 | default: "roa~", 36 | "zh-CN": "嗷呜啊~", 37 | }, 38 | ["please_set_correct_code"]: { 39 | default: "Please set correct code, length must be 4, and no duplicate characters.", 40 | "zh-CN": "请设置正确的编码,编码长度必须为4,并且没有重复字符。", 41 | }, 42 | ["operation_failed"]: { 43 | default: "Operation failedm please check and try again.", 44 | "zh-CN": "操作失败,请检查后重试。", 45 | }, 46 | }; 47 | 48 | // utils 49 | function getLocalStorage (name, defaultValue) { 50 | const value = localStorage.getItem(name); 51 | if (value === null) { 52 | return defaultValue; 53 | } else { 54 | return value; 55 | } 56 | } 57 | 58 | // translate function 59 | const config = { 60 | local: getLocalStorage(LOCAL_STORAGE_KEY_LOCAL, navigator.language || navigator.userLanguage), 61 | }; 62 | 63 | export function setLocal (local) { 64 | config.local = local; 65 | localStorage.setItem(LOCAL_STORAGE_KEY_LOCAL, local); 66 | } 67 | 68 | export function getText (name) { 69 | const item = dict[name]; 70 | if (item === undefined) { return "[No Text]"; } 71 | if (item[config.local] !== undefined) { 72 | return item[config.local]; 73 | } else { 74 | return item.default; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /assembly/index.ts: -------------------------------------------------------------------------------- 1 | // The entry file of your WebAssembly module. 2 | declare function consoleLog(message: string): void; 3 | 4 | export function textToRoar(text: string, code: string): string { 5 | if (code.length != 4) { 6 | throw new Error("code incorrect."); 7 | } 8 | let offset: i32 = 0; 9 | let roars: Array = new Array(text.length * 8); 10 | for (let i: i32 = 0; i < text.length; i++) { 11 | let char: i32 = text.charCodeAt(i); 12 | for (let b: i32 = 0; b < 4; b++) { 13 | let hex: i32 = (i32)(char >> (4 * (3 - b))) & 0x0F; 14 | hex = (i32)(hex + offset) % 0x10; 15 | let p1: i32 = (i32)(hex / 4); 16 | let p2: i32 = (i32)(hex % 4); 17 | roars[ i * 8 + (b * 2)] = code.charCodeAt(p1); 18 | roars[ i * 8 + (b * 2) + 1] = code.charCodeAt(p2); 19 | offset ++; 20 | } 21 | } 22 | return String.fromCharCodes(roars); 23 | } 24 | 25 | export function textFromRoar(roars: string, code: string): string { 26 | if (code.length != 4) { 27 | throw new Error("code incorrect."); 28 | } 29 | const valueMap: Map = new Map(); 30 | for (let i: i32 = 0; i < 4; i++) { 31 | valueMap.set(code.charCodeAt(i), i); 32 | } 33 | if (roars.length % 8 != 0) { 34 | throw new Error("text incorrect."); 35 | } 36 | const slength: i32 = (i32)(roars.length / 8); 37 | let offset: i32 = 0; 38 | let text: Array = new Array(slength); 39 | for (let i: i32 = 0; i < slength; i++) { 40 | let charCode = 0; 41 | for (let b: i32 = 0; b < 4; b++) { 42 | let char1: i32 = roars.charCodeAt(i * 8 + b * 2); 43 | let char2: i32 = roars.charCodeAt(i * 8 + b * 2 + 1); 44 | if (!valueMap.has(char1) || !valueMap.has(char2)) { 45 | throw new Error("code incorrect."); 46 | } 47 | let hex: i32 = valueMap.get(char1) * 4 + valueMap.get(char2); 48 | hex = (i32)(hex - (offset % 0x10) + 0x10) % 0x10; 49 | charCode = (charCode << 4) | hex; 50 | offset ++; 51 | } 52 | text[i] = charCode; 53 | } 54 | return String.fromCharCodes(text); 55 | } 56 | -------------------------------------------------------------------------------- /src/components/Loading.js: -------------------------------------------------------------------------------- 1 | import { ComponentClass } from "./ComponentClass"; 2 | import { e } from "../utils/html"; 3 | const STYLE_ELEMENT_ID = "LOADING_COMPONENT_STYLE"; 4 | const global = { 5 | /** @type {HTMLStyleElement} */ 6 | style: undefined, 7 | }; 8 | 9 | function addGlobalStylesheet () { 10 | // add keyframe only once 11 | if (global.style) { 12 | return; 13 | } 14 | const head = document.head.children; 15 | for (let i = 0; i < head.length; i++) { 16 | const elem = head[i]; 17 | if (elem instanceof HTMLStyleElement && elem.id === STYLE_ELEMENT_ID) { 18 | global.style = elem; 19 | return; 20 | } 21 | } 22 | // create stylesheet 23 | global.style = document.createElement("style"); 24 | global.style.id = STYLE_ELEMENT_ID; 25 | global.style.innerHTML = `@keyframes changeBgColor{ 26 | 0%{ 27 | background: lightgreen; 28 | } 29 | 50%{ 30 | background: lightblue; 31 | } 32 | 100%{ 33 | background: lightgreen; 34 | } 35 | } 36 | @keyframes changeBorderColor{ 37 | 0%{ 38 | border-color: lightgreen; 39 | } 40 | 50%{ 41 | border-color: lightblue; 42 | } 43 | 100%{ 44 | border-color: lightgreen; 45 | } 46 | } 47 | @keyframes turn{ 48 | 0%{ 49 | -webkit-transform: rotate(0deg); 50 | } 51 | 100%{ 52 | -webkit-transform: rotate(360deg); 53 | } 54 | }`; 55 | document.head.append(global.style); 56 | } 57 | function setElementStyleCircle (bigCircle, smallCircle) { 58 | bigCircle.style.cssText = `display: inline-block; 59 | width: 50px; 60 | height: 50px; 61 | border-radius: 50%; 62 | margin: 5px; 63 | position: relative; 64 | border:5px solid lightgreen; 65 | animation: turn 1s linear infinite, changeBorderColor 2s linear infinite;`; 66 | smallCircle.style.cssText = `display: inline-block; 67 | width: 20px; 68 | height: 20px; 69 | border-radius: 50%; 70 | background: lightgreen; 71 | position: absolute; 72 | left: 50%; 73 | margin-top: -10px; 74 | margin-left: -10px; 75 | animation: changeBgColor 2s linear infinite;`; 76 | } 77 | 78 | export class Loading extends ComponentClass { 79 | constructor () { 80 | super(); 81 | addGlobalStylesheet(); 82 | this.smallCircle = e("span", {}); 83 | this.bigCircle = e("span", 84 | {}, 85 | this.smallCircle, 86 | ); 87 | setElementStyleCircle(this.bigCircle, this.smallCircle); 88 | this.element = this.bigCircle; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/utils/file.js: -------------------------------------------------------------------------------- 1 | /** 2 | * download file to local 3 | * @param {Uint8Array} data 4 | * @param {string} name 5 | */ 6 | export function save (data, name) { 7 | const blob = new Blob([data.buffer], { 8 | type: "application/octet-stream", 9 | }); 10 | const a = document.createElement("a"); 11 | a.download = name; 12 | a.href = URL.createObjectURL(blob); 13 | a.click(); 14 | } 15 | /** 16 | * convert bytes to python b'...' expression 17 | * @param {Uint8Array} data 18 | * @returns {string} 19 | */ 20 | export function toPythonExpression (data) { 21 | // 32~126 22 | let pyByteStr = ""; 23 | data.forEach((u8) => { 24 | if (u8 >= 32 && u8 <= 126) { 25 | if (u8 === 34 || u8 === 39 || u8 === 92) { 26 | pyByteStr += "\\" + String.fromCharCode(u8); // \' and \" and \\ 27 | } else { 28 | pyByteStr += String.fromCharCode(u8); 29 | } 30 | } else { 31 | let hex = u8.toString(16); 32 | switch (u8) { 33 | case 7: pyByteStr += "\\a"; break; 34 | case 8: pyByteStr += "\\b"; break; 35 | case 9: pyByteStr += "\\t"; break; 36 | case 10: pyByteStr += "\\n"; break; 37 | case 13: pyByteStr += "\\r"; break; 38 | default: 39 | while (hex.length < 2) hex = "0" + hex; 40 | pyByteStr += `\\x${hex}`; 41 | } 42 | } 43 | }); 44 | return `b'${pyByteStr}'`; 45 | } 46 | /** 47 | * convert bytes to base64 encoded string 48 | * @param {Uint8Array} data 49 | * @returns {string} 50 | */ 51 | export function toBase64 (data) { 52 | let str = ""; 53 | data.forEach((byt) => { str += String.fromCharCode(byt); }); 54 | return btoa(str); 55 | } 56 | /** 57 | * copy text to clipboard 58 | * @param {string} text 59 | * @returns {boolean} 60 | */ 61 | export function saveToClipboard (text) { 62 | const textArea = document.createElement("textarea"); 63 | 64 | // 65 | // *** This styling is an extra step which is likely not required. *** 66 | // 67 | // Why is it here? To ensure: 68 | // 1. the element is able to have focus and selection. 69 | // 2. if element was to flash render it has minimal visual impact. 70 | // 3. less flakyness with selection and copying which **might** occur if 71 | // the textarea element is not visible. 72 | // 73 | // The likelihood is the element won't even render, not even a 74 | // flash, so some of these are just precautions. However in 75 | // Internet Explorer the element is visible whilst the popup 76 | // box asking the user for permission for the web page to 77 | // copy to the clipboard. 78 | // 79 | // Place in top-left corner of screen regardless of scroll position. 80 | textArea.style.position = "fixed"; 81 | textArea.style.top = 0; 82 | textArea.style.left = 0; 83 | // Ensure it has a small width and height. Setting to 1px / 1em 84 | // doesn't work as this gives a negative w/h on some browsers. 85 | textArea.style.width = "2em"; 86 | textArea.style.height = "2em"; 87 | // We don't need padding, reducing the size if it does flash render. 88 | textArea.style.padding = 0; 89 | // Clean up any borders. 90 | textArea.style.border = "none"; 91 | textArea.style.outline = "none"; 92 | textArea.style.boxShadow = "none"; 93 | // Avoid flash of white box if rendered for any reason. 94 | textArea.style.background = "transparent"; 95 | textArea.value = text; 96 | document.body.appendChild(textArea); 97 | textArea.focus(); 98 | textArea.select(); 99 | try { 100 | const successful = document.execCommand("copy"); 101 | document.body.removeChild(textArea); 102 | return successful; 103 | } catch (err) { 104 | document.body.removeChild(textArea); 105 | return false; 106 | } 107 | } 108 | /** 109 | * open a local file. may return undefined 110 | * @param {string} accept 111 | * @param {number} [timeout=30000] 112 | * @returns {Promise} 113 | */ 114 | export function openLocalFile (accept = "*/*", timeout = 30000) { 115 | const ip = document.createElement("input"); 116 | ip.type = "file"; 117 | ip.accept = accept; 118 | return new Promise((resolve) => { 119 | const timerId = setTimeout(() => { 120 | ip.onchange = undefined; 121 | resolve(undefined); 122 | }, timeout); 123 | ip.onchange = () => { 124 | clearTimeout(timerId); 125 | ip.onchange = undefined; 126 | const files = ip.files; 127 | if (files.length <= 0) { 128 | resolve(undefined); 129 | return; 130 | } 131 | const file = files[0]; 132 | resolve(file); 133 | }; 134 | ip.click(); 135 | }); 136 | } 137 | 138 | export function toArrayExpression (data) { 139 | let byteStr = ""; 140 | data.forEach((u8, index) => { 141 | if (index % 8 === 0 && index !== 0) { 142 | byteStr += "\n"; 143 | } 144 | if (index % 8 === 0) { 145 | byteStr += " "; 146 | } else { 147 | byteStr += " "; 148 | } 149 | let hex = u8.toString(16); 150 | while (hex.length < 2) hex = "0" + hex; 151 | byteStr += `0x${hex},`; 152 | }); 153 | return byteStr; 154 | } 155 | -------------------------------------------------------------------------------- /src/pages/MainFrame.js: -------------------------------------------------------------------------------- 1 | import { html, style as applyStyle } from "../utils/html"; 2 | import { button } from "../components/Button"; 3 | import { getText } from "../utils/translate"; 4 | import { toRoar, fromRoar } from "../utils/aowua"; 5 | import overlay from "../components/Overlay"; 6 | import utools from "../utils/utools_helper"; 7 | 8 | let utoolsInput = ""; 9 | (() => { 10 | let firstTimeHide = true 11 | if (utools) { 12 | // 设置utools功能 13 | utools.setSubInput( async ({ text }) => { 14 | utoolsInput = text; 15 | // try decode 16 | try { 17 | let decodedText = await fromRoar(text); 18 | let encoded = getEncodedTextArea(); 19 | encoded.value = decodedText 20 | utools.copyText(decodedText); 21 | } catch { 22 | // try encode 23 | if (firstTimeHide) { 24 | firstTimeHide = false; 25 | let elems = document.querySelectorAll(".utools_hide"); 26 | for (let elem of elems) { 27 | elem.style.display = "none"; 28 | } 29 | } 30 | const code = getCode(); 31 | if (!checkCode(code)) { 32 | await overlay.alert(getText("please_set_correct_code")); 33 | return; 34 | } 35 | let encodedText = await toRoar(text, code); 36 | let encoded = getEncodedTextArea(); 37 | encoded.value = encodedText 38 | utools.copyText(encodedText); 39 | } 40 | }, getText('original_text')); 41 | utools.subInputFocus(); 42 | } 43 | })() 44 | 45 | const LOCAL_STORAGE_KEY_CODE = "config.code"; 46 | const defaultCode = localStorage.getItem(LOCAL_STORAGE_KEY_CODE) || getText("roar_code"); 47 | const onCodeChange = (event) => { 48 | let newCode = event.target.value; 49 | if (checkCode(newCode)) 50 | localStorage.setItem(LOCAL_STORAGE_KEY_CODE, newCode); 51 | if (utools) { 52 | utools.setSubInputValue(utoolsInput) 53 | } 54 | }; 55 | 56 | const checkCode = (code) => { 57 | if (code.length != 4) 58 | return false; 59 | let s = new Set(); 60 | for (let c of code) { 61 | s.add(c); 62 | } 63 | if (s.size != 4) 64 | return false; 65 | return true; 66 | }; 67 | 68 | const getOriginalTextArea = () => { 69 | return document.querySelector("#original_text"); 70 | }; 71 | 72 | const getEncodedTextArea = () => { 73 | return document.querySelector("#encoded_text"); 74 | }; 75 | 76 | const getCode = () => { 77 | return document.querySelector("#code").value; 78 | }; 79 | 80 | const actionEncode = async () => { 81 | const code = getCode(); 82 | if (!checkCode(code)) { 83 | await overlay.alert(getText("please_set_correct_code")); 84 | return; 85 | } 86 | let closeLoading = overlay.loading(); 87 | try { 88 | let roars = await toRoar(getOriginalTextArea().value, code); 89 | getEncodedTextArea().value = roars; 90 | } catch(err) { 91 | console.error(err); 92 | overlay.alert(getText("operation_failed")); 93 | } finally { 94 | closeLoading(); 95 | } 96 | }; 97 | 98 | const actionDecode = async () => { 99 | let closeLoading = overlay.loading(); 100 | try { 101 | let text = await fromRoar(getEncodedTextArea().value); 102 | getOriginalTextArea().value = text; 103 | } catch(err) { 104 | console.error(err); 105 | overlay.alert(getText("operation_failed")); 106 | } finally { 107 | closeLoading(); 108 | } 109 | }; 110 | 111 | /* UI */ 112 | const style = { 113 | content: { 114 | display: "flex", 115 | flexDirection: "column", 116 | }, 117 | title: { 118 | marginTop: "10px", 119 | color: "#03F", 120 | width: "100%", 121 | textAlign: "center", 122 | }, 123 | operationBar: { 124 | display: "flex", 125 | }, 126 | button: { 127 | flex: "1 1 auto", 128 | }, 129 | codeSetting: { 130 | margin: "10px 10px 0 10px", 131 | padding: "5px", 132 | border: "dashed 1px #03F", 133 | borderRadius: "5px", 134 | textAlign: "center", 135 | }, 136 | textarea: { 137 | height: "300px", 138 | margin: "10px", 139 | padding: "5px", 140 | border: "dashed 1px #03F", 141 | borderRadius: "5px", 142 | resize: "none", 143 | }, 144 | }; 145 | const buttonEncode = button(getText("encode")); 146 | const buttonDecode = button(getText("decode")); 147 | applyStyle(buttonEncode, {...style.button, marginLeft: "10px"}); 148 | applyStyle(buttonDecode, {...style.button, marginLeft: "10px", marginRight: "10px"}); 149 | const MainFrame = html`
150 |

${getText("aowua_translator")}

151 | 152 | 153 |
154 | <${buttonEncode} onclick=${actionEncode} /> 155 | <${buttonDecode} onclick=${actionDecode} /> 156 |
157 | 158 | ${overlay.element} 159 |
`; 160 | 161 | export default MainFrame; -------------------------------------------------------------------------------- /src/components/Overlay.js: -------------------------------------------------------------------------------- 1 | import { e } from "../utils/html"; 2 | import { ComponentClass } from "../components/ComponentClass"; 3 | import { Loading } from "./Loading"; 4 | import { getText } from "../utils/translate"; 5 | 6 | /** 7 | * build dialog element 8 | * @param {string | HTMLElement} message 9 | * @param {...string} buttons 10 | */ 11 | function buildDialog (message, ...buttons) { 12 | const buttonList = buttons.map((label, index) => { 13 | return e("button", 14 | { 15 | style: { 16 | marginLeft: index > 0 ? "8px" : "0", 17 | padding: "8px", 18 | borderRadius: "4px", 19 | borderWidth: "0", 20 | color: index > 0 ? "#000" : "#FFF", 21 | backgroundColor: index > 0 ? "#DDD" : "#08E", 22 | }, 23 | }, 24 | label, 25 | ); 26 | }); 27 | let msg; 28 | if (typeof (message) === "object" && message instanceof HTMLElement) { 29 | msg = message; 30 | } else { 31 | msg = e("div", 32 | { 33 | style: { 34 | whiteSpace: "pre-wrap", 35 | wordBreak: "break-word", 36 | }, 37 | }, 38 | String(message), 39 | ); 40 | } 41 | const buttonPanel = e("div", 42 | { 43 | style: { 44 | marginTop: "16px", 45 | display: "flex", 46 | alignItems: "center", 47 | justifyContent: "space-around", 48 | }, 49 | }, 50 | ...buttonList, 51 | ); 52 | const panel = e("div", 53 | { 54 | style: { 55 | fontSize: "16px", 56 | backgroundColor: "#FFF", 57 | minWidth: "240px", 58 | maxWidth: "90vw", 59 | padding: "16px", 60 | color: "#000", 61 | borderRadius: "8px", 62 | }, 63 | }, 64 | msg, 65 | buttonPanel, 66 | ); 67 | return [panel, ...buttonList]; 68 | } 69 | 70 | /** 71 | * build prompt element 72 | * @param {string} message message 73 | * @param {string} defaultText message 74 | * @return {[HTMLElement, HTMLElement]} element,inputElement 75 | */ 76 | function buildPromptBody (message, defaultText = "") { 77 | const msgElement = e("div", 78 | { 79 | style: { 80 | whiteSpace: "pre-wrap", 81 | }, 82 | }, 83 | String(message), 84 | ); 85 | const inputElement = e("input", 86 | { 87 | style: { 88 | fontSize: "1em", 89 | lineHeight: "1.5em", 90 | marginTop: "0.25em", 91 | borderRadius: "0.25em", 92 | border: "1px solid #aaa", 93 | padding: "0 0.5em 0 0.5em", 94 | width: "100%", 95 | }, 96 | value: defaultText, 97 | }, 98 | ); 99 | const element = e("div", 100 | {}, 101 | msgElement, 102 | inputElement, 103 | ); 104 | return [element, inputElement]; 105 | } 106 | 107 | /** build loading panel 108 | * @returns {HTMLElement} 109 | */ 110 | function buildLoadingOverlay () { 111 | return e("div", 112 | { 113 | style: { 114 | backgroundColor: "#FFF", 115 | borderRadius: "8px", 116 | overflow: "hidden", 117 | padding: "8px", 118 | }, 119 | }, 120 | (new Loading()).element, 121 | ); 122 | } 123 | 124 | class Overlay extends ComponentClass { 125 | constructor () { 126 | super(); 127 | this.container = e("div", { 128 | style: { 129 | maxWidth: "100vw", 130 | maxHeight: "100vh", 131 | overflow: "auto", 132 | fontSize: "16px", 133 | }, 134 | }, 135 | ); 136 | this.element = e("div", { 137 | style: { 138 | backgroundColor: "rgba(0,0,0,0.5)", 139 | position: "fixed", 140 | width: "100vw", 141 | height: "100vh", 142 | zIndex: 999, 143 | overflow: "auto", 144 | display: "none", 145 | flexDirection: "column", 146 | alignItems: "center", 147 | }, 148 | }, 149 | e("div", { 150 | style: { 151 | height: "100%", 152 | display: "flex", 153 | alignItems: "center", 154 | }, 155 | }, 156 | this.container, 157 | ), 158 | ); 159 | } 160 | 161 | show () { 162 | this.element.style.display = "flex"; 163 | } 164 | 165 | hide () { 166 | this.element.style.display = "none"; 167 | } 168 | 169 | // useful function 170 | alert (message) { 171 | const [panel, buttonConfirm] = buildDialog(message, getText("component_overlay_confirm")); 172 | this.addChild(panel); 173 | return new Promise((resolve) => { 174 | buttonConfirm.onclick = () => { 175 | this.removeChild(panel); 176 | resolve(); 177 | }; 178 | }); 179 | } 180 | 181 | confirm (message) { 182 | const [panel, buttonConfirm, buttonCancel] = buildDialog(message, getText("component_overlay_confirm"), getText("component_overlay_cancel")); 183 | this.addChild(panel); 184 | return new Promise((resolve) => { 185 | buttonConfirm.onclick = () => { 186 | this.removeChild(panel); 187 | resolve(true); 188 | }; 189 | buttonCancel.onclick = () => { 190 | this.removeChild(panel); 191 | resolve(false); 192 | }; 193 | }); 194 | } 195 | 196 | prompt (message, defaultText = "") { 197 | const [element, inputElement] = buildPromptBody(message, defaultText); 198 | const [panel, buttonConfirm, buttonCancel] = buildDialog(element, getText("component_overlay_confirm"), getText("component_overlay_cancel")); 199 | this.addChild(panel); 200 | return new Promise((resolve) => { 201 | buttonConfirm.onclick = () => { 202 | this.removeChild(panel); 203 | resolve(inputElement.value); 204 | }; 205 | buttonCancel.onclick = () => { 206 | this.removeChild(panel); 207 | resolve(null); 208 | }; 209 | }); 210 | } 211 | 212 | /** 213 | * build a dialog, return a dialog object 214 | * @param {string|HTMLElement} body dialog content 215 | * @param {(event:MouseEvent)=>void|(event:MouseEvent)=>boolean|(event:MouseEvent)=>Promise} onConfirm Confirm button callback, return true to prevent dialog close, return Promise and resolve it to close dialog later 216 | * @param {(event:MouseEvent)=>void|(event:MouseEvent)=>boolean|(event:MouseEvent)=>Promise} onCancel Cancel button callback, return true to prevent dialog close, return Promise and resolve it to close dialog later 217 | * @returns {{element:HTMLElement, buttonConfirm:HTMLElement, buttonCancel:HTMLElement, show:()=>void, hide:()=>void}} 218 | */ 219 | dialog (body, onConfirm, onCancel) { 220 | const [panel, buttonConfirm, buttonCancel] = buildDialog(body, getText("component_overlay_confirm"), getText("component_overlay_cancel")); 221 | const show = () => { 222 | this.addChild(panel); 223 | }; 224 | const hide = () => { 225 | this.removeChild(panel); 226 | }; 227 | buttonConfirm.onclick = (event) => { 228 | if (typeof (onConfirm) !== "function") { 229 | this.removeChild(panel); 230 | return; 231 | } 232 | const res = onConfirm(event); 233 | if (res instanceof Promise) { 234 | // return Promise and resolve to async close 235 | res.then(() => { 236 | this.removeChild(panel); 237 | }).catch(() => { 238 | // do nothing 239 | }); 240 | } else if (!res) { 241 | // return true value to cancel close 242 | this.removeChild(panel); 243 | } 244 | }; 245 | buttonCancel.onclick = (event) => { 246 | if (typeof (onCancel) !== "function") { 247 | this.removeChild(panel); 248 | return; 249 | } 250 | const res = onCancel(event); 251 | if (res instanceof Promise) { 252 | res.then(() => { 253 | this.removeChild(panel); 254 | }).catch(() => {}); 255 | } else if (!res) { 256 | this.removeChild(panel); 257 | } 258 | }; 259 | return { element: panel, buttonConfirm, buttonCancel, show, hide }; 260 | } 261 | 262 | loading () { 263 | const loadinigPanel = buildLoadingOverlay(); 264 | this.addChild(loadinigPanel); 265 | return () => { this.removeChild(loadinigPanel); }; 266 | } 267 | 268 | // base function 269 | /** 270 | * replace overlay child 271 | * @param {Node} child 272 | */ 273 | replaceChild (child) { 274 | this.container.innerHTML = ""; 275 | this.container.appendChild(child); 276 | if (child) { 277 | this.show(); 278 | } else { 279 | this.hide(); 280 | } 281 | } 282 | 283 | addChild (child) { 284 | this.container.appendChild(child); 285 | this.show(); 286 | } 287 | 288 | removeChild (child) { 289 | this.container.removeChild(child); 290 | if (this.container.childNodes.length <= 0) { 291 | this.hide(); 292 | } 293 | } 294 | 295 | // advance function 296 | } 297 | const overlay = new Overlay(); 298 | 299 | export default overlay; 300 | --------------------------------------------------------------------------------