├── 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 |
--------------------------------------------------------------------------------