From edd2cbf52645bbf2f1521ddee110c64985a5908c Mon Sep 17 00:00:00 2001 From: mamingshuai Date: Wed, 2 Jun 2021 00:30:39 +0800 Subject: [PATCH] update OpenHarmony 2.0 Canary --- .eslintrc | 101 + .gitattributes | 15 + BUILD.gn | 80 + LICENSE | 177 + NOTICE | 283 ++ README.OpenSource | 11 + README.en.md | 36 - README.md | 74 +- build.js | 87 + js_framework_build.sh | 58 + package-lock.json | 3153 +++++++++++++++++ package.json | 43 + runtime/lib.d.ts | 25 + runtime/main/app/App.ts | 169 + runtime/main/app/bundle.ts | 199 ++ runtime/main/app/helper.ts | 35 + runtime/main/app/index.ts | 195 + runtime/main/app/map.ts | 19 + runtime/main/config.ts | 19 + runtime/main/extend/dpi/Dpi.ts | 110 + runtime/main/extend/dpi/index.ts | 20 + runtime/main/extend/i18n/I18n.ts | 206 ++ runtime/main/extend/i18n/index.ts | 20 + runtime/main/extend/mediaquery/mediaQuery.ts | 416 +++ .../extend/mediaquery/pluginMediaQuery.ts | 130 + runtime/main/extend/mediaquery/plugins.ts | 32 + .../main/extend/systemplugin/systemPlugins.js | 502 +++ runtime/main/index.ts | 33 + runtime/main/manage/event/CallbackManager.ts | 103 + runtime/main/manage/event/TaskCenter.ts | 232 ++ runtime/main/manage/event/bridge.ts | 97 + .../main/manage/event/callbackIntercept.ts | 146 + runtime/main/manage/instance/life.ts | 83 + runtime/main/manage/instance/misc.ts | 41 + runtime/main/manage/instance/register.ts | 32 + runtime/main/model/compiler.ts | 748 ++++ runtime/main/model/directive.ts | 844 +++++ runtime/main/model/domHelper.ts | 269 ++ runtime/main/model/events.ts | 146 + runtime/main/model/index.ts | 862 +++++ runtime/main/model/pageLife.ts | 290 ++ runtime/main/model/selector.ts | 350 ++ runtime/main/model/vmOptions.ts | 56 + runtime/main/page/Image.ts | 113 + runtime/main/page/Page.ts | 237 ++ runtime/main/page/api/Differ.ts | 129 + runtime/main/page/api/index.ts | 43 + runtime/main/page/api/misc.ts | 170 + runtime/main/page/entry/bundle.ts | 112 + runtime/main/page/entry/init.ts | 163 + runtime/main/page/index.ts | 24 + runtime/main/page/register.ts | 218 ++ runtime/main/reactivity/LICENSE | 21 + runtime/main/reactivity/array.js | 97 + runtime/main/reactivity/dep.js | 71 + runtime/main/reactivity/object.js | 90 + runtime/main/reactivity/observer.js | 265 ++ runtime/main/reactivity/state.js | 125 + runtime/main/reactivity/watcher.js | 217 ++ runtime/main/util/LICENSE | 21 + runtime/main/util/index.js | 106 + runtime/main/util/props.js | 38 + runtime/main/util/shared.js | 80 + runtime/preparation/index.ts | 22 + runtime/preparation/init.ts | 194 + runtime/preparation/methods.ts | 162 + runtime/preparation/service.ts | 76 + runtime/utils/index.ts | 42 + runtime/utils/utils.ts | 134 + runtime/vdom/Comment.ts | 45 + runtime/vdom/Document.ts | 240 ++ runtime/vdom/DocumentElement.ts | 88 + runtime/vdom/Element.ts | 895 +++++ runtime/vdom/NativeElementClassFactory.ts | 77 + runtime/vdom/Node.ts | 245 ++ runtime/vdom/index.ts | 30 + test/fakeLog.ts | 36 + test/lib.ts | 25 + test/ut/app/bundle.ts | 109 + test/ut/app/index.ts | 67 + test/ut/extend/dpi.ts | 73 + test/ut/extend/i18n.ts | 156 + test/ut/manage/bridge.ts | 123 + test/ut/model/directive.ts | 149 + test/ut/model/index.ts | 236 ++ test/ut/runtime.ts | 226 ++ test/ut/utils.ts | 107 + tsconfig.json | 34 + 88 files changed, 16417 insertions(+), 61 deletions(-) create mode 100644 .eslintrc create mode 100644 .gitattributes create mode 100644 BUILD.gn create mode 100644 LICENSE create mode 100755 NOTICE create mode 100644 README.OpenSource delete mode 100644 README.en.md create mode 100644 build.js create mode 100755 js_framework_build.sh create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 runtime/lib.d.ts create mode 100644 runtime/main/app/App.ts create mode 100644 runtime/main/app/bundle.ts create mode 100644 runtime/main/app/helper.ts create mode 100644 runtime/main/app/index.ts create mode 100644 runtime/main/app/map.ts create mode 100644 runtime/main/config.ts create mode 100644 runtime/main/extend/dpi/Dpi.ts create mode 100644 runtime/main/extend/dpi/index.ts create mode 100644 runtime/main/extend/i18n/I18n.ts create mode 100644 runtime/main/extend/i18n/index.ts create mode 100644 runtime/main/extend/mediaquery/mediaQuery.ts create mode 100644 runtime/main/extend/mediaquery/pluginMediaQuery.ts create mode 100644 runtime/main/extend/mediaquery/plugins.ts create mode 100644 runtime/main/extend/systemplugin/systemPlugins.js create mode 100644 runtime/main/index.ts create mode 100644 runtime/main/manage/event/CallbackManager.ts create mode 100644 runtime/main/manage/event/TaskCenter.ts create mode 100644 runtime/main/manage/event/bridge.ts create mode 100644 runtime/main/manage/event/callbackIntercept.ts create mode 100644 runtime/main/manage/instance/life.ts create mode 100644 runtime/main/manage/instance/misc.ts create mode 100644 runtime/main/manage/instance/register.ts create mode 100644 runtime/main/model/compiler.ts create mode 100644 runtime/main/model/directive.ts create mode 100644 runtime/main/model/domHelper.ts create mode 100644 runtime/main/model/events.ts create mode 100755 runtime/main/model/index.ts create mode 100644 runtime/main/model/pageLife.ts create mode 100644 runtime/main/model/selector.ts create mode 100644 runtime/main/model/vmOptions.ts create mode 100644 runtime/main/page/Image.ts create mode 100644 runtime/main/page/Page.ts create mode 100644 runtime/main/page/api/Differ.ts create mode 100644 runtime/main/page/api/index.ts create mode 100644 runtime/main/page/api/misc.ts create mode 100644 runtime/main/page/entry/bundle.ts create mode 100644 runtime/main/page/entry/init.ts create mode 100644 runtime/main/page/index.ts create mode 100644 runtime/main/page/register.ts create mode 100644 runtime/main/reactivity/LICENSE create mode 100644 runtime/main/reactivity/array.js create mode 100644 runtime/main/reactivity/dep.js create mode 100644 runtime/main/reactivity/object.js create mode 100644 runtime/main/reactivity/observer.js create mode 100644 runtime/main/reactivity/state.js create mode 100644 runtime/main/reactivity/watcher.js create mode 100644 runtime/main/util/LICENSE create mode 100644 runtime/main/util/index.js create mode 100644 runtime/main/util/props.js create mode 100644 runtime/main/util/shared.js create mode 100644 runtime/preparation/index.ts create mode 100644 runtime/preparation/init.ts create mode 100644 runtime/preparation/methods.ts create mode 100644 runtime/preparation/service.ts create mode 100644 runtime/utils/index.ts create mode 100644 runtime/utils/utils.ts create mode 100644 runtime/vdom/Comment.ts create mode 100644 runtime/vdom/Document.ts create mode 100644 runtime/vdom/DocumentElement.ts create mode 100644 runtime/vdom/Element.ts create mode 100644 runtime/vdom/NativeElementClassFactory.ts create mode 100644 runtime/vdom/Node.ts create mode 100644 runtime/vdom/index.ts create mode 100644 test/fakeLog.ts create mode 100644 test/lib.ts create mode 100644 test/ut/app/bundle.ts create mode 100644 test/ut/app/index.ts create mode 100644 test/ut/extend/dpi.ts create mode 100644 test/ut/extend/i18n.ts create mode 100644 test/ut/manage/bridge.ts create mode 100644 test/ut/model/directive.ts create mode 100644 test/ut/model/index.ts create mode 100644 test/ut/runtime.ts create mode 100644 test/ut/utils.ts create mode 100644 tsconfig.json diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..3ca3f736 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,101 @@ +{ + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, + + "env": { + "es6": true, + "node": true, + "mocha": true + }, + + "globals": { + "ace": false, + "aceConsole": false, + "aceapp": false, + "markupState": false, + "notifyTrimMemory": false, + "i18nPluralRules": false, + "compileAndRunBundle": false, + "language": false + }, + + "extends": "eslint:recommended", + + "parser": "@typescript-eslint/parser", + + "rules": { + "camelcase": [2, { "properties": "never" }], + "accessor-pairs": 2, + "arrow-spacing": 2, + "block-spacing": 2, + "brace-style": 2, + "comma-dangle": 2, + "comma-spacing": 2, + "comma-style": 2, + "curly": 2, + "dot-location": [2, "property"], + "eol-last": 2, + "eqeqeq": 2, + "indent": [2, 2, { "SwitchCase": 1 }], + "key-spacing": 2, + "keyword-spacing": 2, + "new-cap": 2, + "new-parens": 2, + "no-array-constructor": 2, + "no-caller": 2, + "no-eval": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-extra-parens": 2, + "no-floating-decimal": 2, + "no-implied-eval": 2, + "no-iterator": 2, + "no-label-var": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-multi-spaces": 2, + "no-multi-str": 2, + "no-multiple-empty-lines": [2, { "max": 1 }], + "no-new-object": 2, + "no-new-wrappers": 2, + "no-octal-escape": 2, + "no-proto": 2, + "no-return-assign": 2, + "no-self-compare": 2, + "no-sequences": 2, + "func-call-spacing": 2, + "no-throw-literal": 2, + "no-trailing-spaces": 2, + "no-undef-init": 2, + "no-unmodified-loop-condition": 2, + "no-unneeded-ternary": [2, { "defaultAssignment": false }], + "no-unused-vars": [2, { "vars": "all", "args": "none" }], + "no-useless-computed-key": 2, + "no-useless-constructor": 2, + "no-whitespace-before-property": 2, + "one-var": [2, { "initialized": "never" }], + "padded-blocks": [2, "never"], + "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}], + "semi": 2, + "semi-spacing": 2, + "space-before-blocks": 2, + "space-before-function-paren": [2, "never"], + "space-in-parens": 2, + "space-infix-ops": 2, + "space-unary-ops": [2, { "words": true, "nonwords": false }], + "spaced-comment": 2, + "template-curly-spacing": 2, + "wrap-iife": [2, "any"], + "no-var": 2, + "prefer-const": 2, + "array-bracket-spacing": 2 + }, + + "settings": { + "flowtype": { + "onlyFilesWithFlowAnnotation": true + } + } +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..51c63e29 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ +*.tgz filter=lfs diff=lfs merge=lfs -text +*.trp filter=lfs diff=lfs merge=lfs -text +*.apk filter=lfs diff=lfs merge=lfs -text +*.jar filter=lfs diff=lfs merge=lfs -text +*.mp4 filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.asm filter=lfs diff=lfs merge=lfs -text +*.8svn filter=lfs diff=lfs merge=lfs -text +*.9svn filter=lfs diff=lfs merge=lfs -text +*.dylib filter=lfs diff=lfs merge=lfs -text +*.exe filter=lfs diff=lfs merge=lfs -text +*.a filter=lfs diff=lfs merge=lfs -text +*.so filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text +*.dll filter=lfs diff=lfs merge=lfs -text diff --git a/BUILD.gn b/BUILD.gn new file mode 100644 index 00000000..71ccd0ab --- /dev/null +++ b/BUILD.gn @@ -0,0 +1,80 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("//foundation/ace/ace_engine/ace_config.gni") + +prebuilt_js_path = + get_label_info(":gen_jsf_c", "root_out_dir") + "/dist/strip.native.min.js" + +snapshot_path = + get_label_info(":gen_snapshot", "target_out_dir") + "/strip.native.min.js" + +action("gen_snapshot") { + script = "//third_party/jsframework/js_framework_build.sh" + + js_framework = "//third_party/jsframework/runtime" + node_modules = "//prebuilts/build-tools/common/js-framework/node_modules" + + buildfile = "//third_party/jsframework/build.js" + + package_file = "//third_party/jsframework/package.json" + tsconfig = "//third_party/jsframework/tsconfig.json" + eslint = "//third_party/jsframework/.eslintrc" + test_file = "//third_party/jsframework/test" + + nodejs_path = + "//prebuilts/build-tools/common/nodejs/node-v12.18.4-linux-x64/bin/node" + + args = [ + rebase_path(buildfile, root_build_dir), + rebase_path(nodejs_path, root_build_dir), + rebase_path(js_framework, root_build_dir), + rebase_path(node_modules, root_build_dir), + rebase_path(package_file, root_build_dir), + rebase_path(tsconfig, root_build_dir), + rebase_path(eslint, root_build_dir), + rebase_path(test_file, root_build_dir), + ] + + outputs = [ snapshot_path ] +} + +jsfc_path = get_label_info(":jsf", "target_out_dir") + "/src/jsf.c" + +action("gen_jsf_c") { + visibility = [ ":*" ] # Only targets in this file can depend on this. + + deps = [ ":gen_snapshot" ] + deps += [ "//third_party/quickjs:qjsc(${host_toolchain})" ] + + script = get_label_info("//third_party/quickjs:qjsc(${host_toolchain})", + "root_out_dir") + "/ace/ace_engine_full/qjsc" + + args = [ + "-o", + rebase_path(jsfc_path), + "-N", + "js_framework", + "-c", + rebase_path(prebuilt_js_path), + ] + + inputs = [ snapshot_path ] + outputs = [ jsfc_path ] +} + +ohos_static_library("jsf") { + deps = [ ":gen_jsf_c" ] + sources = [ jsfc_path ] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..8b95865a --- /dev/null +++ b/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/NOTICE b/NOTICE new file mode 100755 index 00000000..463c3b4d --- /dev/null +++ b/NOTICE @@ -0,0 +1,283 @@ +THIRD PARTY OPEN SOURCE SOFTWARE NOTICE + +Please note we provide an open source software notice for the third +party open source software along with this software and/or this software +component contributed by Huawei (in the following just “this SOFTWARE”). +The open source software licenses are granted by the respective right holders. + +Warranty Disclaimer +THE OPEN SOURCE SOFTWARE IN THIS SOFTWARE IS DISTRIBUTED IN THE HOPE THAT +IT WILL BE USEFUL, BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED +WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. +SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + +Copyright Notice and License Texts +Software: weex 0.28.0 +Copyright notice: +Apache Weex +Copyright(C) 2019 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +License: Apache License 2.0 + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +============================================================================== + +Submodules: + +It includes a number of submodules with separate copyright notices +and license terms. Your use of these submodules is subject to the terms and +conditions of the following licenses. + +This product bundles vuejs v1.0.12, which is available under a +"MIT" license. For details, see https://github.com/vuejs/vue/blob/v1.0.12/LICENSE and following files: + runtime/main/reactivity/array.js + runtime/main/reactivity/dep.js + runtime/main/reactivity/object.js + runtime/main/reactivity/observer.js + runtime/main/reactivity/state.js + runtime/main/reactivity/watcher.js + runtime/main/util/index.js + runtime/main/util/props.js + runtime/main/util/shared.js + + The MIT License (MIT) + + Copyright (c) 2013-2014 Yuxi Evan You + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +Software: css-what v2.1.3 +Copyright notice: +Copyright (c) Felix Böhm +All rights reserved. + +License: BSD-2-Clause License + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.OpenSource b/README.OpenSource new file mode 100644 index 00000000..b3dbf864 --- /dev/null +++ b/README.OpenSource @@ -0,0 +1,11 @@ +[ + { + "Name": "weex", + "License": "Apache License V2.0", + "License File": "LICENSE", + "Version Number": "0.28.0", + "Owner": "leijie4@huawei.com", + "Upstream URL": "https://weex.apache.org", + "Description": "A framework for building performant mobile cross-platform UI." + } +] diff --git a/README.en.md b/README.en.md deleted file mode 100644 index ae2ce7ad..00000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# third_party_jsframework - -#### Description -Third-party open-source software jsframework | 三方开源软件jsframework - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md index d595c943..23b70ece 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,61 @@ -# third_party_jsframework +# JSFramework -#### 介绍 -Third-party open-source software jsframework | 三方开源软件jsframework +**The JSFramework is a framework for building mobile cross-platform UI written in TypeScript.** -#### 软件架构 -软件架构说明 +## File Structure +- `runtime/`:all source code + - `./main/`:JS bundle parsing, data methods, event methods, reactivity, etc. + - `./preparation/`:JS framework initialization + - `./utils/`:some utils + - `./vdom/`:VM compilation +- `test/ut/`:unit test file +- `.eslintrc`:eslint configure +- `BUILD.gn`:compiling file of JS UI framework for NinjaJS +- `build.js`:build JS framework +- `js_framework_build.sh`:script file for JS framework building +- `LICENSE`:Apache License +- `NOTICE`:third party open source software notice +- `package.json`:define what libraries will be installed into node_modules when you run `npm install` +- `tsconfig.json`:the compiler options required to compile the project +## Usage -#### 安装教程 +Here are simplified instructions to how to get started. The following commands are the same both on **Windows** and **Mac** platforms. -1. xxxx -2. xxxx -3. xxxx +### 1. Prerequisites -#### 使用说明 +Please make sure that the following commands work before trying to build: -1. xxxx -2. xxxx -3. xxxx +``` +> npm -v + 6.14.8 +> node -v + v12.18.3 +``` +Your `npm` and `node` should be of a later version. You can upgrade them to the **latest stable version**. +### 2. Installing -#### 参与贡献 +RollUp tool for packaging has been configured in `build.js`. So after the preceding conditions are met, we can start installing right now. -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +First, we go to the root directory of the project: +``` +cd .. +cd third_party/jsframework/ +``` +And then install the dependencies: +``` +npm install +``` +**Note**: If some errors occur, delete the generated package `node_modules ` and run `npm install` again. +### 3. Building -#### 特技 +There are two ways for building JS framework: +``` +1. npm run build +2. in the root dir: run the build.sh script, which is built by gn +``` -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +## ESLint + +You can config more babel and ESLint plugins in `.eslintrc`. **You'd better make sure there are no errors of esLint rules after you change them.** diff --git a/build.js b/build.js new file mode 100644 index 00000000..adc4a890 --- /dev/null +++ b/build.js @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const fs = require('fs'); + +const path = require('path'); + +const rollup = require('rollup'); + +const resolve = require('rollup-plugin-node-resolve'); + +const commonjs = require('rollup-plugin-commonjs'); + +const json = require('rollup-plugin-json'); + +const buble = require('rollup-plugin-buble'); + +const typescript = require('rollup-plugin-typescript2'); + +const { uglify } = require('rollup-plugin-uglify'); + +const { + eslint +} = require('rollup-plugin-eslint'); + +const frameworkBanner = `var global=this; var process={env:{}}; ` + `var setTimeout=global.setTimeout;\n`; + +const onwarn = warning => { + // Silence circular dependency warning + if (warning.code === 'CIRCULAR_DEPENDENCY') { + return; + } + console.warn(`(!) ${warning.message}`); +}; + +const tsPlugin = typescript({ + tsconfig: path.resolve(__dirname, 'tsconfig.json'), + check: true +}); + +const esPlugin = eslint({ + include: ['**/*.ts'], + exclude: ['node_modules/**', 'lib/**'] +}); + +const configInput = { + input: path.resolve(__dirname, 'runtime/preparation/index.ts'), + onwarn, + plugins: [esPlugin, tsPlugin, json(), resolve(), commonjs(), buble(), uglify()] +}; + +const configOutput = { + file: path.resolve(__dirname, 'dist/strip.native.min.js'), + format: 'umd', + banner: frameworkBanner +}; + +rollup.rollup(configInput).then(bundle => { + bundle.write(configOutput).then(() => { + countSize(configOutput.file); + }); +}); + +function countSize(filePath) { + const file = path.relative(__dirname, filePath); + fs.stat(filePath, function(error, stats) { + if (error) { + console.error('file size is wrong'); + } else { + const size = (stats.size / 1024).toFixed(2) + 'KB'; + console.log(`generate snapshot file: ${file}...\nthe snapshot file size: ${size}...`); + } + }); +} + diff --git a/js_framework_build.sh b/js_framework_build.sh new file mode 100755 index 00000000..196330cb --- /dev/null +++ b/js_framework_build.sh @@ -0,0 +1,58 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#! /bin/bash +set -e +echo "copy source code..." +prebuiltsPath="../../prebuilts" + +# copy dependency file to generate dir of gn +# the params come from .gn + +# $2 => node $4 => node_modules +cp -r $3 ./ +cp -f $5 ./ + +if [ -d "$prebuiltsPath" ]; then + echo "copy node_modules..." + cp -r $4 ./ +else + echo "download node_modules..." + npm install + cp -r ./node_modules ../../third_party/jsframework +fi + +cp -f $6 ./ +cp -f $7 ./ +cp -f $1 ./ +cp -r $8 ./ + +if [ -d "$prebuiltsPath" ]; then + echo "prebuilts exists" + $2 build.js + # run unit test + $2 ./node_modules/.bin/mocha -r ts-node/register test/lib.ts test/ut/**/*.ts test/ut/*.ts +else + npm run build + # run unit test + npm run test:unit +fi + +# after running, remove dependency file +rm -rf ./node_modules +rm -rf ./runtime +rm -rf ./tsconfig.json +rm -rf ./build.js +rm -rf ./test +rm -rf ./.eslintrc +rm -rf ./package.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..fc1c1fb0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3153 @@ +{ + "name": "JSFramework", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", + "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@eslint/eslintrc": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.2.tgz", + "integrity": "sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "lodash": "^4.17.19", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + } + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.4", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.4", + "fastq": "^1.6.0" + } + }, + "@sinonjs/commons": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", + "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@sinonjs/formatio": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", + "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^5.0.2" + } + }, + "@sinonjs/samsam": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", + "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, + "@types/estree": { + "version": "0.0.46", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz", + "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "dev": true + }, + "@types/node": { + "version": "14.14.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.25.tgz", + "integrity": "sha512-EPpXLOVqDvisVxtlbvzfyqSsFeQxltFbluZNRndIb8tr9KiBnYNLzrc1N3pyKUCww2RNrfHDViqDWWE1LCJQtQ==", + "dev": true + }, + "@types/resolve": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", + "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@typescript-eslint/eslint-plugin": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.8.2.tgz", + "integrity": "sha512-gQ06QLV5l1DtvYtqOyFLXD9PdcILYqlrJj2l+CGDlPtmgLUzc1GpqciJFIRvyfvgLALpnxYINFuw+n9AZhPBKQ==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "4.8.2", + "@typescript-eslint/scope-manager": "4.8.2", + "debug": "^4.1.1", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.0.0", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.8.2.tgz", + "integrity": "sha512-hpTw6o6IhBZEsQsjuw/4RWmceRyESfAiEzAEnXHKG1X7S5DXFaZ4IO1JO7CW1aQ604leQBzjZmuMI9QBCAJX8Q==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.8.2", + "@typescript-eslint/types": "4.8.2", + "@typescript-eslint/typescript-estree": "4.8.2", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.8.2.tgz", + "integrity": "sha512-u0leyJqmclYr3KcXOqd2fmx6SDGBO0MUNHHAjr0JS4Crbb3C3d8dwAdlazy133PLCcPn+aOUFiHn72wcuc5wYw==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "4.8.2", + "@typescript-eslint/types": "4.8.2", + "@typescript-eslint/typescript-estree": "4.8.2", + "debug": "^4.1.1" + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.8.2.tgz", + "integrity": "sha512-qHQ8ODi7mMin4Sq2eh/6eu03uVzsf5TX+J43xRmiq8ujng7ViQSHNPLOHGw/Wr5dFEoxq/ubKhzClIIdQy5q3g==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.8.2", + "@typescript-eslint/visitor-keys": "4.8.2" + } + }, + "@typescript-eslint/types": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.8.2.tgz", + "integrity": "sha512-z1/AVcVF8ju5ObaHe2fOpZYEQrwHyZ7PTOlmjd3EoFeX9sv7UekQhfrCmgUO7PruLNfSHrJGQvrW3Q7xQ8EoAw==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.8.2.tgz", + "integrity": "sha512-HToGNwI6fekH0dOw3XEVESUm71Onfam0AKin6f26S2FtUmO7o3cLlWgrIaT1q3vjB3wCTdww3Dx2iGq5wtUOCg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.8.2", + "@typescript-eslint/visitor-keys": "4.8.2", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.8.2.tgz", + "integrity": "sha512-Vg+/SJTMZJEKKGHW7YC21QxgKJrSbxoYYd3MEUGtW7zuytHuEcksewq0DUmo4eh/CTNrVJGSdIY9AtRb6riWFw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.8.2", + "eslint-visitor-keys": "^2.0.0" + } + }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true + }, + "acorn-dynamic-import": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", + "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + }, + "dependencies": { + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buble": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/buble/-/buble-0.20.0.tgz", + "integrity": "sha512-/1gnaMQE8xvd5qsNBl+iTuyjJ9XxeaVxAMF86dQ4EyxFJOZtsgOS8Ra+7WHgZTam5IFDtt4BguN0sH0tVTKrOw==", + "dev": true, + "requires": { + "acorn": "^6.4.1", + "acorn-dynamic-import": "^4.0.0", + "acorn-jsx": "^5.2.0", + "chalk": "^2.4.2", + "magic-string": "^0.25.7", + "minimist": "^1.2.5", + "regexpu-core": "4.5.4" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "builtin-modules": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", + "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", + "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.14.0.tgz", + "integrity": "sha512-5YubdnPXrlrYAFCKybPuHIAH++PINe1pmKNc5wQRB9HSbqIK1ywAnntE3Wwua4giKu0bjligf1gLF6qxMGOYRA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@eslint/eslintrc": "^0.2.1", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.0", + "esquery": "^1.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash": "^4.17.19", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", + "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fastq": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", + "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "globby": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz", + "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "requires": { + "@types/estree": "*" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "jest-worker": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", + "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "dev": true, + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "just-extend": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", + "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mocha": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.1.tgz", + "integrity": "sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.4.3", + "debug": "4.2.0", + "diff": "4.0.2", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.14.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.2", + "nanoid": "3.1.12", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "7.2.0", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.0.2", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "nanoid": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", + "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "nise": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", + "integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "os-homedir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-2.0.0.tgz", + "integrity": "sha512-saRNz0DSC5C/I++gFIaJTXoFJMRwiP5zHar5vV3xQ2TkgEw6hDCcU5F272JjUylpiVgBrZNQHnfjkLabTfb92Q==", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.2.tgz", + "integrity": "sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", + "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, + "regexpu-core": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz", + "integrity": "sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.0.2", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.1.0" + } + }, + "regjsgen": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", + "dev": true + }, + "regjsparser": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.7.tgz", + "integrity": "sha512-ib77G0uxsA2ovgiYbCVGx4Pv3PSttAx2vIwidqQzbL2U5S4Q+j00HdSAneSBuyVcMvEnTXMjiGgB+DlXozVhpQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dev": true, + "requires": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rollup": { + "version": "2.32.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.32.1.tgz", + "integrity": "sha512-Op2vWTpvK7t6/Qnm1TTh7VjEZZkN8RWgf0DHbkKzQBwNf748YhXbozHVefqpPp/Fuyk/PQPAnYsBxAEtlMvpUw==", + "dev": true, + "requires": { + "fsevents": "~2.1.2" + } + }, + "rollup-plugin-buble": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/rollup-plugin-buble/-/rollup-plugin-buble-0.19.8.tgz", + "integrity": "sha512-8J4zPk2DQdk3rxeZvxgzhHh/rm5nJkjwgcsUYisCQg1QbT5yagW+hehYEW7ZNns/NVbDCTv4JQ7h4fC8qKGOKw==", + "dev": true, + "requires": { + "buble": "^0.19.8", + "rollup-pluginutils": "^2.3.3" + }, + "dependencies": { + "buble": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/buble/-/buble-0.19.8.tgz", + "integrity": "sha512-IoGZzrUTY5fKXVkgGHw3QeXFMUNBFv+9l8a4QJKG1JhG3nCMHTdEX1DCOg8568E2Q9qvAQIiSokv6Jsgx8p2cA==", + "dev": true, + "requires": { + "acorn": "^6.1.1", + "acorn-dynamic-import": "^4.0.0", + "acorn-jsx": "^5.0.1", + "chalk": "^2.4.2", + "magic-string": "^0.25.3", + "minimist": "^1.2.0", + "os-homedir": "^2.0.0", + "regexpu-core": "^4.5.4" + } + } + } + }, + "rollup-plugin-commonjs": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz", + "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==", + "dev": true, + "requires": { + "estree-walker": "^0.6.1", + "is-reference": "^1.1.2", + "magic-string": "^0.25.2", + "resolve": "^1.11.0", + "rollup-pluginutils": "^2.8.1" + } + }, + "rollup-plugin-eslint": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-eslint/-/rollup-plugin-eslint-7.0.0.tgz", + "integrity": "sha512-u35kXiY11ULeNQGTlRkYx7uGJ/hS/Dx3wj8f9YVC3oMLTGU9fOqQJsAKYtBFZU3gJ8Vt3gu8ppB1vnKl+7gatQ==", + "dev": true, + "requires": { + "eslint": "^6.0.0", + "rollup-pluginutils": "^2.7.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + } + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "rollup-plugin-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-json/-/rollup-plugin-json-4.0.0.tgz", + "integrity": "sha512-hgb8N7Cgfw5SZAkb3jf0QXii6QX/FOkiIq2M7BAQIEydjHvTyxXHQiIzZaTFgx1GK0cRCHOCBHIyEkkLdWKxow==", + "dev": true, + "requires": { + "rollup-pluginutils": "^2.5.0" + } + }, + "rollup-plugin-node-resolve": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz", + "integrity": "sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw==", + "dev": true, + "requires": { + "@types/resolve": "0.0.8", + "builtin-modules": "^3.1.0", + "is-module": "^1.0.0", + "resolve": "^1.11.1", + "rollup-pluginutils": "^2.8.1" + } + }, + "rollup-plugin-tslint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-tslint/-/rollup-plugin-tslint-0.2.2.tgz", + "integrity": "sha512-1yF7bnDALlqgReMIsNDCNXOYqzCw9A8Ur5AS4RzlY7IsRBKB0yb+wx8ZCh0zBcXCeJP5tx36oqec4aX2Pzzttw==", + "dev": true, + "requires": { + "rollup-pluginutils": "^2.0.1", + "tslint": "^5.9.1", + "typescript": "^3.0.0" + }, + "dependencies": { + "typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "dev": true + } + } + }, + "rollup-plugin-typescript2": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.9.0.tgz", + "integrity": "sha512-nM2oP7mduvmAv4HSCmJFRJ18XRhULIuoMMDuqP+wQIjuJbKdm3wJne0dRym2q8DgUmarxE3N3IzJs2xq/GuyPQ==", + "dev": true, + "requires": { + "fs-extra": "^4.0.2", + "resolve": "^1.5.0", + "rollup-pluginutils": "^2.0.1", + "tslib": "^1.8.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "rollup-plugin-uglify": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/rollup-plugin-uglify/-/rollup-plugin-uglify-6.0.4.tgz", + "integrity": "sha512-ddgqkH02klveu34TF0JqygPwZnsbhHVI6t8+hGTcYHngPkQb5MIHI0XiztXIN/d6V9j+efwHAqEL7LspSxQXGw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "jest-worker": "^24.0.0", + "serialize-javascript": "^2.1.2", + "uglify-js": "^3.4.9" + }, + "dependencies": { + "serialize-javascript": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", + "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", + "dev": true + } + } + }, + "rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "requires": { + "estree-walker": "^0.6.1" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "rxjs": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", + "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "sinon": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.2.tgz", + "integrity": "sha512-9Owi+RisvCZpB0bdOVFfL314I6I4YoRlz6Isi4+fr8q8YQsDPoCe5UnmNtKHRThX3negz2bXHWIuiPa42vM8EQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/formatio": "^5.0.1", + "@sinonjs/samsam": "^5.3.0", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-node": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", + "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", + "dev": true + }, + "tslint": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", + "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + } + } + }, + "tsutils": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.20.0.tgz", + "integrity": "sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "typescript": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.2.tgz", + "integrity": "sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ==", + "dev": true + }, + "uglify-js": { + "version": "3.12.6", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.6.tgz", + "integrity": "sha512-aqWHe3DfQmZUDGWBbabZ2eQnJlQd1fKlMUu7gV+MiTuDzdgDw31bI3wA2jLLsV/hNcDP26IfyEgSVoft5+0SVw==", + "dev": true + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", + "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", + "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", + "dev": true + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", + "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "workerpool": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", + "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + } + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..412ff2df --- /dev/null +++ b/package.json @@ -0,0 +1,43 @@ +{ + "name": "JSFramework", + "version": "1.0.0", + "description": "The JSFramework is a framework for building mobile cross-platform UI written in TypeScript.", + "license": "Apache 2.0", + "main": "index.js", + "scripts": { + "test:lint": "eslint -c .eslintrc --fix test/**/*.ts", + "test:unit": "mocha -r ts-node/register test/lib.ts test/ut/**/*.ts test/ut/*.ts", + "build": "node build.js" + }, + "keywords": [ + "javascript", + "typescript", + "hml", + "css", + "mvvm" + ], + "dependencies": { + "css-what": "2.1.3" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "4.8.2", + "@typescript-eslint/parser": "4.8.2", + "buble": "0.20.0", + "chai": "4.2.0", + "eslint": "7.14.0", + "mocha": "8.2.1", + "rollup": "2.32.1", + "rollup-plugin-buble": "0.19.8", + "rollup-plugin-commonjs": "10.1.0", + "rollup-plugin-eslint": "7.0.0", + "rollup-plugin-json": "4.0.0", + "rollup-plugin-node-resolve": "5.2.0", + "rollup-plugin-tslint": "0.2.2", + "rollup-plugin-typescript2": "0.9.0", + "rollup-plugin-uglify": "6.0.4", + "sinon": "9.2.2", + "ts-node": "9.0.0", + "tslib": "2.0.3", + "typescript": "4.1.2" + } +} diff --git a/runtime/lib.d.ts b/runtime/lib.d.ts new file mode 100644 index 00000000..a7961384 --- /dev/null +++ b/runtime/lib.d.ts @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare var aceConsole; +declare var aceapp; +declare var treeModeParentNode; +declare var global; +declare var ace; +declare var markupState; +declare var notifyTrimMemory; +declare var compileAndRunBundle; +declare var i18nPluralRules; +declare var language; diff --git a/runtime/main/app/App.ts b/runtime/main/app/App.ts new file mode 100644 index 00000000..bf8089c1 --- /dev/null +++ b/runtime/main/app/App.ts @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Page from '../page/index'; + +/** + * This class defines the information of a application. + */ +export class App { + public static pageMap: Map = new Map(); + private _packageName: string; + private _appInstanceId: string; + private _events: object; + private _globalKeys: any[]; + private _appGlobal: ProxyConstructor; + + constructor(packageName: string, appInstanceId: string) { + this._packageName = packageName || 'notset'; + this._appInstanceId = appInstanceId; + this._events = {}; + this._globalKeys = []; + bindGlobal(this); + } + + /** + * PackageName of this App. + * @type {string} + */ + public get packageName() { + return this._packageName; + } + + public set packageName(packageName: string) { + this._packageName = packageName; + } + + /** + * AppInstanceId of this App. + * @type {string} + */ + public get appInstanceId() { + return this._appInstanceId; + } + + public set appInstanceId(appInstanceId: string) { + this._appInstanceId = appInstanceId; + } + + /** + * GlobalKeys of this App. + * @type {*} + */ + public get globalKeys() { + return this._globalKeys; + } + + public set globalKeys(key: any) { + this._globalKeys.push(key); + } + + /** + * AppGlobal of this App. + * @type {ProxyConstructor} + */ + public get appGlobal() { + return this._appGlobal; + } + + public set appGlobal(appGlobal: ProxyConstructor) { + this._appGlobal = appGlobal; + } + + /** + * Bind life cycle of App. + * @param {string} type - Function name of life cycle. + * @param {Function} handler - Function of life cycle. + */ + public onEvent(type: string, handler: Function): void { + if (!type || typeof handler !== 'function') { + return; + } + const events: object = this._events; + const handlerList: Function[] = events[type] || []; + handlerList.push(handler); + events[type] = handlerList; + } + + /** + * Emit event. + * @param {string} type - Event of type. + * @param {*} errors + */ + public emitEvent(type: string, errors?: any): void { + const events: object = this._events; + const handlerList: Function[] = events[type]; + + if (handlerList) { + handlerList.forEach((handler) => { + handler.call(global.aceapp, errors); + }); + } + } + + /** + * Delete globalKeys of App. + */ + public deleteGlobalKeys(): void { + if (this._globalKeys) { + let i: number = this._globalKeys.length; + while (i--) { + const key: any = this._globalKeys.splice(i, 1)[0]; + if (key === 'setTimeout') { + global[key] = undefined; + } else { + delete global[key]; + } + } + } + } + + /** + * Assign timerAPIs to appGlobal. + * @param {Object} timerAPIs - TimerAPI. + */ + public setTimer(timerAPIs: object): void { + const that = this; + Object.keys(timerAPIs).forEach((api) => { + that._appGlobal[api] = timerAPIs[api]; + }); + } + + /** + * Get page. + * @return {Page} Page. + */ + public getAppInstance(): Page { + return App.pageMap.get(this._appInstanceId); + } +} + +/** + * Assign appGlobal of App. + * @param {App} app - App instance. + */ +function bindGlobal(app: App): void { + app.appGlobal = new Proxy(Object.create(global), { + set(target, key, value, receiver) { + const ret: boolean = Reflect.set(target, key, value, receiver); + if (receiver[key] === target[key]) { + // set in app Global + global[key] = value; + app.globalKeys = key; + } + return ret; + } + }); +} diff --git a/runtime/main/app/bundle.ts b/runtime/main/app/bundle.ts new file mode 100644 index 00000000..e63ec6eb --- /dev/null +++ b/runtime/main/app/bundle.ts @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * 2021.01.08 - Add init global data and hook life cycle of App. + * Copyright (c) 2021 Huawei Device Co., Ltd. + */ + +import { + isModule, + removePrefix, + isApplication, + removeApplicationPrefix, + Log +} from '../../utils/index'; +import { + registerCustomComponent, + requireModule +} from '../page/register'; +import { appMap } from './map'; +import { updateLocale, updateDpi } from './index'; +import Page from '../page/index'; +import { App } from './App'; + +const APP_LIFE_CYCLE_TYPES: string[] = ['onCreate', 'onError', 'onDestroy']; + +/** + * Parse app page code. + * @param {Page} page + * @param {string} packageName - PackageName of App. + * @param {string} name - Name of page. + * @param {*[]} args + */ +export const defineFn = function(page: Page, packageName: string, name?: string, ...args: any[] | null): void { + Log.debug(`Define a page ${name}.`); + const parseContent: Function = args[1]; + let bundleContent: object = null; + + // Function to obtain bundle content. + if (parseContent) { + const pageRequire = (name: string) : any => { + if (isModule(name)) { + const appFunction = (): Page => { + const appPage: Page = appMap[packageName].getAppInstance(); + return appPage || page; + }; + return requireModule(appFunction, removePrefix(name)); + } + }; + const moduleContent = { exports: {} }; + parseContent(pageRequire, moduleContent.exports, moduleContent); + bundleContent = moduleContent.exports; + } + + // Apply bundleContent. + if (isApplication(name)) { + const componetName: string = removeApplicationPrefix(name); + registerCustomComponent(page, componetName, bundleContent); + } +}; + +/** + * Set i18n and dpi data, hook life cycle of App. + * @param {Page} page + * @param {string} packageName - PackageName of App. + * @param {string} name - Name of page. + * @param {*} config + * @param {*} data + * @return {*} + */ +export function bootstrap(page: Page, packageName: string, name: string, config: any, data: any): any { + Log.debug(`Bootstrap for ${name}.`); + Log.debug(`${config} ${data}`); + + // Check component name. + let componentName: string; + if (isApplication(name)) { + componentName = removeApplicationPrefix(name); + } else { + return new Error(`Wrong component name: ${name}.`); + } + + // Init global data when page first load, + // global.aceapp.$data means config.data in manifest.json, can add new data by this.$app.$data api. + if (page.options && page.options.appCreate) { + global.aceapp = {}; + global.aceapp.$data = page.options.appGlobalData || {}; + + // Set i18n and dpi data. + if (page.options.i18n) { + updateLocale(page.options.i18n); + } + if (page.options.resourcesConfiguration) { + updateDpi(page.options.resourcesConfiguration); + } + } + if (page.customComponentMap) { + const app: App = appMap[packageName]; + if (app) { + const options: object = page.customComponentMap[componentName] || {}; + const aceapp: any = global.aceapp || {}; + for (const key in options) { + if (!isReserved(key)) { + app[key] = options[key]; + aceapp[key] = options[key]; + } + } + aceapp.$def = aceapp; + aceapp._def = aceapp.$def; + aceapp._data = aceapp.$data; + + // Exit api to $app. + aceapp.exit = function(): void { + }; + APP_LIFE_CYCLE_TYPES.forEach((type) => { + app.onEvent(`hook:${type}`, options[type]); + }); + + // Last fire on Create. + Log.debug(`Page "onCreate" lifecycle in app(${app.packageName}).`); + app.emitEvent('hook:onCreate'); + } + } +} + +/** + * Check input param is onCreate or onDestroy. + * @param {string} key + * @return {boolean} + */ +function isReserved(key: string): boolean { + if (key === 'onCreate' || key === 'onDestroy') { + return true; + } + return false; +} + +/** + * Return timerAPIs. + * @param {Page} page - Page + * @return {Object} + */ +export function genTimerAPI(page: Page): object { + const timerAPIs: object = {}; + + // Timer APIs polyfill in native + const timer: any = page.requireModule('timer'); + const animation = page.requireModule('animation'); + Object.assign(timerAPIs, { + setTimeout: (...args) => { + const handler = () => { + args[0](...args.slice(2)); + }; + timer.setTimeout(handler, args[1]); + return page.doc.taskCenter.callbackManager.currCallbackId.toString(); + }, + setInterval: (...args) => { + const handler = () => { + args[0](...args.slice(2)); + }; + timer.setInterval(handler, args[1]); + return page.doc.taskCenter.callbackManager.currCallbackId.toString(); + }, + clearTimeout: (n) => { + timer.clearTimeout(n); + page.doc.taskCenter.callbackManager.remove(n); + }, + clearInterval: (n) => { + timer.clearInterval(n); + page.doc.taskCenter.callbackManager.remove(n); + }, + requestAnimationFrame: (...args) => { + const handler = function(timestamp) { + args[0](timestamp, ...args.slice(1)); + }; + animation.requestAnimationFrame(handler); + return page.doc.taskCenter.callbackManager.currCallbackId.toString(); + }, + cancelAnimationFrame: (n) => { + animation.cancelAnimationFrame(n); + } + }); + return timerAPIs; +} diff --git a/runtime/main/app/helper.ts b/runtime/main/app/helper.ts new file mode 100644 index 00000000..c972c95b --- /dev/null +++ b/runtime/main/app/helper.ts @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { appMap } from './map'; + +/** + * Get appGlobal of App. + * @param {string} packageName - Package name. + * @return {Object | ProxyConstructor} + */ +export function getPageGlobal(packageName: string): object | ProxyConstructor { + if (!appMap[packageName] || !appMap[packageName].appGlobal) { + return Object.create(global); + } + return new Proxy(Object.create(appMap[packageName].appGlobal), { + get(trapTarget, key, receiver) { + if (key === '__proto__') { + return appMap[packageName].appGlobal; + } + return Reflect.get(trapTarget, key, receiver); + } + }); +} diff --git a/runtime/main/app/index.ts b/runtime/main/app/index.ts new file mode 100644 index 00000000..839da988 --- /dev/null +++ b/runtime/main/app/index.ts @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Log } from '../../utils/index'; +import { + defineFn, + bootstrap, + genTimerAPI +} from './bundle'; +import { appMap } from './map'; +import { getPageGlobal } from './helper'; +import { App } from './App'; +import Page from '../page/index'; +import { destroy } from '../page/api/index'; +import { mockSystemPlugin } from '../extend/systemplugin/systemPlugins'; +import { compileBundle } from '../page/entry/init'; +import { removePrefix } from '../util/index'; +import { requireModule } from '../page/register'; + +/** + * Device information for mediaQuery. + */ +export interface MediaQueryInfo { + 'orientation': string; + 'deviceType': string; + 'deviceWidth': string; + 'deviceHeight': string; + 'roundScreen': boolean; + 'width': string; + 'height': string; + 'isInit': boolean; + 'resolution': string; + 'aspectRatio': string; +} + +/** + * Information of App. + */ +export interface Options extends MediaQueryInfo { + 'appInstanceId': string; + 'packageName': string; + 'appCreate': boolean; + 'appCode': string; + 'pcPreview': string; + 'resourcesConfiguration': object; + 'i18n': object; + 'language': string; + 'appGlobalData'?: object; + 'bundleUrl': string; +} + +/** + * Framework Services. + */ +export interface Services { + service: object; + I18n?: Function; + dpi?: Function; +} + +interface ParseOptions { + $app_define$(...args: any[]): void; // eslint-disable-line camelcase + $app_bootstrap$(name: string, config: any, _data: any): void; // eslint-disable-line camelcase + $app_require$(name: string): void; // eslint-disable-line camelcase +} + +const pageMap: Map = App.pageMap; + +/** + * Create app page, run jsbundle code. + * @param {Page} page + * @param {Options} options + * @param {Object} data + * @param {Services} services + */ +export function appCreate(page: Page, options: Options, data: object, services: Services): void { + if (!options || !options.appCreate || !options.appCode) { + return; + } + if (options.pcPreview && options.pcPreview === 'enable') { + mockSystemPlugin(); + } + const packageName: string = page.packageName; + const appPage: Page = new Page(options.appInstanceId, options, packageName); + pageMap.set(appPage.id, appPage); + Log.debug(`Create a page with: ${packageName}.`); + appMap[packageName] = new App(packageName, options.appInstanceId); + const timerAPIs: object = genTimerAPI(appPage); + appMap[packageName].setTimer(timerAPIs); + const code: string = options.appCode; + global.__appProto__ = getPageGlobal(packageName); + + // prepare page env methods + const appDefine = (...args: any[]): void => defineFn(page, packageName, ...args); + const appBootstrap = (name: string, config: any, _data: any): void => { + bootstrap(page, packageName, name, config, _data || data); + Log.debug(`After create a page(${page.id}).`); + }; + + // require in top app(instance) + const appRequireModule = name => requireModule(appPage, removePrefix(name)); + const parseOptions: ParseOptions = { + $app_define$: appDefine, + $app_bootstrap$: appBootstrap, + $app_require$: appRequireModule + }; + + // Function with code and use strict mode. + const functionCode: string = `(function(global){\n\n"use strict";\n\n ${code} \n\n})(this.__appProto__)`; + + // Compile js bundle code and get result. + compileBundle(functionCode, 'app.js', parseOptions, timerAPIs, services); +} + +/** + * Emit onError event. + * @param {string} packageName + * @param {*} errors + */ +export function appError(packageName: string, errors: any): void { + Log.debug(`AppError an app with: ${packageName}.`); + const app: App = appMap[packageName]; + if (!app) { + Log.debug(`AppError an app error ${packageName}.`); + return; + } + Log.debug(`AppError an app error ${packageName}.`); + app.emitEvent('hook:onError', errors); +} + +/** + * Emit onDestroy event. + * @param {string} packageName - Package name. + */ +export function appDestroy(packageName: string): void { + Log.debug(`Destroy an app with: ${packageName}.`); + const app: App = appMap[packageName]; + if (!app) { + Log.error(`Destroy an app error ${packageName}.`); + return; + } + app.emitEvent('hook:onDestroy'); + app.deleteGlobalKeys(); + delete appMap[packageName]; + const appPage: Page = pageMap.get(app.appInstanceId); + if (appPage) { + if (appPage.doc.taskCenter.callbackIsEmpty()) { + appPage.callTasks([{ + module: 'internal.jsResult', + method: 'appDestroyFinish', + args: [] + }]); + destroy(appPage); + pageMap.delete(appPage.id); + } else { + appPage.destroyed = true; + } + } +} + +/** + * Init language resource. + * @param {*} i18nData + */ +export function updateLocale(i18nData: any): void { + if (i18nData) { + global.aceapp._i18n_data_ = { messages: i18nData.resources }; + } else { + global.aceapp._i18n_data_ = null; + } +} + +/** + * Init image dpi. + * @param {Object} dpiData + */ +export function updateDpi(dpiData: object): void { + if (dpiData) { + global.aceapp._dpi_data_ = { images: dpiData }; + } else { + global.aceapp._dpi_data_ = null; + } +} diff --git a/runtime/main/app/map.ts b/runtime/main/app/map.ts new file mode 100644 index 00000000..deb28b8a --- /dev/null +++ b/runtime/main/app/map.ts @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Save App. + */ +export const appMap: object = {}; diff --git a/runtime/main/config.ts b/runtime/main/config.ts new file mode 100644 index 00000000..ded497f7 --- /dev/null +++ b/runtime/main/config.ts @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export default { + nativeComponentMap: { + } +}; diff --git a/runtime/main/extend/dpi/Dpi.ts b/runtime/main/extend/dpi/Dpi.ts new file mode 100644 index 00000000..35b2ccc7 --- /dev/null +++ b/runtime/main/extend/dpi/Dpi.ts @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + getValue, + Log +} from '../../../utils/index'; + +interface dpiConstructor { + new(options: object): Dpi; +} + +export interface DPIInterface { + instance?: { dpi: dpiConstructor }; +} + +interface VMInterface { + $r: Function; + _dpi: Function; +} + +const instances = {}; + +/** + * This class provides multi-resolution display support. + */ +class Dpi { + public image: object; + + constructor(options) { + this.image = options.images; + } + + /** + * Check the value of the key in images. + * @param {string} path - Source path. + * @return {Object | string} The value of the key if found. + */ + public $r(path: string): object | string { + if (typeof path !== 'string') { + Log.warn(`Invalid parameter type: The type of 'path' should be string, not ${typeof path}.`); + return; + } + const images = this.image; + let res; + for (const index in images) { + res = getValue(path, images[index]); + if (res) { + return res; + } + } + return path; + } + + /** + * Extend _dpi to Vm. + * @param {VMInterface} Vm - The Vm. + */ + public extend(Vm: VMInterface): void { + Object.defineProperty(Vm, '_dpi', { + configurable: true, + enumerable: true, + get: function proxyGetter() { + return this.dpi ? this.dpi : global.aceapp.dpi; + } + }); + Vm.$r = function(key: string): string { + const dpi = this._dpi; + return dpi.$r(key); + }; + } +} + +/** + * Init the dpi object. + */ +export default { + create: (id: number): DPIInterface => { + instances[id] = []; + if (typeof global.dpi === 'function') { + return {}; + } + const dpiObject = { + dpi: class extends Dpi { + constructor(options) { + super(options); + instances[id].push(this); + } + } + }; + return { + instance: dpiObject + }; + }, + destroy: (id: number): void => { + delete instances[id]; + } +}; diff --git a/runtime/main/extend/dpi/index.ts b/runtime/main/extend/dpi/index.ts new file mode 100644 index 00000000..de746608 --- /dev/null +++ b/runtime/main/extend/dpi/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import dpi from './Dpi'; + +export default { + dpi +}; diff --git a/runtime/main/extend/i18n/I18n.ts b/runtime/main/extend/i18n/I18n.ts new file mode 100644 index 00000000..82e66b78 --- /dev/null +++ b/runtime/main/extend/i18n/I18n.ts @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + isNull, + getValue, + Log +} from '../../../utils/index'; + +interface I18nConstructor { + new(options: object): I18n; +} + +export interface I18nInterface { + instance?: { I18n: I18nConstructor }; +} + +interface VMInterface { + $t: Function; + $tc: Function; + _i18n: Function; +} + +const instances = {}; + +/** + * This class provide internationalization support. + */ +class I18n { + public locale: string; + public messages: any; + + constructor(options) { + this.locale = options.locale || language; + this.messages = options.messages; + } + + /** + * Provide the '$t' method to import simple resources. + * @param {string} path - The path of language resources which to be translated. + * @param {*} [params] - The values of placeholder. + * @return {*} The translated result. + */ + public $t(path: string, params?: any): any { + if (typeof path !== 'string') { + Log.warn(`Invalid parameter type: The type of 'path' should be string, not ${typeof path}.`); + return; + } + if (!this._hasMessage(this.messages)) { + return path; + } + let value = this._getMessage(this.messages, path); + if (isNull(value)) { + return path; + } + if (Object.prototype.toString.call(value) === '[object Object]' || + Object.prototype.toString.call(value) === '[object Array]') { + return value; + } + value = this._translate(path, value, params); + return value; + } + + /** + * Provide the '$tc' method to import singular and plural resources. + * @param {string} path - The path of language resources which to be translated. + * @param {number} [count] - The number which to be translated. + * @return {*} The translated result. + */ + public $tc(path: string, count?: number): any { + if (typeof path !== 'string') { + Log.warn(`Invalid parameter type: The type of 'path' should be string, not ${typeof path}.`); + return; + } + if (typeof count !== 'number' && !isNull(count)) { + Log.warn(`Invalid parameter type: The type of 'count' should be number, not ${typeof count}.`); + return; + } + if (!this._hasMessage(this.messages)) { + return path; + } + let value = this._getMessage(this.messages, path); + if (isNull(value)) { + return path; + } + if (isNull(count)) { + count = 1; + } + value = this._getChoice(count, path, value); + value = this._translate(path, value, count); + return value; + } + + /** + * Extend _i18n to Vm. + * @param {VMInterface} Vm - The Vm. + */ + public extend(Vm: VMInterface): void { + Object.defineProperty(Vm, '_i18n', { + configurable: true, + enumerable: true, + get: function proxyGetter() { + return this.i18n ? this.i18n : global.aceapp.i18n; + } + }); + Vm.$t = function(path, params) { + const i18n = this._i18n; + return i18n.$t(path, params); + }; + Vm.$tc = function(path, count) { + const i18n = this._i18n; + return i18n.$tc(path, count); + }; + } + + private _hasMessage(message: object[]): boolean { + if (!message || message.length === 0) { + Log.debug('I18n message is null.'); + return false; + } + return true; + } + + private _getMessage(messages: any[], path: string): any { + for (const i in messages) { + const value = getValue(path, messages[i]); + if (!isNull(value)) { + return value; + } + } + return null; + } + + private _getChoice(count: number, path: string, message: any): any { + const pluralChoice = i18nPluralRules.select(count); + if (!pluralChoice) { + Log.debug('PluralChoice is null.'); + return path; + } + return getValue(pluralChoice, message); + } + + private _translate(path: string, value: any, params: any): any { + if (isNull(value)) { + return path; + } + if (Object.prototype.toString.call(params) === '[object Array]') { + value = value.replace(/\{(\d+)\}/g, (_, index) => { + if (index > params.length - 1 || index < 0) { + return ''; + } + return params[index]; + }); + } else if (Object.prototype.toString.call(params) === '[object Object]') { + value = value.replace(/\{(\w+)\}/g, (_, name) => { + if (name in params) { + return params[name]; + } + return ''; + }); + } else if (Object.prototype.toString.call(params) === '[object Number]') { + value = value.replace(/\{count\}/g, params); + } else { + return value; + } + return value; + } +} + +/** + * Init the i18n object. + */ +export default { + create: (id: number): I18nInterface | null => { + instances[id] = []; + if (typeof global.I18n === 'function') { + return {}; + } + const i18nObject = { + I18n: class extends I18n { + constructor(options) { + super(options); + instances[id].push(this); + } + } + }; + return { + instance: i18nObject + }; + }, + destroy: (id: number): void => { + delete instances[id]; + } +}; diff --git a/runtime/main/extend/i18n/index.ts b/runtime/main/extend/i18n/index.ts new file mode 100644 index 00000000..6f83f70f --- /dev/null +++ b/runtime/main/extend/i18n/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import I18n from './I18n'; + +export default { + I18n +}; diff --git a/runtime/main/extend/mediaquery/mediaQuery.ts b/runtime/main/extend/mediaquery/mediaQuery.ts new file mode 100644 index 00000000..1a21faa1 --- /dev/null +++ b/runtime/main/extend/mediaquery/mediaQuery.ts @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Log } from '../../../utils/index'; + +const MEDIA_QUERY_RULE = { + CONDITION_WITH_SCREEN: /^(((only|not)screen)|screen)((and|or|,)\([\w.:><=-]+\))*$/, + CONDITION_WITHOUT_SCREEN: /^\([\w.:><=-]+\)((and|or|,)\([\w.:><=-]+\))*$/, + CONDITION_WITH_AND: /^\([.a-z0-9:>=<-]+\)(and\([.a-z0-9:>=<-]+\))+/, + CSS_LEVEL4_MULTI: /^\(([\d.]+(dpi|dppx|dpcm|px)?)(>|<|>=|<=)[a-z0-9:-]+(>|<|>=|<=)([\d.]+(dpi|dppx|dpcm|px)?)\)$/, + CSS_LEVEL4_LEFT: /^\([^m][a-z-]+(>|<|>=|<=)[\d.]+(dpi|dppx|dpcm|px)?\)$/, + CSS_LEVEL4_RIGHT: /^\([\d.]+(dpi|dppx|dpcm|px)?(>|<|>=|<=)[^m][a-z-]+\)$/, + CSS_LEVEL3_RULE: /^\((min|max)-[a-z-]+:[\d.]+(dpi|dppx|dpcm)?\)$/, + ORIENTATION_RULE: /^\(orientation:[a-z]+\)/, + DEVICETYPE_RULE: /^\(device-type:[a-z]+\)/, + SCREEN_SHAPE_RULE: /^\(round-screen:[a-z]+\)/ +}; + +/** + * Enum for MEDIA ERROR. + * @enum {string} + * @readonly + */ +/* eslint-disable no-unused-vars */ +enum MEDIAERROR { + /** + * SYNTAX Type + */ + SYNTAX = 'SYNTAX', + /** + * NONE Type + */ + NONE = 'NONE', +} +/* eslint-enable no-unused-vars */ + +interface MediaMatchInfo { + status: object; + result: boolean; +} + +const queryHistoryList: Map = new Map(); + +/** + * Match media query condition. + * @param {string} condition - Media query condition. + * @param {Object} mediaStatus - The device information. + * @param {boolean} jsQuery + * @return {boolean} + */ +export function matchMediaQueryCondition(condition: string, mediaStatus: object, jsQuery: boolean): boolean { + if (!condition || !mediaStatus) { + return false; + } + + // If width and height are not initialized, and the query condition includes 'width' or 'height', + // return false directly. + if (mediaStatus['width'] === 0 && (condition.includes('width') || condition.includes('height'))) { + return false; + } + if (jsQuery && queryHistoryList.has(condition)) { + const queryHistory: MediaMatchInfo = queryHistoryList.get(condition); + if (queryHistory && JSON.stringify(queryHistory.status) === JSON.stringify(mediaStatus)) { + return queryHistory.result; + } + } + const result: boolean = doMatchMediaQueryCondition(condition, mediaStatus); + queryHistoryList.set(condition, {status: mediaStatus, result: result}); + return result; +} + +interface FailReason { + type: MEDIAERROR; +} + +/** + * Match media query condition. + * @param {string} condition - Media query condition. + * @param {Object} mediaStatus - The device information. + * @return {boolean} + */ +function doMatchMediaQueryCondition(condition: string, mediaStatus: object): boolean { + const noSpace: string = condition.replace(/\s*/g, ''); + let inverse: boolean = false; + const failReason: FailReason = { type: MEDIAERROR.NONE }; + let noScreen: string; + + // Check if the media query condition is legal. + if (MEDIA_QUERY_RULE.CONDITION_WITH_SCREEN.exec(noSpace)) { + if (noSpace.indexOf('notscreen') !== -1) { + inverse = true; + } + const screenPatt: RegExp = /screen[^and:]/g; + if (screenPatt.exec(noSpace)) { + return !inverse; + } + noScreen = noSpace.replace(/^(only|not)?screen(and)?/g, ''); + if (!noScreen) { + return !inverse; + } + } else if (MEDIA_QUERY_RULE.CONDITION_WITHOUT_SCREEN.exec(noSpace)) { + noScreen = noSpace; + } else { + Log.debug('Illegal condition.'); + failReason.type = MEDIAERROR.SYNTAX; + return false; + } + + // Replace 'or' with comma ','. + const commaCondition: string = noScreen.replace(/or[(]/g, ',('); + + // Remove screen and modifier. + const conditionArr: string[] = commaCondition.split(','); + const len: number = conditionArr.length; + for (let i = 0; i < len; i++) { + if (MEDIA_QUERY_RULE.CONDITION_WITH_AND.exec(conditionArr[i])) { + const result: boolean = parseAndCondtion(conditionArr[i], mediaStatus, failReason); + if (failReason.type === MEDIAERROR.SYNTAX) { + return false; + } + if (i + 1 === len) { + return inverse && !result || !inverse && result; + } + } else { + if (parseSingleCondition(conditionArr[i], mediaStatus, failReason)) { + return !inverse; + } + if (failReason.type === MEDIAERROR.SYNTAX) { + return false; + } + } + } + return inverse; +} + +/** + * Parse single condition, such as: (100 < width). + * @param {string} condition - Single condition. + * @param {Object} mediaStatus - Device info. + * @param {FailReason} failReason - Parse fail reason. + * @return {boolean} + */ +function parseSingleCondition(condition: string, mediaStatus: object, failReason: FailReason): boolean { + if (MEDIA_QUERY_RULE.CSS_LEVEL4_MULTI.exec(condition)) { + if (parseCss4MultiCondition(condition, mediaStatus, failReason)) { + return true; + } + } else if (MEDIA_QUERY_RULE.CSS_LEVEL4_LEFT.exec(condition)) { + if (parseCss4LeftCondtion(condition, mediaStatus, failReason)) { + return true; + } + } else if (MEDIA_QUERY_RULE.CSS_LEVEL4_RIGHT.exec(condition)) { + if (parseCss4RightCondition(condition, mediaStatus, failReason)) { + return true; + } + } else if (MEDIA_QUERY_RULE.CSS_LEVEL3_RULE.exec(condition)) { + if (parseCss3Condition(condition, mediaStatus, failReason)) { + return true; + } + } else if (MEDIA_QUERY_RULE.DEVICETYPE_RULE.exec(condition)) { + if (parseDeviceTypeCondition(condition, mediaStatus, failReason)) { + return true; + } + } else if (MEDIA_QUERY_RULE.ORIENTATION_RULE.exec(condition)) { + if (parseOrientationCondition(condition, mediaStatus, failReason)) { + return true; + } + } else if (MEDIA_QUERY_RULE.SCREEN_SHAPE_RULE.exec(condition)) { + if (parseScreenShapeCondition(condition, mediaStatus, failReason)) { + return true; + } + } else { + Log.debug('Illegal condition'); + failReason.type = MEDIAERROR.SYNTAX; + return false; + } + return false; +} + +/** + * Parse conditions connect with 'and', such as: (100 < width) and (width < 1000). + * @param {string} condition - Conditions connect with 'and'. + * @param {Object} mediaStatus - Device info. + * @param {FailReason} failReason - Parse fail reason. + * @return {boolean} + */ +function parseAndCondtion(condition: string, mediaStatus: object, failReason: FailReason): boolean { + // Split and condition to simple conditions. + const noAnd: string = condition.replace(/and[^a-z]/g, ',('); + const conditionArr: string[] = noAnd.split(','); + if (!conditionArr) { + failReason.type = MEDIAERROR.SYNTAX; + return false; + } + for (let i = 0; i < conditionArr.length; i++) { + if (!parseSingleCondition(conditionArr[i], mediaStatus, failReason)) { + return false; + } + } + return true; +} + +/** + * Parse css4 multi-style condition, such as: (100 < width < 1000). + * @param {string} condition - Css4 multi-style condition. + * @param {Object} mediaStatus - Device info. + * @param {FailReason} failReason - Parse fail reason. + * @return {boolean} + */ +function parseCss4MultiCondition(condition:string, mediaStatus: object, failReason: FailReason): boolean { + const patt: RegExp = /([a-z-]+|[\d.a-z]+|[><=]+)/g; + const feature = condition.match(patt); + if (!feature || feature.length !== 5) { + failReason.type = MEDIAERROR.SYNTAX; + return false; + } + const rcondition: string = '(' + feature[0] + feature[1] + feature[2] + ')'; + const lcondition: string = '(' + feature[2] + feature[3] + feature[4] + ')'; + + return parseCss4RightCondition(rcondition, mediaStatus, failReason) && + parseCss4LeftCondtion(lcondition, mediaStatus, failReason); +} + +/** + * Parse css4 style condition, device info is in the left, such as: (width < 1000). + * @param {string} condition - Css4 style condition. + * @param {Object} mediaStatus - Device info. + * @param {FailReason} failReason - Parse fail reason. + * @return {boolean} + */ +function parseCss4LeftCondtion(condition: string, mediaStatus: object, failReason: FailReason): boolean { + const feature = condition.match(/[a-z-]+|[0-9.]+/g); + if (!feature || feature.length < 2) { + failReason.type = MEDIAERROR.SYNTAX; + return false; + } + const conditionValue: string = feature[1]; + const unit: string = feature.length === 3 ? feature[2] : ''; + const relationship = condition.match(/[><=]+/g); + const statusValue: number = transferValue(mediaStatus[feature[0]], unit); + return calculateExpression(statusValue, relationship[0], conditionValue, failReason); +} + +/** + * Parse css4 style condition, device info is in the right, such as: (1000 < width). + * @param {string} condition - Css4 style condition. + * @param {Object} mediaStatus - Device info. + * @param {FailReason} failReason - Parse fail reason. + * @return {boolean} + */ +function parseCss4RightCondition(condition: string, mediaStatus: object, failReason: FailReason): boolean { + const feature = condition.match(/[a-z-]+|[0-9.]+/g); + if (!feature || feature.length < 2) { + failReason.type = MEDIAERROR.SYNTAX; + return false; + } + const conditionValue: string = feature[0]; + let statusValue: number; + let unit: string; + if (feature.length === 3) { + unit = feature[1]; + statusValue = transferValue(mediaStatus[feature[2]], unit); + } else { + unit = ''; + statusValue = transferValue(mediaStatus[feature[1]], unit); + } + const relationship = condition.match(/[><=]+/g); + return calculateExpression(conditionValue, relationship[0], statusValue, failReason); +} + +/** + * Parse css3 style condition, such as: (min-width: 1000). + * @param {String} condition - Css3 style condition. + * @param {Object} mediaStatus - Device info. + * @param {FailReason} failReason - Parse fail reason. + * @return {boolean} + */ +function parseCss3Condition(condition: string, mediaStatus: object, failReason: FailReason): boolean { + const feature = condition.match(/[a-z-]+|[0-9.]+/g); + if (!feature || feature.length < 2) { + failReason.type = MEDIAERROR.SYNTAX; + return false; + } + const conditionValue: string = feature[1]; + const unit: string = feature.length === 3 ? feature[2] : ''; + let relationship: string; + if (feature[0].match(/^(max-)/)) { + relationship = '<='; + } else if (feature[0].match(/^(min-)/)) { + relationship = '>='; + } else { + failReason.type = MEDIAERROR.SYNTAX; + return false; + } + const status: string = feature[0].replace(/(max|min)-/g, ''); + const statusValue: number = transferValue(mediaStatus[status], unit); + return calculateExpression(statusValue, relationship, conditionValue, failReason); +} + +/** + * Parse screen orientation condition, such as: (orientation: portrait). + * @param {string} condition - Orientation type condition. + * @param {Object} mediaStatus - Device info. + * @param {FailReason} failReason - Parse fail reason. + * @return {boolean} + */ +function parseOrientationCondition(condition: string, mediaStatus: object, failReason: FailReason): boolean { + const orientaton = condition.match(/[a-z-]+/g); + if (!orientaton || orientaton.length !== 2) { + failReason.type = MEDIAERROR.SYNTAX; + return false; + } + return orientaton[1] === mediaStatus['orientation']; +} + +/** + * Parse device type condition, such as: (device-type: tv). + * @param {string} condition - Device type condition. + * @param {Object} mediaStatus - Device info. + * @param {FailReason} failReason - Parse fail reason. + * @return {boolean} + */ +function parseDeviceTypeCondition(condition: string, mediaStatus: object, failReason: FailReason): boolean { + const deviceType = condition.match(/[a-z-]+/g); + if (!deviceType || deviceType.length !== 2) { + failReason.type = MEDIAERROR.SYNTAX; + return false; + } + return deviceType[1] === mediaStatus['device-type']; +} + +/** + * Parse screen shape condition, such as: (round-screen: true). + * @param {string} condition - Screen shape condition. + * @param {Object} mediaStatus - Device info. + * @param {FailReason} failReason - Parse fail reason. + * @return {boolean} + */ +function parseScreenShapeCondition(condition: string, mediaStatus: object, failReason: FailReason): boolean { + const shape = condition.match(/[a-z-]+/g); + if (!shape || shape.length !== 2) { + failReason.type = MEDIAERROR.SYNTAX; + return false; + } + return shape[1] === mediaStatus['round-screen'].toString(); +} + +/** + * Transfer unit the same with condition value unit. + * @param {number} value - Device value should be transfer unit the same with condition value. + * @param {string} unit - Condition value unit, such as: dpi/dpcm/dppx. + * @return {number} + */ +function transferValue(value: number, unit: string): number { + let transfer: number; + switch (unit) { + case 'dpi': + transfer = 96; + break; + case 'dpcm': + transfer = 36; + break; + default: + transfer = 1; + } + return value * transfer; +} + +/** + * Calculate expression result. + * @param {number|string} leftValue - Number device value. String condition value. + * @param {string} relationship - >=/>/<=/< + * @param {number|string} rightValue - Number device value. String condition value. + * @param {FailReason} failReason - Parse fail reason. + * @return {boolean} + */ +function calculateExpression(leftValue: number | string, relationship: string, + rightValue: number | string, failReason: FailReason): boolean { + let lvalue: number | string; + let rvalue: number | string; + if (typeof leftValue === 'string') { + lvalue = leftValue.match(/[\d]+\.[\d]+/) ? parseFloat(leftValue) : parseInt(leftValue); + rvalue = rightValue; + } else if (typeof rightValue === 'string') { + lvalue = leftValue; + rvalue = rightValue.match(/[\d]+\.[\d]+/) ? parseFloat(rightValue) : parseInt(rightValue); + } else { + failReason.type = MEDIAERROR.SYNTAX; + return false; + } + switch (relationship) { + case '>=': + return lvalue >= rvalue; + case '>': + return lvalue > rvalue; + case '<=': + return lvalue <= rvalue; + case '<': + return lvalue < rvalue; + default: + failReason.type = MEDIAERROR.SYNTAX; + } + return false; +} diff --git a/runtime/main/extend/mediaquery/pluginMediaQuery.ts b/runtime/main/extend/mediaquery/pluginMediaQuery.ts new file mode 100644 index 00000000..97420b8e --- /dev/null +++ b/runtime/main/extend/mediaquery/pluginMediaQuery.ts @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { matchMediaQueryCondition } from './mediaQuery'; + +interface MediaQueryModule { + addListener: Function; + getDeviceType: Function; +} + +interface MatchData { + matches: boolean; +} + +class MediaQueryList { + private _matches: boolean; + private _condition: string; + private _onchange: any; + private _listeners: any[]; + + constructor(condition: string) { + this._condition = condition; + this._onchange = null; + this._listeners = []; + } + + get matches() { + return this._matches; + } + + set matches(matches: boolean) { + this._matches = matches; + } + + get condition() { + return this._condition; + } + + get media() { + return this._condition; + } + + get onchange() { + return this._onchange; + } + + set onchange(onchange: any) { + this._onchange = onchange; + } + + get listeners() { + return this._listeners; + } + + public addListener(matchFunction: any) { + this._listeners.push(matchFunction); + } + + public removeListener(matchFunction: any) { + const index: number = this._listeners.indexOf(matchFunction); + if (index > -1) { + this._listeners.splice(index, 1); + } + } +} + +/** + * MediaQuery api. + */ +export class MediaQueryApi { + private _module: MediaQueryModule; + private _mqlArray: MediaQueryList[]; + + constructor(module: MediaQueryModule) { + this._module = module; + this._mqlArray = []; + this._module.addListener((data) => { + if (!this._mqlArray || this._mqlArray.length === 0) { + return; + } + data['device-type'] = data.deviceType; + data['aspect-ratio'] = data.aspectRatio; + data['device-width'] = data.deviceWidth; + data['device-height'] = data.deviceHeight; + data['round-screen'] = data.roundScreen; + for (let i = 0; i < this._mqlArray.length; i++) { + const mediaQueryList:MediaQueryList = this._mqlArray[i]; + const result: boolean = matchMediaQueryCondition(mediaQueryList.condition, data, true); + + mediaQueryList.matches = result; + const matchData: MatchData = { matches: false }; + matchData.matches = result; + + if (mediaQueryList.listeners && mediaQueryList.listeners.length !== 0) { + for (let i = 0; i < mediaQueryList.listeners.length; i++) { + const matchFunc = mediaQueryList.listeners[i]; + matchFunc(matchData); + } + } + + if (typeof mediaQueryList.onchange === 'function') { + mediaQueryList.onchange(matchData); + } + } + }); + } + + /** + * Creates a MediaQueryList object based on the query condition. + * @param {string} condition - Media query condition. + * @return {MediaQueryList} + */ + public matchMedia(condition: string): MediaQueryList { + const mediaquerylist: MediaQueryList = new MediaQueryList(condition); + this._mqlArray.push(mediaquerylist); + return mediaquerylist; + } +} diff --git a/runtime/main/extend/mediaquery/plugins.ts b/runtime/main/extend/mediaquery/plugins.ts new file mode 100644 index 00000000..5e26d769 --- /dev/null +++ b/runtime/main/extend/mediaquery/plugins.ts @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { MediaQueryApi } from './pluginMediaQuery'; + +/** + * Create MediaQueryApi object. + * @param {any} module + * @param {string} pkgName - PackageName of module. + * @return {*} + */ +export function getPluginModule(module: any, pkgName: string): any { + switch (pkgName) { + case 'mediaquery': + return new MediaQueryApi(module); + default: + break; + } + return null; +} diff --git a/runtime/main/extend/systemplugin/systemPlugins.js b/runtime/main/extend/systemplugin/systemPlugins.js new file mode 100644 index 00000000..18323c18 --- /dev/null +++ b/runtime/main/extend/systemplugin/systemPlugins.js @@ -0,0 +1,502 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function mockSystemPlugin () { + global.systemplugin = { + notification:{}, + vibrator: {}, + sensor: {}, + geolocation: {}, + network: {}, + brightness: { + argsV: { + value: 80 + }, + argsM: { + mode: 0 + } + }, + volume: { + args: {value: 0.5} + }, + battery: {}, + systemPackage: {}, + wifi: {}, + bluetooth: {}, + tts: {}, + alarm: {}, + request: {}, + fetch: {}, + storage: {}, + file: {} + } + mockNotification() + mockFetch() + mockRequest() + mockStorage() + mockFile() + mockVibrator() + mockSensor() + mockGeolocation() + mockNetwork() + mockBrightness() + mockVolume() + mockBattery() + mockSystemPackage() +} + +function mockNotification () { + global.systemplugin.notification = { + show: function () {} + } +} + +function mockVibrator () { + global.systemplugin.vibrator = { + vibrate: function () { + arguments[0].success() + isComplete(arguments[0].complete) + } + } +} + +function mockSensor () { + mockAccelerometer() + mockCompass() + mockProximity() + mockLight() + mockStepCounter() + mockBarometer() + mockHeartRate() + mockOnBodyState() +} + +function mockAccelerometer () { + Object.assign(global.systemplugin.sensor, { + subscribeAccelerometer: function () { + const time = { + normal: 200, + game: 20, + ui: 60 + } + let ret = {} + let timer = 0 + if(!arguments[0].interval) { + timer = time.normal + } else { + timer = time[arguments[0].interval] + } + this.unsubscribeAccelerometer() + this.unsubscribeAcc = setInterval(() => { + ret.x = Math.ceil(Math.random() * 10) + ret.y = Math.ceil(Math.random() * 10) + ret.z = Math.ceil(Math.random() * 10) + arguments[0].success(ret) + }, timer) + }, + unsubscribeAccelerometer: function () { + clearInterval(this.unsubscribeAcc) + delete this.unsubscribeAcc + } + }) +} + +function mockCompass () { + Object.assign(global.systemplugin.sensor, { + subscribeCompass: function () { + if(!this.unsubscribeDirection) { + let ret = {} + this.unsubscribeDirection = setInterval(() => { + ret.direction = getRandomArbitrary(49, 50) + arguments[0].success(ret) + }, 100) + } + }, + unsubscribeCompass: function () { + clearInterval(this.unsubscribeDirection) + delete this.unsubscribeDirection + } + }) +} + +function mockProximity() { + Object.assign(global.systemplugin.sensor, { + subscribeProximity: function () { + if(!this.unsubscribeDistance) { + let ret = {} + this.unsubscribeDistance = setInterval(() => { + ret.distance = Math.ceil(Math.random() * 100) + arguments[0].success(ret) + }, 1000) + } + }, + unsubscribeProximity: function () { + clearInterval(this.unsubscribeDistance) + delete this.unsubscribeDistance + } + }) +} + +function mockLight () { + Object.assign(global.systemplugin.sensor, { + subscribeLight: function () { + if(!this.unsubscribeIntensity) { + let ret = {} + this.unsubscribeIntensity = setInterval(() => { + ret.intensity = getRandomArbitrary(660, 680) + arguments[0].success(ret) + }, 500) + } + }, + unsubscribeLight: function () { + clearInterval(this.unsubscribeIntensity) + delete this.unsubscribeIntensity + }, + }) +} + +function mockStepCounter() { + Object.assign(global.systemplugin.sensor, { + subscribeStepCounter: function () { + if(!this.unsubscribeSteps) { + let ret = { steps: 0 } + this.unsubscribeSteps = setInterval(() => { + ret.steps += 1 + arguments[0].success(ret) + }, 1000) + } + }, + unsubscribeStepCounter: function () { + clearInterval(this.unsubscribeSteps) + delete this.unsubscribeSteps + } + }) +} + +function mockBarometer() { + Object.assign(global.systemplugin.sensor, { + subscribeBarometer: function () { + if(!this.unsubscribePressure) { + let ret = {} + this.unsubscribePressure = setInterval(() => { + ret.pressure = getRandomArbitrary(1110, 1111) + arguments[0].success(ret) + }, 500) + } + }, + unsubscribeBarometer: function () { + clearInterval(this.unsubscribePressure) + delete this.unsubscribePressure + } + }) +} + +function mockHeartRate() { + Object.assign(global.systemplugin.sensor, { + subscribeHeartRate: function () { + if(!this.unsubscribeRate) { + let ret = {} + this.unsubscribeRate = setInterval(() => { + ret.heartRate = Math.ceil(Math.random() * 30) + arguments[0].success(ret) + }, 500) + } + }, + unsubscribeHeartRate: function () { + clearInterval(this.unsubscribeRate) + delete this.unsubscribeRate + }, + }) +} + +function mockOnBodyState () { + Object.assign(global.systemplugin.sensor, { + subscribeOnBodyState: function () { + if(!this.unsubscribeBodyState) { + let ret = {} + this.unsubscribeBodyState = setInterval(() => { + ret.value = Math.ceil(Math.random() * 20) + arguments[0].success(ret) + }, 500) + } + }, + unsubscribeOnBodyState: function () { + clearInterval(this.unsubscribeBodyState) + delete this.unsubscribeBodyState + } + }) +} + +function mockGeolocation () { + const data = { + latitude: '121.61934', + longitude: '31.257907', + accuracy: '15', + time: '160332896544' + } + global.systemplugin.geolocation = { + getLocation: function () { + arguments[0].success(data) + isComplete(arguments[0].complete) + }, + getLocationType: function () { + let args = {types: ['gps', 'network']} + arguments[0].success(args) + isComplete(arguments[0].complete) + }, + getSupportedCoordTypes() { + return ["wgs84"] + }, + subscribe: function () { + if(!this.unsubscribeLocation) { + this.unsubscribeLocation = setInterval(() => { + data.latitude = getRandomArbitrary(121, 122) + data.longitude = getRandomArbitrary(31, 32) + data.accuracy = getRandomArbitrary(14, 18) + arguments[0].success(data) + }, 1000) + } + }, + unsubscribe: function () { + clearInterval(this.unsubscribeLocation) + delete this.unsubscribeLocation + } + } +} + +function mockNetwork () { + const data = { + metered: true, + type: "5g" + } + global.systemplugin.network = { + getType: function () { + arguments[0].success(data) + arguments[0].complete() + }, + subscribe: function () { + if(!this.unsubscribeNetwork) { + this.unsubscribeNetwork = setInterval(() => { + arguments[0].success(data) + }, 3000) + } + }, + unsubscribe: function () { + clearInterval(this.unsubscribeNetwork) + delete this.unsubscribeNetwork + } + } +} + +function mockBrightness () { + Object.assign(global.systemplugin.brightness, { + getValue: function () { + arguments[0].success(this.argsV) + isComplete(arguments[0].complete) + }, + setValue: function () { + if(arguments[0].value) { + this.argsV.value = arguments[0].value + arguments[0].success("brightness setValue successfully") + isComplete(arguments[0].complete) + } + }, + getMode: function () { + arguments[0].success(this.argsM) + isComplete(arguments[0].complete) + }, + setMode: function () { + this.argsM.mode = arguments[0].mode + arguments[0].success("brightness setMode successfully") + isComplete(arguments[0].complete) + }, + setKeepScreenOn: function () { + arguments[0].success("brightness setKeepScreenOn successfully") + isComplete(arguments[0].complete) + } + }) +} + +function mockVolume () { + Object.assign(global.systemplugin.volume, { + getMediaValue: function () { + arguments[0].success(this.args) + isComplete(arguments[0].complete) + }, + setMediaValue: function () { + if(arguments[0].value) { + this.args.value = arguments[0].value + arguments[0].success("set volume successfully") + isComplete(arguments[0].complete) + } + } + }) +} + +function mockBattery () { + global.systemplugin.battery = { + getStatus: function () { + arguments[0].success.call(this, { level: 1, charging: false} ) + isComplete(arguments[0].complete) + } + } +} + +function mockSystemPackage () { + global.systemplugin.package = { + hasInstalled: function () { + arguments[0].success(true) + isComplete(arguments[0].complete) + } + } +} + +function mockRequest () { + const data = { + code: "[pc Preview]: no internet", + data: "[pc Preview]: no internet", + headers: "[pc Preview]: no internet", + token: "[pc Preview]: no internet", + uri: "[pc Preview]: no internet", + } + global.systemplugin.request = { + upload: function () { + arguments[0].success(data) + isComplete(arguments[0].complete) + }, + download: function () { + arguments[0].success(data) + isComplete(arguments[0].complete) + }, + onDownloadComplete: function () { + arguments[0].success(data) + isComplete(arguments[0].complete) + } + } +} + +function mockFetch () { + const data = { + code: "[pc Preview]: no internet", + data: "[pc Preview]: no internet" + } + global.systemplugin.fetch = { + fetch: function () { + arguments[0].success(data) + isComplete(arguments[0].complete) + } + } +} + +function mockStorage () { + global.systemplugin.storage = { + get: function () { + arguments[0].success("[pc Preview]: no system") + isComplete(arguments[0].complete) + }, + set: function () { + arguments[0].success("[pc Preview]: no system") + isComplete(arguments[0].complete) + }, + clear: function () { + arguments[0].success("[pc Preview]: no system") + isComplete(arguments[0].complete) + }, + delete: function () { + arguments[0].success("[pc Preview]: no system") + isComplete(arguments[0].complete) + } + } +} + +function mockFile () { + global.systemplugin.file = { + move: function () { + arguments[0].success(arguments[0].dstUri) + isComplete(arguments[0].complete) + }, + copy: function () { + arguments[0].success(arguments[0].dstUri) + isComplete(arguments[0].complete) + }, + list: function () { + const data = { + fileList: [{ + uri:'[pc Preview]: no file', + lastModifiedTime:"[pc Preview]: no file", + length:"[pc Preview]: no file", + type: 'file'}] + } + arguments[0].success(data) + isComplete(arguments[0].complete) + }, + get: function () { + const data = { + uri:'[pc Preview]: no file', + lastModifiedTime:"[pc Preview]: no file", + length:"[pc Preview]: no file", + type: 'file', + subFiles: ["[pc Preview]: no file", "[pc Preview]: no file"] + } + arguments[0].success(data) + isComplete(arguments[0].complete) + }, + delete: function () { + arguments[0].success() + isComplete(arguments[0].complete) + }, + writeText: function () { + arguments[0].success() + isComplete(arguments[0].complete) + }, + writeArrayBuffer: function () { + arguments[0].success() + isComplete(arguments[0].complete) + }, + readText: function () { + const data = {text: "[pc Preview]: success default"} + arguments[0].success(data) + isComplete(arguments[0].complete) + }, + readArrayBuffer: function () { + const data = {buffer: ["[pc Preview]: default", "[pc Preview]: default", "[pc Preview]: default"]} + arguments[0].success(data) + isComplete(arguments[0].complete) + }, + access: function () { + arguments[0].success() + isComplete(arguments[0].complete) + }, + mkdir: function () { + arguments[0].success() + isComplete(arguments[0].complete) + }, + rmdir: function () { + arguments[0].success() + isComplete(arguments[0].complete) + } + } +} + +function isComplete() { + if(arguments[0] === undefined) { + return + } + arguments[0].call(this) +} + +function getRandomArbitrary(min, max) { + return Math.random().toFixed(6) * (max - min) + min; +} diff --git a/runtime/main/index.ts b/runtime/main/index.ts new file mode 100644 index 00000000..47e581f7 --- /dev/null +++ b/runtime/main/index.ts @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileOverview framework entry. + */ + +import Vm from './model'; +export { createInstance, destroyInstance } from './manage/instance/life'; +import { registerModules } from './manage/instance/register'; +export { receiveTasks } from './manage/event/bridge'; +export { getRoot } from './manage/instance/misc'; +import { appDestroy } from './app/index'; +import { appError } from './app/index'; + +/** + * Prevent modification of Vm and Vm.prototype. + */ +Object.freeze(Vm); + +export { registerModules, appDestroy, appError }; diff --git a/runtime/main/manage/event/CallbackManager.ts b/runtime/main/manage/event/CallbackManager.ts new file mode 100644 index 00000000..a5d92e96 --- /dev/null +++ b/runtime/main/manage/event/CallbackManager.ts @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Log } from '../../../utils/index'; + +export interface callbackObjInterface { + [key: string]: Function; +} + +/** + *

Callback management of a certain page.

+ *

We can get the real callback that called from native by callback id which is unique for a callback.

+ */ +export default class CallbackManager { + public callbackMap: callbackObjInterface; + public instanceId: string; + public currCallbackId: number; + + constructor(instanceId: string) { + this.instanceId = String(instanceId); + this.currCallbackId = 0; + this.callbackMap = {}; + } + + /** + * Add a callback to callbacks object. + * @param {*} callback - The callback from native. + * @return {number} Last cllback id in object. + */ + public add(callback: Function): number { + this.currCallbackId++; + this.callbackMap[this.currCallbackId] = callback; + return this.currCallbackId; + } + + /** + * Remove a callback by callback id. + * @param {number} callbackId - Callback id. + * @return {Function} Callback that removed. + */ + public remove(callbackId: number): Function { + const callback: Function = this.callbackMap[callbackId]; + delete this.callbackMap[callbackId]; + return callback; + } + + /** + * Consume a callback by callback id. + * @param {number} callbackId - Callback id. + * @param {Object} data - Data that needed. + * @param {boolean} ifKeepAlive - If keepAlive is false, delete this callback. + * @return {*} + */ + public consume(callbackId: number, data: object, ifKeepAlive: boolean): any | Error { + const callback: Function = this.callbackMap[callbackId]; + if (typeof ifKeepAlive === 'undefined' || ifKeepAlive === false) { + delete this.callbackMap[callbackId]; + } + if (typeof callback === 'function') { + try { + return callback.call(null, data); + } catch (error) { + Log.error(`Failed to execute the callback function:\n ${error.toString()}`); + throw error; + } + } + return new Error(`Invalid callback id '${callbackId}'.`); + } + + /** + * Clean all callbacks in callbackMap. + */ + public destroy(): void { + this.callbackMap = {}; + } + + /** + * Check whether the callbacks object is empty. + * @return {boolean} If callbacks object is empty, return true. Otherwise return false. + */ + public isEmpty(): boolean { + if (Object.keys(this.callbackMap).length === 0) { + return true; + } + return false; + } +} diff --git a/runtime/main/manage/event/TaskCenter.ts b/runtime/main/manage/event/TaskCenter.ts new file mode 100644 index 00000000..b4be9080 --- /dev/null +++ b/runtime/main/manage/event/TaskCenter.ts @@ -0,0 +1,232 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * 2021.01.08 - The function 'standardization' is simpler and more accurate. + * And extend function 'send' to fit framework. + * Copyright (c) 2021 Huawei Device Co., Ltd. + */ + +import { + Log, + typof +} from '../../../utils/index'; +import CallbackManager from './CallbackManager'; + +type OptionsType = Partial> + +/** + *

Tasks processing center.

+ *

Instructs the Native module to perform operations based on the message sent by the listener.

+ *

Then the Native module invokes the callNative() callback function in sendTasks()
+ * to send the message to the Native module.

+ */ +export class TaskCenter { + public instanceId: string; + public callbackManager: CallbackManager; + + constructor(id: string) { + this.instanceId = id; + this.callbackManager = new CallbackManager(id); + } + + /** + * Execute the consume() function from callbackManager class. + * @param {number} callbackId - Callback id. + * @param {Object} data - Data that needed. + * @param {boolean} ifKeepAlive - If keepAlive is false, delete this callback. + * @return {*} + */ + public consumeCallback(callbackId: number, data: object, ifKeepAlive: boolean): any | Error { + return this.callbackManager.consume(callbackId, data, ifKeepAlive); + } + + /** + * Execute the close() function from callbackManager class. + * @param {number} callbackId - Callback id. + */ + public destroyCallback(): void { + return this.callbackManager.destroy(); + } + + /** + * Execute the remove() function from callbackManager class. + * @param {number} callbackId - Callback id. + */ + public removeCallback(callbackId: number): void { + this.callbackManager.remove(callbackId); + } + + /** + * Execute the isEmpty() function from callbackManager class. + * @param {number} callbackId - Callback id. + * @return {boolean} If callbacklist object is empty, return true. Otherwise return false. + */ + public callbackIsEmpty(): boolean { + return this.callbackManager.isEmpty(); + } + + /** + * Standardizing a value. Specially, if the value is a function, generate a function id. + * @param {*} arg - Any type. + * @return {*} + */ + public standardization(arg: any): any { + const type = typof(arg); + if (type === 'object') { + const ans = {}; + Object.keys(arg).forEach(key => { + ans[key] = this.standardization(arg[key]); + }); + return ans; + } else if (type === 'function') { + return this.callbackManager.add(arg).toString(); + } else if (type === 'array') { + return arg.map(i => this.standardization(i)); + } else { + return arg; + } + } + + /** + * Instruct the Native module to perform operations based on the message sent by the listener. + * @param {string} type - Such as dom, module and component. + * @param {OptionsType} options - Include action, module and method. + * @param {*} args - Args of a Vm. + */ + public send(type: string, options: OptionsType, args: any): any { + const { + action, + module, + method + } = options; + if (type !== 'dom') { + args = args.map(arg => this.standardization(arg)); + } + switch (type) { + case 'dom': + if (typeof ace !== 'undefined' && + typeof ace.domCreateBody !== 'undefined' && + typeof ace.domAddElement !== 'undefined') { + if (action === 'createBody') { + ace.domCreateBody( + 0, + args[0].type, + args[0].attr, + args[0].style, + args[0].event + ); + } else if (action === 'addElement') { + ace.domAddElement( + args[0], + args[1].ref, + args[1].type, + args[1].attr, + args[1].style, + args[1].event, + args[1].customComponent, + args[2], + this.instanceId + ); + } else if (action === 'updateAttrs') { + ace.updateElementAttrs( + args[0], + args[1], + this.instanceId + ); + } else if (action === 'updateStyle') { + ace.updateElementStyles( + args[0], + args[1], + this.instanceId + ); + } else if (action === 'createFinish') { + ace.onCreateFinish(); + return; + } else if (action === 'updateFinish') { + ace.onUpdateFinish(); + return; + } else if (action === 'removeElement') { + ace.removeElement( + args[0], + this.instanceId + ); + } else { + Log.error( + 'TaskCenter.js: send() unsupported action. IGNORING!' + ); + } + return; + } else { + Log.error( + 'TaskCenter.js: attempting acev1 method for calling native' + ); + return; + } + + case 'module': + switch (module) { + case 'system.fetch': + if (method === 'fetch') { + Log.error( + 'TaskCenter.js: send: module system.fetch. calling ace.fetch.' + ); + ace.onFetchRequest(args[1], JSON.stringify(args)); + return; + } else { + Log.error( + 'TaskCenter.js: send: module system.fetch. unrecognized method. Ignoring.' + ); + } + break; + case 'system.device': + return ace.callNative(JSON.stringify(options), args[args.length - 1]); + case 'system.router': + return ace.callNative(JSON.stringify(options), JSON.stringify(args[0])); + case 'system.prompt': + return ace.callNative(JSON.stringify(options), JSON.stringify(args[0])); + case 'system.app': + return ace.callNative(JSON.stringify(options), args); + case 'system.configuration': + return ace.callNative(JSON.stringify(options), JSON.stringify(args[0])); + case 'system.grid': + return ace.callNative(JSON.stringify(options), args); + case 'internal.jsResult': + return ace.callNative(JSON.stringify(options), args); + case 'timer': + return ace.callNative(JSON.stringify(options), args); + case 'system.image': + return ace.callNative(JSON.stringify(options), JSON.stringify(args[0])); + case 'system.mediaquery': + return ace.callNative(JSON.stringify(options), args); + case 'animation': + return ace.callNative(JSON.stringify(options), args[0]); + case 'system.resource': + return ace.callNative(JSON.stringify(options), args); + default: + break; + } + break; + + case 'component': + return ace.callComponent(options.ref, method, JSON.stringify(args)); + default: + break; + } + } +} diff --git a/runtime/main/manage/event/bridge.ts b/runtime/main/manage/event/bridge.ts new file mode 100644 index 00000000..853cf9c1 --- /dev/null +++ b/runtime/main/manage/event/bridge.ts @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * 2021.01.08 - Add fireEventSync event to eventHandlers and page.destroyed judgment to 'receiveTasks'. + * Copyright (c) 2021 Huawei Device Co., Ltd. + */ + +import { Log } from '../../../utils/index'; +import { App } from '../../app/App'; +import Page from '../../page'; +import { + fireEvent, + callback, + fireEventSync, + destroy +} from '../../page/api/index'; + +const pageMap: Map = App.pageMap; + +const eventHandlers = { + /** + * Invoke the fireEvent function. + * @param {string} id - Page id. + * @param {*} args - Args. + */ + fireEvent: (id: string, ...args: any[]) => { + return fireEvent(pageMap.get(id), ...args); + }, + + /** + * Invoke the callback function. + * @param {string} id - Page id. + * @param {*} args - Args + */ + callback: (id: string, ...args: any[]) => { + return callback(pageMap.get(id), ...args); + }, + + /** + * Invoke the fireEventSync function. + * @param {string} id - Page id. + * @param {*} args - Args. + */ + fireEventSync: (id: string, ...args: any[]) => { + return fireEventSync(pageMap.get(id), ...args); + } +}; + +/** + * Accept calls from native (event or callback). + * @param {string} id - Page id. + * @param {*} tasks list with `method` and `args`. + * @return {*} + */ +export function receiveTasks(id: string, tasks: any[]): any[] | Error { + id = id.toString(); + Log.debug(`ReceiveTasks id ${id}, tasks: ${JSON.stringify(tasks)}`); + const page: Page = pageMap.get(id); + if (page && Array.isArray(tasks)) { + const results = []; + tasks.forEach((task) => { + const handler = eventHandlers[task.method]; + const args = [...task.args]; + if (typeof handler === 'function') { + args.unshift(id); + results.push(handler(...args)); + } + }); + if (page.destroyed && page.doc.taskCenter.callbackIsEmpty()) { + page.callTasks([{ + module: 'internal.jsResult', + method: 'appDestroyFinish', + args: [] + }]); + destroy(page); + pageMap.delete(id); + } + return results; + } + return new Error(`Invalid page id '${id}' or tasks.`); +} diff --git a/runtime/main/manage/event/callbackIntercept.ts b/runtime/main/manage/event/callbackIntercept.ts new file mode 100644 index 00000000..54e52339 --- /dev/null +++ b/runtime/main/manage/event/callbackIntercept.ts @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Intercept callback from native and forward to user-defined callback. + * @param {*} args - Args. + * @param {boolean} [needPromise] - If asynchronous operations are needed. + * @return {Object} If promise are needed, return { args, promise }. Otherwise return args. + */ +export function interceptCallback(args: any, needPromise?: boolean): object { + if (args.length === 0 && !needPromise) { + return args; + } + const first: object = args[0]; + const callbacks: object = {}; + let hasProperty: boolean = false; + if (typeof first === 'object' && + Object.prototype.toString.call(first).toLowerCase() === '[object object]' && + args.length === 1 + ) { + for (const key in first) { + const value: Function = first[key]; + if (typeof value === 'function') { + callbacks[key] = value; + } else { + hasProperty = true; + } + } + } else { + hasProperty = true; + } + + let promise: any; + const callbLength: number = Object.keys(callbacks).length; + if (needPromise) { + if (callbLength <= 0) { + promise = new PromiseRef(); + } + } + if (callbLength > 0 || promise) { + const callb = (msg: { method: string; arguments: any; }) => { + let func = callbacks[msg.method]; + const callbArgs: any = msg.arguments; + + if (func !== undefined) { + func(...callbArgs); + } + + // Always call complete(). + func = callbacks['complete']; + if (func !== undefined) { + func(...callbArgs); + } + if (promise) { + const data: any = callbArgs && + callbArgs.length > 0 + ? callbArgs[0] + : undefined; + if ('success' === msg.method || 'callback' === msg.method) { + promise.resolve({ data }); + } else { + // 200 means common error ,100 :cancel. + const code: any = + 'cancel' === msg.method + ? 100 + : callbArgs && callbArgs.length > 1 + ? callbArgs[1] + : 200; + promise.reject({ data, code }); + } + } + }; + callb.__onlyPromise = callbLength <= 0; + + if (hasProperty) { + args.push(callb); + } else { + args = [callb]; + } + } + return needPromise ? { args, promise } : args; +} + +/** + * This class provide a Promise object for asynchronous operation processing. + */ +class PromiseRef { + private _promise: Promise; + private _reject: Function; + private _resolve: Function; + + constructor() { + this._promise = new Promise((resolve, reject) => { + this._reject = reject; + this._resolve = resolve; + }); + } + + /** + * Promise of this PromiseRef. + * @type {Promise} + */ + public get promise() { + return this._promise; + } + + public set promise(newPromise) { + this._promise = newPromise; + } + + /** + * Reject function using the Promise object. + * @type {Promise} + */ + public get reject() { + return this._reject; + } + + public set reject(data) { + this._reject = data; + } + + /** + * Resolve function using the Promise object. + * @type {Promise} + */ + public get resolve() { + return this._resolve; + } + + public set resolve(data) { + this._resolve = data; + } +} diff --git a/runtime/main/manage/instance/life.ts b/runtime/main/manage/instance/life.ts new file mode 100644 index 00000000..a978397c --- /dev/null +++ b/runtime/main/manage/instance/life.ts @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { destroy } from '../../page/api/index'; +import { App } from '../../app/App'; +import { resetTarget } from '../../reactivity/dep'; +import Page from '../../page'; +import { init as initApp } from '../../page/api/index'; +import { appCreate, Options } from '../../app/index'; + +const pageMap: Map = App.pageMap; + +/** + * Create a page. + * @param {string} id - Page id. + * @param {string} code - JS Bundle code. + * @param {Object} options - Options of a page instance. + * @param {Object} data - Data that needed. + * @param {*} env - Such as: { created, ... services }. + * @return {Object | Error} + */ +export function createInstance(id: string, code: string, options: Options, data: object, env: any): object | Error { + const { services } = env; + const { I18n, dpi } = services; + resetTarget(); + + let page: Page = pageMap.get(id); + let result: object; + if (!page) { + page = new Page(id, options, options.packageName); + page.i18nService = I18n; + page.dpiService = dpi; + appCreate(page, options, data, services); + pageMap.set(id, page); + result = initApp(page, code, data, services); + } else { + result = new Error(`invalid page id "${id}"`); + } + return result; +} + +/** + * Destroy a page by id. + * @param {string} id - Page id. + * @return {Object} The reset pageMap. + */ +export function destroyInstance(id: string): object { + if (typeof markupState === 'function') { + markupState(); + } + resetTarget(); + const page: Page = pageMap.get(id); + if (!page) { + return new Error(`invalid page id '${id}'.`); + } + pageMap.delete(id); + destroy(page); + + // Tell v8 to do a full Garbage Collection every eighteen times. + const remainder: number = Math.round(Number(id)) % 18; + if (!remainder) { + if (typeof notifyTrimMemory === 'function') { + notifyTrimMemory(); + } + } + return pageMap; +} diff --git a/runtime/main/manage/instance/misc.ts b/runtime/main/manage/instance/misc.ts new file mode 100644 index 00000000..ee6742a0 --- /dev/null +++ b/runtime/main/manage/instance/misc.ts @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { App } from '../../app/App'; +import Page from '../../page'; + +const pageMap: Map = App.pageMap; + +/** + * Get an element tree of a page. + * @param {string} id - Page id. + * @return {Object} A virtual dom tree. + */ +export function getRoot(id: string): object { + const page: Page = pageMap.get(id); + let result: any; + if (page) { + const doc: any = page.doc || {}; + const body = doc.body || {}; + result = body.toJSON ? body.toJSON() : {}; + } else { + result = new Error(`invalid page id "${id}"`); + } + return result; +} diff --git a/runtime/main/manage/instance/register.ts b/runtime/main/manage/instance/register.ts new file mode 100644 index 00000000..c3dff7ea --- /dev/null +++ b/runtime/main/manage/instance/register.ts @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + initModules +} from '../../page/register'; + +/** + * Register each module. + * @param {Object} modules - Object of modules. + */ +export function registerModules(modules: object): void { + if (typeof modules === 'object') { + initModules(modules); + } +} diff --git a/runtime/main/model/compiler.ts b/runtime/main/model/compiler.ts new file mode 100644 index 00000000..a406ad5b --- /dev/null +++ b/runtime/main/model/compiler.ts @@ -0,0 +1,748 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * @fileOverview + * ViewModel template parser & data-binding process + */ + +import { + hasOwn, + Log, + removeItem +} from '../../utils/index'; +import { + initData, + initComputed +} from '../reactivity/state'; +import { + bindElement, + setClass, + setIdStyle, + setTagStyle, + setId, + bindSubVm, + bindSubVmAfterInitialized, + newWatch +} from './directive'; +import { + createBlock, + createBody, + createElement, + attachTarget, + moveTarget, + removeTarget +} from './domHelper'; +import { + bindPageLifeCycle +} from './pageLife'; +import Vm from './index'; +import Element from '../../vdom/Element'; +import Comment from '../../vdom/Comment'; +import Node from '../../vdom/Node'; +import { VmOptions } from './vmOptions'; + +export interface FragBlockInterface { + start: Comment; + end: Comment; + element?: Element; + blockId: number; + children?: any[]; + data?: any[]; + vms?: Vm[]; + updateMark?: Node; + display?: boolean; + type?: string; + vm?: Vm; +} + +export interface AttrInterface { + type: string; + value: () => void | string; + tid: number; + append: string; + slot: string; + name: string +} + +export interface TemplateInterface { + type: string; + attr: Partial; + classList?: () => any | string[]; + children?: TemplateInterface[]; + events?: object; + repeat?: () => any | RepeatInterface; + shown?: () => any; + style?: Record; + id?: () => any | string; + append?: string; + onBubbleEvents?: object; + onCaptureEvents?: object; + catchBubbleEvents?: object; + catchCaptureEvents?: object; +} + +interface RepeatInterface { + exp: () => any; + key?: string; + value?: string; + tid?: number; +} + +interface MetaInterface { + repeat: object; + shown: boolean; + type: string; +} + +interface ConfigInterface { + latestValue: undefined | string | number; + recorded: boolean; +} + +export function build(vm: Vm) { + const opt: any = vm.vmOptions || {}; + const template: any = opt.template || {}; + compile(vm, template, vm.parentEl); + Log.debug(`"OnReady" lifecycle in Vm(${vm.type}).`); + vm.$emit('hook:onReady'); + if (vm.parent) { + vm.$emit('hook:onAttached'); + } + vm.ready = true; +} + +/** + * Compile the Virtual Dom. + * @param {Vm} vm - Vm object needs to be compiled. + * @param {TemplateInterface} target - Node need to be compiled. Structure of the label in the template. + * @param {FragBlockInterface | Element} dest - Parent Node's VM of current. + * @param {MetaInterface} [meta] - To transfer data. + */ +function compile(vm: Vm, target: TemplateInterface, dest: FragBlockInterface | Element, meta?: Partial): void { + const app: any = vm.app || {}; + if (app.lastSignal === -1) { + return; + } + meta = meta || {}; + if (targetIsSlot(target)) { + compileSlot(vm, target, dest as Element); + return; + } + + if (targetNeedCheckRepeat(target, meta)) { + if (dest.type === 'document') { + Log.warn('The root element does\'t support `repeat` directive!'); + } else { + compileRepeat(vm, target, dest as Element); + } + return; + } + if (targetNeedCheckShown(target, meta)) { + if (dest.type === 'document') { + Log.warn('The root element does\'t support `if` directive!'); + } else { + compileShown(vm, target, dest, meta); + } + return; + } + const type = meta.type || target.type; + const component: VmOptions | null = targetIsComposed(vm, type); + if (component) { + compileCustomComponent(vm, component, target, dest, type, meta); + return; + } + if (targetIsBlock(target)) { + compileBlock(vm, target, dest); + return; + } + compileNativeComponent(vm, target, dest, type); +} + +/** + * Check if target type is slot. + * + * @param {object} target + * @return {boolean} + */ +function targetIsSlot(target: TemplateInterface) { + return target.type === 'slot'; +} + +/** + * Check if target needs to compile by a list. + * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. + * @param {MetaInterface} meta - To transfer data. + * @return {boolean} - True if target needs repeat. Otherwise return false. + */ +function targetNeedCheckRepeat(target: TemplateInterface, meta: Partial) { + return !hasOwn(meta, 'repeat') && target.repeat; +} + +/** + * Check if target needs to compile by a 'if' or 'shown' value. + * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. + * @param {MetaInterface} meta - To transfer data. + * @return {boolean} - True if target needs a 'shown' value. Otherwise return false. + */ +function targetNeedCheckShown(target: TemplateInterface, meta: Partial) { + return !hasOwn(meta, 'shown') && target.shown; +} + +/** + * Check if this kind of component is composed. + * @param {Vm} vm - Vm object needs to be compiled. + * @param {string} type - Component type. + * @return {VmOptions} Component. + */ +function targetIsComposed(vm: Vm, type: string): VmOptions { + let component; + if (vm.app && vm.app.customComponentMap) { + component = vm.app.customComponentMap[type]; + } + if (component) { + if (component.data && typeof component.data === 'object') { + if (!component.initObjectData) { + component.initObjectData = component.data; + } + const str = JSON.stringify(component.initObjectData); + component.data = JSON.parse(str); + } + } + return component; +} + +/** + * Compile a target with repeat directive. + * @param {Vm} vm - Vm object needs to be compiled. + * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. + * @param {dest} dest - Node need to be appended. + */ +function compileSlot(vm: Vm, target: TemplateInterface, dest: Element): Element { + if (!vm.slotContext) { + // slot in root vm + return; + } + + const slotDest = createBlock(vm, dest); + + // reslove slot contentext + const namedContents = vm.slotContext.content; + const parentVm = vm.slotContext.parentVm; + const slotItem = { target, dest: slotDest }; + const slotName = target.attr.name || 'default'; + + // acquire content by name + const namedContent = namedContents[slotName]; + if (!namedContent) { + compileChildren(vm, slotItem.target, slotItem.dest); + } else { + compileChildren(parentVm, { children: namedContent }, slotItem.dest); + } +} + +/** + * Compile a target with repeat directive. + * @param {Vm} vm - Vm object needs to be compiled. + * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. + * @param {Element} dest - Parent Node's VM of current. + */ +function compileRepeat(vm: Vm, target: TemplateInterface, dest: Element): void { + const repeat = target.repeat; + let getter: any; + let key: any; + let value: any; + let trackBy: any; + + if (isRepeat(repeat)) { + getter = repeat.exp; + key = repeat.key; + value = repeat.value; + trackBy = repeat.tid; + } else { + getter = repeat; + key = '$idx'; + value = '$item'; + trackBy = target.attr && target.attr.tid; + } + if (typeof getter !== 'function') { + getter = function() { + return []; + }; + } + const fragBlock: FragBlockInterface = createBlock(vm, dest); + fragBlock.children = []; + fragBlock.data = []; + fragBlock.vms = []; + bindRepeat(vm, target, fragBlock, { getter, key, value, trackBy }); +} + +/** + * Compile a target with 'if' directive. + * @param {Vm} vm - Vm object needs to be compiled. + * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. + * @param {FragBlockInterface | Element} dest - Parent Node's VM of current. + * @param {MetaInterface} meta - To transfer data. + */ +function compileShown( + vm: Vm, + target: TemplateInterface, + dest: Element | FragBlockInterface, + meta: Partial +): void { + const newMeta: Partial = { shown: true }; + const fragBlock = createBlock(vm, dest); + if (isBlock(dest) && dest.children) { + dest.children.push(fragBlock); + } + if (meta.repeat) { + newMeta.repeat = meta.repeat; + } + bindShown(vm, target, fragBlock, newMeta); +} + +/** + * Support . + * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. + * @return {boolean} True if target supports bolck. Otherwise return false. + */ +function targetIsBlock(target: TemplateInterface): boolean { + return target.type === 'block'; +} + +/** + * If create block and compile the children node. + * @param {Vm} vm - Vm object needs to be compiled. + * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. + * @param {Element | FragBlockInterface} dest - Parent Node's VM of current. + */ +function compileBlock(vm: Vm, target: TemplateInterface, dest: Element | FragBlockInterface): void { + const block = createBlock(vm, dest); + if (isBlock(dest) && dest.children) { + dest.children.push(block); + } + const app: any = vm.app || {}; + const children = target.children; + if (children && children.length) { + children.every((child) => { + compile(vm, child, block); + return app.lastSignal !== -1; + }); + } +} + +/** + * Compile a composed component. + * @param {Vm} vm - Vm object needs to be compiled. + * @param {VmOptions} component - Composed component. + * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. + * @param {Element | FragBlockInterface} dest - Parent Node's VM of current. + * @param {string} type - Component Type. + * @param {MetaInterface} meta - To transfer data. + */ +function compileCustomComponent( + vm: Vm, + component: VmOptions, + target: TemplateInterface, + dest: Element | FragBlockInterface, + type: string, + meta: Partial +): void { + const subVm = new Vm( + type, + component, + vm, + dest, + undefined, + { + 'hook:_innerInit': function() { + // acquire slot content of context + const namedContents = {}; + if (target.children) { + target.children.forEach(item => { + const slotName = item.attr.slot || 'default'; + if (namedContents[slotName]) { + namedContents[slotName].push(item); + } else { + namedContents[slotName] = [item]; + } + }); + } + this.slotContext = { content: namedContents, parentVm: vm }; + setId(vm, null, target.id, this); + + // Bind template earlier because of lifecycle issues. + this.externalBinding = { + parent: vm, + template: target + }; + + // Bind props before init data. + bindSubVm(vm, this, target, meta.repeat); + } + }); + bindSubVmAfterInitialized(vm, subVm, target, dest); +} + +/** + * Reset the element style. + * @param {Vm} vm - Vm object needs to be compiled. + * @param {Element} element - To be reset. + */ +function resetElementStyle(vm: Vm, element: Element): void { + const len = element.children.length; + for (let ii = 0; ii < len; ii++) { + const el = element.children[ii] as Element; + resetElementStyle(vm, el); + } + if (element.type) { + setTagStyle(vm, element, element.type); + } + if (element.id) { + setIdStyle(vm, element, element.id); + } + if (element.classList) { + setClass(vm, element, element.classList); + } +} + +/** + *

Generate element from template and attach to the dest if needed.

+ *

The time to attach depends on whether the mode status is node or tree.

+ * @param {Vm} vm - Vm object needs to be compiled. + * @param {TemplateInterface} template - Generate element from template. + * @param {FragBlockInterface | Element} dest - Parent Node's VM of current. + * @param {string} type - Vm type. + */ +function compileNativeComponent(vm: Vm, template: TemplateInterface, dest: FragBlockInterface | Element, type: string): void { + function handleViewSizeChanged(e) { + if (!vm.mediaStatus) { + vm.mediaStatus = {}; + } + vm.mediaStatus.orientation = e.orientation; + vm.mediaStatus.width = e.width; + vm.mediaStatus.height = e.height; + vm.mediaStatus.resolution = e.resolution; + vm.mediaStatus['device-type'] = e.deviceType; + vm.mediaStatus['aspect-ratio'] = e.aspectRatio; + vm.mediaStatus['device-width'] = e.deviceWidth; + vm.mediaStatus['device-height'] = e.deviceHeight; + vm.mediaStatus['round-screen'] = e.roundScreen; + const css = vm.vmOptions && vm.vmOptions.style || {}; + const mqArr = css['@MEDIA']; + if (!mqArr) { + return; + } + if (e.isInit && vm.init) { + return; + } + vm.init = true; + resetElementStyle(vm, e.currentTarget); + e.currentTarget.addEvent('show'); + } + + let element; + if (!isBlock(dest) && dest.ref === '_documentElement') { + // If its parent is documentElement then it's a body. + element = createBody(vm, type); + } else { + element = createElement(vm, type); + element.destroyHook = function() { + if (element.block !== undefined) { + removeTarget(element.block); + } + if (element.watchers !== undefined) { + element.watchers.forEach(function(watcher) { + watcher.teardown(); + }); + element.watchers = []; + } + }; + } + + if (!vm.rootEl) { + vm.rootEl = element; + + // Bind event earlier because of lifecycle issues. + const binding: any = vm.externalBinding || {}; + const target = binding.template; + const parentVm = binding.parent; + if (target && target.events && parentVm && element) { + for (const type in target.events) { + const handler = parentVm[target.events[type]]; + if (handler) { + element.addEvent(type, handler.bind(parentVm)); + } + } + } + // Page show hide life circle hook function. + bindPageLifeCycle(vm, element); + element.setCustomFlag(); + element.customFlag = true; + vm.init = true; + element.addEvent('viewsizechanged', handleViewSizeChanged); + } + + // Dest is parent element. + bindElement(vm, element, template, dest); + if (element.event && element.event['appear']) { + element.fireEvent('appear', {}); + } + + if (template.attr && template.attr.append) { + template.append = template.attr.append; + } + if (template.append) { + element.attr = element.attr || {}; + element.attr.append = template.append; + } + let treeMode = template.append === 'tree'; + const app: any = vm.app || {}; + + // Record the parent node of treeMode, used by class selector. + if (treeMode) { + if (!global.treeModeParentNode) { + global.treeModeParentNode = dest; + } else { + treeMode = false; + } + } + if (app.lastSignal !== -1 && !treeMode) { + app.lastSignal = attachTarget(element, dest); + } + if (app.lastSignal !== -1) { + compileChildren(vm, template, element); + } + if (app.lastSignal !== -1 && treeMode) { + delete global.treeModeParentNode; + app.lastSignal = attachTarget(element, dest); + } +} + +/** + * Set all children to a certain parent element. + * @param {Vm} vm - Vm object needs to be compiled. + * @param {any} template - Generate element from template. + * @param {Element | FragBlockInterface} dest - Parent Node's VM of current. + * @return {void | boolean} If there is no children, return null. Return true if has node. + */ +function compileChildren(vm: Vm, template: any, dest: Element | FragBlockInterface): void | boolean { + const app: any = vm.app || {}; + const children = template.children; + if (children && children.length) { + children.every((child) => { + compile(vm, child, dest); + return app.lastSignal !== -1; + }); + } +} + +/** + * Watch the list update and refresh the changes. + * @param {Vm} vm - Vm object need to be compiled. + * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. + * @param {FragBlockInterface} fragBlock - {vms, data, children} + * @param {*} info - {getter, key, value, trackBy, oldStyle} + */ +function bindRepeat(vm: Vm, target: TemplateInterface, fragBlock: FragBlockInterface, info: any): void { + const vms = fragBlock.vms; + const children = fragBlock.children; + const { getter, trackBy } = info; + const keyName = info.key; + const valueName = info.value; + + function compileItem(item: any, index: number, context: Vm) { + const mergedData = {}; + mergedData[keyName] = index; + mergedData[valueName] = item; + const newContext = mergeContext(context, mergedData); + vms.push(newContext); + compile(newContext, target, fragBlock, { repeat: item }); + } + const list = watchBlock(vm, fragBlock, getter, 'repeat', + (data) => { + Log.debug(`The 'repeat' item has changed ${data}.`); + if (!fragBlock || !data) { + return; + } + const oldChildren = children.slice(); + const oldVms = vms.slice(); + const oldData = fragBlock.data.slice(); + + // Collect all new refs track by. + const trackMap = {}; + const reusedMap = {}; + data.forEach((item, index) => { + const key = trackBy && item[trackBy] !== undefined ? item[trackBy] : index; + if (key === null || key === '') { + return; + } + trackMap[key] = item; + }); + + // Remove unused element foreach old item. + const reusedList: any[] = []; + oldData.forEach((item, index) => { + const key = trackBy && item[trackBy] !== undefined ? item[trackBy] : index; + if (hasOwn(trackMap, key)) { + reusedMap[key] = { + item, index, key, + target: oldChildren[index], + vm: oldVms[index] + }; + reusedList.push(item); + } else { + removeTarget(oldChildren[index]); + } + }); + + // Create new element for each new item. + children.length = 0; + vms.length = 0; + fragBlock.data = data.slice(); + fragBlock.updateMark = fragBlock.start; + + data.forEach((item, index) => { + const key = trackBy && item[trackBy] !== undefined ? item[trackBy] : index; + const reused = reusedMap[key]; + if (reused) { + if (reused.item === reusedList[0]) { + reusedList.shift(); + } else { + removeItem(reusedList, reused.item); + moveTarget(reused.target, fragBlock.updateMark); + } + children.push(reused.target); + vms.push(reused.vm); + reused.vm[valueName] = item; + + reused.vm[keyName] = index; + fragBlock.updateMark = reused.target; + } else { + compileItem(item, index, vm); + } + }); + delete fragBlock.updateMark; + } + ); + if (list && Array.isArray(list)) { + fragBlock.data = list.slice(0); + list.forEach((item, index) => { + compileItem(item, index, vm); + }); + } +} + +/** + * Watch the display update and add/remove the element. + * @param {Vm} vm - Vm object needs to be compiled. + * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. + * @param {FragBlockInterface} fragBlock - {vms, data, children} + * @param {MetaInterface} meta - To transfer data. + */ +function bindShown( + vm: Vm, + target: TemplateInterface, + fragBlock: FragBlockInterface, + meta: Partial +): void { + const display = watchBlock(vm, fragBlock, target.shown, 'shown', + (display) => { + Log.debug(`The 'if' item was changed ${display}.`); + if (!fragBlock || !!fragBlock.display === !!display) { + return; + } + fragBlock.display = !!display; + if (display) { + compile(vm, target, fragBlock, meta); + } else { + removeTarget(fragBlock, true); + } + } + ); + + fragBlock.display = !!display; + if (display) { + compile(vm, target, fragBlock, meta); + } +} + +/** + * Watch calc changes and append certain type action to differ. + * @param {Vm} vm - Vm object needs to be compiled. + * @param {FragBlockInterface} fragBlock - {vms, data, children} + * @param {Function} calc - Function. + * @param {string} type - Vm type. + * @param {Function} handler - Function. + * @return {*} Init value of calc. + */ +function watchBlock(vm: Vm, fragBlock: FragBlockInterface, calc: Function, type: string, handler: Function): any { + const differ = vm && vm.app && vm.app.differ; + const config: Partial = {}; + const newWatcher = newWatch(vm, calc, (value) => { + config.latestValue = value; + if (differ && !config.recorded) { + differ.append(type, fragBlock.blockId.toString(), () => { + const latestValue = config.latestValue; + handler(latestValue); + config.recorded = false; + config.latestValue = undefined; + }); + } + config.recorded = true; + }); + fragBlock.end.watchers.push(newWatcher); + return newWatcher.value; +} + +/** + * Clone a context and merge certain data. + * @param {Vm} context - Context value. + * @param {Object} mergedData - Certain data. + * @return {*} The new context. + */ +function mergeContext(context: Vm, mergedData: object): any { + const newContext = Object.create(context); + newContext.data = mergedData; + newContext.shareData = {}; + initData(newContext); + initComputed(newContext); + newContext.realParent = context; + return newContext; +} + +/** + * Check if it needs repeat. + * @param {Function | RepeatInterface} repeat - Repeat value. + * @return {boolean} - True if it needs repeat. Otherwise return false. + */ +function isRepeat(repeat: Function | RepeatInterface): repeat is RepeatInterface { + const newRepeat = repeat; + return newRepeat.exp !== undefined; +} + +/** + * Check if it is a block. + * @param {FragBlockInterface | Node} node - Node value. + * @return {boolean} - True if it is a block. Otherwise return false. + */ +export function isBlock(node: FragBlockInterface | Node): node is FragBlockInterface { + const newNode = node; + return newNode.blockId !== undefined; +} diff --git a/runtime/main/model/directive.ts b/runtime/main/model/directive.ts new file mode 100644 index 00000000..b39e19fd --- /dev/null +++ b/runtime/main/model/directive.ts @@ -0,0 +1,844 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * 2021.01.08 - Rewrite some functions and remove some redundant judgments to fit framework. + * Copyright (c) 2021 Huawei Device Co., Ltd. + */ + +/** + * @fileOverview + * Directive Parser + */ + +import { + typof, + camelize, + Log +} from '../../utils/index'; +import Watcher from '../reactivity/watcher'; +import { + setDescendantStyle +} from './selector'; +import { + getDefaultPropValue +} from '../util/props'; +import { + matchMediaQueryCondition +} from '../extend/mediaquery/mediaQuery'; +import { + TemplateInterface, + FragBlockInterface, + AttrInterface +} from './compiler'; +import Vm from './index'; +import Element from '../../vdom/Element'; + +const SETTERS = { + attr: 'setAttr', + style: 'setStyle', + event: 'addEvent', + idStyle: 'setIdStyle', + tagStyle: 'setTagStyle' +}; + +/** + * Bind id, attr, classnames, style, events to an element. + * @param {Vm} vm - Vm object. + * @param {Element} el - Element to be bind. + * @param {TemplateInterface} template - Structure of the component. + * @param {Element | FragBlockInterface} parentElement - Parent element of current element. + */ +export function bindElement(vm: Vm, el: Element, template: TemplateInterface, parentElement: Element | FragBlockInterface): void { + setId(vm, el, template.id, vm); + setAttr(vm, el, template.attr); + setStyle(vm, el, template.style); + setIdStyle(vm, el, template.id); + setClass(vm, el, template.classList); + setTagStyle(vm, el, template.type); + applyStyle(vm, el); + + // Set descendant style. + setDescendantStyle( + vm.selector, + { + id: template.id, + class: template.classList, + tag: template.type + }, + parentElement, + vm, + function(style: {[key: string]: any}) { + if (!style) { + return; + } + const css = vm.css || {}; + setAnimation(style, css); + setFontFace(style, css); + setStyle(vm, el, style); + } + ); + bindEvents(vm, el, template.events); + bindEvents(vm, el, template.onBubbleEvents, ''); + bindEvents(vm, el, template.onCaptureEvents, 'capture'); + bindEvents(vm, el, template.catchBubbleEvents, 'catchbubble'); + bindEvents(vm, el, template.catchCaptureEvents, 'catchcapture'); + if (!vm.isHide && !vm.init) { + el.addEvent('hide'); + vm.isHide = true; + } +} + +/** + *

Bind all props to sub vm and bind all style, events to the root element

+ *

of the sub vm if it doesn't have a replaced multi-node fragment.

+ * @param {Vm} vm - Vm object. + * @param {Vm} subVm - Sub vm. + * @param {TemplateInterface} template - Structure of the component. + * @param {Object} repeatItem - Item object. + */ +export function bindSubVm(vm: Vm, rawSubVm: Vm, rawTemplate: TemplateInterface, repeatItem: object): void { + const subVm: any = rawSubVm || {}; + const template: any = rawTemplate || {}; + const options: any = subVm.vmOptions || {}; + + let props = options.props; + if (isArray(props) || !props) { + if (isArray(props)) { + props = props.reduce((result, value) => { + result[value] = true; + return result; + }, {}); + } + mergeProps(repeatItem, props, vm, subVm); + mergeProps(template.attr, props, vm, subVm); + } else { + const attrData = template.attr || {}; + const repeatData = repeatItem || {}; + Object.keys(props).forEach(key => { + const prop = props[key]; + let value = attrData[key] || repeatData[key] || undefined; + if (value === undefined) { + value = getDefaultPropValue(vm, prop); + } + mergePropsObject(key, value, vm, subVm); + }); + } +} + +/** + * Merge class and styles from vm to sub vm. + * @param {Vm} vm - Vm object. + * @param {Vm} subVm - Sub vm. + * @param {TemplateInterface} template - Structure of the component. + * @param {Element | FragBlockInterface} target - The target of element. + */ +export function bindSubVmAfterInitialized(vm: Vm, subVm: Vm, template: TemplateInterface, target: Element | FragBlockInterface): void { + mergeClassStyle(template.classList, vm, subVm); + mergeStyle(template.style, vm, subVm); + if (target.children) { + target.children[target.children.length - 1]._vm = subVm; + } else { + target.vm = subVm; + } + bindSubEvent(vm, subVm, template); +} + +/** + * Bind custom event from vm to sub vm for calling parent method. + * @param {Vm} vm - Vm object. + * @param {Vm} subVm - Sub vm. + * @param {TemplateInterface} template - Structure of the component. + */ +function bindSubEvent(vm: Vm, subVm: Vm, template: TemplateInterface): void { + if (template.events) { + for (const type in template.events) { + subVm.$on(camelize(type), function() { + const args = []; + for (const i in arguments) { + args[i] = arguments[i]; + } + if (vm[template.events[type]] + && typeof vm[template.events[type]] === 'function') { + vm[template.events[type]].apply(vm, args); + } + }); + } + } +} + +/** + * Merge props from vm to sub vm. + * @param {string} key - Get vm object by key. + * @param {*} value - Default Value. + * @param {Vm} vm - Vm object. + * @param {Vm} subVm - Sub vm. + * @return {*} Sub vm object. + */ +function mergePropsObject(key: string, value: any, vm: Vm, subVm: Vm): any { + subVm.props.push(key); + if (typeof value === 'function') { + const returnValue = watch(vm, value, function(v) { + subVm[key] = v; + }); + subVm[key] = returnValue; + } else { + const realValue = + value && value.__hasDefault ? value.__isDefaultValue : value; + subVm[key] = realValue; + } + return subVm[key]; +} + +/** + * Bind props from vm to sub vm and watch their updates. + * @param {Object} target - Target object. + * @param {*} props - Vm props. + * @param {Vm} vm - Vm object. + * @param {Vm} subVm - Sub vm. + */ +function mergeProps(target: object, props: any, vm: Vm, subVm: Vm): void { + if (!target) { + return; + } + for (const key in target) { + if (!props || props[key]) { + subVm.props.push(key); + const value = target[key]; + if (typeof value === 'function') { + const returnValue = watch(vm, value, function(v) { + subVm[key] = v; + }); + subVm[key] = returnValue; + } else { + subVm[key] = value; + } + } + } +} + +/** + * Bind style from vm to sub vm and watch their updates. + * @param {Object} target - Target object. + * @param {Vm} vm - Vm object. + * @param {Vm} subVm - Sub vm. + */ +function mergeStyle(target: { [key: string]: any }, vm: Vm, subVm: Vm): void { + for (const key in target) { + const value = target[key]; + if (typeof value === 'function') { + const returnValue = watch(vm, value, function(v) { + if (subVm.rootEl) { + subVm.rootEl.setStyle(key, v); + } + }); + subVm.rootEl.setStyle(key, returnValue); + } else { + if (subVm.rootEl) { + subVm.rootEl.setStyle(key, value); + } + } + } +} + +/** + * Bind class and style from vm to sub vm and watch their updates. + * @param {Object} target - Target object. + * @param {Vm} vm - Vm object. + * @param {Vm} subVm - Sub vm. + */ +function mergeClassStyle(target: Function | string[], vm: Vm, subVm: Vm): void { + const css = vm.css || {}; + if (!subVm.rootEl) { + return; + } + + /** + * Class name. + * @constant {string} + */ + const CLASS_NAME = '@originalRootEl'; + css['.' + CLASS_NAME] = subVm.rootEl.classStyle; + + function addClassName(list, name) { + if (typof(list) === 'array') { + list.unshift(name); + } + } + + if (typeof target === 'function') { + const value = watch(vm, target, v => { + addClassName(v, CLASS_NAME); + setClassStyle(subVm.rootEl, css, v); + }); + addClassName(value, CLASS_NAME); + setClassStyle(subVm.rootEl, css, value); + } else if (target !== undefined) { + addClassName(target, CLASS_NAME); + setClassStyle(subVm.rootEl, css, target); + } +} + +/** + * Bind id to an element. Note: Each id is unique in a whole vm. + * @param {Vm} vm - Vm object. + * @param {Element} el - Element object. + * @param {Function | string} id - Unique vm id. + * @param {Vm} target - Target vm. + */ +export function setId(vm: Vm, el: Element, id: Function | string, target: Vm): void { + const map = Object.create(null); + Object.defineProperties(map, { + vm: { + value: target, + writable: false, + configurable: false + }, + el: { + get: () => el || target.rootEl, + configurable: false + } + }); + if (typeof id === 'function') { + const handler = id; + const newId = handler.call(vm); + if (newId || newId === 0) { + setElementId(el, newId); + vm.ids[newId] = map; + } + watch(vm, handler, (newId) => { + if (newId) { + setElementId(el, newId); + vm.ids[newId] = map; + } + }); + } else if (id && typeof id === 'string') { + setElementId(el, id); + vm.ids[id] = map; + } +} + +/** + * Set id to Element. + * @param {Element} el - Element object. + * @param {string} id - Element id. + */ +function setElementId(el: Element, id: string): void { + if (el) { + el.id = id; + } +} + +/** + * Bind attr to an element. + * @param {Vm} vm - Vm object. + * @param {Element} el - Element. + * @param {AttrInterface} attr - Attr to bind. + */ +function setAttr(vm: Vm, el: Element, attr: Partial): void { + bindDir(vm, el, 'attr', attr); +} + +/** + * Set font family and get font resource. + * @param {Object} css - Css style. + * @param {string | string[]} fontFamilyNames - Font family names. + * @return {*} Font resource. + */ +function _getFontFamily(css: any, fontFamilyNames: string | string[]): any[] { + let results = []; + const familyMap = css['@FONT-FACE']; + if (typeof fontFamilyNames === 'string') { + fontFamilyNames.split(',').forEach(fontFamilyName => { + fontFamilyName = fontFamilyName.trim(); + let find = false; + if (familyMap && Array.isArray(familyMap)) { + let len = familyMap.length; + while (len) { + if ( + familyMap[len - 1].fontFamily && + familyMap[len - 1].fontFamily === fontFamilyName + ) { + results.push(familyMap[len - 1]); + find = true; + } + len--; + } + } else if (familyMap && typeof familyMap === 'object') { + const definedFontFamily = familyMap[fontFamilyName]; + if (definedFontFamily && definedFontFamily.src) { + if (Array.isArray(definedFontFamily.src)) { + definedFontFamily.src = definedFontFamily.src.map(item => `url("${item}")`).join(','); + } + results.push(definedFontFamily); + find = true; + } + } + if (!find) { + results.push({ 'fontFamily': fontFamilyName }); + } + }); + } else if (Array.isArray(fontFamilyNames)) { + results = fontFamilyNames; + } else if (fontFamilyNames) { + Log.warn(`GetFontFamily Array error, unexpected fontFamilyNames type [${typeof fontFamilyNames}].`); + } + return results; +} + +/** + * Select class style. + * @param {Object} css - Css style. + * @param {Function | string[]} classList - List of class label. + * @param {number} index - Index of classList. + * @return {*} Select style. + */ +function selectClassStyle(css: object, classList: Function | string[], index: number, vm: Vm): any { + const key = '.' + classList[index]; + return selectStyle(css, key, vm); +} + +/** + * Select id style. + * @param {Object} css - Css style. + * @param {string} id - Id label. + * @param {Vm} vm - Vm object. + * @return {*} Select style. + */ +function selectIdStyle(css: object, id: string, vm: Vm): any { + const key = '#' + id; + return selectStyle(css, key, vm); +} + +/** + * Replace style. + * @param {*} oStyle - Current style. + * @param {*} rStyle - New style. + */ +function replaceStyle(oStyle: any, rStyle: any): void { + if (!rStyle || rStyle.length <= 0) { + return; + } + Object.keys(rStyle).forEach(function(key) { + oStyle[key] = rStyle[key]; + }); +} + +/** + * Select style for class label, id label. + * @param {Object} css - Css style. + * @param {string} key - Key index. + * @param {Vm} vm - Vm object. + * @return {*} + */ +function selectStyle(css: object, key: string, vm: Vm): any { + const style = css[key]; + if (!vm) { + return style; + } + const mediaStatus = vm.mediaStatus; + if (!mediaStatus) { + return style; + } + const mqArr = css['@MEDIA']; + if (!mqArr) { + vm.init = true; + return style; + } + const classStyle = {}; + if (style) { + Object.keys(style).forEach(function(key) { + classStyle[key] = style[key]; + }); + } + for (let i$1 = 0; i$1 < mqArr.length; i$1++) { + if (matchMediaQueryCondition(mqArr[i$1].condition, mediaStatus, false)) { + replaceStyle(classStyle, mqArr[i$1][key]); + } + } + return classStyle; +} + +/** + * Set class style after SelectClassStyle. + * @param {Element} el - Element object. + * @param {Object} css - Css style. + * @param {string[]} classList - List of class label. + */ +function setClassStyle(el: Element, css: object, classList: string[], vm?: Vm): void { + const classStyle = {}; + const length = classList.length; + if (length === 1) { + const style = selectClassStyle(css, classList, 0, vm); + if (style) { + Object.keys(style).forEach((key) => { + classStyle[key] = style[key]; + }); + } + } else { + const rets = []; + const keys = Object.keys(css || {}); + for (let i = 0; i < length; i++) { + const clsKey = '.' + classList[i]; + const style = selectStyle(css, clsKey, vm); + if (style) { + const order = clsKey === '.@originalRootEl' ? -1000 : keys.indexOf(clsKey); + rets.push({style: style, order: order}); + } + } + if (rets.length === 1) { + const style = rets[0].style; + if (style) { + Object.keys(style).forEach((key) => { + classStyle[key] = style[key]; + }); + } + } else if (rets.length > 1) { + rets.sort(function(a, b) { + if (!a) { + return -1; + } else if (!b) { + return 1; + } else { + return a.order > b.order ? 1 : -1; + } + }); + const retStyle = {}; + rets.forEach(function(key) { + if (key && key.style) { + Object.assign(retStyle, key.style); + } + }); + Object.keys(retStyle).forEach((key) => { + classStyle[key] = retStyle[key]; + }); + } + } + + const keyframes = css['@KEYFRAMES']; + if (keyframes) { + /* + * Assign @KEYFRAMES's value. + */ + const animationName = classStyle['animationName']; + if (animationName) { + classStyle['animationName'] = keyframes[animationName]; + } + const transitionEnter = classStyle['transitionEnter']; + if (transitionEnter) { + classStyle['transitionEnter'] = keyframes[transitionEnter]; + } + const transitionExit = classStyle['transitionExit']; + if (transitionExit) { + classStyle['transitionExit'] = keyframes[transitionExit]; + } + const sharedTransitionName = classStyle['sharedTransitionName']; + if (sharedTransitionName) { + classStyle['sharedTransitionName'] = keyframes[sharedTransitionName]; + } + } + const fontFace = classStyle['fontFamily']; + if (fontFace) { + const fontCompileList = _getFontFamily(css, fontFace); + classStyle['fontFamily'] = fontCompileList; + } + el.setClassStyle(classStyle); + el.classList = classList; +} + +/** + * Bind classnames to an element + * @param {Vm} vm - Vm object. + * @param {Element} el - Element object. + * @param {Function | string[]} classList - List of class label. + */ +export function setClass(vm: Vm, el: Element, classList: Function | string[]): void { + if (typeof classList !== 'function' && !Array.isArray(classList)) { + return; + } + if (Array.isArray(classList) && !classList.length) { + el.setClassStyle({}); + return; + } + const style = vm.css || {}; + if (typeof classList === 'function') { + const value = watch(vm, classList, v => { + setClassStyle(el, style, v, vm); + }); + setClassStyle(el, style, value, vm); + } else { + setClassStyle(el, style, classList, vm); + } +} + +/** + * Support css selector by id and component. + * @param {Vm} vm - Vm object. + * @param {Element} el - ELement component. + * @param {Function | string} id - Id label. + */ +export function setIdStyle(vm: Vm, el: Element, id: Function | string): void { + if (id) { + const css = vm.css || {}; + if (typeof id === 'function') { + const value = watch(vm, id, v => { + doSetStyle(vm, el, selectIdStyle(css, v, vm), css, 'idStyle'); + }); + doSetStyle(vm, el, selectIdStyle(css, value, vm), css, 'idStyle'); + } else if (typeof id === 'string') { + doSetStyle(vm, el, selectIdStyle(css, id, vm), css, 'idStyle'); + } + } +} + +/** + * Set style. + * @param {Vm} vm - Vm object. + * @param {Element} el - ELement component. + * @param {*} style - Style to be Set. + * @param {*} css - Css style. + * @param {string} name - Bind by name. + */ +function doSetStyle(vm: Vm, el: Element, style: any, css: any, name: string): void { + if (!style) { + return; + } + const typeStyle = {}; + Object.assign(typeStyle, style); + setAnimation(typeStyle, css); + setFontFace(typeStyle, css); + bindDir(vm, el, name, typeStyle); +} + +/** + * Set FontFace. + * @param {*} style - Style. + * @param {*} css - Css style. + */ +function setFontFace(style: any, css: any): void { + const fontFace = style['fontFamily']; + if (fontFace) { + const fontCompileList = _getFontFamily(css, fontFace); + style['fontFamily'] = fontCompileList; + } +} + +/** + * Set Animation + * @param {*} style - Style. + * @param {*} css - Css style. + */ +function setAnimation(style: any, css: any): void { + const animationName = style['animationName']; + const keyframes = css['@KEYFRAMES']; + if (animationName && keyframes) { + style['animationName'] = keyframes[animationName]; + } +} + +/** + * Set tag style. + * @param {Vm} vm - Vm object. + * @param {Element} el - ELement component. + * @param {string} tag - Tag. + */ +export function setTagStyle(vm: Vm, el: Element, tag: string): void { + const css = vm.css || {}; + if (tag && typeof tag === 'string') { + doSetStyle(vm, el, selectStyle(css, tag, vm), css, 'tagStyle'); + } +} + +/** + * Bind style to an element. + * @param {Vm} vm - Vm object. + * @param {Element} el - ELement component. + * @param {*} style - Style. + */ +function setStyle(vm: Vm, el: Element, style: any): void { + bindDir(vm, el, 'style', style); +} + +/** + * Add an event type and handler to an element and generate a dom update. + * @param {Vm} vm - Vm object. + * @param {Element} el - ELement component. + * @param {string} type - Type added to event. + * @param {Function} handler - Handle added to event. + */ +function setEvent(vm: Vm, el: Element, type: string, handler: Function): void { + el.addEvent(type, handler.bind(vm)); +} + +/** + * Add all events of an element. + * @param {Vm} vm - Vm object. + * @param {Element} el - ELement component. + * @param {Object} events - Events of an element. + */ +function bindEvents(vm: Vm, el: Element, events: object, eventType?: string): void { + if (!events) { + return; + } + const keys = Object.keys(events); + let i = keys.length; + while (i--) { + const key = keys[i]; + let handler = events[key]; + if (typeof handler === 'string') { + handler = vm[handler]; + if (!handler || typeof handler !== 'function') { + Log.warn(`The event handler '${events[key]}' is undefined or is not function.`); + continue; + } + } + const eventName: string = eventType ? eventType + key : key; + setEvent(vm, el, eventName, handler); + } +} + +/** + *

Set a series of members as a kind of an element.

+ *

for example: style, attr, ...

+ *

if the value is a function then bind the data changes.

+ * @param {Vm} vm - Vm object. + * @param {Element} el - ELement component. + * @param {string} name - Method name. + * @param {Object} data - Data that needed. + */ +function bindDir(vm: Vm, el: Element, name: string, data: object): void { + if (!data) { + return; + } + const keys = Object.keys(data); + let i = keys.length; + if (!i) { + return; + } + const methodName = SETTERS[name]; + const method = el[methodName]; + const isSetStyle = methodName === 'setStyle'; + while (i--) { + const key = keys[i]; + const value = data[key]; + if (key === 'ref') { + vm.$refs[value] = el; + } + const isSetFont = isSetStyle && key === 'fontFamily'; + const setValue = function(value) { + if (isSetFont) { + value = filterFontFamily(vm, value); + } + method.call(el, key, value); + }; + if (typeof value === 'function') { + bindKey(vm, el, setValue, value); + } else { + setValue(value); + } + } +} + +/** + * Bind data changes to a certain key to a name series in an element. + * @param {Vm} vm - Vm object. + * @param {Element} el - ELement component. + * @param {Function} setValue - Set value. + * @param {Function} calc - Watch the calc and return a value by calc.call(). + */ +function bindKey(vm: Vm, el: Element, setValue: Function, calc: Function): void { + // Watch the calc, and returns a value by calc.call(). + const watcher = newWatch(vm, calc, (value) => { + function handler() { + setValue(value); + } + const differ = vm && vm.app && vm.app.differ; + if (differ) { + differ.append('element', el.ref, handler); + } else { + handler(); + } + }); + el.watchers.push(watcher); + setValue(watcher.value); +} + +/** + * FontFamily Filter. + * @param {Vm} vm - Vm object. + * @param {string} fontFamilyName - FontFamily name. + * @return {*} FontFamily Filter. + */ +export function filterFontFamily(vm: Vm, fontFamilyName: string): any[] { + const css = vm.css || {}; + return _getFontFamily(css, fontFamilyName); +} + +/** + * Watch the calc. + * @param {Vm} vm - Vm object. + * @param {Function} calc - Watch the calc, and returns a value by calc.call(). + * @param {Function} callback - Callback callback Function. + * @return {Watcher} New watcher for rhe calc value. + */ +export function newWatch(vm: Vm, calc: Function, callback: Function): Watcher { + const watcher = new Watcher(vm, calc, function(value, oldValue) { + if (typeof value !== 'object' && value === oldValue) { + return; + } + callback(value); + }, null); + return watcher; +} + +/** + * Watch a calc function and callback if the calc value changes. + * @param {Vm} vm - Vm object. + * @param {Function} calc - Watch the calc, and returns a value by calc.call(). + * @param {Function} callback - Callback callback Function. + * @return {*} Watcher value. + */ +export function watch(vm: Vm, calc: Function, callback: Function): any { + const watcher = new Watcher(vm, calc, function(value, oldValue) { + if (typeof value !== 'object' && value === oldValue) { + return; + } + callback(value); + }, null); + return watcher.value; +} + +/** + * Apply style to an element. + * @param {Vm} vm - Vm object. + * @param {Element} el - Element object. + */ +function applyStyle(vm: Vm, el: Element): void { + const css = vm.css || {}; + const allStyle = el.style; + setAnimation(allStyle, css); +} + +/** + * Check if it is an Array. + * @param {*} params - Any value. + * @return {boolean} Return true if it is an array. Otherwise return false. + */ +function isArray(params: any): params is Array { + return Array.isArray(params); +} diff --git a/runtime/main/model/domHelper.ts b/runtime/main/model/domHelper.ts new file mode 100644 index 00000000..963fab6e --- /dev/null +++ b/runtime/main/model/domHelper.ts @@ -0,0 +1,269 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * @fileOverview Document & Element Helpers. + * + * required: + * Document#: createElement, createComment, getRef + * Element#: appendChild, insertBefore, removeChild, nextSibling + */ +import Vm from './index'; +import Element from '../../vdom/Element'; +import Comment from '../../vdom/Comment'; +import Node from '../../vdom/Node'; +import { FragBlockInterface, isBlock } from './compiler'; +import { emitSubVmLife } from './pageLife'; + +/** + * Create a body by type. + * @param {Vm} vm - Vm object. + * @param {string} type - Element type. + * @return {Node} Body of Node by type. + */ +export function createBody(vm: Vm, type: string): Node { + const doc = vm.app.doc; + return doc.createBody(type); +} + +/** + * Create an element by type + * @param {Vm} vm - Vm object. + * @param {string} type - Element type. + * @return {Element} Element of Node by type. + */ +export function createElement(vm: Vm, type: string): Element { + const doc = vm.app.doc; + return doc.createElement(type); +} + +/** + * Create and return a frag block for an element. + * @param {Vm} vm - Vm object. + * @param {Element} element - Element object. + * @return {FragBlockInterface} New block. + */ +export function createBlock(vm: Vm, element: Element | FragBlockInterface): FragBlockInterface { + const start = createBlockStart(vm); + const end = createBlockEnd(vm); + const blockId = lastestBlockId++; + const newBlock: FragBlockInterface = {start, end, blockId}; + if (isBlock(element)) { + let updateMark = element.updateMark; + if (updateMark) { + if (isBlock(updateMark)) { + updateMark = updateMark.end; + } + element.element.insertAfter(end, updateMark); + element.element.insertAfter(start, updateMark); + element.updateMark = end; + } else { + element.element.insertBefore(start, element.end); + element.element.insertBefore(end, element.end); + } + newBlock.element = element.element; + } else { + element.appendChild(start); + element.appendChild(end); + newBlock.element = element; + element.block = newBlock; + } + return newBlock; +} + +let lastestBlockId = 1; + +/** + * Create and return a block starter. + * @param {Vm} vm - Vm object. + * @return {Comment} A block starter. + */ +function createBlockStart(vm: Vm): Comment { + const doc = vm.app.doc; + const anchor = doc.createComment('start'); + return anchor; +} + +/** + * Create and return a block ender. + * @param {Vm} vm - Vm object. + * @return {Comment} A block starter. + */ +function createBlockEnd(vm: Vm): Comment { + const doc = vm.app.doc; + const anchor = doc.createComment('end'); + anchor.destroyHook = function() { + if (anchor.watchers !== undefined) { + anchor.watchers.forEach(function(watcher) { + watcher.teardown(); + }); + anchor.watchers = []; + } + }; + return anchor; +} + +/** + * Attach target to a certain dest using appendChild by default. + * @param {Element} target - If the dest is a frag block then insert before the ender. + * @param {FragBlockInterface | Element} dest - A certain dest. + * @return {*} + */ +export function attachTarget(target: Element, dest: FragBlockInterface | Element): any { + if (isBlock(dest)) { + const before = dest.end; + const after = dest.updateMark; + if (dest.children) { + dest.children.push(target); + } + if (after) { + const signal = moveTarget(target, after); + if (isBlock(target)) { + dest.updateMark = target.end; + } else { + dest.updateMark = target; + } + return signal; + } else if (isBlock(target)) { + dest.element.insertBefore(target.start, before); + dest.element.insertBefore(target.end, before); + } else { + return dest.element.insertBefore(target, before); + } + } else { + if (isBlock(target)) { + dest.appendChild(target.start); + dest.appendChild(target.end); + } else { + return dest.appendChild(target); + } + } +} + +/** + * Move target before a certain element. The target maybe block or element. + * @param {Element | FragBlockInterface} target - Block or element. + * @param {Node} after - Node object after moving. + * @return {*} + */ +export function moveTarget(target: Element | FragBlockInterface, after: Node): any { + if (isBlock(target)) { + return moveBlock(target, after); + } + return moveElement(target, after); +} + +/** + * Move element before a certain element. + * @param {Element} element - Element object. + * @param {Node} after - Node object after moving. + * @return {*} + */ +function moveElement(element: Element, after: Node): any { + const parent = after.parentNode as Element; + if (parent && parent.children.indexOf(after) !== -1) { + return parent.insertAfter(element, after); + } +} + +/** + * Move all elements of the block before a certain element. + * @param {FragBlockInterface} fragBlock - Frag block. + * @param {Node} after - Node object after moving. + */ +function moveBlock(fragBlock: FragBlockInterface, after: Node): any { + const parent = after.parentNode as Element; + if (parent) { + let el = fragBlock.start as Node; + let signal; + const group = [el]; + while (el && el !== fragBlock.end) { + el = el.nextSibling; + group.push(el); + } + let temp = after; + group.every((el) => { + signal = parent.insertAfter(el, temp); + temp = el; + return signal !== -1; + }); + return signal; + } +} + +/** + * Remove target from DOM tree. + * @param {Element | FragBlockInterface} target - If the target is a frag block then call _removeBlock + * @param {boolean} [preserveBlock] - Preserve block. + */ +export function removeTarget(target: Element | FragBlockInterface, preserveBlock?: boolean): void { + if (!preserveBlock) { + preserveBlock = false; + } + if (isBlock(target)) { + removeBlock(target, preserveBlock); + } else { + removeElement(target); + } + if (target.vm) { + target.vm.$emit('hook:onDetached'); + emitSubVmLife(target.vm, 'onDetached'); + target.vm.$emit('hook:destroyed'); + } +} + +/** + * Remove an element. + * @param {Element | Comment} target - Target element. + */ +function removeElement(target: Element | Comment): void { + const parent = target.parentNode as Element; + if (target instanceof Element && target.event && target.event['disappear']) { + target.fireEvent('disappear', {}); + } + if (parent) { + parent.removeChild(target); + } +} + +/** + * Remove a frag block. + * @param {FragBlockInterface} fragBlock - Frag block. + * @param {boolean} [preserveBlock] - If preserve block. + */ +function removeBlock(fragBlock: FragBlockInterface, preserveBlock?: boolean): void { + if (!preserveBlock) { + preserveBlock = false; + } + const result = []; + let el = fragBlock.start.nextSibling; + while (el && el !== fragBlock.end) { + result.push(el); + el = el.nextSibling; + } + if (!preserveBlock) { + removeElement(fragBlock.start); + } + result.forEach((el) => { + removeElement(el); + }); + if (!preserveBlock) { + removeElement(fragBlock.end); + } +} diff --git a/runtime/main/model/events.ts b/runtime/main/model/events.ts new file mode 100644 index 00000000..58b49efe --- /dev/null +++ b/runtime/main/model/events.ts @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * 2021.01.08 - Reconstruct the class 'Evt' and make it more adaptable to framework. + * Copyright (c) 2021 Huawei Device Co., Ltd. + */ + +/** + * @fileOverview + * Everything about component event which includes event object, event listener, + * event emitter and lifecycle hooks. + */ + +import Vm from './index'; +import { PageLifecycleHooks } from './pageLife'; + +export type ExternalEvent = {'hook:_innerInit': () => void} | null; + +/** + *

Event object definition. An event object has `type`, `timestamp` and `detail`

+ *

from which a component emit. The event object could be dispatched to

+ *

parents or broadcasted to children except `this.stop()` is called.

+ */ +export class Evt { + private _timestamp: number; + private _detail: any; + private _type: string; + private _shouldStop: boolean; + + constructor(type: string, detail: any) { + this._timestamp = Date.now(); + this._detail = detail; + this._type = type; + if (detail instanceof Evt) { + return detail; + } + } + + /** + * Stop dispatch and broadcast. + */ + public stop() { + this.shouldStop = true; + } + + /** + * Check if it can't be dispatched or broadcasted + */ + public hasStopped() { + return this.shouldStop; + } + + /** + * ShouldStop of this Evt. + * @type {boolean} + */ + public get shouldStop() { + return this._shouldStop; + } + + public set shouldStop(newStop: boolean) { + this._shouldStop = newStop; + } + + /** + * Detail of this Evt. + * @type {*} + * @readonly + */ + public get detail() { + return this._detail; + } + + /** + * Timestamp of this Evt. + * @type {number} + * @readonly + */ + public get timestamp() { + return this._timestamp; + } + + /** + * Type of this Evt. + * @type {string} + * @readonly + */ + public get type() { + return this._type; + } +} + +export const LIFE_CYCLE_TYPES: Array = [ + PageLifecycleHooks.ONINIT, + PageLifecycleHooks.ONREADY, + PageLifecycleHooks.ONSHOW, + PageLifecycleHooks.ONHIDE, + PageLifecycleHooks.ONBACKPRESS, + PageLifecycleHooks.ONMENUPRESS, + PageLifecycleHooks.ONMENUBUTTONPRESS, + PageLifecycleHooks.ONSTARTCONTINUATUIN, + PageLifecycleHooks.ONCOMPLETECONTINUATION, + PageLifecycleHooks.ONSAVEDATA, + PageLifecycleHooks.ONRESTOREDATA, + PageLifecycleHooks.ONNEWREQUEST, + PageLifecycleHooks.ONCONFIGURATIONUPDATED, + PageLifecycleHooks.ONACTIVE, + PageLifecycleHooks.ONINACTIVE, + PageLifecycleHooks.ONLAYOUTREADY, + 'onAttached', + 'onDetached', + 'onPageShow', + 'onPageHide', + 'onDestroy' +]; + +/** + * Init events. + * @param {Vm} vm - Vm object. + * @param {ExternalEvent} externalEvents - External events. + */ +export function initEvents(vm: Vm, externalEvents: ExternalEvent): void { + const options = vm.vmOptions || {}; + for (const externalEvent in externalEvents) { + vm.$on(externalEvent, externalEvents[externalEvent]); + } + LIFE_CYCLE_TYPES.forEach((type) => { + vm.$on(`hook:${type}`, options[type]); + }); +} diff --git a/runtime/main/model/index.ts b/runtime/main/model/index.ts new file mode 100755 index 00000000..cde726de --- /dev/null +++ b/runtime/main/model/index.ts @@ -0,0 +1,862 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * 2021.01.08 - Reconstruct the class 'Vm' and make it more adaptable to framework. + * Copyright (c) 2021 Huawei Device Co., Ltd. + */ + +/** + * @fileOverview + * ViewModel Constructor & definition + */ + +import { + extend, + Log, + removeItem +} from '../../utils/index'; +import { + initState, + initBases +} from '../reactivity/state'; +import { + build, FragBlockInterface +} from './compiler'; +import { + set, + del +} from '../reactivity/observer'; +import { + watch, + initPropsToData +} from './pageLife'; +import { + initEvents, + ExternalEvent, + Evt +} from './events'; +import { + selector +} from './selector'; +import Page from '../page/index'; +import Element from '../../vdom/Element'; +import { + ComputedInterface, + cssType, + Props, + VmOptions, + MediaStatusInterface, + ExternalBindingInterface +} from './vmOptions'; + +/** + * VM constructor. + * @param {string} type - Type. + * @param {null | VmOptions} options - Component options. + * @param {Vm} parentVm which contains _app. + * @param {Element | FragBlockInterface} parentEl - root element or frag block. + * @param {Object} mergedData - External data. + * @param {ExternalEvent} externalEvents - External events. + */ +export default class Vm { + private _parent: Vm; + private _app: Page; + private _computed: ComputedInterface; + private _css: cssType; + private _type: string; + private readonly _descriptor: string; + private _props: Props; + private _vmOptions: VmOptions; + private _selector: object; + private _ids: Record; + private _init: boolean; + private _ready: boolean; + private _valid: boolean; + private _vmEvents: object; + private _childrenVms: Vm[]; + private _visible: boolean; + private _data: any; + private _shareData: any; + private _realParent: Vm + private _parentEl: Element | FragBlockInterface; + private _rootEl: Element; + private _$refs: Record; + private _externalBinding: ExternalBindingInterface; + private _isHide: boolean; + private _mediaStatus: Partial>; + private _methods: Record any>; + private _slotContext: { content: Record, parentVm: Vm }; + private _$app: any; + + constructor( + type: string, + options: null | VmOptions, + parentVm: Vm | any, + parentEl: Element | FragBlockInterface, + mergedData: object, + externalEvents: ExternalEvent + ) { + this._$app = global.aceapp; + this._parent = parentVm._realParent ? parentVm._realParent : parentVm; + this._app = parentVm._app; + parentVm._childrenVms && parentVm._childrenVms.push(this); + + if (!options && this._app.customComponentMap) { + options = this._app.customComponentMap[type]; + } + const data = options.data || {}; + const shareData = options.shareData || {}; + this._vmOptions = options; + this._computed = options.computed; + this._css = options.style; + this._selector = selector(this._css); + this._ids = {}; + this._$refs = {}; + this._vmEvents = {}; + this._childrenVms = []; + this._type = type; + this._valid = true; + this._props = []; + this._methods = {}; + + // Bind events and lifecycles. + initEvents(this, externalEvents); + + Log.debug( + `'_innerInit' lifecycle in Vm(${this._type}) and mergedData = ${JSON.stringify(mergedData)}.` + ); + this.$emit('hook:_innerInit'); + this._data = typeof data === 'function' ? data.apply(this) : data; + this._shareData = typeof shareData === 'function' ? shareData.apply(this) : shareData; + this._descriptor = options._descriptor; + if (global.aceapp && global.aceapp.i18n && global.aceapp.i18n.extend) { + global.aceapp.i18n.extend(this); + } + if (global.aceapp && global.aceapp.dpi && global.aceapp.dpi.extend) { + global.aceapp.dpi.extend(this); + } + + // MergedData means extras params. + if (mergedData) { + dataAccessControl(this, mergedData, this._app.options && this._app.options.appCreate); + extend(this._data, mergedData); + } + initPropsToData(this); + initState(this); + initBases(this); + Log.debug(`"onInit" lifecycle in Vm(${this._type})`); + this.$emit('hook:onInit'); + if (!this._app.doc) { + return; + } + this.mediaStatus = {}; + this.mediaStatus.orientation = this._app.options.orientation; + this.mediaStatus.width = this._app.options.width; + this.mediaStatus.height = this._app.options.height; + this.mediaStatus.resolution = this._app.options.resolution; + this.mediaStatus['device-type'] = this._app.options.deviceType; + this.mediaStatus['aspect-ratio'] = this._app.options.aspectRatio; + this.mediaStatus['device-width'] = this._app.options.deviceWidth; + this.mediaStatus['device-height'] = this._app.options.deviceHeight; + this.mediaStatus['round-screen'] = this._app.options.roundScreen; + + // If there is no parentElement, specify the documentElement. + this._parentEl = parentEl || this._app.doc.documentElement; + build(this); + } + + /** + * Get the element by id. + * @param {string | number} [id] - Element id. + * @return {Element} Element object. if get null, return root element. + */ + public $element(id?: string | number): Element { + if (id) { + if (typeof id !== 'string' && typeof id !== 'number') { + Log.warn(`Invalid parameter type: The type of 'id' should be string or number, not ${typeof id}.`); + return; + } + const info: any = this.ids[id]; + if (info) { + return info.el; + } + } else { + return this.rootEl; + } + } + + /** + * Get the vm by id. + * @param {string} id - Vm id. + * @return {Vm} Vm object. + */ + public $vm(id: string): Vm { + const info = this.ids[id]; + if (info) { + return info.vm; + } + } + + /** + * Get parent Vm of current. + */ + public $parent(): Vm { + return this.parent; + } + + /** + * Get child Vm of current. + */ + public $child(id: string): Vm { + if (typeof id !== 'string') { + Log.warn(`Invalid parameter type: The type of 'id' should be string, not ${typeof id}.`); + return; + } + return this.$vm(id); + } + + /** + * Get root element of current. + */ + public $rootElement(): Element { + return this.rootEl; + } + + /** + * Get root Vm of current. + */ + public $root(): Vm { + return getRoot(this); + } + + /** + * Execution Method. + * @param {string} type - Type. + * @param {Object} [detail] - May needed for Evt. + * @param {*} args - Arg list. + * @return {*} + */ + public $emit(type: string, detail?: object, ...args: any[]): any[] { + if (typeof type !== 'string') { + Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`); + return; + } + const events = this._vmEvents; + const handlerList = events[type]; + if (handlerList) { + const results = []; + const evt = new Evt(type, detail); + handlerList.forEach((handler) => { + results.push(handler.call(this, evt, ...args)); + }); + return results; + } + } + + /** + * Execution Method directly. + * @param {string} type - Type. + * @param {*} args - Arg list. + * @return {*} + */ + public $emitDirect(type: string, ...args: any[]): any[] { + if (typeof type !== 'string') { + Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`); + return; + } + const events = this._vmEvents; + const handlerList = events[type]; + if (handlerList) { + const results = []; + handlerList.forEach((handler) => { + results.push(handler.call(this, ...args)); + }); + return results; + } + } + + /** + * Dispatch events, passing upwards along the parent. + * @param {string} type - Type. + * @param {Object} [detail] - May needed for Evt. + */ + public $dispatch(type: string, detail?: object): void { + if (typeof type !== 'string') { + Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`); + return; + } + const evt = new Evt(type, detail); + this.$emit(type, evt); + if (!evt.hasStopped() && this._parent && this._parent.$dispatch) { + this._parent.$dispatch(type, evt); + } + } + + /** + * Broadcast event, which is passed down the subclass. + * @param {string} type - Type. + * @param {Object} [detail] - May be needed for Evt. + */ + public $broadcast(type: string, detail?: object): void { + if (typeof type !== 'string') { + Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`); + return; + } + const evt = new Evt(type, detail); + this.$emit(type, evt); + if (!evt.hasStopped() && this._childrenVms) { + this._childrenVms.forEach((subVm) => { + subVm.$broadcast(type, evt); + }); + } + } + + /** + * Add the event listener. + * @param {string} type - Type. + * @param {Function} handler - To add. + */ + public $on(type: string, handler: Function): void { + if (typeof type !== 'string') { + Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`); + return; + } + if (typeof handler !== 'function') { + Log.warn(`Invalid parameter type: The type of 'handler' should be function, not ${typeof handler}.`); + return; + } + const events = this._vmEvents; + const handlerList = events[type] || []; + handlerList.push(handler); + events[type] = handlerList; + if (type === 'hook:onReady' && this._ready) { + this.$emit('hook:onReady'); + } + } + + /** + * Remove the event listener. + * @param {string} type - Type. + * @param {Function} handler - To remove. + */ + public $off(type: string, handler: Function): void { + if (typeof type !== 'string') { + Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`); + return; + } + if (typeof handler !== 'function') { + Log.warn(`Invalid parameter type: The type of 'handler' should be function, not ${typeof handler}.`); + return; + } + const events = this._vmEvents; + if (!handler) { + delete events[type]; + return; + } + const handlerList = events[type]; + if (!handlerList) { + return; + } + removeItem(handlerList, handler); + } + + /** + * Execution element.fireEvent Method. + * @param {string} type - Type. + * @param {Object} data - needed for Evt. + * @param {string} id - Element id. + */ + public $emitElement(type: string, data: object, id: string): void { + if (typeof type !== 'string') { + Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`); + return; + } + if (typeof id !== 'string') { + Log.warn(`Invalid parameter type: The type of 'id' should be string, not ${typeof id}.`); + return; + } + const info = this.ids[id]; + if (info) { + const element = info.el; + const evt = new Evt(type, data); + element.fireEvent(type, evt, false); + } else { + Log.warn('The id is invalid, id = ' + id); + } + } + + /** + * Watch a calc function and callback if the calc value changes. + * @param {string} data - Data that needed. + * @param {Function} callback - Callback function. + */ + public $watch(data: string, callback: (...args: any) => any): void { + if (typeof data !== 'string') { + Log.warn(`Invalid parameter type: The type of 'data' should be string, not ${typeof data}.`); + return; + } + if (typeof callback !== 'function') { + Log.warn(`Invalid parameter type: The type of 'callback' should be function, not ${typeof callback}.`); + return; + } + watch(this, data, callback); + } + + /** + * Set a property on an object. + * @param {string} key - Get value by key. + * @param {*} value - Property + */ + public $set(key: string, value: any): void { + if (typeof key !== 'string' || key.indexOf('.') === -1) { + Log.warn(`Invalid parameter type: The type of 'key' should be string with '.', not ${typeof key}.`); + return; + } + _proxySet(this.data, key, value); + set(this.data, key, value); + } + + /** + * Delete a property and trigger change. + * @param {string} key - Get by key. + */ + public $delete(key: string): void { + if (typeof key !== 'string') { + Log.warn(`Invalid parameter type: The type of 'key' should be string, not ${typeof key}.`); + return; + } + del(this.data, key); + } + + /** + * Delete Vm object. + * @param {Vm} vm - Vm object. + */ + public destroyVm(vm: Vm): void { + Log.debug(`[JS Framework] "onDestroy" lifecycle in Vm(${vm.type})`); + this.$emit('hook:onDestroy'); + vm.$emit('hook:onDetached'); + this._valid = false; + + delete this._app; + delete this._computed; + delete this._css; + delete this._data; + delete this._ids; + delete this._vmOptions; + delete this._parent; + delete this._parentEl; + delete this._rootEl; + delete this._$refs; + + // Destroy child vms recursively. + if (this._childrenVms) { + let vmCount: number = this._childrenVms.length; + while (vmCount--) { + this.destroyVm.call(this._childrenVms[vmCount], this._childrenVms[vmCount]); + } + delete this._childrenVms; + } + delete this._type; + delete this._vmEvents; + } + + /** + * $t function. + * @param {string} key - Key. + * @return {string} - Key. + */ + public $t(key: string): string { + return key; + } + + /** + * $tc function. + * @param {string} key - Key. + * @return {string} - Key. + */ + public $tc(key: string): string { + return key; + } + + /** + * $r function. + * @param {string} key - Key. + * @return {string} - Key. + */ + public $r(key: string): string { + return key; + } + + /** + * $app function. + * @return {*} - aceapp. + */ + public get $app(): any { + return this._$app; + } + + /** + * Methods of this Vm. + * @type {Object} + * @readonly + */ + public get methods() { + return this._methods; + } + + /** + * Type of this Vm. + * @type {string} + * @readonly + */ + public get type() { + return this._type; + } + + /** + * Css of this Vm. + * @type {[key: string]: any} + * @readonly + */ + public get css() { + return this._css; + } + + /** + * Options of this Vm. + * @type {VmOptions} + */ + public get vmOptions() { + return this._vmOptions; + } + + public set vmOptions(newOptions: VmOptions) { + this._vmOptions = newOptions; + } + + /** + * Parent of this Vm. + * @type {Vm} + * @readonly + */ + public get parent() { + return this._parent; + } + + /** + * RealParent of this Vm. + * @type {Vm} + */ + public get realParent() { + return this._realParent; + } + + public set realParent(realParent: Vm) { + this._realParent = realParent; + } + + /** + * Computed of this Vm. + * @type {ComputedInterface} + */ + public get computed() { + return this._computed; + } + + public set computed(newComputed: ComputedInterface) { + this._computed = newComputed; + } + + /** + * Selector of this Vm. + * @type {Object} + * @readonly + */ + public get selector() { + return this._selector; + } + + /** + * ParentEl of this Vm. + * @type {FragBlockInterface | Element} + */ + public get parentEl() { + return this._parentEl; + } + + public set parentEl(newParentEl: FragBlockInterface | Element) { + this._parentEl = newParentEl; + } + + /** + * App of this Vm. + * @type {Page} + */ + public get app() { + return this._app; + } + + public set app(newApp: Page) { + this._app = newApp; + } + + /** + * ShareData of this Vm. + * @type {*} + */ + public get shareData() { + return this._shareData; + } + + public set shareData(newShareData: object) { + this._shareData = newShareData; + } + + /** + * Data of this Vm. + * @type {*} + */ + public get data() { + return this._data; + } + + public set data(newData: any) { + this._data = newData; + } + + /** + * Props of this Vm. + * @type {Props} + * @readonly + */ + public get props() { + return this._props; + } + + /** + * Init of this Vm. + * @type {boolean} + */ + public get init() { + return this._init; + } + + public set init(newInit: boolean) { + this._init = newInit; + } + + /** + * Valid of this Vm. + * @type {boolean} + * @readonly + */ + public get valid() { + return this._valid; + } + + /** + * Visible of this Vm. + * @type {boolean} + */ + public get visible() { + return this._visible; + } + + public set visible(newVisible) { + this._visible = newVisible; + } + + /** + * Ready of this Vm. + * @type {boolean} + */ + public get ready() { + return this._ready; + } + + public set ready(newReady: boolean) { + this._ready = newReady; + } + + /** + * RootEl of this Vm. + * @type {Element} + */ + public get rootEl() { + return this._rootEl; + } + + public set rootEl(newRootEl: Element) { + this._rootEl = newRootEl; + } + + /** + * Ids of this Vm. + * @type {{[key: string]: { vm: Vm, el: Element}}} + * @readonly + */ + public get ids() { + return this._ids; + } + + /** + * VmEvents of this Vm. + * @type {Object} + * @readonly + */ + public get vmEvents() { + return this._vmEvents; + } + + /** + * children of vm. + * @return {Array} - children of Vm. + */ + public get childrenVms() { + return this._childrenVms; + } + + /** + * ExternalBinding of this Vm. + * @type {ExternalBinding} + */ + public get externalBinding() { + return this._externalBinding; + } + + public set externalBinding(newExternalBinding: ExternalBindingInterface) { + this._externalBinding = newExternalBinding; + } + + /** + * Descriptor of this Vm. + * @type {string} + * @readonly + */ + public get descriptor() { + return this._descriptor; + } + + /** + * IsHide of this Vm. + * @type {boolean} + */ + public get isHide() { + return this._isHide; + } + + public set isHide(newIsHide: boolean) { + this._isHide = newIsHide; + } + + /** + * MediaStatus of this Vm. + * @type {MediaStatusInterface} + */ + public get mediaStatus() { + return this._mediaStatus; + } + + public set mediaStatus(newMediaStatus: Partial>) { + this._mediaStatus = newMediaStatus; + } + + /** + * $refs of this Vm. + * @type {[key: string]: Element} + * @readonly + */ + public get $refs() { + return this._$refs; + } + + /** + * slotContext of this Vm. + * @type { content: Record, parentVm: Vm } + */ + public get slotContext() { + return this._slotContext; + } + + public set slotContext(newMSoltContext: { content: Record, parentVm: Vm }) { + this._slotContext = newMSoltContext; + } +} + +/** + * Set proxy. + * @param {Object} data - Data that needed. + * @param {string} key - Get prop by key. + * @param {*} value - Property. + */ +function _proxySet(data: object, key: string, value: any): void { + let tempObj = data; + const keys = key.split('.'); + const len = keys.length; + for (let i = 0; i < len; i++) { + const prop = keys[i]; + if (i === len - 1) { + set(tempObj, prop, value); + tempObj = null; + break; + } + if (tempObj[prop] === null || typeof tempObj[prop] !== 'object' && typeof tempObj[prop] !== 'function') { + Log.warn(`Force define property '${prop}' of '${JSON.stringify(tempObj)}' with value '{}', ` + + `old value is '${tempObj[prop]}'.`); + set(tempObj, prop, {}); + } + tempObj = tempObj[prop]; + } +} + +/** + * Control data access. + * @param {Vm} vm - Vm object. + * @param {Object} mergedData - Merged data. + * @param {boolean} external - If has external data. + */ +function dataAccessControl(vm: any, mergedData: object, external: boolean): void { + if (vm._descriptor) { + const keys = Object.keys(mergedData); + keys.forEach(key => { + const desc = vm._descriptor[key]; + if (!desc || desc.access === 'private' || external && desc.access === 'protected') { + Log.error(`(${key}) can not modify`); + delete mergedData[key]; + } + }); + } +} + +/** + * Get root Vm. + * @param {Vm} vm - Vm object. + * @return {Vm} Root vm. + */ +function getRoot(vm: any): Vm { + const parent = vm.parent; + if (!parent) { + return vm; + } + if (parent._rootVm) { + return vm; + } + return getRoot(parent); +} diff --git a/runtime/main/model/pageLife.ts b/runtime/main/model/pageLife.ts new file mode 100644 index 00000000..73660cf9 --- /dev/null +++ b/runtime/main/model/pageLife.ts @@ -0,0 +1,290 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Log } from '../../utils/index'; +import Watcher from '../reactivity/watcher'; +import Element from '../../vdom/Element'; +import Vm from './index'; + +/** + * Enum for Page lifecycle hooks. + * @enum {string} + * @readonly + */ +/* eslint-disable no-unused-vars */ +export const enum PageLifecycleHooks { + /** + * ONINIT Type + */ + ONINIT = 'onInit', + /** + * ONREADY Type + */ + ONREADY = 'onReady', + /** + * ONSHOW Type + */ + ONSHOW = 'onShow', + /** + * ONHIDE Type + */ + ONHIDE = 'onHide', + /** + * ONBACKPRESS Type + */ + ONBACKPRESS = 'onBackPress', + /** + * ONMENUPRESS Type + */ + ONMENUPRESS = 'onMenuPress', + /** + * ONMENUBUTTONPRESS Type + */ + ONMENUBUTTONPRESS = 'onMenuButtonPress', + /** + * ONSUSPENDED Type + */ + ONSUSPENDED = 'onSuspended', + /** + * ONSTARTCONTINUATUIN Type + */ + ONSTARTCONTINUATUIN = 'onStartContinuation', + /** + * ONCOMPLETECONTINUATION Type + */ + ONCOMPLETECONTINUATION = 'onCompleteContinuation', + /** + * ONSAVEDATA Type + */ + ONSAVEDATA = 'onSaveData', + /** + * ONRESTOREDATA Type + */ + ONRESTOREDATA = 'onRestoreData', + /** + * ONNEWREQUEST Type + */ + ONNEWREQUEST = 'onNewRequest', + /** + * ONCONFIGURATIONUPDATED Type + */ + ONCONFIGURATIONUPDATED = 'onConfigurationUpdated', + /** + * ONLAYOUTREADY Type + */ + ONLAYOUTREADY = 'onLayoutReady', + /** + * ONACTIVE Type + */ + ONACTIVE = 'onActive', + /** + * ONLAYOUTREADY Type + */ + ONINACTIVE = 'onInactive' +} +/* eslint-enable no-unused-vars */ + +const PAGE_LIFE_CYCLE_TYPES: Array = [ + PageLifecycleHooks.ONSHOW, + PageLifecycleHooks.ONHIDE, + PageLifecycleHooks.ONBACKPRESS, + PageLifecycleHooks.ONMENUPRESS, + PageLifecycleHooks.ONMENUBUTTONPRESS, + PageLifecycleHooks.ONSTARTCONTINUATUIN, + PageLifecycleHooks.ONCOMPLETECONTINUATION, + PageLifecycleHooks.ONSAVEDATA, + PageLifecycleHooks.ONRESTOREDATA, + PageLifecycleHooks.ONNEWREQUEST, + PageLifecycleHooks.ONCONFIGURATIONUPDATED, + PageLifecycleHooks.ONLAYOUTREADY, + PageLifecycleHooks.ONACTIVE, + PageLifecycleHooks.ONINACTIVE +]; + +/** + * Bind page lifeCycle. + * @param {Vm} vm - Vm object. + * @param {Element} element - Element object. + */ +export function bindPageLifeCycle(vm: Vm, element: Element): void { + const options = vm.vmOptions || {}; + PAGE_LIFE_CYCLE_TYPES.forEach(type => { + let eventType; + if (type === PageLifecycleHooks.ONSHOW) { + eventType = 'viewappear'; + } else if (type === PageLifecycleHooks.ONHIDE) { + eventType = 'viewdisappear'; + } else if (type === PageLifecycleHooks.ONBACKPRESS) { + eventType = 'clickbackitem'; + } else if (type === PageLifecycleHooks.ONSUSPENDED) { + eventType = 'viewsuspended'; + } else if (type === 'onConfigurationUpdated') { + eventType = 'onConfigurationUpdated'; + } else if (type === 'onLayoutReady') { + eventType = 'layoutReady'; + } else if (type === 'onActive') { + eventType = 'viewactive'; + } else if (type === 'onInactive') { + eventType = 'viewinactive'; + } else { + eventType = type; + } + + const handle = options[type]; + let isEmitEvent = false; + if (handle) { + isEmitEvent = true; + element.addEvent(eventType, eventHandle); + } else { + if (type === PageLifecycleHooks.ONSHOW || type === PageLifecycleHooks.ONHIDE) { + element.addEvent(eventType, eventHandle); + } + } + + /** + * Hadle event methods. + * @param {*} event - Event methods. + * @param {*} args - Arg list. + * @return {*} + */ + function eventHandle(event, ...args: any[]): any { + if (type === PageLifecycleHooks.ONSHOW) { + emitSubVmLife(vm, 'onPageShow'); + vm.visible = true; + } else if (type === PageLifecycleHooks.ONHIDE) { + emitSubVmLife(vm, 'onPageHide'); + vm.visible = false; + } else if (type === PageLifecycleHooks.ONCONFIGURATIONUPDATED) { + return vm.$emitDirect('hook:${type}', ...args); + } + + Log.debug(`EventHandle: isEmitEvent = ${isEmitEvent}, event = ${event}, args = ${JSON.stringify(args)}.`); + if (isEmitEvent) { + if (type === PageLifecycleHooks.ONNEWREQUEST) { + return handleNewRequest(args[0]); + } else if (type === PageLifecycleHooks.ONSAVEDATA) { + return handleSaveData(); + } else if (type === PageLifecycleHooks.ONRESTOREDATA) { + return handleRestoreData(args[0]); + } else if (type === PageLifecycleHooks.ONCOMPLETECONTINUATION) { + return vm.$emitDirect(`hook:${type}`, ...args); + } else { + return vm.$emit(`hook:${type}`, {}, ...args); + } + } + } + + /** + * Handle saveData. + * @return {string | boolean} If no hook, return false. Otherwise return vm.shareData. + */ + function handleSaveData(): string | boolean { + const allData = { + saveData: {}, + shareData: {} + }; + const result = vm.$emitDirect(`hook:${type}`, allData.saveData); + if (!result) { + return false; + } + const shareResult = vm.shareData || {}; + if (shareResult instanceof Object && !(shareResult instanceof Array)) { + allData.shareData = shareResult; + } + return JSON.stringify(allData); + } + + /** + * Handle restore Data. + * @param {*} restoreData - Restore data. + * @return {*} + */ + function handleRestoreData(restoreData: any) { + const saveData = restoreData.saveData || {}; + const shareData = restoreData.shareData || {}; + + Object.assign(vm.shareData, shareData); + return vm.$emitDirect(`hook:${type}`, saveData); + } + function handleNewRequest(data: any) { + Object.assign(vm.data, data); + return vm.$emitDirect(`hook:${type}`); + } + }); +} + +/** + * Watch a calc function and callback if the calc value changes. + * @param {Vm} vm - Vm object. + * @param {string} data - Data that needed. + * @param {Function} callback - Callback function. + * @return {*} + */ +export function watch(vm: Vm, data: string, callback: (...args: any) => any): any { + function calc() { + let arr = []; + arr = data.split('.'); + let retData = this; + arr.forEach(type => { + if (retData) { + retData = retData[type]; + } + }); + return retData; + } + const watcher = new Watcher(vm, calc, function(value, oldValue) { + if (typeof value !== 'object' && value === oldValue) { + return; + } + if (typeof callback === 'function') { + callback(value, oldValue); + } else { + if (vm.methods[callback] && typeof vm.methods[callback] === 'function') { + vm.methods[callback](value, oldValue); + } + } + }, null); + return watcher.value; +} + +/** + * Prop is assigned to data to observe prop. + * @param {Vm} vm - Vm object. + */ +export function initPropsToData(vm: Vm): void { + vm.props.forEach(prop => { + if (vm.data) { + vm.data[prop] = vm[prop]; + } + }); +} + +/** + * Emit subVm lifecycle + * @param {Vm} vm - Vm object. + * @param {String} type - event type + */ +export function emitSubVmLife(vm: Vm, type:string) { + if (vm.childrenVms) { + vm.childrenVms.forEach((subVm) => { + subVm.$emit(`hook:${type}`); + emitSubVmLife(subVm, type); + }); + } +} diff --git a/runtime/main/model/selector.ts b/runtime/main/model/selector.ts new file mode 100644 index 00000000..4b6d742f --- /dev/null +++ b/runtime/main/model/selector.ts @@ -0,0 +1,350 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Log } from '../../utils/index'; +import { watch } from './directive'; +import cssWhat from 'css-what'; +import Element from '../../vdom/Element'; +import Vm from './index'; +import { cssType } from './vmOptions'; +import { + FragBlockInterface, + isBlock +} from './compiler'; + +interface ContentInterface { + type: string; + name?: string; + action?: string; + value?: string; + ignoreCase?: boolean; +} + +interface ParamsInterface { + id: string | Function; + class: string[] | Function, + tag: string +} + +interface EvidenceInterface { + id: string; + ruleDef: ContentInterface[]; + style: Record; + score: number; + order: number; + priority?: number; +} + +type ListType = Partial> + +type ResStyle = Partial<{resArray: T[], watchValue: T}>; + +/** + * Support css descendant selector. + * @param {cssType } styleOri - Css style. + * @return {ListType} object: {tagList, idList, classList} + */ +export function selector(styleOri: cssType): ListType { + const style = addMetaToStyle(styleOri); + const list: ListType = {}; + const keys = Object.keys(style || {}); + keys.forEach((key) => { + if (style[key] && style[key]._meta) { + const ruleDef = style[key]._meta.ruleDef; + if (ruleDef) { + const rule = ruleDef[ruleDef.length - 1]; + const object: EvidenceInterface = { + id: rule.type === 'tag' ? rule.name : rule.value, + ruleDef: ruleDef, + style: style[key], + score: getScore(ruleDef), + order: keys.indexOf(key) + }; + if (rule.type === 'tag') { + list.tagList = list.tagList || []; + list.tagList.push(object); + } else if (rule.type === 'attribute' && rule.name === 'id') { + list.idList = list.idList || []; + list.idList.push(object); + } else if (rule.type === 'attribute' && rule.name === 'class') { + list.classList = list.classList || []; + list.classList.push(object); + } else { + Log.error(`Invalid ruleDef, t = ${rule.type}, name = ${rule.name}.`); + } + } + } + }); + return list; +} + +/** + * Set descendant style. + * @param {ListType} selector - Descendant selector. + * @param {ParamsInterface} params - Param: {id, class, tag...} + * @param {Element | FragBlockInterface} parentElement - Parent element object. + * @param {Vm} vm - Vm object. + * @param {Function} styleFunction - StyleFunction. + */ +export function setDescendantStyle( + selector: ListType, + params: Partial, + parentElement: Element | FragBlockInterface, + vm: Vm, + styleFunction: (style: object) => void +): void { + const retStyle = {}; + let oldStyle; + const applyStyleRes = applyStyle(selector, params, parentElement, vm, undefined); + assignStyleByPriority(retStyle, applyStyleRes); + styleFunction(retStyle); + oldStyle = retStyle; + const watchValue = applyStyleRes.watchValue; + if (watchValue) { + watch(vm, watchValue.value, v => { + const applyStyleRes = applyStyle(selector, params, parentElement, vm, v); + const retStyle = {}; + assignStyleByPriority(retStyle, applyStyleRes); + if (oldStyle) { + Object.keys(oldStyle).forEach(function(key) { + if (!retStyle[key]) { + retStyle[key] = ''; + } + }); + } + styleFunction(retStyle); + oldStyle = retStyle; + }); + } +} + +/** + * Apply style. + * @param {ListType} selector - Descendant selector. + * @param {ParamsInterface} params - Param: {id, class, tag...} + * @param {Element | FragBlockInterface} parentElement - Parent element object. + * @param {Vm} vm - Vm object. + * @param {string[]} [classValue] - If has class value. + * @return {ResStyle} {resArray, watchValue} + */ +function applyStyle( + selector: ListType, + params: Partial, + parentElement: Element | FragBlockInterface, + vm: Vm, + classValue?: string[] +): ResStyle { + const applyStyleRes: ResStyle = {}; + applyStyleRes.resArray = []; + if (params.id) { + const value = isIdFunction(params.id) ? params.id.call(vm, vm) : params.id; + const rets = setDescendant(selector.idList, value, parentElement); + if (rets) { + applyStyleRes.resArray = applyStyleRes.resArray.concat(rets); + } + } + if (!classValue) { + if (params.class) { + classValue = isClassFunction(params.class) ? params.class.call(vm, vm) : params.class; + applyStyleRes.watchValue = isClassFunction(params.class) ? {list: selector.classList, value: params.class} : undefined; + } + } + if (Array.isArray(classValue)) { + classValue.forEach(function(value) { + const rets = setDescendant(selector.classList, value, parentElement); + if (rets) { + applyStyleRes.resArray = applyStyleRes.resArray.concat(rets); + } + }); + } else { + const rets = setDescendant(selector.classList, classValue, parentElement); + if (rets) { + applyStyleRes.resArray = applyStyleRes.resArray.concat(rets); + } + } + + if (params.tag) { + const rets = setDescendant(selector.tagList, params.tag, parentElement); + if (rets) { + applyStyleRes.resArray = applyStyleRes.resArray.concat(rets); + } + } + return applyStyleRes; +} + +/** + * Assign style by priority. + * @param {Object} retStyle - Returned style. + * @param {ResStyle} applyStyleRes - {resArray, watchValue} + */ +function assignStyleByPriority(retStyle: object, applyStyleRes: ResStyle): void { + const arr = applyStyleRes.resArray; + arr.sort(function(a, b) { + if (!a) { + return -1; + } else if (!b) { + return 1; + } else { + return a.priority > b.priority ? 1 : -1; + } + }); + arr.forEach(function(key) { + if (key && key.style) { + Object.assign(retStyle, key.style); + } + }); +} + +/** + * Set descendant for Node. + * @param {EvidenceInterface} list - Node list to be set priority. + * @param {string} value - Value. + * @param {Element | FragBlockInterface} parentElement - Parent element. + * @return {*} Node list after setting priority + */ +function setDescendant(list: EvidenceInterface[], value: string, parentElement: Element | FragBlockInterface) { + if (!list) { + return; + } + const rets = []; + for (let i = 0; i < list.length; i++) { + if (list[i].id === value) { + const ruleDef = list[i].ruleDef; + let parent = parentElement; + let markElement; + for (let j = ruleDef.length - 3; j >= 0; j = j - 2) { + const rule = ruleDef[j]; + markElement = getElement( + rule.type === 'tag' ? rule.type : rule.name, + ruleDef[j + 1].type !== 'child', + rule.type === 'tag' ? rule.name : rule.value, + parent + ); + if (!markElement) { + break; + } + parent = markElement.parentNode; + if (parent === undefined && markElement.ref !== '_documentElement' && global.treeModeParentNode) { + parent = global.treeModeParentNode; + } + } + if (markElement) { + const ret = list[i]; + ret.priority = ret.score + ret.order * 0.01; + rets.push(ret); + } + } + } + return rets; +} + +/** + * Get parent by condition + * @param {string} type - Condition type: class id tag. + * @param {string} iter - If Can get parent`s parent node. + * @param {string} value - Condition value, class value/id value/tag value. + * @param {Element | FragBlockInterface} element - ParentNode. + * @return {*} ParentNode. + */ +function getElement(type: string, iter: boolean, value: string, element: Element | FragBlockInterface) { + if (element === null) { + return undefined; + } + if (isBlock(element)) { + element = element.element; + } + let parentNode = element.parentNode as Element; + if (parentNode === null && element.ref !== '_documentElement' && global.treeModeParentNode) { + parentNode = global.treeModeParentNode; + } + // Type : class id tag per. + const condition = type === 'class' ? element.classList && element.classList.indexOf(value) !== -1 + : type === 'id' ? element.id === value : element.type === value; + return condition ? element : iter ? getElement(type, iter, value, parentNode) : undefined; +} + +/** + * Get score of Node. + * @param {ContentInterface[]} ruleDef - Rule. + * @return {number} - Score. + */ +function getScore(ruleDef: ContentInterface[]): number { + let score = 0; + for (let i = 0; i < ruleDef.length; i++) { + const rule = ruleDef[i]; + if (rule.type === 'tag') { + score += 1; + } else if (rule.type === 'attribute' && rule.name === 'id') { + score += 10000; + } else if (rule.type === 'attribute' && rule.name === 'class') { + score += 100; + } + } + return score; +} + +/** + * Add meta to style. + * @param {cssType} classObject - Class style Info. + * @return {cssType } Class style Info List. + */ +function addMetaToStyle(classObject: cssType): cssType { + const classList = classObject; + const classListkey = Object.keys(classList || {}); + classListkey.forEach(key => { + // Style is not empty and _meta exists. + if ( + Object.keys(classList[key]).length > 0 && + !classList[key]['_meta'] && + key !== '.@originalRootEl' && + key !== '@KEYFRAMES' && + key !== '@MEDIA' && + key !== '@FONT-FACE' && + key !== '@TRANSITION' + ) { + const meta: { ruleDef?: string[] } = {}; + const keys = cssWhat(key); + meta.ruleDef = keys[0]; + if (meta.ruleDef && meta.ruleDef.length >= 3) { + classList[key]['_meta'] = meta; + } + } + }); + return classList; +} + +/** + * Check if it is a Id function. + * @param {Function | string} param - Any type. + * @return {boolean} Param is Function or not. + */ +function isIdFunction(param: Function | string): param is Function { + const newParam = param; + return newParam.call !== undefined; +} + +/** + * Check if it is a class function. + * @param {Function | string} param - Any type. + * @return {boolean} Param is Function or not. + */ +function isClassFunction(param: Function | string[]): param is Function { + const newParam = param; + return newParam.call !== undefined; +} diff --git a/runtime/main/model/vmOptions.ts b/runtime/main/model/vmOptions.ts new file mode 100644 index 00000000..fcc7bd29 --- /dev/null +++ b/runtime/main/model/vmOptions.ts @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Vm from './index'; +import { TemplateInterface } from './compiler'; + +export interface ComputedInterface { + [key: string]: Function | WritableComputedOptions +} + +type ComputedGetter = (ctx?: any) => T +type ComputedSetter = (v: T) => void + +interface WritableComputedOptions { + get: ComputedGetter; + set?: ComputedSetter; +} + +export type cssType = Record; +export type Props = string[]; +export type PropOptions = string[] | {[key: string]: {default?: string}}; + +export type VmOptions = Partial>; + +export interface ExternalBindingInterface { + parent: Vm; + template: TemplateInterface; +} + +export interface MediaStatusInterface { + 'orientation': T, + 'width': T, + 'height': T, + 'resolution': T, + 'device-type': T, + 'aspect-ratio': T, + 'device-width': T, + 'device-height': T, + 'round-screen': K +} diff --git a/runtime/main/page/Image.ts b/runtime/main/page/Image.ts new file mode 100644 index 00000000..5efe28b4 --- /dev/null +++ b/runtime/main/page/Image.ts @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Page from './index'; + +let image; + +/** + * This class provide a Image object. + */ +export class Image { + private _src: string; + private _height: number; + private _width: number; + private _onload: (...args: any | null) => void; + private _onerror: (...args: any | null) => void; + + constructor(page: Page) { + image = page.requireModule('system.image'); + this._src = ''; + this._height = 0; + this._width = 0; + this._onload = null; + this._onerror = null; + } + + /** + * Src of this Image. + * @type {string} + */ + public get src() { + return this._src; + } + + public set src(src) { + this._src = src; + image.getImage({ + src: this._src, + width: this._width, + height: this._height, + success: data => { + if (this.onload && typeof this.onload === 'function') { + this.onload(data); + } + }, + fail: data => { + if (this.onerror && typeof this.onerror === 'function') { + this.onerror(data); + } + } + }); + } + + /** + * Width of this Image. + * @type {number} + */ + public get width() { + return this._width; + } + + public set width(width) { + this._width = width; + } + + /** + * Height of this Image. + * @type {number} + */ + public get height() { + return this._height; + } + + public set height(height) { + this._height = height; + } + + /** + * Triggered when the image is successfully loaded. + * @type {Function} + */ + public get onload() { + return this._onload; + } + + public set onload(onload: (...args: any | null) => void) { + this._onload = onload; + } + + /** + * Triggered when the image fails to be loaded. + * @type {Function} + */ + public get onerror() { + return this._onerror; + } + + public set onerror(onerror: (...args: any | null) => void) { + this._onerror = onerror; + } +} diff --git a/runtime/main/page/Page.ts b/runtime/main/page/Page.ts new file mode 100644 index 00000000..f0785430 --- /dev/null +++ b/runtime/main/page/Page.ts @@ -0,0 +1,237 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * @fileOverview + * Page constructor & definition + */ + +import Differ from './api/Differ'; +import { requireModule } from './register'; +import { updateActions, callTasks } from './api/index'; +import { Options } from '../app/index'; +import Vm from '../model/index'; +import Document from '../../vdom/Document'; + +/** + * Directive description to native. + */ +export interface Task { + module: string; + method: string; + args: number[]; +} + +/** + * Page constructor for framework. + * @param {string} id + * @param {Options} options + * @param {string} packageName + */ +export default class Page { + private _packageName: string; + private _id: string; + private _options: Options; + private _vm: Vm; + private _customComponentMap: object; + private _commonModules: object; + private _doc: Document; + private _differ: Differ; + private _i18nService: object; + private _dpiService: object; + private _lastSignal: number; + private _destroyed: boolean; + + constructor(id: string, options: Options, packageName: string) { + this._packageName = packageName || 'notset'; + this._id = id; + this._options = options || null; + this._vm = null; + this._customComponentMap = {}; + this._commonModules = {}; + this._doc = new Document(id, this._options.bundleUrl); + this._differ = new Differ(id); + } + + /** + * LastSignal of this Page. + * @type {number} + */ + get lastSignal() { + return this._lastSignal; + } + + set lastSignal(newLastSignal: number) { + this._lastSignal = newLastSignal; + } + + /** + * PackageName of this Page. + * @type {string} + */ + get packageName() { + return this._packageName; + } + + /** + * Id of this Page. + * @type {string} + */ + get id() { + return this._id; + } + + set id(id: string) { + this._id = id; + } + + /** + * Options of this Page. + * @type {Options} + */ + get options() { + return this._options; + } + + set options(options: Options) { + this._options = options; + } + + /** + * Vm of this Page. + * @type {Vm} + */ + get vm() { + return this._vm; + } + + set vm(vm: Vm) { + this._vm = vm; + } + + /** + * CustomComponentMap of this Page. + * @type {Object} + */ + get customComponentMap() { + return this._customComponentMap; + } + + set customComponentMap(customComponentMap: object) { + this._customComponentMap = customComponentMap; + } + + /** + * CommonModules of this Page. + * @type {Object} + */ + get commonModules() { + return this._commonModules; + } + + set commonModules(commonModules: object) { + this._commonModules = commonModules; + } + + /** + * Doc of this Page. + * @type {Document} + */ + get doc() { + return this._doc; + } + + set doc(doc: Document) { + this._doc = doc; + } + + /** + * Differ of this Page. + * @type {Differ} + */ + get differ() { + return this._differ; + } + + set differ(differ: Differ) { + this._differ = differ; + } + + /** + * I18nService of this Page. + * @type {Object} + */ + get i18nService() { + return this._i18nService; + } + + set i18nService(i18nService: object) { + this._i18nService = i18nService; + } + + /** + * DpiService of this page. + * @type {Object} + */ + get dpiService() { + return this._dpiService; + } + + set dpiService(dpiService: object) { + this._dpiService = dpiService; + } + + /** + * Destroyed of this page. + * @type {boolean} + */ + get destroyed() { + return this._destroyed; + } + + set destroyed(destroyed: boolean) { + this._destroyed = destroyed; + } + + /** + * Obtain methods of a module. + * @param {string} name + * @return {*} + */ + public requireModule(name: string): any { + return requireModule(this, name); + } + + /** + * Collect all virtual-DOM mutations together and send them to renderer. + * @return {*} + */ + public updateActions(): any { + return updateActions(this); + } + + /** + * Call all tasks from a page to renderer (native). + * @param {Task[] | Task} tasks + * @return {*} + */ + public callTasks(tasks: Task[] | Task): any { + return callTasks(this, tasks); + } +} diff --git a/runtime/main/page/api/Differ.ts b/runtime/main/page/api/Differ.ts new file mode 100644 index 00000000..bda3c612 --- /dev/null +++ b/runtime/main/page/api/Differ.ts @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * This class provides action for page refresh. + */ +export default class Differ { + private _id: string; + private _map: object[]; + private _hooks: Function[]; + private _hasTimer: boolean; + + constructor(id: string) { + this._id = id; + this._map = []; + this._hooks = []; + } + + /** + * Check whether the map is empty. + * @return {boolean} + */ + public isEmpty(): boolean { + return this._map.length === 0; + } + + /** + * Id of the page. + * @type {string} + */ + public get id() { + return this._id; + } + + /** + * Append action. + * @param {string} type + * @param {string} ref + * @param {Function} handler + */ + public append(type: string, ref: string, handler: Function): void { + // Ignore depth to speed up render. + const defaultDepth: number = 1; + if (!this._hasTimer) { + this._hasTimer = true; + + // Use setTimeout instead of setTimeoutDiffer + setTimeout(() => { + this._hasTimer = false; + this.flush(); + }, 0); + } + const map: object[] = this._map; + if (!map[defaultDepth]) { + map[defaultDepth] = {}; + } + const group: object = map[defaultDepth]; + if (!group[type]) { + group[type] = {}; + } + if (type === 'element') { + if (!group[type][ref]) { + group[type][ref] = []; + } + group[type][ref].push(handler); + } else { + group[type][ref] = handler; + } + } + + /** + * Execute actions of differ. + */ + public flush(): void { + const map: object[] = this._map.slice(); + this._map.length = 0; + map.forEach((group) => { + callTypeList(group, 'element'); + callTypeMap(group, 'repeat'); + callTypeMap(group, 'shown'); + }); + + const hooks: Function[] = this._hooks.slice(); + this._hooks.length = 0; + hooks.forEach((fn) => { + fn(); + }); + + if (!this.isEmpty()) { + this.flush(); + } + } + then(fn) { + this._hooks.push(fn); + } +} + +function callTypeMap(group: any, type: string): void { + const map: Function[] = group[type]; + for (const ref in map) { + map[ref](); + } +} + +function callTypeList(group: any, type: string): void { + const map: any = group[type]; + for (const ref in map) { + const list: Function[] = map[ref]; + list.forEach((handler) => { + handler(); + }); + } +} diff --git a/runtime/main/page/api/index.ts b/runtime/main/page/api/index.ts new file mode 100644 index 00000000..c0ccbdeb --- /dev/null +++ b/runtime/main/page/api/index.ts @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * @fileOverview + * page controls from native + * + * - init bundle + * - destroy + * - fire event + * - callback + * - fireEventSync + * - updateActions + * - callTasks + * + * corresponded with the API of page manager (framework.js) + */ +export { init } from '../entry/init'; + +export { + destroy, + fireEvent, + callback, + fireEventSync, + updateActions, + callTasks +} from './misc'; diff --git a/runtime/main/page/api/misc.ts b/runtime/main/page/api/misc.ts new file mode 100644 index 00000000..25b317aa --- /dev/null +++ b/runtime/main/page/api/misc.ts @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * @fileOverview + * page controls from native + * + * - fire event + * - callback + * - refresh + * - destroy + * + * corresponded with the API of page manager (framework.js) + */ + +import { + typof, + Log +} from '../../../utils/index'; +import Page from '../index'; +import Element from '../../../vdom/Element'; + +/** + * Destroy a page. + * @param {Page} page + */ +export function destroy(page: Page): void { + Log.debug(`Destroy a page(${page.id}).`); + + if (page.vm) { + page.vm.destroyVm(page.vm); + } + + page.id = null; + page.options = null; + page.vm = null; + page.doc.taskCenter.destroyCallback(); + page.doc.destroy(); + page.doc = null; + page.customComponentMap = null; + page.commonModules = null; +} + +/** + * Fire an event to update domChanges of a page. + * @param {Page} page + * @param {*} ref + * @param {*} type + * @param {Object} e + * @param {Object} domChanges + * @param {*[] | null} args + * @return any + */ +export function fireEvent(page: Page, ref?: any, type?: any, e?: object, domChanges?: boolean, ...args: any[] | null): any { + Log.debug(`Fire a '${type}' event on an element(${ref}) in page(${page.id}), args = ${JSON.stringify(args)}.`); + if (Array.isArray(ref)) { + ref.some((ref) => { + return fireEvent(page, ref, type, e) !== false; + }); + return; + } + const el: Element = page.doc.getRef(ref); + if (el) { + const options: {params?: any} = {}; + if (args) { + options.params = [...args]; + } + const result: any = page.doc.fireEvent(el, type, e, domChanges, options); + page.differ.flush(); + page.doc.taskCenter.send('dom', { action: 'updateFinish' }, []); + return result; + } + return new Error(`Invalid element reference '${ref}'.`); +} + +/** + * Update page content on a callback. + * @param {Page} page + * @param {number} callbackId + * @param {Object} data + * @param {boolean} ifKeepAlive + * @return {*} + */ +export function callback(page: Page, callbackId?: number, data?: object, ifKeepAlive?: boolean): any { + Log.debug(`runtime/main/page/api/misc.js: Invoke a callback(${callbackId}) with ` + + `${JSON.stringify(data)} in page(${page.id}) ifKeepAlive = ${ifKeepAlive}.`); + const result: any = page.doc.taskCenter.consumeCallback(callbackId, data, ifKeepAlive); + updateActions(page); + page.doc.taskCenter.send('dom', { action: 'updateFinish' }, []); + return result; +} + +/** + * Fire an event to update domChanges of a page. + * @param {Page} page - page. + * @param {*} - correspond to fireEvent args. + * @return {*} + */ +export function fireEventSync(page: Page, ...args: any[]): any { + const result: any = fireEvent(page, ...args); + if (page && page.callTasks) { + let callbackId = ''; + for (let i = 0; i < args.length; i++) { + if (args[i]._callbackId !== undefined) { + callbackId = args[i]._callbackId; + break; + } + } + let resultStr = result || {}; + if (Array.isArray(resultStr)) { + resultStr = result[0] || {}; + } + if (resultStr.constructor !== String) { + resultStr = JSON.stringify(resultStr); + } + return page.callTasks([{ + module: 'internal.jsResult', + method: 'callbackNative', + args: [callbackId, resultStr] + }]); + } +} + +/** + * Collect all virtual-DOM mutations together and send them to renderer. + * @param {Page} page + */ +export function updateActions(page: Page): void { + page.differ.flush(); +} + +/** + * Call all tasks from a page to renderer (native). + * @param {object} page + * @param {*} tasks + * @return {*} + */ +export function callTasks(page: Page, tasks: any): any { + let result: any; + if (typof(tasks) !== 'array') { + tasks = [tasks]; + } + tasks.forEach(task => { + result = page.doc.taskCenter.send( + 'module', + { + module: task.module, + method: task.method + }, + task.args + ); + }); + return result; +} diff --git a/runtime/main/page/entry/bundle.ts b/runtime/main/page/entry/bundle.ts new file mode 100644 index 00000000..23cce912 --- /dev/null +++ b/runtime/main/page/entry/bundle.ts @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * 2021.01.08 - Add i18n and dpi service. + * Copyright (c) 2021 Huawei Device Co., Ltd. + */ + +import { + isComponent, + isModule, + removePrefix, + Log +} from '../../../utils/index'; +import { + registerCustomComponent, + requireModule +} from '../register'; +import { appMap } from '../../app/map'; +import Vm from '../../model/index'; +import Page from '../index'; + +/** + * Parse page code. + * @param {Page} page - Page. + * @param {string} name - Name of page. + * @param {*[]} args + */ +export const defineFn = function(page: Page, name?: string, ...args: any[] | null): void { + Log.debug(`Define a component ${name}.`); + + const parseContent: Function = args[1]; + let bundleContent: object = null; + const moduleContent = { exports: {} }; + + // Function to obtain bundle content. + if (parseContent) { + const pageRequire = (name: string) : any => { + if (isModule(name)) { + const packageName = page.packageName; + const appFunction = (): Page => { + if (page && page.doc) { + return page; + } + if (packageName === 'notset') { + return page; + } + const appPage: Page = appMap[packageName].getAppInstance(); + return appPage || page; + }; + const moduleName: string = removePrefix(name); + return requireModule(appFunction, moduleName); + } + }; + parseContent(pageRequire, moduleContent.exports, moduleContent); + bundleContent = moduleContent.exports; + } + if (isComponent(name)) { + const componetName: string = removePrefix(name); + registerCustomComponent(page, componetName, bundleContent); + } +}; + +/** + * Create i18n and dpi service, a new Vm. + * @param {Page} page + * @param {string} name - Name of page. + * @param {*} config + * @param {*} data + * @return {*} + */ +export function bootstrap(page: Page, name: string, data: any): any { + Log.debug(`Bootstrap for ${name}.`); + + // Check component name. + let componentName: string; + if (isComponent(name)) { + componentName = removePrefix(name); + } else { + return new Error(`Wrong component name: ${name}.`); + } + + // Start i18n service. + if (global && global.aceapp && global.aceapp._i18n_data_ && page.i18nService) { + const I18nService: any = page.i18nService; + global.aceapp.i18n = new I18nService(global.aceapp._i18n_data_); + } + + // Start dpi service. + if (global && global.aceapp && global.aceapp._dpi_data_ && page.dpiService) { + const DpiService: any = page.dpiService; + global.aceapp.dpi = new DpiService(global.aceapp._dpi_data_); + } + + // Create a new Vm and mark rootVm. + page.vm = new Vm(componentName, null, { _app: page, _rootVm: true }, null, data, null); +} diff --git a/runtime/main/page/entry/init.ts b/runtime/main/page/entry/init.ts new file mode 100644 index 00000000..d339a81c --- /dev/null +++ b/runtime/main/page/entry/init.ts @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * @fileOverview + * page controls from native + * + * - init bundle + * + * corresponded with the API of page manager (framework.js) + */ + +import { + Log +} from '../../../utils/index'; +import { removePrefix } from '../../util/index'; +import { + defineFn, + bootstrap +} from './bundle'; +import { updateActions } from '../api/misc'; +import { getPageGlobal } from '../../app/helper'; +import { Image } from '../Image'; +import Page from '../index'; +import { Services } from '../../app/index'; +import { requireModule } from '../register'; +import { App } from '../../app/App'; + +interface ParseOptions { + $app_define$(...args: any[]): void; // eslint-disable-line camelcase + $app_bootstrap$(name: string): void; // eslint-disable-line camelcase + $app_require$(name: string): void; // eslint-disable-line camelcase + Image(): void; +} + +/** + * Init a page by run code with data. + * @param {Page} page + * @param {string} code + * @param {Object} data + * @param {Services} services + * @return {*} + */ +export function init(page: Page, code: string | Function, data: object, services: Services): any { + Log.debug('Intialize a page with:\n', data); + let result; + + // Methods to parse code. + const pageDefine = (...args) => defineFn(page, ...args); + const pageBoot = (name) => { + result = bootstrap(page, name, data); + updateActions(page); + page.doc.taskCenter.send('dom', { action: 'createFinish' }, []); + Log.debug(`After initialized a page(${page.id}).`); + }; + + const packageName = page.packageName; + const appFunction = () => { + if (page && page.doc) { + return page; + } + // card not has packageName + if (packageName === 'notset') { + return page; + } + const instance = App.pageMap.get(page.id); + return instance || page; + }; + + const pageRequireModule = name => requireModule(appFunction, removePrefix(name)); + + const imageObj: () => Image = function() { + return new Image(page); + }; + const options: ParseOptions = { + $app_define$: pageDefine, + $app_bootstrap$: pageBoot, + $app_require$: pageRequireModule, + Image: imageObj + }; + + // Support page global and init language. + global.__appProto__ = getPageGlobal(page.packageName); + global.language = page.options.language; + + let functionCode: string; + if (typeof code !== 'function') { + functionCode = `(function(global){\n\n"use strict";\n\n ${code} \n\n})(this.__appProto__)`; + } + + // Compile js bundle code and get result. + if (typeof code === 'function') { + code.call(global, options); + } else { + compileBundle(functionCode, page.doc.url, options, services); + } + return result; +} + +/** + * Run bundle code by a new function. + * @param {string} functionCode - Js bundle code. + * @param {Object[]} args - Global methods for compile js bundle code. + * @return {*} + */ +export function compileBundle(functionCode: string, file: string, ...args: object[]): any { + const funcKeys: string[] = []; + const funcValues: Function[] = []; + args.forEach((module) => { + for (const key in module) { + funcKeys.push(key); + funcValues.push(module[key]); + } + }); + + // If failed to run code on native, then run code on framework. + if (!compileBundleNative(funcKeys, funcValues, functionCode, file)) { + const resolveFunction: Function = new Function(funcKeys.toString(), functionCode); + return resolveFunction(...funcValues); + } +} + +/** + * Call a new function generated on the V8 native side. + * @param {string[]} funcKeys + * @param {Function[]} funcValues + * @param {string} functionCode + * @return {boolean} Return true if no error occurred. + */ +function compileBundleNative(funcKeys: string[], funcValues: Function[], functionCode: string, file: string): boolean { + if (typeof compileAndRunBundle !== 'function') { + return false; + } + + let isSuccess: boolean = false; + const bundle: string = `(function (${funcKeys.toString()}) {${functionCode}})`; + try { + const compileFunction: Function = compileAndRunBundle(bundle, file); + if (compileFunction && typeof compileFunction === 'function') { + compileFunction(...funcValues); + isSuccess = true; + } + } catch (e) { + Log.error(e); + } + return isSuccess; +} diff --git a/runtime/main/page/index.ts b/runtime/main/page/index.ts new file mode 100644 index 00000000..9f7681f9 --- /dev/null +++ b/runtime/main/page/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Page from './Page'; + +/** + * Prevent modification of Page and Page.prototype + */ +Object.freeze(Page); +Object.freeze(Page.prototype); + +export default Page; diff --git a/runtime/main/page/register.ts b/runtime/main/page/register.ts new file mode 100644 index 00000000..43b81f46 --- /dev/null +++ b/runtime/main/page/register.ts @@ -0,0 +1,218 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * 2021.01.08 - Strengthen init modules, getter and setter of modules. + * Copyright (c) 2021 Huawei Device Co., Ltd. + */ + +/* + * native module register + */ +import { Log } from '../../utils/index'; +import { interceptCallback } from '../manage/event/callbackIntercept'; +import { getPluginModule } from '../extend/mediaquery/plugins'; +import Page from './index'; +import Document from '../../vdom/Document'; +import { TaskCenter } from '../manage/event/TaskCenter'; + +let nativeModulesPkg: object = {}; + +/** + * for UT + */ +export function getModule(moduleName) { + return nativeModulesPkg[moduleName]; +} + +/** + * for UT + */ +export function allModules() { + return nativeModulesPkg; +} + +/** + * for UT + */ +export function clearModules() { + nativeModulesPkg = {}; +} + +/** + * Init modules for an app page. + * @param {Object} modules + */ +export function initModules(modules: object): void { + for (const moduleName in modules) { + /* Obtains the module package name. + * If modulename does not contain the package name, the default package name is _global_. + */ + const s: string[] = moduleName.split('.'); + let pkg: string = '_global_'; + let cls: string; + if (s.length === 1) { + cls = s[0]; + } else { + pkg = s[0]; + cls = s[1]; + } + let clss: object = nativeModulesPkg[pkg]; + if (!clss) { + clss = {}; + nativeModulesPkg[pkg] = clss; + } + let methods: object = clss[cls]; + if (!methods) { + methods = {}; + clss[cls] = methods; + } + + // Push each non-existed new method. + for (let method of modules[moduleName]) { + method = { + name: method + }; + if (!methods[method.name]) { + methods[method.name] = method; + } + } + } +} + +/** + * Get a module of methods for an app page. + * @param {Page | Function} app + * @param {string} name + * @return {Object} + */ +export function requireModule(app: Page | Function, name: string): object { + const s: string[] = name.split('.'); + let pkg: string = s[0]; + let cls: string = s[1]; + const moduleName: string = s[1]; + let clss: object = nativeModulesPkg[pkg]; + + // If can not found pkg from nativeModulesPkg, then use '_global_' as pkg to find again. + if (!clss && !cls) { + cls = pkg; + pkg = '_global_'; + clss = nativeModulesPkg[pkg]; + } + if (cls) { + const target: object = {}; + const methods: object = clss[cls]; + bind(app, target, methods, name, true); + if (pkg && (pkg === 'system' || pkg === '@system')) { + const module: object = getPluginModule(target, moduleName); + if (module) { + return module; + } + } + return target; + } else { + const target: object = {}; + for (const clsName in clss) { + target[clsName] = {}; + const methods: object = clss[clsName]; + bind(app, target[clsName], methods, name + '.' + clsName, true); + } + return target; + } +} + +function isGlobalModule(name: string) { + const clss: object = nativeModulesPkg['_global_']; + const globalModules: string[] = Object.keys(clss); + if (globalModules.indexOf(name) === -1) { + return false; + } + return true; +} + +function bind(app: Page | Function, target: object, methods: object, moduleName: string, needPromise: boolean) { + for (const methodName in methods) { + Object.defineProperty(target, methodName, { + configurable: true, + enumerable: true, + get: function moduleGetter() { + if (this._modifyMethods && this._modifyMethods[methodName] && + typeof this._modifyMethods[methodName] === 'function') { + return this._modifyMethods[methodName]; + } + return (...args) => { + let promise; + if (!isGlobalModule(moduleName)) { + const ret: any = interceptCallback(args, needPromise); + args = needPromise ? ret.args : ret; + promise = needPromise ? ret.promise : undefined; + } + const appInstance: Page = typeof app === 'function' ? app() : app; + const ret: any = appInstance.callTasks({ + module: moduleName, + method: methodName, + args: args + }); + + // API return exception. + if (ret && ret.__EXCEPTION__) { + const e: any = new Error(); + e.code = ret.code; + e.message = ret.message; + throw e; + } + if (ret && ret.__PROMISE__ && promise) { + return promise.promise; + } else { + const taskCenter: Document | TaskCenter = appInstance.doc && appInstance.doc.taskCenter; + if (taskCenter) { + if (args[0] && args[0].__onlyPromise) { + taskCenter.removeCallback(args[0].__callbackId); + } else if (args.length > 1 && args[1] && args[1].__onlyPromise) { + taskCenter.removeCallback(args[1].__callbackId); + } + } + return ret; + } + }; + }, + set: function moduleSetter(value: Function) { + if (typeof value === 'function') { + this._modifyMethods = this._modifyMethods || {}; + this._modifyMethods[methodName] = value; + } + } + }); + } +} + +/** + * Register a custom component options. + * @param {Page} app + * @param {string} name + * @param {Object} def - The content of component. + * @return {*} + */ +export function registerCustomComponent(app: Page, name: string, def: object): any { + const { customComponentMap } = app; + if (customComponentMap[name]) { + Log.error(`Define a component(${name}) that already exists.`); + return; + } + customComponentMap[name] = def; +} diff --git a/runtime/main/reactivity/LICENSE b/runtime/main/reactivity/LICENSE new file mode 100644 index 00000000..4645bc4b --- /dev/null +++ b/runtime/main/reactivity/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2015 Yuxi Evan You + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/runtime/main/reactivity/array.js b/runtime/main/reactivity/array.js new file mode 100644 index 00000000..3b3a7341 --- /dev/null +++ b/runtime/main/reactivity/array.js @@ -0,0 +1,97 @@ +/* eslint-disable */ + +import { def, Log } from '../../utils/index.ts'; + +const arrayProto = Array.prototype; +export const arrayMethods = Object.create(arrayProto); + +/* + * Intercept mutating methods and emit events + */ + +;[ + 'push', + 'pop', + 'shift', + 'unshift', + 'splice', + 'sort', + 'reverse' +] +.forEach(function (method) { + // cache original method + const original = arrayProto[method]; + def(arrayMethods, method, function mutator() { + // avoid leaking arguments: + // http://jsperf.com/closure-with-arguments + let i = arguments.length; + const args = new Array(i); + while (i--) { + args[i] = arguments[i]; + } + const result = original.apply(this, args); + const ob = this.__ob__; + let inserted; + switch (method) { + case 'push': + inserted = args; + break; + case 'unshift': + inserted = args; + break; + case 'splice': + inserted = args.slice(2); + break; + } + if (inserted) { + ob.observeArray(inserted); + } + // Notify change. + ob.dep.notify(); + return result; + }) +}) + +/** + * Swap the element at the given index with a new value and emits corresponding event. + * @param {Number} index + * @param {*} val + * @return {*} - replaced element + */ + +def( + arrayProto, + '$set', + function $set (index, val) { + Log.warn(`"Array.prototype.$set" is not a standard API, ` + + `it will be removed in the next version.`); + if (index >= this.length) { + this.length = index + 1; + } + return this.splice(index, 1, val)[0]; + } +) + +/** + * Convenience method to remove the element at given index. + * @param {Number} index + * @param {*} val + */ + +def( + arrayProto, + '$remove', + function $remove (index) { + Log.warn(`"Array.prototype.$remove" is not a standard API,` + + ` it will be removed in the next version.`); + if (!this.length) { + return; + } + if (typeof index !== 'number') { + index = this.indexOf(index); + } + if (index > -1) { + this.splice(index, 1); + } + } +) diff --git a/runtime/main/reactivity/dep.js b/runtime/main/reactivity/dep.js new file mode 100644 index 00000000..9d7db04a --- /dev/null +++ b/runtime/main/reactivity/dep.js @@ -0,0 +1,71 @@ +/* eslint-disable */ + +import { remove } from '../../utils/index.ts'; + +let uid = 0; + +/** + * A dep is an observable that can have multiple directives subscribing to it. + * @constructor + */ +export default function Dep () { + this.id = uid++; + this.subs = []; +} + +// The current target watcher being evaluated. +// This is globally unique because there could be only one watcher being evaluated at any time. +Dep.target = null; +let targetStack = []; + +export function pushTarget (_target) { + if (Dep.target) { + targetStack.push(Dep.target); + } + Dep.target = _target; +} + +export function popTarget () { + Dep.target = targetStack.pop(); +} + +export function resetTarget () { + Dep.target = null; + targetStack = []; +} + +/** + * Add a directive subscriber. + * @param {Directive} sub + */ +Dep.prototype.addSub = function (sub) { + this.subs.push(sub); +} + +/** + * Remove a directive subscriber. + * @param {Directive} sub + */ +Dep.prototype.removeSub = function (sub) { + remove(this.subs, sub); +} + +/** + * Add self as a dependency to the target watcher. + */ +Dep.prototype.depend = function () { + if (Dep.target) { + Dep.target.addDep(this); + } +} + +/** + * Notify all subscribers of a new value. + */ +Dep.prototype.notify = function () { + // Stabilize the subscriber list first. + const subs = this.subs.slice(); + for (let i = 0, l = subs.length; i < l; i++) { + subs[i].update(); + } +} diff --git a/runtime/main/reactivity/object.js b/runtime/main/reactivity/object.js new file mode 100644 index 00000000..fedc1bac --- /dev/null +++ b/runtime/main/reactivity/object.js @@ -0,0 +1,90 @@ +/* eslint-disable */ + +import { + Log, + hasOwn +} from '../../utils/index.ts'; +import * as _ from '../../utils/index.ts'; + +var objProto = Object.prototype; + +/** + * Add a new property to an observed object and emits corresponding event. + * @param {String} key + * @param {*} val + * @public + */ +_.define( + objProto, + '$add', + function $add (key, val) { + Log.warn(`'Object.prototype.$add' is not a standard API,` + + ` it will be removed in the next version.`); + if (hasOwn(this, key)) { + return; + } + var ob = this.__ob__; + if (!ob || _.isReserved(key)) { + this[key] = val; + return; + } + ob.convert(key, val); + ob.notify(); + if (ob.vms) { + var i = ob.vms.length; + while (i--) { + var vm = ob.vms[i]; + vm._proxy(key); + // vm._digest() // todo + } + } + } +) + +/** + * Set a property on an observed object, calling add to ensure the property is observed. + * @param {String} key + * @param {*} val + * @public + */ +_.define( + objProto, + '$set', + function $set (key, val) { + Log.warn(`"Object.prototype.$set" is not a standard API,` + + ` it will be removed in the next version.`); + this.$add(key, val); + this[key] = val; + } +) + +/** + * Deletes a property from an observed object and emits corresponding event. + * @param {String} key + * @public + */ +_.define( + objProto, + '$delete', + function $delete (key) { + Log.warn(`"Object.prototype.$delete" is not a standard API,` + + ` it will be removed in the next version.`); + if (!this.hasOwnProperty(key)) { + return; + } + delete this[key]; + var ob = this.__ob__; + if (!ob || _.isReserved(key)) { + return; + } + ob.notify(); + if (ob.vms) { + var i = ob.vms.length; + while (i--) { + var vm = ob.vms[i]; + vm._unproxy(key); + // vm._digest() // todo + } + } + } +) diff --git a/runtime/main/reactivity/observer.js b/runtime/main/reactivity/observer.js new file mode 100644 index 00000000..09e4fb2e --- /dev/null +++ b/runtime/main/reactivity/observer.js @@ -0,0 +1,265 @@ +/* eslint-disable */ + +import Dep from './dep'; +import { arrayMethods } from './array'; +import { + def, + remove, + isObject, + isPlainObject, + hasOwn, + isReserved +} from '../../utils/index.ts'; + +const arrayKeys = Object.getOwnPropertyNames(arrayMethods); + +/** + *

Observer class that are attached to each observed object.

+ *

Once attached, the observer converts target object's property keys
+ * into getter/setters that collect dependencies and dispatches updates.

+ * @param {Array|Object} value + * @constructor + */ +export function Observer (value) { + this.value = value; + this.dep = new Dep(); + def(value, '__ob__', this) + if (Array.isArray(value)) { + copyAugment(value, arrayMethods, arrayKeys); + this.observeArray(value); + } else { + this.walk(value); + } +} + +// Instance methods + +/** + *

Walk through each property and convert them into getter/setters.

+ *

This method should only be called when value type is Object.

+ * @param {Object} obj + */ +Observer.prototype.walk = function (obj) { + for (let key in obj) { + this.convert(key, obj[key]); + } +} + +/** + * Observe a list of Array items. + * @param {Array} items + */ +Observer.prototype.observeArray = function (items) { + for (let i = 0, l = items.length; i < l; i++) { + observe(items[i]); + } +} + +/** + * Convert a property into getter/setter so we can emit the events when the property is accessed/changed. + * @param {String} key + * @param {*} val + */ +Observer.prototype.convert = function (key, val) { + defineReactive(this.value, key, val); +} + +/** + *

Add an owner vm, so that when $set/$delete mutations
+ * happen we can notify owner vms to proxy the keys and digest the watchers.

+ *

This is only called when the object is observed as a page's root $data.

+ * @param {Vue} vm + */ +Observer.prototype.addVm = function (vm) { + (this.vms || (this.vms = [])).push(vm); +} + +/** + * Remove an owner vm. This is called when the object is swapped out as a page's $data object. + * @param {Vue} vm + */ +Observer.prototype.removeVm = function (vm) { + remove(this.vms, vm); +} + +/** + * Augment an target Object or Array by defining hidden properties. + * @param {Object|Array} target + * @param {Object} proto + */ +function copyAugment (target, src, keys) { + for (let i = 0, l = keys.length; i < l; i++) { + const key = keys[i]; + def(target, key, src[key]); + } +} + +/** + *

Attempt to create an observer page for a value,
+ * returns the new observer if successfully observed,
+ * or the existing observer if the value already has one.

+ * @param {*} value + * @param {Vue} [vm] + * @return {Observer|undefined} + * @static + */ +export function observe (value, vm) { + if (!isObject(value)) { + return; + } + let ob; + if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { + ob = value.__ob__; + } else if ( + (Array.isArray(value) || isPlainObject(value)) && + Object.isExtensible(value) && + !value._isVue + ) { + ob = new Observer(value); + } + if (ob && vm) { + ob.addVm(vm); + } + return ob; +} + +/** + * Define a reactive property on an Object. + * @param {Object} obj + * @param {String} key + * @param {*} val + */ +export function defineReactive (obj, key, val) { + const dep = new Dep(); + const property = Object.getOwnPropertyDescriptor(obj, key); + if (property && property.configurable === false) { + return; + } + + // Cater for pre-defined getter/setters. + const getter = property && property.get; + const setter = property && property.set; + + let childOb = observe(val); + Object.defineProperty(obj, key, { + enumerable: true, + configurable: true, + get: function reactiveGetter () { + const value = getter ? getter.call(obj) : val; + if (Dep.target) { + dep.depend(); + if (childOb) { + childOb.dep.depend(); + } + if (Array.isArray(value)) { + for (let e, i = 0, l = value.length; i < l; i++) { + e = value[i]; + e && e.__ob__ && e.__ob__.dep.depend(); + } + } + } + return value; + }, + set: function reactiveSetter (newVal) { + const value = getter ? getter.call(obj) : val; + if (newVal === value) { + return; + } + if (setter) { + setter.call(obj, newVal); + } else { + val = newVal; + } + childOb = observe(newVal); + dep.notify(); + } + }) +} + +/** + *

Set a property on an object.

+ *

Adds the new property and triggers change notification if the property doesn't already exist.

+ * @param {Object} obj + * @param {String} key + * @param {*} val + * @public + */ +export function set (obj, key, val) { + if (Array.isArray(obj)) { + return obj.splice(key, 1, val); + } + if (hasOwn(obj, key)) { + obj[key] = val; + return; + } + if (obj._isVue) { + set(obj._data, key, val); + return; + } + const ob = obj.__ob__; + if (!ob) { + obj[key] = val; + return; + } + ob.convert(key, val); + ob.dep.notify(); + if (ob.vms) { + let i = ob.vms.length; + while (i--) { + const vm = ob.vms[i]; + proxy(vm, key); + } + } + return val +} + +/** + * Delete a property and trigger change if necessary. + * @param {Object} obj + * @param {String} key + */ +export function del (obj, key) { + if (!hasOwn(obj, key)) { + return; + } + delete obj[key]; + const ob = obj.__ob__; + if (!ob) { + if (obj._isVue) { + delete obj._data[key]; + } + return; + } + ob.dep.notify(); + if (ob.vms) { + let i = ob.vms.length; + while (i--) { + const vm = ob.vms[i]; + unproxy(vm, key); + } + } +} + +// Add $item ,modify $index to $idx. +const KEY_WORDS = ['$idx', '$value', '$event','$item'] +export function proxy (vm, key, segment) { + segment = segment || vm._data; + if (KEY_WORDS.indexOf(key) > -1 || !isReserved(key)) { + Object.defineProperty(vm, key, { + configurable: true, + enumerable: true, + get: function proxyGetter () { + return segment[key]; + }, + set: function proxySetter (val) { + segment[key] = val; + } + }) + } +} + +export function unproxy (vm, key) { + if (!isReserved(key)) { + delete vm[key]; + } +} diff --git a/runtime/main/reactivity/state.js b/runtime/main/reactivity/state.js new file mode 100644 index 00000000..e8c58456 --- /dev/null +++ b/runtime/main/reactivity/state.js @@ -0,0 +1,125 @@ +/* eslint-disable */ +import Watcher from './watcher'; +import Dep from './dep'; +import { + observe, + proxy +} from './observer'; +import { + isPlainObject, + hasOwn +} from '../../utils/index.ts'; + +export function initState (vm) { + vm._watchers = []; + initData(vm); + initComputed(vm); + initMethods(vm); +} + +export function initData (vm) { + let data = vm._data; + initDataSegment(vm, data); + let shareData = vm._shareData; + initDataSegment(vm, shareData); +} + +export function initDataSegment (vm, data) { + if (!isPlainObject(data)) { + data = {}; + } + + // Proxy data on page. + const keys = Object.keys(data); + let i = keys.length; + while (i--) { + proxy(vm, keys[i], data); + } + // Observe data. + observe(data, vm); +} + +export function initBases(vm) { + const options = vm.vmOptions + // mixins exist? + if(hasOwn(options, 'mixins')) { + options['mixins'].forEach(mixin => { + if(typeof mixin == 'object') { + Object.keys(mixin).forEach(key => { + vm[key] = mixin[key] + }) + } + else if (typeof mixin == 'function') { + vm[mixin.name] = mixin.bind(vm) + } + else { + aceConsole.error("[JS Framework] mixin must be plain object or function") + } + }) + } +} + +function noop () { +} + +export function initComputed (vm) { + const computed = vm._computed; + if (computed) { + for (let key in computed) { + const userDef = computed[key]; + const def = { + enumerable: true, + configurable: true + }; + if (typeof userDef === 'function') { + def.get = makeComputedGetter(userDef, vm); + def.set = noop; + } else { + def.get = userDef.get + ? userDef.cache !== false + ? makeComputedGetter(userDef.get, vm) + : userDef.get.bind(vm) + : noop; + def.set = userDef.set + ? userDef.set.bind(vm) + : noop; + } + Object.defineProperty(vm, key, def); + } + } +} + +function makeComputedGetter (getter, owner) { + const watcher = new Watcher(owner, getter, null, { + lazy: true + }); + return function computedGetter () { + if (watcher.dirty) { + watcher.evaluate(); + } + if (Dep.target) { + watcher.depend(); + } + return watcher.value; + } +} + +export function initMethods (vm) { + const options = vm._vmOptions; + for (let key in options) { + if (typeof options[key] === 'function' && key !== 'data') { + vm._methods[key] = options[key].bind(vm); + proxyMethods(vm, key); + } + } +} + +function proxyMethods(vm, key) { + Object.defineProperty(vm, key, { + configurable: true, + enumerable: true, + get: function proxyGetter () { + return vm._methods[key]; + } + }) +} diff --git a/runtime/main/reactivity/watcher.js b/runtime/main/reactivity/watcher.js new file mode 100644 index 00000000..711c3b04 --- /dev/null +++ b/runtime/main/reactivity/watcher.js @@ -0,0 +1,217 @@ +/* eslint-disable */ +import Dep, { pushTarget, popTarget } from './dep'; +import { + remove, + extend, + isObject +} from '../../utils/index.ts'; + +let uid = 0; + +/** + * A watcher parses an expression, collects dependencies, + * and fires callback when the expression value changes. + * This is used for both the $watch() api and directives. + * @param {Vue} vm + * @param {String|Function} expOrFn + * @param {Function} cb + * @param {Object} options + * - {Array} filters + * - {Boolean} twoWay + * - {Boolean} deep + * - {Boolean} user + * - {Boolean} sync + * - {Boolean} lazy + * - {Function} [preProcess] + * - {Function} [postProcess] + * @constructor + */ + +export default function Watcher (vm, expOrFn, cb, options) { + // Mix in options. + if (options) { + extend(this, options); + } + const isFn = typeof expOrFn === 'function'; + this.vm = vm; + vm._watchers.push(this); + this.expression = expOrFn; + this.cb = cb; + this.id = ++uid; + this.active = true; + this.dirty = this.lazy; + this.deps = []; + this.newDeps = []; + this.depIds = new Set(); + this.newDepIds = new Set(); + + if (isFn) { + this.getter = expOrFn; + } + this.value = this.lazy + ? undefined + : this.get(); + + // State for avoiding false triggers for deep and Array watchers during vm._digest(). + this.queued = this.shallow = false; +} + +/** + * Evaluate the getter, and re-collect dependencies. + */ +Watcher.prototype.get = function () { + pushTarget(this); + const value = this.getter.call(this.vm, this.vm); + + // "touch" every property so they are all tracked as dependencies for deep watching. + if (this.deep) { + traverse(value); + } + popTarget(); + this.cleanupDeps(); + return value; +} + +/** + * Add a dependency to this directive. + * @param {Dep} dep + */ +Watcher.prototype.addDep = function (dep) { + const id = dep.id; + if (!this.newDepIds.has(id)) { + this.newDepIds.add(id); + this.newDeps.push(dep); + if (!this.depIds.has(id)) { + dep.addSub(this); + } + } +} + +/** + * Clean up for dependency collection. + */ +Watcher.prototype.cleanupDeps = function () { + let i = this.deps.length; + while (i--) { + const dep = this.deps[i]; + if (!this.newDepIds.has(dep.id)) { + dep.removeSub(this); + } + } + let tmp = this.depIds + this.depIds = this.newDepIds + this.newDepIds = tmp + this.newDepIds.clear() + tmp = this.deps + this.deps = this.newDeps + this.newDeps = tmp + this.newDeps.length = 0 +} + +/** + * Subscriber interface. Will be called when a dependency changes. + * @param {Boolean} shallow + */ +Watcher.prototype.update = function (shallow) { + if (this.lazy) { + this.dirty = true; + } else { + this.run(); + } +} + +/** + * Batcher job interface. Will be called by the batcher. + */ +Watcher.prototype.run = function () { + if (this.active) { + const value = this.get(); + if ( + value !== this.value || + ((isObject(value) || this.deep) && !this.shallow) + ) { + // Set new value. + const oldValue = this.value; + this.value = value; + this.cb.call(this.vm, value, oldValue); + } + this.queued = this.shallow = false; + } +} + +/** + * Evaluate the value of the watcher. This only gets called for lazy watchers. + */ +Watcher.prototype.evaluate = function () { + this.value = this.get(); + this.dirty = false; +} + +/** + * Depend on all deps collected by this watcher. + */ +Watcher.prototype.depend = function () { + let i = this.deps.length; + while (i--) { + this.deps[i].depend(); + } +} + +/** + * Remove self from all dependencies' subcriber list. + */ +Watcher.prototype.teardown = function () { + if (this.active) { + /* Remove self from vm's watcher list. + * This is a somewhat expensive operation so we skip it + * if the vm is being destroyed or is performing a v-for + * re-render (the watcher list is then filtered by v-for). + */ + if (!this.vm._isBeingDestroyed && !this.vm._vForRemoving) { + remove(this.vm._watchers, this); + } + let i = this.deps.length; + while (i--) { + this.deps[i].removeSub(this); + } + this.active = false; + this.vm = this.cb = this.value = null; + } +} + +/** + *

Recursively traverse an object to evoke all converted
+ * getters, so that every nested property inside the object
+ * is collected as a "deep" dependency.

+ * @param {*} val + * @param {Set} seen + */ +const seenObjects = new Set(); + +function traverse (val, seen) { + let i, keys, isA, isO; + if (!seen) { + seen = seenObjects; + seen.clear(); + } + isA = Array.isArray(val); + isO = isObject(val); + if (isA || isO) { + if (val.__ob__) { + const depId = val.__ob__.dep.id; + if (seen.has(depId)) { + return; + } else { + seen.add(depId); + } + } + if (isA) { + i = val.length; + while (i--) traverse(val[i], seen); + } else if (isO) { + keys = Object.keys(val); + i = keys.length; + while (i--) traverse(val[keys[i]], seen); + } + } +} diff --git a/runtime/main/util/LICENSE b/runtime/main/util/LICENSE new file mode 100644 index 00000000..4645bc4b --- /dev/null +++ b/runtime/main/util/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2015 Yuxi Evan You + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/runtime/main/util/index.js b/runtime/main/util/index.js new file mode 100644 index 00000000..cb4b1a78 --- /dev/null +++ b/runtime/main/util/index.js @@ -0,0 +1,106 @@ +/** + * Verify whether a string starts with $ or _. + * @param {string} str - The string which to be verified. + * @return {boolean} The result whether the string starts with $ or _. + */ +export function isReserved(str) { + const c = (str + '').charCodeAt(0); + return c === 0x24 || c === 0x5F; +} + +/** + * Create a cached version of a function. + * @param {Function} func - The function which to be created a cached version. + * @return {Function} The cached version of the function. + */ +function cached(func) { + const cache = Object.create(null); + return function cachedFn(str) { + const hit = cache[str]; + return hit || (cache[str] = func(str)); + }; +} + +/** + * Camelize a hyphen-delmited string. + * @param {string} str - A hyphen-delmited string. + * @return {string} A camelized string. + */ +export const camelize = cached(str => { + return str.replace(/-(\w)/g, (_, s) => s ? s.toUpperCase() : ''); +}); + +/** + * Check the type of a variable. + * @param {*} any - The variable which to be checked. + * @return {string} The type of the variable. + */ +export function typof(any) { + const objType = Object.prototype.toString.call(any); + return objType.substring(8, objType.length - 1).toLowerCase(); +} + +/** + * Regular expression for component. + * @constant {RegExp} + */ +const COMPONENT_REG = /^@app-component\//; + +/** + * Regular expression for module. + * @constant {RegExp} + */ +const MODULE_REG = /^@app-module\//; + +/** + * Regular expression for application. + * @constant {RegExp} + */ +const APPLICATION_REG = /^@app-application\//; + +/** + * Verify whether is a component. + * @param {string} name - The name which need to be verified. + * @return {boolean} The result whether is a component. + */ +export function isComponent(name) { + return !!name.match(COMPONENT_REG); +} + +/** + * Verify whether is a module. + * @param {string} name - The name which need to be verified. + * @return {boolean} The result whether is a module. + */ +export function isModule(name) { + return !!name.match(MODULE_REG); +} + +/** + * Verify whether is an application. + * @param {string} name - The name which need to be verified. + * @return {boolean} The result whether is an APP. + */ +export function isApplication(name) { + return !!name.match(APPLICATION_REG); +} + +/** + * Remove "@app-application". + * @param {string} name - The name which need to be removed. + * @return {string} The result which has been removed "@app-application". + */ +export function removeApplicationPrefix(str) { + const result = str.replace(APPLICATION_REG, ''); + return result; +} + +/** + * Remove "@app-component and @app-module". + * @param {string} name - The name which need to be removed. + * @return {string} The result which has been removed "@app-component" and "@app-module". + */ +export function removePrefix(str) { + const result = str.replace(COMPONENT_REG, '').replace(MODULE_REG, ''); + return result; +} diff --git a/runtime/main/util/props.js b/runtime/main/util/props.js new file mode 100644 index 00000000..dd307e41 --- /dev/null +++ b/runtime/main/util/props.js @@ -0,0 +1,38 @@ +import Vm from '../model'; +import { hasOwn } from '../../utils/index'; + +/** + * Get a prop's type. + * @param {Function} fn - Prop.type. + * @return {string} - Prop's type. + */ +function getPropType(fn) { + const match = fn && fn.toString().match(/^\s*function (\w+)/); + return match ? match[1] : ''; +} + +/** + * Get default prop value. + * @param {Vm} vm - Vm object. + * @param {PropInterface} prop - Default prop. + * @return {PropVauleInterfance | undefined} Default prop value or Null. + */ +function getDefaultPropValue(vm, prop) { + if (!prop) { + return undefined; + } + if (!hasOwn(prop, 'default')) { + return undefined; + } + const __hasDefault = true; + const def = prop.default; + const __isDefaultValue = + typeof def === 'function' && getPropType(prop.type) !== 'Function' + ? def.call(vm) + : def; + return { __hasDefault, __isDefaultValue }; +} + +export { + getDefaultPropValue +}; diff --git a/runtime/main/util/shared.js b/runtime/main/util/shared.js new file mode 100644 index 00000000..eed03320 --- /dev/null +++ b/runtime/main/util/shared.js @@ -0,0 +1,80 @@ +/** + * Add properties to object. + * @param {Object} obj - The object which need be added properties. + * @param {*[]} prop - The properties which to be added. + * @return {Object} The object which has been added properties. + */ +export function extend(obj, ...prop) { + if (typeof Object.assign === 'function') { + Object.assign(obj, ...prop); + } else { + const first = prop.shift(); + for (const key in first) { + obj[key] = first[key]; + } + if (prop.length) { + extend(obj, ...prop); + } + } + return obj; +} + +/** + * Add or modify a property. + * @param {Object} obj - The object which need be added or modified a property. + * @param {string} prop - The key of the property. + * @param {*} val - The value of the the property. + * @param {boolean} [enumerable] - If the property is enumerable. + */ +export function def(obj, prop, val, enumerable) { + Object.defineProperty(obj, prop, { + value: val, + enumerable: !!enumerable, + writable: true, + configurable: true + }); +} + + +/** + * Remove an item from an array. + * @param {*[]} arr - The array from which removes an item. + * @param {*} item - The item which to be removed. + * @return {*} The item which has been removed. + */ +export function remove(arr, item) { + if (arr.length) { + const index = arr.indexOf(item); + if (index >= 0) { + return arr.splice(index, 1); + } + } +} + +/** + * Verify whether the property exists or not in object. + * @param {Object} obj - The object which should be verified. + * @param {string} prop - The property which should be verified. + * @return {boolean} The result whether the property exists or not. + */ +export function hasOwn(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +/** + * Verify whether a variable is object. + * @param {*} any - The variable which should be verified. + * @return {boolean} The result whether a variable is an object. + */ +export function isObject(any) { + return any !== null && typeof any === 'object'; +} + +/** + * Verify whether a variable is an plain JavaScript object. + * @param {*} any - The variable which should be verified. + * @return {boolean} The result whether a variable is an plain JavaScript object. + */ +export function isPlainObject(any) { + return Object.prototype.toString.call(any) === '[object Object]'; +} diff --git a/runtime/preparation/index.ts b/runtime/preparation/index.ts new file mode 100644 index 00000000..7ee305fc --- /dev/null +++ b/runtime/preparation/index.ts @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { initFramework } from './init'; + +initFramework(); diff --git a/runtime/preparation/init.ts b/runtime/preparation/init.ts new file mode 100644 index 00000000..c14b0872 --- /dev/null +++ b/runtime/preparation/init.ts @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * 2021.01.08 - Rewrite the function 'initFramework' and make it simpler. + * Copyright (c) 2021 Huawei Device Co., Ltd. + */ + +import service from './service'; +import i18n from '../main/extend/i18n/index'; +import dpi from '../main/extend/dpi/index'; +import { Log } from '../utils/utils'; +import { Options } from '../main/app'; +import globalApi from './methods'; +import NativeElementClassFactory from '../vdom/NativeElementClassFactory'; + +export interface GlobalInterface { + createInstance: (id: string, code: string, config: Options, data: object) => any | Error; + registerModules: (modules: object) => void; + appDestroy: (packageName: string) => void; + appError: (packageName: string, errors: any) => void; + destroyInstance: (pageId: string) => any | Error; + getRoot: (...args: any[]) => any | Error; + callJS: (pageId: string, tasks: any[]) => any | Error; +} + +/** + * Setup framework: register services and initialize the global methods. + */ +export function initFramework(): void { + for (const serviceName in i18n) { + service.register(serviceName, i18n[serviceName]); + } + for (const serviceName in dpi) { + service.register(serviceName, dpi[serviceName]); + } + + const globalMethods: GlobalInterface = { + 'createInstance': globalApi.createInstance, + 'registerModules': globalApi.registerModules, + 'appDestroy': globalApi.appDestroy, + 'appError': globalApi.appError, + 'destroyInstance': globalApi.destroyInstance, + 'getRoot': globalApi.getRoot, + 'callJS': globalApi.callJS + }; + + // registerModules and registerComponents + ModulesInfo.forEach(modules => { + globalMethods['registerModules'](modules); + }); + + ComponentsInfo.forEach((name) => { + if (name && name.type && name.methods) { + NativeElementClassFactory.createNativeElementClass( + name.type, + name.methods + ); + } + }); + + for (const methodName in globalMethods) { + global[methodName] = (...args: any) => { + const res: any = globalMethods[methodName](...args); + if (res instanceof Error) { + Log.error(res.toString()); + } + return res; + }; + } +} + +const ModulesInfo: Record[] = [ + {'system.router': ['push', 'replace', 'back', 'clear', 'getLength', 'getState']}, + {'system.app': ['getInfo', 'getPackageInfo', 'terminate', 'requestFullWindow', 'screenOnVisible', 'setSwipeToDismiss']}, + {'system.prompt': ['showToast', 'showDialog']}, + {'system.configuration': ['getLocale']}, + {'timer': ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval']}, + {'system.image': ['getImage']}, + {'system.device': ['getInfo']}, + {'system.grid': ['getSystemLayoutInfo']}, + {'system.mediaquery': ['addListener', 'getDeviceType']}, + {'animation': ['requestAnimationFrame', 'cancelAnimationFrame']}, + {'system.resource': ['readText']} +]; + +type components = { + 'methods': T[], + 'type': T +} + +const ComponentsInfo: components[] = [ + {'methods': ['focus', 'animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'clock'}, + {'methods': ['focus', 'animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'image'}, + {'methods': ['focus', 'animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'label'}, + {'methods': ['focus', 'animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'list-item'}, + {'methods': ['focus', 'animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'list-item-group'}, + {'methods': ['focus', 'animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'progress'}, + {'methods': ['focus', 'animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'rating'}, + {'methods': ['focus', 'animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'select'}, + {'methods': ['focus', 'animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'switch'}, + {'methods': ['focus', 'animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'tabs'}, + {'methods': ['focus', 'animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'tab-bar'}, + {'methods': ['focus', 'animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'tab-content'}, + {'methods': ['focus', 'animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'text'}, + { + 'methods': ['setProgress', 'focus', 'animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], + 'type': 'button' + }, + {'methods': ['append', 'focus', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'chart'}, + {'methods': ['goto', 'focus', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'calendar'}, + { + 'methods': ['getContext', 'toDataURL', 'animate', 'focus', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], + 'type': 'canvas' + }, + {'methods': ['show', 'close', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'dialog'}, + {'methods': ['focus', 'animate', 'getScrollOffset', 'scrollBy', 'getBoundingClientRect'], 'type': 'div'}, + {'methods': ['animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'divider'}, + { + 'methods': ['getColumns', 'getColumnWidth', 'getGutterWidth', 'getSizeType', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], + 'type': 'grid-container' + }, + { + 'methods': ['start', 'stop', 'pause', 'resume', 'getState', 'animate', 'focus', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], + 'type': 'image-animator' + }, + { + 'methods': ['showError', 'focus', 'animate', 'delete', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], + 'type': 'input' + }, + { + 'methods': ['scrollTo', 'scrollBy', 'focus', 'scrollArrow', 'scrollTop', 'scrollBottom', 'scrollPage', 'collapseGroup', 'expandGroup', 'currentOffset', 'rotation', 'animate', 'chainanimation', 'getBoundingClientRect', 'getScrollOffset'], + 'type': 'list' + }, + { + 'methods': ['start', 'stop', 'focus', 'animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], + 'type': 'marquee' + }, + {'methods': ['show', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'menu'}, + {'methods': ['focus', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'option'}, + {'methods': ['show', 'close', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'panel'}, + {'methods': ['show', 'animate', 'focus', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'picker'}, + { + 'methods': ['rotation', 'animate', 'focus', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], + 'type': 'picker-view' + }, + {'methods': ['focus', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'piece'}, + {'methods': ['focus', 'show', 'hide', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'popup'}, + {'methods': ['animate', 'focus', 'delete', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], 'type': 'search'}, + { + 'methods': ['rotation', 'focus', 'animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], + 'type': 'slider' + }, + {'methods': ['focus', 'animate', 'getScrollOffset', 'scrollBy', 'getBoundingClientRect'], 'type': 'stack'}, + { + 'methods': ['swipeTo', 'focus', 'showPrevious', 'showNext', 'rotation', 'animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], + 'type': 'swiper' + }, + { + 'methods': ['start', 'pause', 'setCurrentTime', 'requestFullscreen', 'exitFullscreen', 'focus', 'animate', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], + 'type': 'video' + }, + { + 'methods': ['setNextButtonStatus', 'focus', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], + 'type': 'stepper' + }, + { + 'methods': ['focus', 'animate', 'delete', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], + 'type': 'textarea' + }, + { + 'methods': ['reload', 'getBoundingClientRect', 'scrollBy', 'getScrollOffset'], + 'type': 'web' + }, + { + 'methods': ['takePhoto', 'startRecorder', 'closeRecorder'], + 'type': 'camera' + } +]; diff --git a/runtime/preparation/methods.ts b/runtime/preparation/methods.ts new file mode 100644 index 00000000..8204faca --- /dev/null +++ b/runtime/preparation/methods.ts @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as framework from '../main'; +import { services } from './service'; +import { Log } from '../utils/utils'; +import { Options } from '../main/app'; +import { I18nInterface } from '../main/extend/i18n/I18n'; +import { DPIInterface } from '../main/extend/dpi/Dpi'; + +export interface EnvInterface { + config: Options; + created: number; + services: ServiceMapInterface; +} + +export interface pageMapInterface { + [key: string]: EnvInterface; +} + +export interface ServicesInterface { + I18n?: I18nInterface; + dpi?: DPIInterface; +} + +export interface ServiceMapInterface extends ServicesInterface { + service?: any; +} + +const pageMap: pageMapInterface = {}; + +/** + *

Create instance by framework.

+ *

The framework is based on JS Bundle code.

+ * @param {string} id - Id of a page. + * @param {string} code - JS Bundle code. + * @param {Options} config - Page config + * @param {Object} data - Data that needed. + * @return {*} + */ +function createInstance(id: string, code: string, config: Options, data: object): any | Error { + const page = pageMap[id]; + if (!page) { + config = JSON.parse(JSON.stringify(config || {})); + Log.debug(`Create a page.`); + + const env: EnvInterface = { + config, + created: Date.now(), + services: createServices(id) + }; + pageMap[id] = env; + if (config && config.appCreate && config.appCode && config.packageName) { + pageMap[config.appInstanceId] = env; + } + return framework.createInstance(id, code, config, data, env); + } + return new Error(`Invalid instance id '${id}'.`); +} + +/** + * Get root page by pageId. + * @param {*} args - Args. + * @return {*} Root page. + */ +function getRoot(...args: any[]): any | Error { + const pageId = args[0]; + const page = getPage(pageId); + if (page && framework) { + return framework['getRoot'](pageId); + } + return new Error(`Invalid instance id '${pageId}'.`); +} + +/** + * Destroy a page. + * @param {string} pageId - Id of a page. + * @return {*} + */ +function destroyInstance(pageId: string): any | Error { + const page = getPage(pageId); + const result = framework.destroyInstance(pageId); + if (page && framework) { + services.forEach(service => { + const destroy: (pageId: string) => void = service.options.destroy; + if (destroy) { + destroy(pageId); + } + }); + delete pageMap[pageId]; + return result; + } + return new Error(`Invalid page id '${pageId}'.`); +} + +/** + *

When native invokes this method,
+ * the receiveTasks method of the instance corresponding to the pageID is invoked.

+ * @param {string} pageId - Id of a page. + * @param {*} tasks - Tasks from native. + * @return {*} + */ +function callJS(pageId: string, tasks: any[]): any | Error { + const page = getPage(pageId); + if (page && framework) { + return framework.receiveTasks(pageId, tasks); + } + return new Error(`Invalid page id '${pageId}'.`); +} + +/** + * Get page by id. + * @param {string} id - Id of a page. + * @return {EnvInterface} Page Env. + */ +function getPage(id: string): EnvInterface { + return pageMap[id]; +} + +/** + * Init JavaScript services for this instance. + * @param {string} id - Create service by id. + * @return {ServiceMapInterface} service map. + */ +function createServices(id: string): ServiceMapInterface { + const serviceMap: ServiceMapInterface = {}; + services.forEach((service) => { + Log.debug(`[JS Runtime] Create service ${service.name}.`); + const create: (id: string) => any = service.options.create; + if (create) { + const result: any = create(id); + Object.assign(serviceMap, result.instance); + } + }); + return serviceMap; +} + +export default { + createInstance: createInstance, + getRoot: getRoot, + callJS: callJS, + destroyInstance: destroyInstance, + appError: framework.appError, + appDestroy: framework.appDestroy, + registerModules: framework.registerModules +}; diff --git a/runtime/preparation/service.ts b/runtime/preparation/service.ts new file mode 100644 index 00000000..9d293e20 --- /dev/null +++ b/runtime/preparation/service.ts @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * 2021.01.08 - Rewrite the function 'register' and make it simpler. + * Copyright (c) 2021 Huawei Device Co., Ltd. + */ + +import { Log } from '../utils/utils'; + +export interface OptionsInterface { + create: (id: string) => any + destroy: (id: string) => void + refresh?: (id:string, data: object) => Error | void +} + +export interface ServicesInterface { + name: string + options: OptionsInterface +} + +export const services: ServicesInterface[] = []; + +/** + * Register a service. + * @param {string} name - Service name. + * @param {OptionsInterface} options - Could have { create, destroy, refresh } lifecycle methods. + */ +export function register(name: string, options: OptionsInterface): void { + const hasName = services.map( + service => service.name + ).indexOf(name) >= 0; + + if (hasName) { + Log.warn(`Service '${name}' has been registered already!`); + } else { + options = Object.assign({}, options); + services.push({ + name, + options + }); + } +} + +/** + * Unregister a service by name. + * @param {string} name - Service name. + */ +export function unregister(name: string): void { + services.some((service: ServicesInterface, index: number) => { + if (service.name === name) { + services.splice(index, 1); + return true; + } + }); +} + +export default { + register, + unregister +}; diff --git a/runtime/utils/index.ts b/runtime/utils/index.ts new file mode 100644 index 00000000..b865879e --- /dev/null +++ b/runtime/utils/index.ts @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { + isReserved, + camelize, + typof, + isComponent, + isModule, + isApplication, + removeApplicationPrefix, + removePrefix +} from '../main/util/index'; + +export { + extend, + def, + remove, + hasOwn, + isObject, + isPlainObject +} from '../main/util/shared'; + +export { + isEmpty, + isNull, + removeItem, + getValue, + Log +} from './utils'; diff --git a/runtime/utils/utils.ts b/runtime/utils/utils.ts new file mode 100644 index 00000000..c8e71b58 --- /dev/null +++ b/runtime/utils/utils.ts @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * 2021.01.08 - Add some utils. + * Copyright (c) 2021 Huawei Device Co., Ltd. + */ + +import { hasOwn } from './index'; + +/** + * Verify whether the variable is empty. + * @param {*} any - The variable which should be verified. + * @return {boolean} The result whether the variable is empty. + */ +export function isEmpty(any) { + if (!any || typeof any !== 'object') { + return true; + } + for (const item in any) { + if (hasOwn(any, item)) { + return false; + } + } + return true; +} + +/** + * Verify whether the valiable is null or undefined. + * @param {*} any - The valiable which should be verified. + * @return {boolean} The result whether the variable is null or undefined. + */ +export function isNull(any: any): boolean { + return any === null || any === undefined; +} + +/** + * Remove an item from an array. + * @param {*[]} arr - The array from which removes an item. + * @param {*} item - The item which to be removed. + * @return {*} The item which has been removed. + */ +export function removeItem(array: any[], item: any) { + if (!array.length) { + return; + } + if (typeof item !== 'number') { + item = array.indexOf(item); + } + if (item > -1) { + array.splice(item, 1); + } +} + +/** +* Find the value of the key. +* @param {string} key - The key. +* @param {Object} message - The object which to be checked. +* @return {*} The value of the key. +*/ +export function getValue(key: string, message: object): any { + const keys: string[] = key.split('.'); + if (keys.length === 0) { + return null; + } + let value = message; + for (const i in keys) { + value = value[keys[i]]; + if (isNull(value)) { + return null; + } + } + return value; +} + +/** + * This class provide log. + */ +export class Log { + /** + * Provide debug log. + * @param {*[]} message - The debug message. + * @example + * Log.debug('This is a debug message.'); + */ + public static debug(...message: any[]): void { + aceConsole.info('[JS Framework] (debug) ', message); + } + + /** + * Provide info log. + * @param {*[]} message - The info message. + * @example + * Log.info('This is an info message.'); + */ + public static info(...message: any[]): void { + aceConsole.info('[JS Framework] (info) ', message); + } + + /** + * Provide warn log. + * @param {*[]} message - The warn message. + * @example + * Log.warn('This is a warn message.'); + */ + public static warn(...message: any[]): void { + aceConsole.warn('[JS Framework] (warn) ', message); + } + + /** + * Provide error log. + * @param {*[]} message - The error message. + * @example + * Log.error('This is an error message.'); + */ + public static error(...message: any[]): void { + aceConsole.error('[JS Framework] (error) ', message); + } +} diff --git a/runtime/vdom/Comment.ts b/runtime/vdom/Comment.ts new file mode 100644 index 00000000..9f9f80e0 --- /dev/null +++ b/runtime/vdom/Comment.ts @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Node from './Node'; + +/** + * Comment Node of vdom, which is used as separator between blocks. + * @extends Node + */ +class Comment extends Node { + private _content: string; + + constructor(content) { + super(); + this._nodeType = Node.NodeType.Comment; + this._type = 'comment'; + this._content = content; + } + + /** + * Convert to HML comment string. + * @return {string} HML string. + */ + public toString() { + return ``; + } +} + +export default Comment; diff --git a/runtime/vdom/Document.ts b/runtime/vdom/Document.ts new file mode 100644 index 00000000..171ccbc0 --- /dev/null +++ b/runtime/vdom/Document.ts @@ -0,0 +1,240 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * 2021.01.08 - Remove docMap. + * Copyright (c) 2021 Huawei Device Co., Ltd. + */ + +import Comment from './Comment'; +import DocumentElement from './DocumentElement'; +import Node from './Node'; +import Element from './Element'; +import { TaskCenter } from '../main/manage/event/TaskCenter'; +import { Log } from '../utils'; + +/** + * When a document is loaded into a application runtime, it becomes a document object.
+ * The document object is the root node of the vdom document. + */ +class Document { + private _id: string; + private _nodeMap: any; + private _taskCenter: TaskCenter; + private _documentElement: DocumentElement; + private _body: Node; + private _url: string + + constructor(id = '', url) { + this._id = id; + this._url = url; + this._nodeMap = {}; + this._taskCenter = new TaskCenter(id); + this._createDocumentElement(); + } + + /** + * Body of this document. + * @type {Node} + * @readonly + */ + public get body() { + return this._body; + } + + /** + * ID of this document. + * @type {string} + * @readonly + */ + public get id() { + return this._id; + } + + /** + * Document element of this document. + * @type {DocumentElement} + * @readonly + */ + public get documentElement() { + return this._documentElement; + } + + /** + * url of this document (page). + * @type {url} + * @readonly + */ + public get url() { + return this._url; + } + + /** + * Node map of this document. + * @type {Map} + * @readonly + */ + public get nodeMap() { + return this._nodeMap; + } + + /** + * Task center of this document. + * @type {TaskCenter} + * @readonly + */ + public get taskCenter() { + return this._taskCenter; + } + + /** + * Set up body node. + * @param {Node} el - Target element. + */ + public setElementToBody(el: Element): void { + el.role = 'body'; + el.depth = 1; + delete this._nodeMap[el.nodeId]; + el.ref = '_root'; + this._nodeMap._root = el; + this._body = el; + } + + /** + * Send body of this Document to native. + * @param {Node} node - body element. + */ + public sentBodyToNative(node: Element): void { + const body = node.toJSON(); + if (this._taskCenter && typeof this._taskCenter.send === 'function') { + this._taskCenter.send('dom', { action: 'createBody' }, [body]); + } + } + + /** + * Get the node from nodeMap. + * @param {string} ref - id of target node. + * @return {object} node from node map. + */ + public getRef(ref: string) { + return this._nodeMap[ref]; + } + + /** + * Create element of body. + * @param {string} tagName - Tag name of body element. + * @param {Object} options - Properties of element. + * @return {Node} Body element. + */ + public createBody(tagName: string, options?: any): Node { + if (!this._body) { + const el = new Element(tagName, options); + this.setElementToBody(el); + } + return this._body; + } + + /** + * Create an element. + * @param {string} tagName - Tag name of element. + * @param {Object} options - Properties of element. + * @return {Node} New element + */ + public createElement(tagName: string, options?: any): Element { + return new Element(tagName, options); + } + + /** + * Create an comment. + * @param {string} commentText - Text of comment. + * @return {object} comment + */ + public createComment(commentText: string): Comment { + return new Comment(commentText); + } + + /** + * Fire an event on specified element manually. + * @param {Element} element - Event target element. + * @param {string} eventType - Event name + * @param {Object} eventObj - Event object. + * @param {boolean} isDomChanges - if need to change dom + * @param {object} options - Event options + * @return {*} anything returned by handler function + */ + public fireEvent(element: Element, eventType: string, eventObj: any, isDomChanges: boolean, options: any) { + Log.debug(`Document#fireEvent, element = ${element}, eventType = ${eventType}, eventObj = ${eventObj}, isDomChanges = ${isDomChanges}.`); + if (!element) { + return; + } + eventObj = eventObj || {}; + eventObj.type = eventObj.type || eventType; + eventObj.target = element; + eventObj.currentTarget = element; + eventObj.timestamp = Date.now(); + if (isDomChanges) { + this._updateElement(element, isDomChanges); + } + let isBubble; + const $root = this.getRef('_root'); + if ($root && $root.attr) { + isBubble = $root.attr['bubble'] === 'true'; + } + return element.fireEvent(eventType, eventObj, isBubble, options); + } + + /** + * Destroy current document, and remove itself form docMap. + */ + public destroy() { + this.taskCenter.destroyCallback(); + delete this._nodeMap; + delete this._taskCenter; + } + + /** + * Create the document element. + * @return {object} documentElement + */ + private _createDocumentElement(): void { + if (!this._documentElement) { + const el = new DocumentElement('document'); + el.docId = this._id; + el.ownerDocument = this; + el.role = 'documentElement'; + el.depth = 0; + el.ref = '_documentElement'; + this._nodeMap._documentElement = el; + this._documentElement = el; + } + } + + private _updateElement(el: Element, changes: any): void { + Log.debug(`Document#_updateElement, el = ${el}, changes = ${JSON.stringify(changes)}.`); + const attrs = changes.attrs || {}; + for (const name in attrs) { + el.setAttr(name, attrs[name], true); + } + const style = changes.style || {}; + for (const name in style) { + el.setStyle(name, style[name], true); + } + } +} + +export default Document; diff --git a/runtime/vdom/DocumentElement.ts b/runtime/vdom/DocumentElement.ts new file mode 100644 index 00000000..5b8f4c7f --- /dev/null +++ b/runtime/vdom/DocumentElement.ts @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Element from './Element'; +import Node from './Node'; +import { Log } from '../utils'; + +/** + * Document element is the root element in a Document. + * @extends Element + */ +class DocumentElement extends Element { + constructor(type: string = 'div', props: object = {}, isExtended?: boolean) { + super(type, props, isExtended); + } + + /** + * Destroy this document element. + */ + public destroy() { + const doc = this._ownerDocument; + if (doc) { + delete this.docId; + delete doc.nodeMap[this.nodeId]; + } + super.destroy(); + } + + /** + * Append a child node. + * @param {Node} node - Target node. + * @param {number} before - The node next to the target position. + * @override + */ + public appendChild(node: Node, before?: Node): void { + Log.debug(`DocumentElement#appendChild, node = ${node}, before = ${before}.`); + + if (this.pureChildren.length > 0 || node.parentNode) { + return; + } + const children = this.children; + const beforeIndex = children.indexOf(before); + if (beforeIndex < 0) { + children.push(node); + } else { + children.splice(beforeIndex, 0, node); + } + + if (node.nodeType === Node.NodeType.Element) { + const element = node as Element; + if (element.role === 'body') { + element.docId = this.id; + element.ownerDocument = this.ownerDocument; + element.parentNode = this; + this.linkChild(element); + } else { + element.children.forEach(child => { + child.parentNode = element; + }); + const document = this.ownerDocument; + document.setElementToBody(element); + element.docId = document.id; + element.ownerDocument = document; + this.linkChild(this); + delete this.ownerDocument.nodeMap[element.nodeId]; + } + this.pureChildren.push(element); + this.ownerDocument.sentBodyToNative(element); + } else { + node.parentNode = this; + this.ownerDocument.nodeMap[node.ref] = node; + } + } +} + +export default DocumentElement; diff --git a/runtime/vdom/Element.ts b/runtime/vdom/Element.ts new file mode 100644 index 00000000..993a707b --- /dev/null +++ b/runtime/vdom/Element.ts @@ -0,0 +1,895 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * 2021.01.08 - Move element's method from operation.js to Element class. + * Copyright (c) 2021 Huawei Device Co., Ltd. + */ + +import { Log } from '../utils/index'; +import Node from './Node'; +import NativeElementClassFactory from './NativeElementClassFactory'; +import Document from './Document'; +import { TaskCenter } from '../main/manage/event/TaskCenter'; +import { FragBlockInterface } from '../main/model/compiler'; +import Vm from '../main/model'; + +/** + * Element is a basic class to describe a tree node in vdom. + * @extends Node + */ +class Element extends Node { + private _style: any; + private _classStyle: any; + private _event: any; + private _idStyle: any; + private _tagStyle: any; + private _id: string | null; + private _classList: any[]; + private _block: FragBlockInterface; + private _vm: Vm; + private _isCustomComponent: boolean; + + protected _children: Node[]; + protected _pureChildren: Element[]; + protected _role: string; + protected _attr: any; + + constructor(type = 'div', props:any = {}, isExtended: boolean = false) { + super(); + const NativeElementClass = NativeElementClassFactory.nativeElementClassMap.get(type); + if (NativeElementClass && !isExtended) { + return new NativeElementClass(props); + } + + this._nodeType = Node.NodeType.Element; + this._type = type; + this._attr = props.attr || {}; + this._style = props.style || {}; + this._classStyle = props.classStyle || {}; + this._event = {}; + this._idStyle = {}; + this._tagStyle = {}; + this._id = null; + this._classList = []; + this._children = []; + this._pureChildren = []; + this._isCustomComponent = false; + } + + /** + * Children array except comment node. + * @type {Node[]} + */ + public set pureChildren(newPureChildren: Element[]) { + this._pureChildren = newPureChildren; + } + + public get pureChildren() { + return this._pureChildren; + } + + /** + * event of element + * @type {Node[]} + */ + public get event() { + return this._event; + } + + /** + * Children array. + * @type {Node[]} + */ + public set children(newChildren: Node[]) { + this._children = newChildren; + } + + public get children() { + return this._children; + } + + /** + * View model of this Element. + * @type {Vm} + */ + public get vm() { + return this._vm; + } + + public set vm(newVm: Vm) { + this._vm = newVm; + } + + /** + * Style object of this Element, which keys is style name, and values is style values. + * @type {JSON} + */ + public get classStyle() { + return this._classStyle; + } + + /** + * Block in this Element. + * @type {FragBlock} + */ + public get block() { + return this._block; + } + + public set block(newBlock: FragBlockInterface) { + this._block = newBlock; + } + + /** + * Role of this element. + * @type {string} + */ + public set role(role: string) { + this._role = role; + } + + public get role() { + return this._role; + } + + /** + * ID of this element. + * @type {string} + */ + public get id() { + return this._id; + } + + public set id(value) { + this._id = value; + } + + /** + * Class list of this element. + * @type {string[]} + */ + public get classList() { + return this._classList; + } + + public set classList(value: string[]) { + this._classList = value.slice(0); + } + + /** + * Attributes object of this Element. + * @type {Object} + */ + public set attr(attr: any) { + this._attr = attr; + } + + public get attr() { + return this._attr; + } + + /** + * Flag of whether the element is the root of customeComponent. + * @param {bollean} + */ + public set isCustomComponent(isCustomComponent: boolean) { + this._isCustomComponent = isCustomComponent; + } + + public get isCustomComponent() { + return this._isCustomComponent; + } + + /** + * Style object of this Element. + * @type {Object} + */ + public set style(style: any) { + this._style = style; + } + + public get style() { + return this._style; + } + + /** + * Get TaskCenter instance by id. + * @param {string} id + * @return {TaskCenter} TaskCenter + */ + public getTaskCenter(id: string): TaskCenter | null { + const doc: Document = this._ownerDocument; + if (doc && doc.taskCenter) { + return doc.taskCenter; + } + return null; + } + + /** + * Establish the connection between parent and child node. + * @param {Node} child - Target node. + */ + public linkChild(child: Node): void { + child.parentNode = this; + if (this._docId) { + child.docId = this._docId; + child.ownerDocument = this._ownerDocument; + child.ownerDocument.nodeMap[child.nodeId] = child; + child.depth = this._depth + 1; + } + if (child.nodeType === Node.NodeType.Element) { + const element = child as Element; + element.children.forEach((grandChild: Node) => { + element.linkChild(grandChild); + }); + } + } + + /** + * Insert a node into list at the specified index. + * @param {Node} target - Target node. + * @param {number} newIndex - Target index. + * @param {Object} [options] - options of insert method. + * @param {boolean} [options.changeSibling=false] - If need to change sibling's index. + * @param {boolean} [options.isInPureChildren=false] - If in pure children array or children array. + * @return {number} New index of node. + */ + public insertIndex(target: Node, newIndex: number, { changeSibling = false, isInPureChildren = false }): number { + const list: Node[] = isInPureChildren ? this._pureChildren : this._children; + if (newIndex < 0) { + newIndex = 0; + } + const before: Node = list[newIndex - 1]; + const after: Node = list[newIndex]; + list.splice(newIndex, 0, target); + if (changeSibling) { + before && (before.nextSibling = target); + target.previousSibling = before; + target.nextSibling = after; + after && (after.previousSibling = target); + } + return newIndex; + } + + /** + * Move the node to a new index in list. + * @param {Node} target - Target node. + * @param {number} newIndex - Target index. + * @param {Object} [options] - options of insert method. + * @param {boolean} [options.changeSibling=false] - If need to change sibling's index. + * @param {boolean} [options.isInPureChildren=false] - If in pure children array or children array. + * @return {number} New index of node. + */ + public moveIndex(target: Node, newIndex: number, { changeSibling = false, isInPureChildren = false }): number { + const list: Node[] = isInPureChildren ? this._pureChildren : this._children; + const index: number = list.indexOf(target); + if (index < 0) { + return -1; + } + if (changeSibling) { + const before: Node = list[index - 1]; + const after: Node = list[index + 1]; + before && (before.nextSibling = after); + after && (after.previousSibling = before); + } + list.splice(index, 1); + let newIndexAfter = newIndex; + if (index <= newIndex) { + newIndexAfter = newIndex - 1; + } + const beforeNew: Node = list[newIndexAfter - 1]; + const afterNew: Node = list[newIndexAfter]; + list.splice(newIndexAfter, 0, target); + if (changeSibling) { + if (beforeNew) { + beforeNew.nextSibling = target; + } + target.previousSibling = beforeNew; + target.nextSibling = afterNew; + if (afterNew) { + afterNew.previousSibling = target; + } + } + if (index === newIndexAfter) { + return -1; + } + return newIndex; + } + + /** + * Remove the node from list. + * @param {Node} target - Target node. + * @param {Object} [options] - options of insert method. + * @param {boolean} [options.changeSibling=false] - If need to change sibling's index. + * @param {boolean} [options.isInPureChildren=false] - If in pure children array or children array. + */ + public removeIndex(target, { changeSibling = false, isInPureChildren = false}): void { + const list: Node[] = isInPureChildren ? this._pureChildren : this._children; + const index: number = list.indexOf(target); + if (index < 0) { + return; + } + if (changeSibling) { + const before: Node = list[index - 1]; + const after: Node = list[index + 1]; + before && (before.nextSibling = after); + after && (after.previousSibling = before); + } + list.splice(index, 1); + } + + /** + * Get the next sibling element. + * @param {Node} node - Target node. + * @return {Node} Next node of target node. + */ + public nextElement(node: Node): Element { + while (node) { + if (node.nodeType === Node.NodeType.Element) { + return node as Element; + } + node = node.nextSibling; + } + } + + /** + * Get the previous sibling element. + * @param {Node} node - Target node. + * @return {Node} Previous node of target node. + */ + public previousElement(node: Node): Element { + while (node) { + if (node.nodeType === Node.NodeType.Element) { + return node as Element; + } + node = node.previousSibling; + } + } + + /** + * Append a child node. + * @param {Node} node - Target node. + * @return {number} the signal sent by native + */ + public appendChild(node: Node): void { + if (node.parentNode && node.parentNode !== this) { + return; + } + + if (!node.parentNode) { + this.linkChild(node as Element); + this.insertIndex(node, this.children.length, { changeSibling: true }); + if (this.docId) { + this.registerNode(node); + } + if (node.nodeType === Node.NodeType.Element) { + const element = node as Element; + this.insertIndex(element, this.pureChildren.length, { isInPureChildren: true }); + const taskCenter = this.getTaskCenter(this.docId); + if (taskCenter) { + return taskCenter.send( + 'dom', + { action: 'addElement' }, + [this.ref, element.toJSON(), -1] + ); + } + } + } else { + this.moveIndex(node, this.children.length, { changeSibling: true }); + if (node.nodeType === Node.NodeType.Element) { + const index = this.moveIndex(node, this.pureChildren.length, { isInPureChildren: true }); + const taskCenter = this.getTaskCenter(this.docId); + if (taskCenter && index >= 0) { + return taskCenter.send( + 'dom', + { action: 'moveElement' }, + [node.ref, this.ref, index] + ); + } + } + } + } + + /** + * Insert a node before specified node. + * @param {Node} node - Target node. + * @param {Node} before - The node next to the target position. + * @return {number} the signal sent by native + */ + public insertBefore(node: Node, before: Node): void { + if (node.parentNode && node.parentNode !== this) { + return; + } + if (node === before || node.nextSibling && node.nextSibling === before) { + return; + } + // If before is not exist, return. + if (this.children.indexOf(before) < 0) { + return; + } + if (!node.parentNode) { + this.linkChild(node as Element); + this.insertIndex(node, this.children.indexOf(before), { changeSibling: true }); + if (this.docId) { + this.registerNode(node); + } + if (node.nodeType === Node.NodeType.Element) { + const element = node as Element; + const pureBefore = this.nextElement(before); + const index = this.insertIndex( + element, + pureBefore + ? this.pureChildren.indexOf(pureBefore) + : this.pureChildren.length, + { isInPureChildren: true } + ); + const taskCenter = this.getTaskCenter(this.docId); + if (taskCenter) { + return taskCenter.send( + 'dom', + { action: 'addElement' }, + [this.ref, element.toJSON(), index] + ); + } + } + } else { + this.moveIndex(node, this.children.indexOf(before), { changeSibling: true }); + if (node.nodeType === Node.NodeType.Element) { + const pureBefore = this.nextElement(before); + const index = this.moveIndex( + node, + pureBefore + ? this.pureChildren.indexOf(pureBefore) + : this.pureChildren.length, + { isInPureChildren: true} + ); + const taskCenter = this.getTaskCenter(this.docId); + if (taskCenter && index >= 0) { + return taskCenter.send( + 'dom', + { action: 'moveElement' }, + [node.ref, this.ref, index] + ); + } + } + } + } + + /** + * Insert a node after specified node. + * @param {Node} node - Target node. + * @param {Node} after - The node in front of the target position. + * @return {number} the signal sent by native + */ + public insertAfter(node: Node, after: Node) { + if (node.parentNode && node.parentNode !== this) { + return; + } + if (node === after || node.previousSibling && node.previousSibling === after) { + return; + } + if (!node.parentNode) { + this.linkChild(node as Element); + this.insertIndex(node, this.children.indexOf(after) + 1, { changeSibling: true }); + + if (this.docId) { + this.registerNode(node); + } + if (node.nodeType === Node.NodeType.Element) { + const element = node as Element; + const index = this.insertIndex( + element, + this.pureChildren.indexOf(this.previousElement(after)) + 1, + { isInPureChildren: true } + ); + const taskCenter = this.getTaskCenter(this.docId); + + if (taskCenter) { + return taskCenter.send( + 'dom', + { action: 'addElement' }, + [this.ref, element.toJSON(), index] + ); + } + } + } else { + this.moveIndex(node, this.children.indexOf(after) + 1, { changeSibling: true}); + if (node.nodeType === Node.NodeType.Element) { + const index = this.moveIndex( + node, + this.pureChildren.indexOf(this.previousElement(after)) + 1, + { isInPureChildren: true } + ); + const taskCenter = this.getTaskCenter(this.docId); + if (taskCenter && index >= 0) { + return taskCenter.send( + 'dom', + { action: 'moveElement' }, + [node.ref, this.ref, index] + ); + } + } + } + } + + /** + * Remove a child node, and decide whether it should be destroyed. + * @param {Node} node - Target node. + * @param {boolean} [preserved=false] - If need to keep the target node. + */ + public removeChild(node: Node, preserved: boolean = false): void { + if (node.parentNode) { + this.removeIndex(node, { changeSibling: true }); + if (node.nodeType === Node.NodeType.Element) { + this.removeIndex(node, { isInPureChildren: true}); + const taskCenter = this.getTaskCenter(this.docId); + if (taskCenter) { + taskCenter.send( + 'dom', + { action: 'removeElement' }, + [node.ref] + ); + } + } + } + if (!preserved) { + node.destroy(); + } + } + + /** + * Clear all child nodes. + */ + public clear(): void { + const taskCenter: TaskCenter = this.getTaskCenter(this._docId); + if (taskCenter) { + this._pureChildren.forEach(child => { + taskCenter.send('dom', { action: 'removeElement' }, [child.ref]); + }); + } + this._children.forEach(node => { + node.destroy(); + }); + this._children.length = 0; + this._pureChildren.length = 0; + } + + /** + * Set an attribute, and decide whether the task should be send to native. + * @param {string} key - Arribute name. + * @param {string | number} value - Arribute value. + * @param {boolean} [silent=false] - If use silent mode. + */ + public setAttr(key: string, value: string | number, silent: boolean = false): void { + if (this.attr[key] === value && silent !== false) { + return; + } + this.attr[key] = value; + const taskCenter = this.getTaskCenter(this.docId); + if (!silent && taskCenter) { + const result = {}; + result[key] = value; + taskCenter.send('dom', { action: 'updateAttrs' }, [this.ref, result]); + } + } + + /** + * Set a style property, and decide whether the task should be send to native. + * @param {string} key - Style name. + * @param {string | number} value - Style value. + * @param {boolean} [silent=false] - If use silent mode. + */ + public setStyle(key: string, value: string | number, silent: boolean = false): void { + if (this.style[key] === value && silent !== false) { + return; + } + this.style[key] = value; + const taskCenter = this.getTaskCenter(this.docId); + if (!silent && taskCenter) { + const result = {}; + result[key] = value; + taskCenter.send('dom', { action: 'updateStyle' }, [this.ref, this.toStyle()]); + } + } + + /** + * Set style properties from class. + * @param {object} classStyle - Style properties. + */ + public setClassStyle(classStyle: any): void { + for (const key in this._classStyle) { + this._classStyle[key] = ''; + } + + Object.assign(this._classStyle, classStyle); + const taskCenter = this.getTaskCenter(this.docId); + if (taskCenter) { + taskCenter.send('dom', { action: 'updateStyle' }, [this.ref, this.toStyle()]); + } + } + + /** + * Set style properties from class. + * @param {object} classStyle - Style properties. + */ + public setCustomFlag(): void { + this._isCustomComponent = true; + } + + /** + * Set IdStyle properties from class. + * @param {string} key - Style name. + * @param {string|number} value - Style value. + * @param {boolean} [silent=false] - If use silent mode. + */ + public setIdStyle(key: string, value: string | number, silent: boolean = false): void { + if (this._idStyle[key] === value && silent !== false) { + return; + } + // if inline style has define return + if (this.style[key]) { + return; + } + this._idStyle[key] = value; + const taskCenter = this.getTaskCenter(this.docId); + if (!silent && taskCenter) { + const result = {}; + result[key] = value; + taskCenter.send('dom', { action: 'updateStyle' }, [this.ref, result]); + } + } + + /** + * Set TagStyle properties from class. + * @param {string} key - Style name. + * @param {string|number} value - Style value. + * @param {boolean} [silent=false] - If use silent mode. + */ + public setTagStyle(key: string, value: string | number, silent: boolean = false): void { + if (this._tagStyle[key] === value && silent !== false) { + return; + } + // If inline id class style has define return. + if (this.style[key] || this._idStyle[key] || this._classStyle[key]) { + return; + } + this._tagStyle[key] = value; + const taskCenter = this.getTaskCenter(this.docId); + if (!silent && taskCenter) { + const result = {}; + result[key] = value; + taskCenter.send('dom', { action: 'updateStyle' }, [this.ref, result]); + } + } + + /** + * Add an event handler. + * @param {string} type - Event name. + * @param {Function} handler - Event handler. + * @param {Object} [params] - Event parameters. + */ + public addEvent(type: string, handler?: Function, params?: any): void { + if (!this._event) { + this._event = {}; + } + if (!this._event[type]) { + this._event[type] = { handler, params }; + const taskCenter = this.getTaskCenter(this.docId); + if (taskCenter) { + taskCenter.send( + 'dom', + { action: 'addEvent' }, + [this.ref, type] + ); + } + } + } + + /** + * Remove an event handler. + * @param {string} type - Event name + */ + public removeEvent(type: string): void { + if (this._event && this._event[type]) { + delete this._event[type]; + const taskCenter = this.getTaskCenter(this.docId); + if (taskCenter) { + taskCenter.send( + 'dom', + { action: 'removeEvent' }, + [this.ref, type] + ); + } + } + } + + /** + * Fire an event manually. + * @param {string} type - Event name. + * @param {function} event - Event handler. + * @param {boolean} isBubble - Whether or not event bubble + * @param {boolean} [options] - Event options + * @return {*} anything returned by handler function. + */ + public fireEvent(type: string, event: any, isBubble?: boolean, options?: any) { + Log.debug(`Element#fireEvent, type = ${type}, event = ${event}, isBubble = ${isBubble}, options = ${options}.`); + const BUBBLE_EVENTS = [ + 'mouse', 'click', 'longpress', 'touchstart', + 'touchmove', 'touchend', 'panstart', 'panmove', + 'panend', 'horizontalpan', 'verticalpan', 'swipe' + ]; + let result = null; + let isStopPropagation = false; + const eventDesc = this._event[type]; + if (eventDesc && event) { + const handler = eventDesc.handler; + event.stopPropagation = () => { + isStopPropagation = true; + }; + if (options && options.params) { + result = handler.call(this, event, ...options.params); + } else { + result = handler.call(this, event); + } + } + + if (!isStopPropagation && isBubble && BUBBLE_EVENTS.indexOf(type) !== -1) { + if (this._parentNode) { + const parentNode = this._parentNode as Element; + event.currentTarget = parentNode; + parentNode.fireEvent(type, event, isBubble); // no options + } + } + + return result; + } + + /** + * Get all styles of current element. + * @return {object} style + */ + public toStyle(): any { + // Selector Specificity inline > #id > .class > tag. + // Return Object.assign({}, this._tagStyle,this.classStyle, this._idStyle,this.style). + const style = Object.assign({}, this._tagStyle); + this.assignStyle(style, this._classStyle); + this.assignStyle(style, this._idStyle); + this.assignStyle(style, this.style); + return style; + } + + /** + * Assign style. + * @param {*} src - Source style object. + * @param {*} dest - Target style object. + */ + public assignStyle(src: any, dest: any): void { + const keys = Object.keys(dest); + + // Margin and padding style: the style should be empty in the first. + keys.sort(function(style1, style2) { + if (dest[style1] === '') { + return 1; + } else { + return -1; + } + }); + let i = keys.length; + while (i--) { + const key = keys[i]; + const val = dest[key]; + if (val) { + src[key] = val; + } else { + if ((val === '' || val === undefined) && src[key]) { + return; + } + src[key] = val; + } + } + } + + /** + * Convert current element to JSON like object. + * @param {boolean} [ignoreChildren=false] - whether to ignore child nodes, default false + * @return {JSON} JSON object of this element. + */ + public toJSON(ignoreChildren = false): JSON { + const result: any = { + ref: this.ref, + type: this._type, + attr: this.attr, + style: this.toStyle(), + customComponent: this._isCustomComponent + }; + const event = []; + for (const type in this._event) { + const { params } = this._event[type]; + if (!params) { + event.push(type); + } else { + event.push({ type, params }); + } + } + if (event.length) { + result.event = event; + } + if (!ignoreChildren && this._pureChildren.length) { + result.children = this._pureChildren.map(child => child.toJSON()); + } + if (this._id) { + result.id = this._id; + } + return result; + } + + /** + * Convert to HML element tag string. + * @override + * @return {string} hml of this element. + */ + public toString(): string { + const id = this._id !== null ? this._id : ''; + return '<' + this._type + + ' id =' + id + + ' attr=' + JSON.stringify(this.attr) + + ' style=' + JSON.stringify(this.toStyle()) + '>' + + this.pureChildren.map((child) => child.toString()).join('') + + ''; + } + + /** + * Destroy this element + */ + public destroy() { + Log.debug(`Element#destroy this._type = ${this._type}.`); + this._attr = null; + this._style = null; + this._classStyle = {}; + this._event = null; + this._idStyle = null; + this._tagStyle = null; + this._classList.length = 0; + this._classList = null; + + if (this.destroyHook) { + this.destroyHook(); + this.destroyHook = null; + } + if (this._children) { + this._children.forEach((child: Node): void => { + child.destroy(); + }); + this._children.length = 0; + this._children = null; + } + if (this._pureChildren) { + this._pureChildren.length = 0; + this._pureChildren = null; + } + super.destroy(); + } + + private registerNode(node) { + const doc = this._ownerDocument; + doc.nodeMap[node.nodeId] = node; + } +} + +export default Element; diff --git a/runtime/vdom/NativeElementClassFactory.ts b/runtime/vdom/NativeElementClassFactory.ts new file mode 100644 index 00000000..42248fbe --- /dev/null +++ b/runtime/vdom/NativeElementClassFactory.ts @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { interceptCallback } from '../main/manage/event/callbackIntercept'; +import Element from './Element'; + +/** + * This factory class create native components classes, like div, image, text, etc...
+ * Each class is extends from Element, and saved in static property nativeElementClassMap. + */ +class NativeElementClassFactory { + /** + * Map for native element class + * @type {Map} + * @static + */ + public static nativeElementClassMap: Map = new Map(); + + /** + * Create a native element class form native, and saved in nativeElementClassMap. + * @param {string} tagName - Name of element class. + * @param {Function[]} methods - Prototype methods of element class. + * @static + */ + public static createNativeElementClass(tagName: string, methods: any[]) { + // Skip when no special component methods. + if (!methods || !methods.length) { + return; + } + + class NativeElement extends Element { + constructor(props) { + super(tagName, props, true); + } + } + + // Add methods to prototype. + methods.forEach(methodName => { + Object.defineProperty(NativeElement.prototype, methodName, { + configurable: true, + enumerable: true, + get: function moduleGetter() { + return (...args: any) => { + const taskCenter = this.getTaskCenter(this.docId); + if (taskCenter) { + // support aceapp callback style + args = interceptCallback(args); + const ret = taskCenter.send('component', { + ref: this.ref, + component: tagName, + method: methodName + }, args); + return ret; + } + }; + } + }); + }); + + // Add to element type map. + this.nativeElementClassMap.set(tagName, NativeElement); + } +} + +export default NativeElementClassFactory; diff --git a/runtime/vdom/Node.ts b/runtime/vdom/Node.ts new file mode 100644 index 00000000..dc7f8568 --- /dev/null +++ b/runtime/vdom/Node.ts @@ -0,0 +1,245 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Document from './Document'; +import Watcher from '../main/reactivity/watcher'; + +/** + * This class generate an unique id. + */ +class IDGenerator { + private static _id: number = 0; + + /** + * Return an unique id, which value is a string converted from number. + * @return {string} A string converted from number. + */ + public static getUniqueId(): string { + this._id++; + return this._id.toString(); + } +} + +/** + * Enum for NodeTypes + * @enum {number} + */ +/* eslint-disable no-unused-vars */ +enum NodeType { + /** + * Element type + */ + Element = 1, + + /** + * Comment type + */ + Comment = 8 +} +/* eslint-enable no-unused-vars */ + +/** + * Node is base class for Element and Comment.
+ * It has basic method for setting nodeId, getting children, etc... + */ +class Node { + public static NodeType = NodeType; + + protected _depth: number; + protected _nodeId: string; + protected _nodeType: number; + protected _ref: string; + protected _nextSibling: Node; + protected _previousSibling: Node; + protected _watchers: Watcher[]; + protected _destroyHook: () => void; + protected _type: string; + protected _parentNode: Node; + protected _ownerDocument: Document; + protected _docId: string; + + constructor() { + this._nodeId = IDGenerator.getUniqueId(); + this._ref = this.nodeId; + this._nextSibling = null; + this._previousSibling = null; + this._parentNode = null; + this._watchers = []; + this._destroyHook = null; + } + + /** + * Parent node. + * @type {Node} + */ + public set parentNode(newParentNode: Node) { + this._parentNode = newParentNode; + } + + public get parentNode() { + return this._parentNode; + } + + /** + * Watchers for recativity in a Node. + * @type {Watcher[]} + */ + public get watchers() { + return this._watchers; + } + + public set watchers(newWatchers: Watcher[]) { + this._watchers = newWatchers; + } + + /** + * Node ID. + * @type {string} + */ + public get nodeId() { + return this._nodeId; + } + + public set nodeId(newNodeId: string) { + this._nodeId = newNodeId; + } + + /** + * Node type. + */ + public get nodeType() { + return this._nodeType; + } + + public set nodeType(newNodeType: NodeType) { + this._nodeType = newNodeType; + } + + /** + * Destroy hook. + * @type {Function} + */ + public set destroyHook(hook: () => void) { + this._destroyHook = hook; + } + + public get destroyHook() { + return this._destroyHook; + } + + /** + * The level from current node to root element. + * @type {number} + */ + public set depth(newValue: number) { + this._depth = newValue; + } + + public get depth() { + return this._depth; + } + + /** + *

XML tag name, like div, button.

+ *

If node type is NodeType.Comment, it's value is "comment".

+ * @type {string} + */ + public set type(newType: string) { + this._type = newType; + } + + public get type() { + return this._type; + } + + /** + *

Node Reference, it's value is same as nodeId, It will send to native.

+ *

Document element's ref is "_documentElement", root element's ref is "root".

+ * @type {string} + */ + public set ref(newRef: string) { + this._ref = newRef; + } + + public get ref() { + return this._ref; + } + + /** + * Next sibling node. + * @type {Node} + */ + public set nextSibling(nextSibling: Node) { + this._nextSibling = nextSibling; + } + + public get nextSibling() { + return this._nextSibling; + } + + /** + * Previous sibling node. + * @type {Node} + */ + public set previousSibling(previousSibling: Node) { + this._previousSibling = previousSibling; + } + + public get previousSibling() { + return this._previousSibling; + } + + /** + * Document reference which this element belong to. + * @type {Document} + */ + public set ownerDocument(doc: Document) { + this._ownerDocument = doc; + } + + public get ownerDocument() { + return this._ownerDocument; + } + + /** + * ID of the document which element belong to. + * @type {string} + */ + public get docId() { + return this._docId; + } + + public set docId(value: string) { + this._docId = value; + } + + /** + * Destroy current node, and remove itself form nodeMap. + */ + public destroy(): void { + this._nextSibling = null; + this._previousSibling = null; + this._parentNode = null; + this._watchers = null; + this._destroyHook = null; + this._ownerDocument = null; + } +} + +export default Node; diff --git a/runtime/vdom/index.ts b/runtime/vdom/index.ts new file mode 100644 index 00000000..620360aa --- /dev/null +++ b/runtime/vdom/index.ts @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Node from './Node'; +import Element from './Element'; +import Comment from './Comment'; +import Document from './Document'; + +export { + Document, + Node, + Element, + Comment +}; diff --git a/test/fakeLog.ts b/test/fakeLog.ts new file mode 100644 index 00000000..4b00d986 --- /dev/null +++ b/test/fakeLog.ts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import sinon from 'sinon'; +import { Log } from '../runtime/utils/utils'; + +let fakeDebug: any; +let fakeInfo: any; +let fakeWarn: any; +let fakeError: any; + +export function fakeLog() { + fakeDebug = sinon.stub(Log, 'debug').callsFake(); + fakeInfo = sinon.stub(Log, 'info').callsFake(); + fakeWarn = sinon.stub(Log, 'warn').callsFake(); + fakeError = sinon.stub(Log, 'error').callsFake(); +} + +export function fakeLogRestore() { + fakeDebug.restore(); + fakeInfo.restore(); + fakeWarn.restore(); + fakeError.restore(); +} diff --git a/test/lib.ts b/test/lib.ts new file mode 100644 index 00000000..a7961384 --- /dev/null +++ b/test/lib.ts @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare var aceConsole; +declare var aceapp; +declare var treeModeParentNode; +declare var global; +declare var ace; +declare var markupState; +declare var notifyTrimMemory; +declare var compileAndRunBundle; +declare var i18nPluralRules; +declare var language; diff --git a/test/ut/app/bundle.ts b/test/ut/app/bundle.ts new file mode 100644 index 00000000..aa05e9a8 --- /dev/null +++ b/test/ut/app/bundle.ts @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import chai from 'chai'; +import { + before, + describe, + it +} from 'mocha'; +import { + fakeLog, + fakeLogRestore +} from '../../fakeLog'; +import Document from '../../../runtime/vdom/Document'; +import { + defineFn, + bootstrap +} from '../../../runtime/main/app/bundle'; + +const expect = chai.expect; + +describe('use defineFn/bootstrap', () => { + fakeLog(); + + let doc: Document; + const options = { + orientation: 'portrait', + deviceType: 'phone', + resolution: '3.0', + aspectRatio: 'string', + deviceWidth: '1176', + deviceHeight: '2400', + width: '0', + height: '0', + isInit: true, + appInstanceId: '10002', + packageName: 'com.example.test' + }; + const componentTemplate = { + type: 'div', + children: [{ + type: 'text', + attr: { + value: 'value' + } + }] + }; + const page = { doc, customComponentMap: {}, options }; + + before(() => { + const id = Date.now() + ''; + const url = "" + doc = new Document(id, url); + }); + + describe('defineFn', () => { + it('application with factory and deps', () => { + // @ts-ignore + defineFn(page, options.packageName, '@app-application/a', [], (require, exports, module) => { + module.exports = { + template: componentTemplate + } + }); + expect(page.customComponentMap['a'].template).eql(componentTemplate); + }); + }); + + describe('bootstrap', () => { + + before(() => { + // @ts-ignore + defineFn(page, options.packageName, '@app-application/main', [], (require, exports, module) => { + module.exports = { + template: componentTemplate, + } + }); + }); + + it('not an application', () => { + // @ts-ignore + const result = bootstrap(page, options.packageName, '@app-module/dom', undefined, undefined); + expect(result).instanceof(Error); + }); + + it('an application', () => { + // @ts-ignore + const result = bootstrap(page, options.packageName, '@app-application/dom', undefined, undefined); + expect(result).not.instanceof(Error); + }); + }); + + fakeLogRestore(); +}); diff --git a/test/ut/app/index.ts b/test/ut/app/index.ts new file mode 100644 index 00000000..f8ef77c3 --- /dev/null +++ b/test/ut/app/index.ts @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import chai from 'chai'; +import { + before, + describe, + it +} from 'mocha'; +import { + fakeLog, + fakeLogRestore +} from '../../fakeLog'; +import { App } from '../../../runtime/main/app/App'; + +const expect = chai.expect; + +describe('App Instance', () => { + fakeLog(); + + let app: App; + let appInstanceId: string; + + before(() => { + const appInstanceId = Date.now() + ''; + app = new App('test', appInstanceId); + }); + + describe('normal check', () => { + it('is a class', () => { + expect(typeof App).eql('function'); + }); + + it('being created', () => { + expect(app).to.be.an('object'); + expect(app).to.be.instanceof(App); + }); + + it('with some apis', () => { + expect(typeof app.getAppInstance).eql('function'); + expect(typeof app.deleteGlobalKeys).eql('function'); + }); + + it('run apis', () => { + expect(app.getAppInstance()).eql(appInstanceId); + expect(app.deleteGlobalKeys()).to.be.undefined; + }); + }); + + fakeLogRestore(); +}); \ No newline at end of file diff --git a/test/ut/extend/dpi.ts b/test/ut/extend/dpi.ts new file mode 100644 index 00000000..e6368226 --- /dev/null +++ b/test/ut/extend/dpi.ts @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import chai from 'chai'; +import { + describe, + it, +} from 'mocha'; +import { + fakeLog, + fakeLogRestore +} from '../../fakeLog'; +import dpi from '../../../runtime/main/extend/dpi'; + +const expect = chai.expect; + +describe('api of dpi', () => { + fakeLog(); + + const dpiOptions = { + images: [ + { + 'image': { + 'wearable': 'common/wearable.png', + 'computer': 'image/computer.jpg', + 'object': { + 'image0': 'common/wearable.png', + 'image1': 'image/computer.jpg' + }, + 'array': [ + 'common/wearable.png', + 'image/computer.jpg' + ] + } + } + ] + }; + + const Dpi = dpi.dpi.create(0).instance.dpi; + const dpiInstance = new Dpi(dpiOptions); + + describe('dpi', () => { + it('$r(path)', () => { + expect(dpiInstance.$r).to.be.an.instanceof(Function); + expect(dpiInstance.$r('image.wearable')).eql('common/wearable.png'); + expect(dpiInstance.$r('image.com')).eql('image.com'); + expect(dpiInstance.$r('image.object')).eql({ + 'image0': 'common/wearable.png', + 'image1': 'image/computer.jpg' + }); + expect(dpiInstance.$r('image.array')[0]).eql('common/wearable.png'); + expect(dpiInstance.$r(null)).to.be.undefined; + expect(dpiInstance.$r(undefined)).to.be.undefined; + }); + + // @ts-ignore + expect(dpiInstance.$r(1)).to.be.undefined; + }); + + fakeLogRestore(); +}); diff --git a/test/ut/extend/i18n.ts b/test/ut/extend/i18n.ts new file mode 100644 index 00000000..a871445a --- /dev/null +++ b/test/ut/extend/i18n.ts @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import chai from 'chai'; +import sinon from 'sinon'; +import { + describe, + it, + after +} from 'mocha'; +import { + fakeLog, + fakeLogRestore +} from '../../fakeLog'; +import i18n from '../../../runtime/main/extend/i18n'; +import { getValue } from '../../../runtime/utils'; + +const expect = chai.expect; + +describe('api of i18n', () => { + fakeLog(); + + const i18nOptions0 = { + locale: 'zh', + messages: [ + { + 'strings': { + 'hello': 'hello' + }, + 'files': { + 'image': 'image/rain_bg.jpg' + } + }, + { + 'strings': { + 'hello': 'Hello world!', + 'object': 'Object parameter substitution-{name}', + 'object1': 'Object parameter substitution-{name1}-{name0}', + 'array': 'Array type parameter substitution-{0}', + 'array1': 'Array type parameter substitution-{1}-{0}-{2}', + 'num': 1, + 'bool': true, + 'plurals': { + 'one': 'one person', + 'other': '{count} people' + } + }, + 'files': { + 'image': 'image/en_picture.PNG' + } + } + ] + }; + const i18nOptions1 = { + locale: 'en', + messages: [[], {'strings': 'hello'}] + }; + const i18nPluralRules = { + select: (count) => { + return count === 1 ? 'one' : 'other'; + } + }; + const I18n = i18n.I18n.create(0).instance.I18n; + const i18nInstance0 = new I18n(i18nOptions0); + const i18nInstance1 = new I18n(i18nOptions1); + const fakeGetChoice = sinon.stub(i18nInstance0, '_getChoice').callsFake((count, path, message) => { + const pluralChoice = i18nPluralRules.select(count); + if (!pluralChoice) { + return path; + } + return getValue(pluralChoice, message); + }); + + after(() => { + fakeGetChoice.restore(); + }); + + describe('i18n', () => { + it('$t(path)', () => { + expect(i18nInstance0.$t).to.be.an.instanceof(Function); + expect(i18nInstance0.$t('strings.hello')).eql('hello'); + expect(i18nInstance0.$t('strings.num')).eql(1); + expect(i18nInstance0.$t('strings.bool')).to.be.true; + expect(i18nInstance0.$t('files.image')).eql('image/rain_bg.jpg'); + expect(i18nInstance0.$t('strings.plurals.one')).eql('one person'); + expect(i18nInstance0.$t('strings.plurals')).eql({ + 'one': 'one person', + 'other': '{count} people' + }); + expect(i18nInstance0.$t('strings.plurals')['one']).eql('one person'); + expect(i18nInstance0.$t('strings.plurals')['two']).eql(undefined); + expect(i18nInstance0.$t('strings.hell.key')).eql('strings.hell.key'); + expect(i18nInstance0.$t(null)).to.be.undefined; + expect(i18nInstance0.$t(undefined)).to.be.undefined; + + // @ts-ignore + expect(i18nInstance0.$t(1)).to.be.undefined; + }); + + it('$t(path, params)', () => { + expect(i18nInstance0.$t('strings.object', {'name': 'object_value'})) + .eql('Object parameter substitution-object_value'); + expect(i18nInstance0.$t('strings.object1', {'name0': 'object_value_0', 'name1': 'object_value_1'})) + .eql('Object parameter substitution-object_value_1-object_value_0'); + expect(i18nInstance0.$t('strings.array', [1])) + .eql('Array type parameter substitution-1'); + expect(i18nInstance0.$t('strings.array1', [1, false])) + .eql('Array type parameter substitution-false-1-'); + expect(i18nInstance0.$t('strings.array1', null)) + .eql('Array type parameter substitution-{1}-{0}-{2}'); + expect(i18nInstance0.$t('strings.array1', undefined)) + .eql('Array type parameter substitution-{1}-{0}-{2}'); + expect(i18nInstance0.$t('strings.array1', 1)) + .eql('Array type parameter substitution-{1}-{0}-{2}'); + expect(i18nInstance0.$t('strings.array1', {0: 'object_value_0', 1: 'object_value_1'})) + .eql('Array type parameter substitution-object_value_1-object_value_0-'); + expect(i18nInstance0.$t('strings.array1', {'name0': 'object_value_0', 'name1': 'object_value_1'})) + .eql('Array type parameter substitution---'); + }); + + it('$tc(path, count)', () => { + expect(i18nInstance0.$tc).to.be.an.instanceof(Function); + expect(i18nInstance0.$tc('strings.plurals', 0)).eql('0 people'); + expect(i18nInstance0.$tc('strings.plurals', 1)).eql('one person'); + expect(i18nInstance0.$tc('strings.plurals', undefined)).eql('one person'); + expect(i18nInstance0.$tc('strings.plurals')).eql('one person'); + expect(i18nInstance0.$tc('strings.plurals', null)).eql('one person'); + expect(i18nInstance0.$tc('strings.hello', 0)).eql('strings.hello'); + expect(i18nInstance0.$tc('strings.hello')).eql('strings.hello'); + expect(i18nInstance0.$tc(null)).to.be.undefined; + expect(i18nInstance0.$tc(undefined)).to.be.undefined; + + // @ts-ignore + expect(i18nInstance0.$tc(1)).to.be.undefined; + }); + + it('messages are null', () => { + expect(i18nInstance1.$t('strings.hello')).eql('strings.hello'); + expect(i18nInstance1.$t('strings')).eql('hello'); + }); + }); + + fakeLogRestore(); +}); diff --git a/test/ut/manage/bridge.ts b/test/ut/manage/bridge.ts new file mode 100644 index 00000000..1b3df206 --- /dev/null +++ b/test/ut/manage/bridge.ts @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import chai from 'chai'; +import { + before, + after, + describe, + it +} from 'mocha'; +import { + fakeLog, + fakeLogRestore +} from '../../fakeLog'; +import { initFramework } from '../../../runtime/preparation/init'; +import framework from '../../../runtime/preparation/methods'; +import { receiveTasks } from '../../../runtime/main/manage/event/bridge'; + +const expect = chai.expect; + +describe('receiveTasks', () => { + fakeLog(); + + const instanceId = Date.now() + ''; + const options = { + orientation: 'portrait', + deviceType: 'phone', + resolution: '3.0', + aspectRatio: 'string', + deviceWidth: '1176', + deviceHeight: '2400', + roundScreen: false, + width: '0', + height: '0', + isInit: true, + pcPreview: 'disable', + appInstanceId: '10002', + packageName: 'com.example.helloworld', + resourcesConfiguration: [], + i18n: { + resources: [ + {'strings': {'hello': 'hello', 'world': 'world'}, + 'Files': {}}, + {'strings': {'hello': 'Hello', 'world': 'World'}, + 'Files': {}} + ] + }, + language: 'zh_CN', + appCreate: true, + appCode: '', + bundleUrl: '' + }; + const code: string = ` + $app_define$('@app-component/index', [], + function($app_require$, $app_exports$, $app_module$) { + $app_module$.exports = { + data: {}, + } + $app_module$.exports.template = { + 'type': 'div', + 'attr': {}, + 'children': [ + { + 'type': 'text', + 'attr': { + 'value': 'This is the index page.' + }, + 'classList': [ + 'title' + ], + "events": { + "click": '1' + }, + } + ] + } + }) + + $app_bootstrap$('@app-component/index',undefined,undefined) + `; + + before(() => { + initFramework(); + framework.createInstance(instanceId, code, options, null); + }); + + after(() => { + framework.destroyInstance(instanceId); + }); + + it('normal check of tasks', () => { + expect(receiveTasks).to.be.an.instanceof(Function); + const test1 = receiveTasks('invalid id', undefined); + expect(test1).to.be.an.instanceof(Error); + + // @ts-ignore + const test2 = receiveTasks(instanceId, {}); + expect(test2).to.be.an.instanceof(Error); + const test3 = receiveTasks('invalid id', [{ + method: 'whatever', + args: [] + }]); + expect(test3).to.be.an.instanceof(Error); + }); + + fakeLogRestore(); +}); diff --git a/test/ut/model/directive.ts b/test/ut/model/directive.ts new file mode 100644 index 00000000..dd7659de --- /dev/null +++ b/test/ut/model/directive.ts @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import chai from 'chai'; +import sinon from 'sinon'; +import { + describe, + before, + it +} from 'mocha'; +import { + fakeLog, + fakeLogRestore +} from '../../fakeLog'; +import { + bindSubVm, + bindSubVmAfterInitialized +} from '../../../runtime/main/model/directive'; +import { initState } from '../../../runtime/main/reactivity/state'; +import config from '../../../runtime/main/config'; + +const expect = chai.expect; +const { nativeComponentMap } = config; +const directive = {}; + +function extendVm(vm, methodNames) { + methodNames.forEach((name) => { + vm[name] = directive[name]; + }); + initState(vm); +} + +function initElement(el) { + el.setAttr = function(k, v) { + this.attr[k] = v; + }; + el.setStyle = function(k, v) { + this.style[k] = v; + }; + el.setClassStyle = function(style) { + this.classStyle = style; + }; + el.addEvent = function(t, h) { + this.event[t] = h; + }; + el.setClassList = function(classList) { + this.classList = classList; + }; +} + +describe('bind external infomations to sub vm', () => { + fakeLog(); + + let vm: any; + let subVm: any; + + before(() => { + vm = { + data: { a: 1, b: 2, c: 'class-style1' }, + watchers: [], + app: { eventManager: { add: () => {} }}, + options: { + style: { + 'class-style1': { + aaa: 1, + bbb: 2 + }, + 'class-style2': { + aaa: 2, + ccc: 3 + } + } + }, + foo: function() {} + }; + extendVm(vm, []); + subVm = { + options: { + props: { + a: String, + b: String + } + }, + props: [] + }; + }); + + it('bind to no-root-element sub vm', () => { + bindSubVm(vm, subVm, { + // @ts-ignore + attr: { a: 3, c: 4 }, + + // @ts-ignore + style: { a: 2 }, + events: { click: 'foo' } + }, {}); + expect(subVm.a).eql(3); + expect(subVm.b).to.be.undefined; + expect(subVm.rootEl).to.be.undefined; + }); + + it('bind props with external data', () => { + bindSubVm(vm, subVm, { + // @ts-ignore + attr: { a: function() { + return this.data.b; + } } + }, {}); + expect(subVm.a).eql(2); + }); + + it('bind styles to a sub vm with root element', () => { + subVm.rootEl = { + attr: {}, + style: {}, + event: [] + }; + const template: any = { + style: { aaa: 2, bbb: function() { + return this.data.a; + } } + }; + initElement(subVm.rootEl); + bindSubVm(vm, subVm, template, {}); + + // @ts-ignore + bindSubVmAfterInitialized(vm, subVm, template, {}); + expect(subVm.rootEl.style.aaa).eql(2); + expect(subVm.rootEl.style.bbb).eql(1); + }); + + fakeLogRestore(); +}); diff --git a/test/ut/model/index.ts b/test/ut/model/index.ts new file mode 100644 index 00000000..d9beb95c --- /dev/null +++ b/test/ut/model/index.ts @@ -0,0 +1,236 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import chai from 'chai'; +import sinon from 'sinon'; +import { + describe, + it +} from 'mocha'; +import { + fakeLog, + fakeLogRestore +} from '../../fakeLog'; +import Vm from '../../../runtime/main/model'; +import Doc from '../../../runtime/vdom/Document'; +import Differ from '../../../runtime/main/page/api/Differ'; + +const expect = chai.expect; + +describe('api of communication between vm and data methods', () => { + fakeLog(); + + const options = { + orientation: 1, + width: 1, + height: 1, + aspectRatio: 1, + deviceWidth: 1, + deviceHeight: 1, + resolution: 1, + accessType: 1 + }; + + const doc = new Doc('1', ''); + const differ = new Differ('test'); + const customComponentMap: any = {}; + const spyEl = sinon.spy(); + + customComponentMap.parent = { + template: { + type: 'div', + id: 'myDiv', + children: [ + { + type: 'text', + id: 'myBar', + events: { + 'click': 'proxyClick' + } + }, + { + type: 'child', + id: 'myChild' + } + ] + }, + proxyClick: function(e) { + spyEl(e); + } + }; + + customComponentMap.child = { + template: { + type: 'div' + } + }; + + const page = {doc, customComponentMap, options}; + + const vm = new Vm( + 'parent', + null, + { + _app: page, + _rootVm: true + }, + null, + null, + null + ); + + const spyParent = sinon.spy(); + const spyChild = sinon.spy(); + const subVm = vm.childrenVms[0]; + vm.$on('event1', spyParent); + subVm.$on('event1', spyChild); + + it('$on && $off', () => { + const fn = function() {}; + vm.$on('event1', fn); + expect(vm.vmEvents['event1'].length).eql(2); + expect(typeof vm.vmEvents['event1'][1]).eql('function'); + + vm.$off('event1', fn); + expect(vm.vmEvents['event1'].length).eql(1); + expect(typeof vm.vmEvents['event1'][1]).eql('undefined'); + }); + + it('$emit', () => { + expect(vm.type).eql('parent'); + expect(subVm.type).eql('child'); + + let detail = { lunch: 'banana' }; + vm.$emit('event1', detail); + expect(spyChild.args.length).eql(0); + expect(spyParent.args.length).eql(1); + + detail = { lunch: 'banana' }; + subVm.$emit('event1', detail); + expect(spyChild.args.length).eql(1); + expect(spyParent.args.length).eql(1); + }); + + it('$dispatch', () => { + const detail = { lunch: 'banana' }; + vm.$dispatch('event1', detail); + expect(spyParent.args.length).eql(2); + expect(spyChild.args.length).eql(1); + + subVm.$dispatch('event1', detail); + expect(spyParent.args.length).eql(3); + expect(spyChild.args.length).eql(2); + }); + + it('$broadCast', () => { + const detail = { lunch: 'banana' }; + vm.$broadcast('event1', detail); + expect(spyParent.args.length).eql(4); + expect(spyChild.args.length).eql(3); + + subVm.$broadcast('event1', detail); + expect(spyParent.args.length).eql(4); + expect(spyChild.args.length).eql(4); + }); + + it('$emitDirect', () => { + const detail = { lunch: 'banana' }; + vm.$emitDirect('event1', detail); + expect(spyParent.args.length).eql(5); + }); + + it('$emitElement', () => { + const detail = { lunch: 'banana' }; + vm.$emitElement('click', detail, 'myBar'); + sinon.assert.calledOnce(spyEl); + expect(spyEl.args.length).eql(1); + expect(spyEl.args[0][0].detail).to.deep.equal(detail); + }); + + it('$root', () => { + expect(subVm.$root().type).eql('parent'); + }); + + it('$parent', () => { + expect(subVm.$parent().type).eql('parent'); + }); + + it('$child', () => { + expect(vm.$child('myChild').type).eql('child'); + }); + + it('$element', () => { + expect(vm.$element('myDiv').type).eql('div'); + expect(vm.$element('myChild').type).eql('div'); + }); + + it('$set && $delete', () => { + const data = { + x: 11, + y: 22, + z: 'test-style' + }; + customComponentMap.foo = { + template: { + type: 'test.aaa', + children: [{ type: 'type1', component: true }] + } + }; + customComponentMap.bar = { + template: { + type: 'test.bbb' + } + }; + + const app = { doc, customComponentMap, differ, options }; + const vm = new Vm('foo', customComponentMap.foo, { _app: app, _rootVm: true }, null, data, null); + doc.destroy(); + const detail = { aaa: 1 }; + vm.$set('test.aaa', detail); + expect(typeof vm.data['test.aaa']).eql('object'); + vm.$delete('test.aaa'); + expect(typeof vm.data['test.aaa']).eql('undefined'); + }); + + it('$watch', () => { + const data = { + x: { + y: 1 + } + }; + customComponentMap.foo = { + template: { + type: 'div' + }, + data: data + }; + + const app = { doc, customComponentMap, differ, options }; + const vm = new Vm('foo', customComponentMap.foo, { _app: app, _rootVm: true }, null, data, null); + expect(vm.app).to.deep.equal(app); + expect(typeof doc.body).eql('object'); + + data.x.y = 5; + vm.$watch('x.y', (value) => { + expect(value).eql(5); + }); + }); + + fakeLogRestore(); +}); diff --git a/test/ut/runtime.ts b/test/ut/runtime.ts new file mode 100644 index 00000000..bdcdf0fa --- /dev/null +++ b/test/ut/runtime.ts @@ -0,0 +1,226 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import chai from 'chai'; +import { + before, + after, + describe, + it +} from 'mocha'; +import { + fakeLog, + fakeLogRestore +} from '../fakeLog'; +import { initFramework } from '../../runtime/preparation/init'; +import framework from '../../runtime/preparation/methods'; +import { + getModule, + clearModules, + allModules +} from '../../runtime/main/page/register'; +import { App } from '../../runtime/main/app/App'; +import Page from '../../runtime/main/page'; +import config from '../../runtime/main/config'; + +const expect = chai.expect; + +function clearRefs(json) { + delete json.ref; + if (json.children) { + json.children.forEach(clearRefs); + } +} + +describe('framework entry', () => { + fakeLog(); + + const pageMap: Map = App.pageMap; + let instanceId; + const options = { + orientation: 'portrait', + deviceType: 'phone', + resolution: '3.0', + aspectRatio: 'string', + deviceWidth: '1176', + deviceHeight: '2400', + roundScreen: false, + width: '0', + height: '0', + isInit: true, + pcPreview: 'disable', + appInstanceId: '10002', + packageName: 'com.example.helloworld', + resourcesConfiguration: [], + i18n: { + resources: [ + {'strings': {'hello': 'hello', 'world': 'world'}, + 'Files': {}}, + {'strings': {'hello': 'Hello', 'world': 'World'}, + 'Files': {}} + ] + }, + language: 'zh_CN', + appCreate: true, + appCode: '', + bundleUrl: '' + }; + const { nativeComponentMap } = config; + + before(() => { + initFramework(); + global.callNative = (id, tasks, callbackId) => { + if (callbackId !== '-1') { + framework.callJS(id, [{ + method: 'callback', + args: [callbackId, null, true] + }]); + } + }; + }); + + after(() => { + fakeLogRestore(); + framework.destroyInstance(instanceId); + }); + + it('createInstance', () => { + instanceId = Date.now() + ''; + const code: string = ` + $app_define$('@app-component/index', [], + function($app_require$, $app_exports$, $app_module$) { + $app_module$.exports = { + data: {}, + } + $app_module$.exports.template = { + 'type': 'div', + 'attr': {}, + 'children': [ + { + 'type': 'text', + 'attr': { + 'value': 'This is the index page.' + }, + 'classList': [ + 'title' + ] + } + ] + } + $app_module$.exports.style = { + '.title': { + 'fontSize': '50px' + } + } + }) + $app_bootstrap$('@app-component/index',undefined,undefined) + `; + const expectComponent = { + 'index': { + 'data': {}, + 'template': { + 'type': 'div', + 'attr': {}, + 'children': [{'type': 'text', 'attr': {'value': 'This is the index page.'}, 'classList': ['title']}] + }, + 'style': {'.title': {'fontSize': '50px'}} + } + }; + framework.createInstance(instanceId, code, options, null); + expect(pageMap.get(instanceId).customComponentMap).eql(expectComponent); + }); + + describe('getRoot', () => { + it('with an exist instanceId', () => { + const json = framework.getRoot(instanceId); + expect(json.ref).eql('_root'); + clearRefs(json); + const expectJSON = { + type: 'div', + attr: {}, + style: {}, + children: [{ + type: 'text', + attr: { + value: 'This is the index page.' + }, + customComponent: false, + style: {fontSize: '50px'} + }], + event: ['viewappear', 'viewdisappear', 'viewsizechanged'], + customComponent: true + }; + expect(json).eql(expectJSON); + }); + }); + + describe('callJS', () => { + it('fireEvent with no params', () => { + const result = framework.callJS(undefined, undefined); + expect(result).to.be.an.instanceof(Error); + }); + + it('non-exist instanceId', () => { + const result = framework.callJS('123', [{ + method: 'fireEvent', + args: [] + }]); + expect(result).to.be.an.instanceof(Error); + }); + + it('non-array tasks', () => { + const result = framework.callJS(instanceId, { + // @ts-ignore + method: 'fireEvent', + args: [] + }); + expect(result).to.be.an.instanceof(Error); + }); + }); + + describe('destroyInstance', () => { + it('with no params', () => { + const result = framework.destroyInstance(undefined); + expect(result).to.be.an.instanceof(Error); + }); + + it('with an exist instanceId', () => { + const result = framework.destroyInstance(instanceId); + expect(result[instanceId]).to.be.undefined; + }); + + it('non-exist instanceId', () => { + const result = framework.destroyInstance('123'); + expect(result).to.be.an.instanceof(Error); + }); + }); + + describe('registerModules', () => { + it('with object of modules', () => { + clearModules(); + expect(allModules()).to.deep.equal({}); + const modules = { + 'system.test': ['getInfo', 'getAvailableStorage', 'getCpuInfo'] + }; + framework.registerModules(modules); + expect(getModule('system')).to.be.an('object'); + clearModules(); + }); + }); +}); diff --git a/test/ut/utils.ts b/test/ut/utils.ts new file mode 100644 index 00000000..c492a8d8 --- /dev/null +++ b/test/ut/utils.ts @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import chai from 'chai'; +import { + describe, + it +} from 'mocha'; +const expect = chai.expect; + +import * as utils from '../../runtime/utils/index'; + +describe('utils', () => { + it('should be an array or object', () => { + expect(utils.isObject).to.be.an.instanceof(Function); + expect(utils.isObject({})).eql(true); + expect(utils.isObject([])).eql(true); + expect(utils.isObject('a')).eql(false); + expect(utils.isObject(0)).eql(false); + expect(utils.isObject(true)).eql(false); + expect(utils.isObject(null)).eql(false); + expect(utils.isObject(undefined)).eql(false); + expect(utils.isObject(function() {})).eql(false); + expect(utils.isObject(/\w*/)).eql(true); + expect(utils.isObject(new Date())).eql(true); + }); + + it('should be an real object', () => { + expect(utils.isPlainObject).to.be.an.instanceof(Function); + expect(utils.isPlainObject({})).eql(true); + expect(utils.isPlainObject([])).eql(false); + expect(utils.isPlainObject('a')).eql(false); + expect(utils.isPlainObject(0)).eql(false); + expect(utils.isPlainObject(true)).eql(false); + expect(utils.isPlainObject(null)).eql(false); + expect(utils.isPlainObject(undefined)).eql(false); + expect(utils.isPlainObject(function() {})).eql(false); + expect(utils.isPlainObject(/\w*/)).eql(false); + expect(utils.isPlainObject(new Date())).eql(false); + }); + + it('has own property', () => { + expect(utils.hasOwn).to.be.an.instanceof(Function); + function Point() { + this.x = 0; + } + Point.prototype.y = 1; + const p = new Point(); + expect(p.x).eql(0); + expect(p.y).eql(1); + expect(utils.hasOwn(p, 'x')).eql(true); + expect(utils.hasOwn(p, 'y')).eql(false); + }); + + it('own property is empty or not', () => { + expect(utils.isEmpty).to.be.an.instanceof(Function); + expect(utils.isEmpty({})).eql(true); + expect(utils.isEmpty([])).eql(true); + expect(utils.isEmpty('a')).eql(true); + expect(utils.isEmpty(0)).eql(true); + expect(utils.isEmpty(true)).eql(true); + expect(utils.isEmpty(null)).eql(true); + expect(utils.isEmpty(undefined)).eql(true); + expect(utils.isEmpty(function() {})).eql(true); + expect(utils.isEmpty(/\w*/)).eql(true); + expect(utils.isEmpty(new Date())).eql(true); + function Point() { + this.x = 0; + } + const p = new Point(); + expect(p.x).eql(0); + expect(utils.isEmpty(p)).eql(false); + Point.prototype.y = 1; + expect(p.y).eql(1); + expect(utils.isEmpty(p)).eql(false); + }); + + it('is null or undefined', () => { + expect(utils.isNull).to.be.an.instanceof(Function); + expect(utils.isNull({})).eql(false); + expect(utils.isNull([])).eql(false); + expect(utils.isNull('a')).eql(false); + expect(utils.isNull(0)).eql(false); + expect(utils.isNull(true)).eql(false); + expect(utils.isNull(null)).eql(true); + expect(utils.isNull(undefined)).eql(true); + expect(utils.isNull(function() {})).eql(false); + expect(utils.isNull(/\w*/)).eql(false); + expect(utils.isNull(new Date())).eql(false); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..d4e84946 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "allowJs": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "noImplicitAny": false, + "noUnusedLocals": false, + "noUnusedParameters": false, + "experimentalDecorators": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "module": "commonjs", + "target": "es5", + "types": [ + "node" + ], + "typeRoots": [ + "./node_modules/@types" + ], + "lib": [ + "es2015" + ] + }, + "include": [ + "runtime/**/*.ts" + ], + "exclude": [ + "node_modules" + ], + "ts-node": { + "logError": true + } +}