From c9e17678e74bdca81fd8805788694403f5a76e4d Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Wed, 25 Aug 2021 17:01:21 +0200 Subject: [PATCH 1/7] base skeleton --- packages/web-app-skeleton/package.json | 17 + packages/web-app-skeleton/rollup.config.js | 22 + packages/web-app-skeleton/src/App.vue | 16 + packages/web-app-skeleton/src/app.js | 34 + packages/web-app-skeleton/yarn.lock | 1114 ++++++++++++++++++++ 5 files changed, 1203 insertions(+) create mode 100644 packages/web-app-skeleton/package.json create mode 100644 packages/web-app-skeleton/rollup.config.js create mode 100644 packages/web-app-skeleton/src/App.vue create mode 100644 packages/web-app-skeleton/src/app.js create mode 100644 packages/web-app-skeleton/yarn.lock diff --git a/packages/web-app-skeleton/package.json b/packages/web-app-skeleton/package.json new file mode 100644 index 00000000000..4c594ce32d9 --- /dev/null +++ b/packages/web-app-skeleton/package.json @@ -0,0 +1,17 @@ +{ + "name": "skeleton", + "version": "0.0.0", + "description": "ownCloud web app skeleton", + "license": "AGPL-3.0", + "scripts": { + "build": "rollup -c", + "serve": "SERVER=true rollup -c -w" + }, + "devDependencies": { + "rollup": "^2.41.4", + "rollup-plugin-serve": "^1.1.0", + "rollup-plugin-vue": "^5", + "vue-template-compiler": "^2.6.12", + "vue": "^2.6.10" + } +} diff --git a/packages/web-app-skeleton/rollup.config.js b/packages/web-app-skeleton/rollup.config.js new file mode 100644 index 00000000000..3de4577f38f --- /dev/null +++ b/packages/web-app-skeleton/rollup.config.js @@ -0,0 +1,22 @@ +import path from 'path' +import serve from 'rollup-plugin-serve' +import vue from 'rollup-plugin-vue' + +const dev = process.env.SERVER === 'true' + +export default { + input: path.resolve(__dirname, 'src/app.js'), + output: { + format: 'amd', + file: 'dist/app.js' + }, + external: ['vue'], + plugins: [ + vue(), + dev && + serve({ + port: 3000, + contentBase: ['dist'] + }) + ] +} diff --git a/packages/web-app-skeleton/src/App.vue b/packages/web-app-skeleton/src/App.vue new file mode 100644 index 00000000000..a6288f12a68 --- /dev/null +++ b/packages/web-app-skeleton/src/App.vue @@ -0,0 +1,16 @@ + + + diff --git a/packages/web-app-skeleton/src/app.js b/packages/web-app-skeleton/src/app.js new file mode 100644 index 00000000000..25bdceafdf8 --- /dev/null +++ b/packages/web-app-skeleton/src/app.js @@ -0,0 +1,34 @@ +import App from './App.vue' + +const appInfo = { + name: 'web-app-skeleton', + id: 'skeleton', + icon: 'folder', + isFileEditor: false, + extensions: [] +} + +export default { + appInfo, + navItems: [ + { + name: 'skeleton', + iconMaterial: appInfo.icon, + route: { + path: `/${appInfo.id}/` + } + } + ], + routes: [ + { + name: 'skeleton', + path: '/', + components: { + app: App + } + } + ], + mounted() { + console.log('from skeleton app') + } +} diff --git a/packages/web-app-skeleton/yarn.lock b/packages/web-app-skeleton/yarn.lock new file mode 100644 index 00000000000..371c66f7028 --- /dev/null +++ b/packages/web-app-skeleton/yarn.lock @@ -0,0 +1,1114 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/helper-validator-identifier@^7.14.9": + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" + integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g== + +"@babel/parser@^7.6.0", "@babel/parser@^7.9.6": + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.3.tgz#3416d9bea748052cfcb63dbcc27368105b1ed862" + integrity sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA== + +"@babel/types@^7.6.1", "@babel/types@^7.9.6": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.0.tgz#61af11f2286c4e9c69ca8deb5f4375a73c72dcbd" + integrity sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ== + dependencies: + "@babel/helper-validator-identifier" "^7.14.9" + to-fast-properties "^2.0.0" + +"@vue/component-compiler-utils@^3.0.0", "@vue/component-compiler-utils@^3.1.2": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-3.2.2.tgz#2f7ed5feed82ff7f0284acc11d525ee7eff22460" + integrity sha512-rAYMLmgMuqJFWAOb3Awjqqv5X3Q3hVr4jH/kgrFJpiU0j3a90tnNBplqbj+snzrgZhC9W128z+dtgMifOiMfJg== + dependencies: + consolidate "^0.15.1" + hash-sum "^1.0.2" + lru-cache "^4.1.2" + merge-source-map "^1.1.0" + postcss "^7.0.36" + postcss-selector-parser "^6.0.2" + source-map "~0.6.1" + vue-template-es2015-compiler "^1.9.0" + optionalDependencies: + prettier "^1.18.2" + +"@vue/component-compiler@^4.2.3": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@vue/component-compiler/-/component-compiler-4.2.4.tgz#db8c485c33b74c7d0e54c19a945f1a4cb65c9dc4" + integrity sha512-tFGw3h3+nxiqnyborwWQ+rUgKAwSFl0Sdg+BCZkWTyFfkEF5fqunTNoklEUDdtRQMmVqsajn1pOZdm0zh4Uicw== + dependencies: + "@vue/component-compiler-utils" "^3.0.0" + clean-css "^4.1.11" + hash-sum "^1.0.2" + postcss-modules-sync "^1.0.0" + source-map "0.6.*" + optionalDependencies: + less "^3.9.0" + pug "^3.0.1" + sass "^1.18.0" + stylus "^0.54.5" + +acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +asap@~2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + +assert-never@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/assert-never/-/assert-never-1.2.1.tgz#11f0e363bf146205fb08193b5c7b90f4d1cf44fe" + integrity sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw== + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +babel-walk@3.0.0-canary-5: + version "3.0.0-canary-5" + resolved "https://registry.yarnpkg.com/babel-walk/-/babel-walk-3.0.0-canary-5.tgz#f66ecd7298357aee44955f235a6ef54219104b11" + integrity sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw== + dependencies: + "@babel/types" "^7.9.6" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +big.js@^3.1.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" + integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +bluebird@^3.1.1: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +character-parser@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/character-parser/-/character-parser-2.2.0.tgz#c7ce28f36d4bcd9744e5ffc2c5fcde1c73261fc0" + integrity sha1-x84o821LzZdE5f/CxfzeHHMmH8A= + dependencies: + is-regex "^1.0.3" + +"chokidar@>=3.0.0 <4.0.0": + version "3.5.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" + integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +clean-css@^4.1.11: + version "4.2.3" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" + integrity sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA== + dependencies: + source-map "~0.6.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +consolidate@^0.15.1: + version "0.15.1" + resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7" + integrity sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw== + dependencies: + bluebird "^3.1.1" + +constantinople@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/constantinople/-/constantinople-4.0.1.tgz#0def113fa0e4dc8de83331a5cf79c8b325213151" + integrity sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw== + dependencies: + "@babel/parser" "^7.6.0" + "@babel/types" "^7.6.1" + +copy-anything@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.3.tgz#842407ba02466b0df844819bbe3baebbe5d45d87" + integrity sha512-GK6QUtisv4fNS+XcI7shX0Gx9ORg7QqIznyfho79JTnX1XhLiyZHfftvGiziqzRiEi/Bjhgpi+D2o7HxJFPnDQ== + dependencies: + is-what "^3.12.0" + +css-parse@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-2.0.0.tgz#a468ee667c16d81ccf05c58c38d2a97c780dbfd4" + integrity sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q= + dependencies: + css "^2.0.0" + +css-selector-tokenizer@^0.7.0: + version "0.7.3" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz#735f26186e67c749aaf275783405cf0661fae8f1" + integrity sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg== + dependencies: + cssesc "^3.0.0" + fastparse "^1.1.2" + +css@^2.0.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" + integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== + dependencies: + inherits "^2.0.3" + source-map "^0.6.1" + source-map-resolve "^0.5.2" + urix "^0.1.0" + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +de-indent@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" + integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0= + +debug@^4.1.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + +debug@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +doctypes@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9" + integrity sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk= + +emojis-list@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" + integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= + +errno@^0.1.1: + version "0.1.8" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== + dependencies: + prr "~1.0.1" + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +estree-walker@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" + integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== + +fastparse@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" + integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +generic-names@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/generic-names/-/generic-names-1.0.3.tgz#2d786a121aee508876796939e8e3bff836c20917" + integrity sha1-LXhqEhruUIh2eWk56OO/+DbCCRc= + dependencies: + loader-utils "^0.2.16" + +get-intrinsic@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.6: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + 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" + +graceful-fs@^4.1.2: + version "4.2.8" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" + integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + dependencies: + ansi-regex "^2.0.0" + +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hash-sum@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04" + integrity sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ= + +he@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +icss-replace-symbols@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" + integrity sha1-Bupvg2ead0njhs/h/oEq5dsiPe0= + +image-size@~0.5.0: + version "0.5.5" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" + integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.2.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.6.0.tgz#d7553b2526fe59b92ba3e40c8df757ec8a709e19" + integrity sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ== + dependencies: + has "^1.0.3" + +is-expression@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-expression/-/is-expression-4.0.0.tgz#c33155962abf21d0afd2552514d67d2ec16fd2ab" + integrity sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A== + dependencies: + acorn "^7.1.1" + object-assign "^4.1.1" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-promise@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" + integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== + +is-regex@^1.0.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-what@^3.12.0: + version "3.14.1" + resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1" + integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA== + +js-base64@^2.1.9: + version "2.6.4" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" + integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== + +js-stringify@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/js-stringify/-/js-stringify-1.0.2.tgz#1736fddfd9724f28a3682adc6230ae7e4e9679db" + integrity sha1-Fzb939lyTyijaCrcYjCufk6Weds= + +json5@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= + +jstransformer@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/jstransformer/-/jstransformer-1.0.0.tgz#ed8bf0921e2f3f1ed4d5c1a44f68709ed24722c3" + integrity sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM= + dependencies: + is-promise "^2.0.0" + promise "^7.0.1" + +less@^3.9.0: + version "3.13.1" + resolved "https://registry.yarnpkg.com/less/-/less-3.13.1.tgz#0ebc91d2a0e9c0c6735b83d496b0ab0583077909" + integrity sha512-SwA1aQXGUvp+P5XdZslUOhhLnClSLIjWvJhmd+Vgib5BFIr9lMNlQwmwUNOjXThF/A0x+MCYYPeWEfeWiLRnTw== + dependencies: + copy-anything "^2.0.1" + tslib "^1.10.0" + optionalDependencies: + errno "^0.1.1" + graceful-fs "^4.1.2" + image-size "~0.5.0" + make-dir "^2.1.0" + mime "^1.4.1" + native-request "^1.0.5" + source-map "~0.6.0" + +loader-utils@^0.2.16: + version "0.2.17" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" + integrity sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g= + dependencies: + big.js "^3.1.3" + emojis-list "^2.0.0" + json5 "^0.5.0" + object-assign "^4.0.1" + +lru-cache@^4.1.2: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +magic-string@^0.25.7: + version "0.25.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" + integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== + dependencies: + sourcemap-codec "^1.4.4" + +make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +merge-source-map@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646" + integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw== + dependencies: + source-map "^0.6.1" + +mime@>=2.4.6: + version "2.5.2" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" + integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== + +mime@^1.4.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +mkdirp@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +native-request@^1.0.5: + version "1.0.9" + resolved "https://registry.yarnpkg.com/native-request/-/native-request-1.0.9.tgz#2269748379c004415d9721c204d8da3455abc175" + integrity sha512-KTRwqMwWCkoLZfjes3yBhK6XHwZ5Q1jPsdVra9hug8HNRbMsfTJm8a8L6/WOYi1h5eWNwlBaYy8V5SpJwkDgKw== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +object-assign@^4.0.1, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +opener@1: + version "1.5.2" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-parse@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +postcss-modules-local-by-default@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" + integrity sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk= + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-scope@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" + integrity sha1-1upkmUx5+XtipytCb75gVqGUu5A= + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-sync@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-sync/-/postcss-modules-sync-1.0.0.tgz#619a719cf78dd16a4834135140b324cf77334be1" + integrity sha1-YZpxnPeN0WpINBNRQLMkz3czS+E= + dependencies: + generic-names "^1.0.2" + icss-replace-symbols "^1.0.2" + postcss "^5.2.5" + postcss-modules-local-by-default "^1.1.1" + postcss-modules-scope "^1.0.2" + string-hash "^1.1.0" + +postcss-selector-parser@^6.0.2: + version "6.0.6" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea" + integrity sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss@^5.2.5: + version "5.2.18" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5" + integrity sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg== + dependencies: + chalk "^1.1.3" + js-base64 "^2.1.9" + source-map "^0.5.6" + supports-color "^3.2.3" + +postcss@^6.0.1: + version "6.0.23" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" + integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== + dependencies: + chalk "^2.4.1" + source-map "^0.6.1" + supports-color "^5.4.0" + +postcss@^7.0.36: + version "7.0.36" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.36.tgz#056f8cffa939662a8f5905950c07d5285644dfcb" + integrity sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + +prettier@^1.18.2: + version "1.19.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" + integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== + +promise@^7.0.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== + dependencies: + asap "~2.0.3" + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= + +pug-attrs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pug-attrs/-/pug-attrs-3.0.0.tgz#b10451e0348165e31fad1cc23ebddd9dc7347c41" + integrity sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA== + dependencies: + constantinople "^4.0.1" + js-stringify "^1.0.2" + pug-runtime "^3.0.0" + +pug-code-gen@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/pug-code-gen/-/pug-code-gen-3.0.2.tgz#ad190f4943133bf186b60b80de483100e132e2ce" + integrity sha512-nJMhW16MbiGRiyR4miDTQMRWDgKplnHyeLvioEJYbk1RsPI3FuA3saEP8uwnTb2nTJEKBU90NFVWJBk4OU5qyg== + dependencies: + constantinople "^4.0.1" + doctypes "^1.1.0" + js-stringify "^1.0.2" + pug-attrs "^3.0.0" + pug-error "^2.0.0" + pug-runtime "^3.0.0" + void-elements "^3.1.0" + with "^7.0.0" + +pug-error@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pug-error/-/pug-error-2.0.0.tgz#5c62173cb09c34de2a2ce04f17b8adfec74d8ca5" + integrity sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ== + +pug-filters@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pug-filters/-/pug-filters-4.0.0.tgz#d3e49af5ba8472e9b7a66d980e707ce9d2cc9b5e" + integrity sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A== + dependencies: + constantinople "^4.0.1" + jstransformer "1.0.0" + pug-error "^2.0.0" + pug-walk "^2.0.0" + resolve "^1.15.1" + +pug-lexer@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/pug-lexer/-/pug-lexer-5.0.1.tgz#ae44628c5bef9b190b665683b288ca9024b8b0d5" + integrity sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w== + dependencies: + character-parser "^2.2.0" + is-expression "^4.0.0" + pug-error "^2.0.0" + +pug-linker@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pug-linker/-/pug-linker-4.0.0.tgz#12cbc0594fc5a3e06b9fc59e6f93c146962a7708" + integrity sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw== + dependencies: + pug-error "^2.0.0" + pug-walk "^2.0.0" + +pug-load@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pug-load/-/pug-load-3.0.0.tgz#9fd9cda52202b08adb11d25681fb9f34bd41b662" + integrity sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ== + dependencies: + object-assign "^4.1.1" + pug-walk "^2.0.0" + +pug-parser@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/pug-parser/-/pug-parser-6.0.0.tgz#a8fdc035863a95b2c1dc5ebf4ecf80b4e76a1260" + integrity sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw== + dependencies: + pug-error "^2.0.0" + token-stream "1.0.0" + +pug-runtime@^3.0.0, pug-runtime@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/pug-runtime/-/pug-runtime-3.0.1.tgz#f636976204723f35a8c5f6fad6acda2a191b83d7" + integrity sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg== + +pug-strip-comments@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz#f94b07fd6b495523330f490a7f554b4ff876303e" + integrity sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ== + dependencies: + pug-error "^2.0.0" + +pug-walk@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pug-walk/-/pug-walk-2.0.0.tgz#417aabc29232bb4499b5b5069a2b2d2a24d5f5fe" + integrity sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ== + +pug@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/pug/-/pug-3.0.2.tgz#f35c7107343454e43bc27ae0ff76c731b78ea535" + integrity sha512-bp0I/hiK1D1vChHh6EfDxtndHji55XP/ZJKwsRqrz6lRia6ZC2OZbdAymlxdVFwd1L70ebrVJw4/eZ79skrIaw== + dependencies: + pug-code-gen "^3.0.2" + pug-filters "^4.0.0" + pug-lexer "^5.0.1" + pug-linker "^4.0.0" + pug-load "^3.0.0" + pug-parser "^6.0.0" + pug-runtime "^3.0.1" + pug-strip-comments "^2.0.0" + +querystring@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" + integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +resolve@^1.15.1: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + +rollup-plugin-serve@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-serve/-/rollup-plugin-serve-1.1.0.tgz#0654a57021a21b903340c69940f7463706e8288d" + integrity sha512-pYkSsuA0/psKqhhictkJw1c2klya5b+LlCvipWqI9OE1aG2M97mRumZCbBlry5CMEOzYBBgSDgd1694sNbmyIw== + dependencies: + mime ">=2.4.6" + opener "1" + +rollup-plugin-vue@^5: + version "5.1.9" + resolved "https://registry.yarnpkg.com/rollup-plugin-vue/-/rollup-plugin-vue-5.1.9.tgz#8769cfdac5531c2d1970222b7887b883db37b866" + integrity sha512-DXzrBUD2j68Y6nls4MmuJsFL1SrQDpdgjxvhk/oy04LzJmXJoX1x31yLEBFkkmvpbon6Q885WJLvEMiMyT+3rA== + dependencies: + "@vue/component-compiler" "^4.2.3" + "@vue/component-compiler-utils" "^3.1.2" + debug "^4.1.1" + hash-sum "^1.0.2" + magic-string "^0.25.7" + querystring "^0.2.0" + rollup-pluginutils "^2.8.2" + source-map "0.7.3" + vue-runtime-helpers "^1.1.2" + +rollup-pluginutils@^2.8.2: + version "2.8.2" + resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" + integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== + dependencies: + estree-walker "^0.6.1" + +rollup@^2.41.4: + version "2.56.3" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.56.3.tgz#b63edadd9851b0d618a6d0e6af8201955a77aeff" + integrity sha512-Au92NuznFklgQCUcV96iXlxUbHuB1vQMaH76DHl5M11TotjOHwqk9CwcrT78+Tnv4FN9uTBxq6p4EJoYkpyekg== + optionalDependencies: + fsevents "~2.3.2" + +safer-buffer@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sass@^1.18.0: + version "1.38.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.38.1.tgz#54dfb17fb168846b5850324b82fc62dc68f51bad" + integrity sha512-Lj8nPaSYOuRhgqdyShV50fY5jKnvaRmikUNalMPmbH+tKMGgEKVkltI/lP30PEfO2T1t6R9yc2QIBLgOc3uaFw== + dependencies: + chokidar ">=3.0.0 <4.0.0" + +sax@~1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +source-map-resolve@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-url@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" + integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== + +source-map@0.6.*, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@0.7.3, source-map@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +sourcemap-codec@^1.4.4: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + +string-hash@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" + integrity sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs= + +strip-ansi@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +stylus@^0.54.5: + version "0.54.8" + resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.8.tgz#3da3e65966bc567a7b044bfe0eece653e099d147" + integrity sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg== + dependencies: + css-parse "~2.0.0" + debug "~3.1.0" + glob "^7.1.6" + mkdirp "~1.0.4" + safer-buffer "^2.1.2" + sax "~1.2.4" + semver "^6.3.0" + source-map "^0.7.3" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + +supports-color@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= + dependencies: + has-flag "^1.0.0" + +supports-color@^5.3.0, supports-color@^5.4.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +token-stream@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/token-stream/-/token-stream-1.0.0.tgz#cc200eab2613f4166d27ff9afc7ca56d49df6eb4" + integrity sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ= + +tslib@^1.10.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +void-elements@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk= + +vue-runtime-helpers@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vue-runtime-helpers/-/vue-runtime-helpers-1.1.2.tgz#446b7b820888ab0c5264d2c3a32468e72e4100f3" + integrity sha512-pZfGp+PW/IXEOyETE09xQHR1CKkR9HfHZdnMD/FVLUNI+HxYTa82evx5WrF6Kz4s82qtqHvMZ8MZpbk2zT2E1Q== + +vue-template-compiler@^2.6.12: + version "2.6.14" + resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz#a2f0e7d985670d42c9c9ee0d044fed7690f4f763" + integrity sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g== + dependencies: + de-indent "^1.0.2" + he "^1.1.0" + +vue-template-es2015-compiler@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825" + integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw== + +vue@^2.6.10: + version "2.6.14" + resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235" + integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ== + +with@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/with/-/with-7.0.2.tgz#ccee3ad542d25538a7a7a80aad212b9828495bac" + integrity sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w== + dependencies: + "@babel/parser" "^7.9.6" + "@babel/types" "^7.9.6" + assert-never "^1.2.1" + babel-walk "3.0.0-canary-5" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= From 547ef6665bc485fe6d3e4e728c24e3fe35dcb55a Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Thu, 26 Aug 2021 15:06:45 +0200 Subject: [PATCH 2/7] feat(api): lazy extension registration introduce runtime api and use it to lazy register extensions from remote applications --- packages/web-app-skeleton/src/app.js | 40 +++++++++++++++++++++-- packages/web-runtime/src/index.js | 5 ++- packages/web-runtime/src/services/api.ts | 18 +++++++++++ packages/web-runtime/src/store/apps.js | 41 ++++++++++++++---------- 4 files changed, 83 insertions(+), 21 deletions(-) create mode 100644 packages/web-runtime/src/services/api.ts diff --git a/packages/web-app-skeleton/src/app.js b/packages/web-app-skeleton/src/app.js index 25bdceafdf8..50dc484a68c 100644 --- a/packages/web-app-skeleton/src/app.js +++ b/packages/web-app-skeleton/src/app.js @@ -4,10 +4,44 @@ const appInfo = { name: 'web-app-skeleton', id: 'skeleton', icon: 'folder', - isFileEditor: false, + isFileEditor: true, extensions: [] } +const injectExtensions = async api => { + // the promise is just there to deomo lazy loading of extensions + await new Promise(resolve => setTimeout(resolve, 2000)) + console.log('#############################################################################') + console.log('# FROM THE APP (SKELETON)') + console.log('# a maybe long running task like wopi started') + console.log('#############################################################################') + await new Promise(resolve => setTimeout(resolve, 10000)) + console.log('#############################################################################') + console.log('# FROM THE APP (SKELETON)') + console.log('# the server answered and we have all information to register a extension') + console.log('#############################################################################') + + api.registerExtension(appInfo.id, { + extension: 'txt', + newFileMenu: { + menuTitle($gettext) { + return $gettext('Extension from skeleton') + } + }, + routes: [ + 'files-personal', + 'files-favorites', + 'files-shared-with-others', + 'files-shared-with-me', + 'files-public-list' + ] + }) + console.log('#############################################################################') + console.log('# FROM THE APP (SKELETON)') + console.log('# new extension is now registered, ...') + console.log('#############################################################################') +} + export default { appInfo, navItems: [ @@ -28,7 +62,7 @@ export default { } } ], - mounted() { - console.log('from skeleton app') + async mounted({ api }) { + await injectExtensions(api) } } diff --git a/packages/web-runtime/src/index.js b/packages/web-runtime/src/index.js index 6cda4602b50..9ff3530d4de 100644 --- a/packages/web-runtime/src/index.js +++ b/packages/web-runtime/src/index.js @@ -55,6 +55,7 @@ import 'owncloud-design-system/dist/system/system.css' import Avatar from './components/Avatar.vue' import { registerClient } from './services/clientRegistration' +import { Api } from './services/api' import { loadConfig } from './helpers/config' import { loadTheme } from './helpers/theme' @@ -113,6 +114,7 @@ const supportedLanguages = { const translations = merge({}, coreTranslations, odsTranslations) const appRegistry = [] +const api = new Api(store) const loadApp = async path => { const app = await new Promise((resolve, reject) => @@ -228,7 +230,8 @@ const finalizeInit = async () => { mounted() { appRegistry.forEach( app => - app.mounted && app.mounted({ store, router, runtime: this, portal: portal(this, app.id) }) + app.mounted && + app.mounted({ store, router, runtime: this, portal: portal(this, app.id), api }) ) } }) diff --git a/packages/web-runtime/src/services/api.ts b/packages/web-runtime/src/services/api.ts new file mode 100644 index 00000000000..79df2c9e41d --- /dev/null +++ b/packages/web-runtime/src/services/api.ts @@ -0,0 +1,18 @@ +import { Store } from 'vuex' + +export class Api { + private readonly store: Store + + constructor(store: Store) { + this.store = store + } + + public registerExtension(app: string, extension: { [key: string]: any }): void { + console.log('#############################################################################') + console.log('# FROM THE API (RUNTIME)') + console.log(`# app '${app}', requested to register a new extension...`) + console.log('#############################################################################') + + this.store.commit('REGISTER_EXTENSION', { app, extension }) + } +} diff --git a/packages/web-runtime/src/store/apps.js b/packages/web-runtime/src/store/apps.js index 7e15af8dc0e..fe0f19d59b8 100644 --- a/packages/web-runtime/src/store/apps.js +++ b/packages/web-runtime/src/store/apps.js @@ -44,26 +44,33 @@ const actions = { } const mutations = { + REGISTER_EXTENSION(state, extensionBundle) { + const { app, extension } = extensionBundle + const editor = { + app, + icon: extension.icon, + newTab: extension.newTab || false, + routeName: extension.routeName, + routes: extension.routes || [], + extension: extension.extension, + handler: extension.handler + } + + state.fileEditors.push(editor) + + if (extension.newFileMenu) { + extension.newFileMenu.ext = extension.extension + extension.newFileMenu.action = editor + state.newFileHandlers.push(extension.newFileMenu) + } + }, REGISTER_APP(state, appInfo) { if (appInfo.extensions) { - appInfo.extensions.forEach(e => { - const editor = { + appInfo.extensions.forEach(extension => { + this.commit('REGISTER_EXTENSION', { app: appInfo.id, - icon: e.icon, - newTab: e.newTab || false, - routeName: e.routeName, - routes: e.routes || [], - extension: e.extension, - handler: e.handler - } - - state.fileEditors.push(editor) - - if (e.newFileMenu) { - e.newFileMenu.ext = e.extension - e.newFileMenu.action = editor - state.newFileHandlers.push(e.newFileMenu) - } + extension + }) }) } if (appInfo.fileSideBars) { From dad1a0a3d40771e0e3946a5f63f8e98360d95362 Mon Sep 17 00:00:00 2001 From: pwengerter Date: Thu, 26 Aug 2021 18:22:41 +0100 Subject: [PATCH 3/7] feat(web-runtime): introduce skeleton app and runtime-hooks in the proccess of adding a skeleton application we faced the issue of not having real runtime hooks. this introduces 3 hooks and exposes a dedicated runtime api to the application. this is fully backwards compatible --- docs/custom-apps/_index.md | 86 +++++ docs/theming/_index.md | 1 - packages/web-app-skeleton/src/app.js | 20 +- packages/web-container/index.html.ejs | 2 +- packages/web-runtime/src/container/api.ts | 266 +++++++++++++++ .../src/container/application/classic.ts | 110 ++++++ .../src/container/application/index.ts | 68 ++++ .../src/container/application/next.ts | 16 + .../web-runtime/src/container/bootstrap.ts | 212 ++++++++++++ packages/web-runtime/src/container/error.ts | 20 ++ packages/web-runtime/src/container/index.ts | 2 + packages/web-runtime/src/container/store.ts | 7 + packages/web-runtime/src/container/types.ts | 71 ++++ packages/web-runtime/src/defaults/index.ts | 24 ++ packages/web-runtime/src/defaults/json.js | 9 + packages/web-runtime/src/defaults/vue.js | 55 +++ packages/web-runtime/src/helpers/theme.js | 5 +- packages/web-runtime/src/index.js | 317 ------------------ packages/web-runtime/src/index.ts | 58 ++++ packages/web-runtime/src/vue.js | 6 - 20 files changed, 1013 insertions(+), 342 deletions(-) create mode 100644 docs/custom-apps/_index.md create mode 100644 packages/web-runtime/src/container/api.ts create mode 100644 packages/web-runtime/src/container/application/classic.ts create mode 100644 packages/web-runtime/src/container/application/index.ts create mode 100644 packages/web-runtime/src/container/application/next.ts create mode 100644 packages/web-runtime/src/container/bootstrap.ts create mode 100644 packages/web-runtime/src/container/error.ts create mode 100644 packages/web-runtime/src/container/index.ts create mode 100644 packages/web-runtime/src/container/store.ts create mode 100644 packages/web-runtime/src/container/types.ts create mode 100644 packages/web-runtime/src/defaults/index.ts create mode 100644 packages/web-runtime/src/defaults/json.js create mode 100644 packages/web-runtime/src/defaults/vue.js delete mode 100644 packages/web-runtime/src/index.js create mode 100644 packages/web-runtime/src/index.ts delete mode 100644 packages/web-runtime/src/vue.js diff --git a/docs/custom-apps/_index.md b/docs/custom-apps/_index.md new file mode 100644 index 00000000000..3ca880f3c1a --- /dev/null +++ b/docs/custom-apps/_index.md @@ -0,0 +1,86 @@ +--- +title: "Custom Apps" +date: 2021-08-26T00:00:00+00:00 +weight: 55 +geekdocRepo: https://github.com/owncloud/web +geekdocEditPath: edit/master/docs/custom-apps +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +{{< toc >}} + +## Introduction + +In the new `web` frontend, the way custom/external apps are handled has fundamentally changed from the approach of PHP-based ownCloud X stack. +Now, all you have to do is provide a bundled Vue app and reference it in your configuration! Please note that any existing apps you may be used to can not simply be ported from the old to the new frontend. + +This page documents how you can set up an example app within your frontend repo and play around with different kinds of apps and extensions. This way, you can explore how to provide users of your ownCloud with more functionality. + +## Setting up the example "skeleton app" + +{{< hint info >}} +This guide assumes you have either an oCIS or ownCloud 10 backend running and followed the [getting started guide]({{< ref "../getting-started.md" >}}) for setting up a development environment with the `web` frontend, having it running via either `yarn serve` or `yarn build:w`. You should be able to use the web UI on localhost using the respective port you've assigned (defaults are `:8080` for OC10 and `:9200` for oCIS) and see changes to your . +{{< /hint >}} + +From the root of the [web repository](https://github.com/owncloud/web), change into the example skeleton app by running + +```sh +cd packages/web-app-skeleton/ +``` + +Then, you can install the necessary dependencies, bundle the code and start a development server by running + +```sh +yarn install && yarn serve +``` + +In your terminal, you should see a success message and rollup (our bundler of choice) serving the content under `localhost:3000`. However, there's nothing to find under this address. + + +In your `config/config.json`, add the skeleton example app to the `"external_apps"` array like below: + + +```json +{ + "id": "skeleton", + "path": "http://localhost:3000/app.js" +} +``` + +After saving the config file (make sure it's still a valid `JSON` format), your running `web` app should be updated automatically. Open your browsers development tools, reload the page and watch for `(SKELETON)` messages in the browser console. + +## Configuration options + +``` +To be defined/documented +- Name, ID, logo +- Nav items, appswitcher items, quick actions +- ... +``` + +## Ways of providing an app + +``` +To be defined/documented (Bundling, CDN, app store, ...) +``` + +## Types of apps + +``` +To be defined/documented +- Application (renderable), e.g. iFrame +- Extension, e.g. media-viewer +- Quick action, e.g. based on WOPI/Appserver +- ... +``` + +## Accessing existing functionality + +``` +To be defined/documented +- Styles, Theming +- Notifications +- Authentication, SDK +- ... +``` \ No newline at end of file diff --git a/docs/theming/_index.md b/docs/theming/_index.md index d05ed068e7b..f27fb03d786 100644 --- a/docs/theming/_index.md +++ b/docs/theming/_index.md @@ -1,4 +1,3 @@ - --- title: "Theming" date: 2021-04-01T00:00:00+00:00 diff --git a/packages/web-app-skeleton/src/app.js b/packages/web-app-skeleton/src/app.js index 50dc484a68c..f896860b526 100644 --- a/packages/web-app-skeleton/src/app.js +++ b/packages/web-app-skeleton/src/app.js @@ -9,20 +9,12 @@ const appInfo = { } const injectExtensions = async api => { - // the promise is just there to deomo lazy loading of extensions + // the promise is just there to showcase lazy loading of extensions await new Promise(resolve => setTimeout(resolve, 2000)) - console.log('#############################################################################') - console.log('# FROM THE APP (SKELETON)') - console.log('# a maybe long running task like wopi started') - console.log('#############################################################################') - await new Promise(resolve => setTimeout(resolve, 10000)) - console.log('#############################################################################') - console.log('# FROM THE APP (SKELETON)') - console.log('# the server answered and we have all information to register a extension') - console.log('#############################################################################') - api.registerExtension(appInfo.id, { + api.announceExtension({ extension: 'txt', + isFileEditor: true, newFileMenu: { menuTitle($gettext) { return $gettext('Extension from skeleton') @@ -36,10 +28,6 @@ const injectExtensions = async api => { 'files-public-list' ] }) - console.log('#############################################################################') - console.log('# FROM THE APP (SKELETON)') - console.log('# new extension is now registered, ...') - console.log('#############################################################################') } export default { @@ -62,7 +50,7 @@ export default { } } ], - async mounted({ api }) { + async mounted(api) { await injectExtensions(api) } } diff --git a/packages/web-container/index.html.ejs b/packages/web-container/index.html.ejs index a4d14d28875..05bab1859ee 100644 --- a/packages/web-container/index.html.ejs +++ b/packages/web-container/index.html.ejs @@ -37,7 +37,7 @@ }) requirejs(['web-runtime'], function (runtime) { - runtime.exec() + runtime.bootstrap('config.json').then(runtime.renderSuccess).catch(runtime.renderFailure) }) diff --git a/packages/web-runtime/src/container/api.ts b/packages/web-runtime/src/container/api.ts new file mode 100644 index 00000000000..0392a41b7a4 --- /dev/null +++ b/packages/web-runtime/src/container/api.ts @@ -0,0 +1,266 @@ +import VueRouter, { RouteConfig } from 'vue-router' +import clone from 'lodash-es/clone' +import { + ClassicApplicationScript, + RuntimeApi, + ApplicationNavigationItem, + ApplicationQuickActions +} from './types' +import { ApiError } from './error' +import { get, isEqual, isObject, isArray } from 'lodash-es' +import { Store } from 'vuex' +import Vue, { Component } from 'vue' +import { Wormhole } from 'portal-vue' + +/** + * inject application specific routes into runtime + * + * @param applicationId + * @param router + * @param routes + */ +const announceRoutes = (applicationId: string, router: VueRouter, routes: RouteConfig[]): void => { + if (!isArray(routes)) { + throw new ApiError("routes can't be blank") + } + + const applicationRoutes = routes.map(applicationRoute => { + if (!isObject(applicationRoute)) { + throw new ApiError("route can't be blank", applicationRoute) + } + + const route = clone(applicationRoute) + route.name = applicationId === route.name ? route.name : `${applicationId}-${route.name}` + route.path = `/${encodeURI(applicationId)}${route.path}` + + if (route.children) { + route.children = route.children.map(childRoute => { + if (!isObject(applicationRoute)) { + throw new ApiError("route children can't be blank", applicationRoute, childRoute) + } + + const route = clone(childRoute) + route.name = `${applicationId}-${childRoute.name}` + return route + }) + } + + return route + }) + + router.addRoutes(applicationRoutes) +} + +/** + * inject application specific navigation items into runtime + * + * @param applicationId + * @param store + * @param navigationItems + */ +const announceNavigationItems = ( + applicationId: string, + store: Store, + navigationItems: ClassicApplicationScript['navItems'] +): void => { + if (!isObject(navigationItems)) { + throw new ApiError("navigationItems can't be blank") + } + + store.commit('SET_NAV_ITEMS_FROM_CONFIG', { + extension: applicationId, + navItems: navigationItems + }) +} + +/** + * inject application specific extension into runtime + * + * @param applicationId + * @param store + * @param extension + */ +const announceExtension = ( + applicationId: string, + store: Store, + extension: { [key: string]: unknown } +): void => { + store.commit('REGISTER_EXTENSION', { app: applicationId, extension }) +} + +/** + * inject application specific translations into runtime + * + * @param translations + * @param appTranslations + * @param supportedLanguages + */ +const announceTranslations = ( + supportedLanguages: { [key: string]: string }, + translations: unknown, + appTranslations: ClassicApplicationScript['translations'] +): void => { + if (!isObject(translations)) { + throw new ApiError("translations can't be blank") + } + + Object.keys(supportedLanguages).forEach(lang => { + if (translations[lang] && appTranslations[lang]) { + Object.assign(translations[lang], appTranslations[lang]) + } + }) +} + +/** + * inject application specific quickActions into runtime + * + * @param store + * @param quickActions + */ +const announceQuickActions = ( + store: Store, + quickActions: ClassicApplicationScript['quickActions'] +): void => { + if (!isObject(quickActions)) { + throw new ApiError("quickActions can't be blank") + } + + store.commit('ADD_QUICK_ACTIONS', quickActions) +} + +/** + * inject application specific store into runtime + * + * @param applicationName + * @param store + * @param applicationStore + */ +const announceStore = ( + applicationName: string, + store: Store, + applicationStore: unknown +): void => { + const obtainedStore: Store = get(applicationStore, 'default', applicationStore) + + if (!isObject(obtainedStore)) { + throw new ApiError("store can't be blank") + } + + store.registerModule(applicationName, obtainedStore) +} + +/** + * open a wormhole portal, this wraps vue-portal + * + * @param instance + * @param applicationId + * @param toApp + * @param toPortal + * @param order + * @param components + */ +const openPortal = ( + applicationId: string, + instance: typeof Vue.prototype, + toApp: string, + toPortal: string, + order: number, + components: Component[] +): void => { + Wormhole.open({ + to: ['app', toApp, toPortal].filter(Boolean).join('.'), + from: ['app', applicationId, toPortal, order].filter(Boolean).join('.'), + order: order, + passengers: components.map(instance.$createElement) + }) +} + +/** + * expose store to the application + * + * @deprecated use with caution + * + * @param store + */ +const requestStore = (store: Store): Store => { + if (isEqual(process.env.NODE_ENV, 'development')) { + console.warn('requestStore // store api is deprecated, use with caution') + } + + return store +} + +/** + * expose router to the application + * + * @deprecated use with caution + * + * @param router + */ +const requestRouter = (router: VueRouter): VueRouter => { + if (isEqual(process.env.NODE_ENV, 'development')) { + console.warn('requestRouter // router api is deprecated, use with caution') + } + + return router +} + +/** + * exposed runtime api, this wraps all available api actions in a closure and provides application + * specific data to the implementations. + * + * each application get its own provisioned api! + * + * @param applicationName + * @param applicationId + * @param store + * @param router + * @param translations + * @param supportedLanguages + */ +export const buildRuntimeApi = ({ + applicationName, + applicationId, + store, + router, + translations, + supportedLanguages +}: { + applicationName: string + applicationId: string + store: Store + translations: unknown + router: VueRouter + supportedLanguages: { [key: string]: string } +}): RuntimeApi => { + if (!applicationName) { + throw new ApiError("applicationName can't be blank") + } + + if (!applicationId) { + throw new ApiError("applicationId can't be blank") + } + + return { + announceRoutes: (routes: RouteConfig[]): void => announceRoutes(applicationId, router, routes), + announceNavigationItems: (navigationItems: ApplicationNavigationItem[]): void => + announceNavigationItems(applicationId, store, navigationItems), + announceTranslations: (appTranslations: unknown): void => + announceTranslations(supportedLanguages, translations, appTranslations), + announceQuickActions: (quickActions: ApplicationQuickActions): void => + announceQuickActions(store, quickActions), + announceStore: (applicationStore: Store): void => + announceStore(applicationName, store, applicationStore), + announceExtension: (extension: { [key: string]: unknown }): void => + announceExtension(applicationId, store, extension), + requestStore: (): Store => requestStore(store), + requestRouter: (): VueRouter => requestRouter(router), + openPortal: ( + instance: typeof Vue.prototype, + toApp: string, + toPortal: string, + order: number, + components: Component[] + ): void => openPortal(applicationId, instance, toApp, toPortal, order, components) + } +} diff --git a/packages/web-runtime/src/container/application/classic.ts b/packages/web-runtime/src/container/application/classic.ts new file mode 100644 index 00000000000..e1179312218 --- /dev/null +++ b/packages/web-runtime/src/container/application/classic.ts @@ -0,0 +1,110 @@ +import { ClassicApplicationScript, RuntimeApi } from '../types' +import { buildRuntimeApi } from '../api' +import Vue from 'vue' +import { isFunction, isObject } from 'lodash-es' +import { NextApplication } from './next' +import { Store } from 'vuex' +import VueRouter from 'vue-router' +import { RuntimeError } from '../error' + +/** + * this wraps a classic application structure into a next application format. + * it is fully backward compatible and will stay around as a fallback. + */ +class ClassicApplication extends NextApplication { + private readonly applicationScript: ClassicApplicationScript + + constructor(runtimeApi: RuntimeApi, applicationScript: ClassicApplicationScript) { + super(runtimeApi) + this.applicationScript = applicationScript + } + + initialize(): Promise { + const { routes, navItems, translations, quickActions, store } = this.applicationScript + + routes && this.runtimeApi.announceRoutes(routes) + navItems && this.runtimeApi.announceNavigationItems(navItems) + translations && this.runtimeApi.announceTranslations(translations) + quickActions && this.runtimeApi.announceQuickActions(quickActions) + store && this.runtimeApi.announceStore(store) + + return Promise.resolve(undefined) + } + + ready(): Promise { + const { ready: readyHook } = this.applicationScript + this.attachPublicApi(readyHook) + return Promise.resolve(undefined) + } + + mounted(instance: Vue): Promise { + const { mounted: mountedHook } = this.applicationScript + this.attachPublicApi(mountedHook, instance) + return Promise.resolve(undefined) + } + + private attachPublicApi(hook: unknown, instance?: Vue) { + isFunction(hook) && + hook({ + ...(instance && { + portal: { + open: (...args) => this.runtimeApi.openPortal.apply(instance, [instance, ...args]) + } + }), + store: this.runtimeApi.requestStore(), + router: this.runtimeApi.requestRouter(), + announceExtension: this.runtimeApi.announceExtension + }) + } +} + +/** + * + * @param applicationPath + * @param store + * @param router + * @param translations + * @param supportedLanguages + */ +export const convertClassicApplication = async ({ + applicationScript, + store, + router, + translations, + supportedLanguages +}: { + applicationScript: ClassicApplicationScript + store: Store + router: VueRouter + translations: unknown + supportedLanguages: { [key: string]: string } +}): Promise => { + const { appInfo } = applicationScript + + if (!isObject(appInfo)) { + throw new RuntimeError("appInfo can't be blank") + } + + const { id: applicationId, name: applicationName } = appInfo + + if (!applicationId) { + throw new RuntimeError("appInfo.id can't be blank") + } + + if (!applicationName) { + throw new RuntimeError("appInfo.name can't be blank") + } + + const runtimeApi = buildRuntimeApi({ + applicationName, + applicationId, + store, + router, + translations, + supportedLanguages + }) + + await store.dispatch('registerApp', applicationScript.appInfo) + + return new ClassicApplication(runtimeApi, applicationScript) +} diff --git a/packages/web-runtime/src/container/application/index.ts b/packages/web-runtime/src/container/application/index.ts new file mode 100644 index 00000000000..e910e6ed9e3 --- /dev/null +++ b/packages/web-runtime/src/container/application/index.ts @@ -0,0 +1,68 @@ +import { Store } from 'vuex' +import VueRouter from 'vue-router' +import { NextApplication } from './next' +import { convertClassicApplication } from './classic' +import { ClassicApplicationScript } from '../types' +import { RuntimeError } from '../error' +import { applicationStore } from '../store' +import { isObject } from 'lodash-es' +export { NextApplication } from './next' + +/** shim requirejs, trust me it's there... :( */ +const requirejs = (window as any).requirejs + +/** + * sniffs arguments and decides if given manifest is of next or current application style. + * + * @param args + */ +export const buildApplication = async ({ + applicationPath, + store, + router, + translations, + supportedLanguages +}: { + applicationPath: string + store: Store + router: VueRouter + translations: unknown + supportedLanguages: { [key: string]: string } +}): Promise => { + if (applicationStore.has(applicationPath)) { + throw new RuntimeError('application already announced', applicationPath) + } + + const applicationScript: ClassicApplicationScript = await new Promise((resolve, reject) => + requirejs( + [applicationPath], + app => resolve(app), + err => reject(err) + ) + ).catch(() => { + throw new RuntimeError('cannot load application', applicationPath) + }) + + let application: NextApplication + + try { + /** add valuable sniffer to detect next applications, then implement next application factory */ + if (!isObject(applicationScript.appInfo)) { + throw new RuntimeError('next applications not implemented yet, stay tuned') + } else { + application = await convertClassicApplication({ + applicationScript, + store, + router, + translations, + supportedLanguages + }).catch() + } + } catch (err) { + throw new RuntimeError('cannot create application', err.message, applicationPath) + } + + applicationStore.set(applicationPath, application) + + return application +} diff --git a/packages/web-runtime/src/container/application/next.ts b/packages/web-runtime/src/container/application/next.ts new file mode 100644 index 00000000000..98b50ea05d7 --- /dev/null +++ b/packages/web-runtime/src/container/application/next.ts @@ -0,0 +1,16 @@ +import Vue from 'vue' +import { RuntimeApi } from '../types' + +export abstract class NextApplication { + protected readonly runtimeApi: RuntimeApi + + protected constructor(runtimeApi: RuntimeApi) { + this.runtimeApi = runtimeApi + } + + abstract initialize(): Promise + + abstract ready(): Promise + + abstract mounted(instance: Vue): Promise +} diff --git a/packages/web-runtime/src/container/bootstrap.ts b/packages/web-runtime/src/container/bootstrap.ts new file mode 100644 index 00000000000..4d081ef1e6f --- /dev/null +++ b/packages/web-runtime/src/container/bootstrap.ts @@ -0,0 +1,212 @@ +import { registerClient } from '../services/clientRegistration' +import { RuntimeConfiguration } from './types' +import { buildApplication } from './application' +import { Store } from 'vuex' +import VueRouter from 'vue-router' +import { VueConstructor } from 'vue' +import { loadTheme } from '../helpers/theme' +import OwnCloud from 'owncloud-sdk' +import { sync as routerSync } from 'vuex-router-sync' +import getTextPlugin from 'vue-gettext' +import set from 'lodash-es/set' + +/** + * fetch runtime configuration, this step is optional, all later steps can use a static + * configuration object as well + * + * @remarks + * does not check if the configuration is valid, for now be careful until a schema is declared + * + * @param path - path to main configuration + */ +export const requestConfiguration = async (path: string): Promise => { + const request = await fetch(path) + if (request.status !== 200) { + throw new Error(`config could not be loaded. HTTP status-code ${request.status}`) + } + + return await request.json().catch(error => { + throw new Error(`config could not be parsed. ${error}`) + }) +} + +/** + * announce auth client to the runtime, currently only openIdConnect is supported here + * + * @remarks + * if runtimeConfiguration does not ship any options for openIdConnect this step get skipped + * + * @param runtimeConfiguration + */ +export const announceClient = async (runtimeConfiguration: RuntimeConfiguration): Promise => { + const { openIdConnect = {} } = runtimeConfiguration + + if (!openIdConnect.dynamic) { + return + } + + const { client_id: clientId, client_secret: clientSecret } = await registerClient(openIdConnect) + openIdConnect.client_id = clientId + openIdConnect.client_secret = clientSecret +} + +/** + * announce applications to the runtime, it takes care that all requirements are fulfilled and then: + * - bulk build all applications + * - bulk register all applications, no other application is guaranteed to be registered here, don't request one + * - bulk activate all applications, all applications are registered, it's safe to request a application api here + * + * @param runtimeConfiguration + * @param store + * @param router + * @param translations + * @param supportedLanguages + */ +export const announceApplications = async ({ + runtimeConfiguration, + store, + router, + translations, + supportedLanguages +}: { + runtimeConfiguration: RuntimeConfiguration + store: Store + router: VueRouter + translations: unknown + supportedLanguages: { [key: string]: string } +}): Promise => { + const { apps: applications = [], external_apps: externalApplications = [] } = runtimeConfiguration + + const allApplications = [ + ...applications.map(application => `web-app-${application}`), + ...externalApplications.map(applications => applications.path) + ].filter(Boolean) + + const applicationImplementations = await Promise.all( + allApplications.map(applicationPath => + buildApplication({ applicationPath, store, supportedLanguages, router, translations }) + ) + ) + await Promise.all(applicationImplementations.map(application => application.initialize())) + await Promise.all(applicationImplementations.map(application => application.ready())) +} + +/** + * announce runtime theme to the runtime, this also takes care that the store + * and designSystem has all needed information to render the customized ui + * + * @param themeLocation + * @param store + * @param vue + * @param designSystem + */ +export const announceTheme = async ({ + themeLocation, + store, + vue, + designSystem +}: { + themeLocation?: string + store: Store + vue: VueConstructor + designSystem: any +}): Promise => { + const { theme } = await loadTheme(themeLocation) + await store.dispatch('loadTheme', { theme: theme.default }) + + vue.use(designSystem, { + tokens: store.getters.theme.designTokens + }) +} + +/** + * announce runtime translations by injecting them into the getTextPlugin + * + * @param vue + * @param supportedLanguages + * @param translations + */ +export const announceTranslations = ({ + vue, + supportedLanguages, + translations +}: { + vue: VueConstructor + supportedLanguages: unknown + translations: unknown +}): void => { + vue.use(getTextPlugin, { + availableLanguages: supportedLanguages, + defaultLanguage: navigator.language.substring(0, 2), + translations, + silent: true + }) +} + +/** + * announce owncloud SDK and inject it into vue + * + * @param vue + * @param runtimeConfiguration + */ +export const announceOwncloudSDK = ({ + vue, + runtimeConfiguration +}: { + vue: VueConstructor + runtimeConfiguration: RuntimeConfiguration +}): void => { + const sdk = new OwnCloud() + sdk.init({ baseUrl: runtimeConfiguration.server || window.location.origin }) + vue.prototype.$client = sdk +} + +/** + * announce runtime defaults, this is usual the last needed announcement before rendering the actual ui + * + * @param vue + * @param runtimeConfiguration + * @param store + * @param router + */ +export const announceDefaults = async ({ + vue, + runtimeConfiguration, + store, + router +}: { + vue: VueConstructor + runtimeConfiguration: RuntimeConfiguration + store: Store + router: VueRouter +}): Promise => { + // set home route + const appIds = store.getters.appIds + let defaultExtensionId = store.getters.configuration.options.defaultExtension + if (!defaultExtensionId || appIds.indexOf(defaultExtensionId) < 0) { + defaultExtensionId = appIds[0] + } + + router.addRoutes([ + { path: '/', redirect: () => store.getters.getNavItemsByExtension(defaultExtensionId)[0].route } + ]) + + routerSync(store, router) + + // inject custom config into vuex + await store.dispatch('loadConfig', runtimeConfiguration) + + /** + * TODO: Find a different way to access store from withit JS files + * potential options are: + * - use the api which already is in place but deprecated + * - use a global object + * + * at the moment it is not clear if this api should be exposed or not. + * we need to decide if we extend the api more or just expose the store and de deprecate + * the apis for retrieving it. + */ + set(vue, '$store', store) + + return Promise.resolve(undefined) +} diff --git a/packages/web-runtime/src/container/error.ts b/packages/web-runtime/src/container/error.ts new file mode 100644 index 00000000000..9e9571a3690 --- /dev/null +++ b/packages/web-runtime/src/container/error.ts @@ -0,0 +1,20 @@ +/** + * generic error implementation which captures the stackTrace and has custom naming + */ +export class RuntimeError extends Error { + name = 'RuntimeError' + constructor(message?: unknown, ...additional: unknown[]) { + super([message, ...additional].filter(Boolean).join(', ')) + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor) + } + } +} + +/** + * error dedicated to api type errors + */ +export class ApiError extends RuntimeError { + name = 'ApiError' +} diff --git a/packages/web-runtime/src/container/index.ts b/packages/web-runtime/src/container/index.ts new file mode 100644 index 00000000000..f73e106d671 --- /dev/null +++ b/packages/web-runtime/src/container/index.ts @@ -0,0 +1,2 @@ +export * from './bootstrap' +export * from './store' diff --git a/packages/web-runtime/src/container/store.ts b/packages/web-runtime/src/container/store.ts new file mode 100644 index 00000000000..210c7da261d --- /dev/null +++ b/packages/web-runtime/src/container/store.ts @@ -0,0 +1,7 @@ +import { NextApplication } from './application' + +/** + * main purpose of this is to keep a reference to all announced applications. + * this is used for example in the mounted hook outside container module + */ +export const applicationStore = new Map() diff --git a/packages/web-runtime/src/container/types.ts b/packages/web-runtime/src/container/types.ts new file mode 100644 index 00000000000..54ba576ba07 --- /dev/null +++ b/packages/web-runtime/src/container/types.ts @@ -0,0 +1,71 @@ +import { Store } from 'vuex' +import VueRouter, { RouteConfig } from 'vue-router' +import Vue, { Component } from 'vue' + +/** shim configuration for now, should be typed in a later step */ +export type RuntimeConfiguration = any + +/** application information describes needed information from a application */ +export interface ApplicationInformation { + id?: string + name?: string + icon?: string + isFileEditor?: boolean + extensions?: any[] + fileSideBars?: any[] +} + +/** + * application navigation item describes a application navigation item. + * a example use for this is the registration of application specific navigation items in the runtime + */ +export interface ApplicationNavigationItem { + name?: string + iconMaterial?: string + route?: { + name?: string + path?: string + } +} + +/** application quick action describes a application action that is used in the runtime */ +export interface ApplicationQuickActions { + [key: string]: { + id?: string + label?: string + icon?: string + handler?: () => Promise + displayed?: boolean + } +} + +/** reflects classic application script structure */ +export interface ClassicApplicationScript { + appInfo?: ApplicationInformation + store?: Store + routes?: RouteConfig[] + navItems?: ApplicationNavigationItem[] + quickActions?: ApplicationQuickActions + translations?: unknown + ready?: () => void + mounted?: () => void +} + +/** definition of public available runtime api */ +export interface RuntimeApi { + announceRoutes: (routes: RouteConfig[]) => void + announceNavigationItems: (navigationItems: ApplicationNavigationItem[]) => void + announceTranslations: (appTranslations: unknown) => void + announceQuickActions: (quickActions: ApplicationQuickActions) => void + announceStore: (applicationStore: Store) => void + announceExtension: (extension: { [key: string]: unknown }) => void + requestStore: () => Store + requestRouter: () => VueRouter + openPortal: ( + instance: typeof Vue.prototype, + toApp: string, + toPortal: string, + order: number, + components: Component[] + ) => void +} diff --git a/packages/web-runtime/src/defaults/index.ts b/packages/web-runtime/src/defaults/index.ts new file mode 100644 index 00000000000..cd785b1a2a9 --- /dev/null +++ b/packages/web-runtime/src/defaults/index.ts @@ -0,0 +1,24 @@ +import 'owncloud-design-system/dist/system/system.css' +import merge from 'lodash-es/merge' +import App from '../App.vue' +import missingOrInvalidConfigPage from '../pages/missingOrInvalidConfig.vue' +import Store from '../store' +import { coreTranslations, odsTranslations } from './json' +import { createStore } from 'vuex-extensions' +import Vuex from 'vuex' +export { default as Vue } from './vue' +export { default as DesignSystem } from 'owncloud-design-system' +export { default as Router } from '../router' + +export const store = createStore(Vuex.Store, { ...Store }) +export const pages = { success: App, failure: missingOrInvalidConfigPage } +export const translations = merge({}, coreTranslations, odsTranslations) +export const supportedLanguages = { + en: 'English', + de: 'Deutsch', + es: 'Español', + cs: 'Czech', + fr: 'Français', + it: 'Italiano', + gl: 'Galego' +} diff --git a/packages/web-runtime/src/defaults/json.js b/packages/web-runtime/src/defaults/json.js new file mode 100644 index 00000000000..ea04496c14b --- /dev/null +++ b/packages/web-runtime/src/defaults/json.js @@ -0,0 +1,9 @@ +/** + * typescript breaks in current setup when importing json, investigate and fix + * as workaround export it from a js file + */ +import CoreTranslations from '../../l10n/translations.json' +import OdsTranslations from 'owncloud-design-system/dist/system/translations.json' + +export const coreTranslations = CoreTranslations +export const odsTranslations = OdsTranslations diff --git a/packages/web-runtime/src/defaults/vue.js b/packages/web-runtime/src/defaults/vue.js new file mode 100644 index 00000000000..55fc278ae5e --- /dev/null +++ b/packages/web-runtime/src/defaults/vue.js @@ -0,0 +1,55 @@ +import 'vue-resize/dist/vue-resize.css' +import Vue from 'vue' +import MediaSource from '../plugins/mediaSource.js' +import WebPlugin from '../plugins/web' +import ChunkedUpload from '../plugins/upload' +import Avatar from '../components/Avatar.vue' +import focusMixin from '../mixins/focusMixin' +import lifecycleMixin from '../mixins/lifecycleMixin' +import ClickOutsideDirective from '../directives/clickOutside' +import VueEvents from 'vue-events' +import VueScrollTo from 'vue-scrollto' +import VueResize from 'vue-resize' +import VueMeta from 'vue-meta' +import Vue2TouchEvents from 'vue2-touch-events' +import PortalVue from 'portal-vue' +import AsyncComputed from 'vue-async-computed' +import { Drag, Drop } from 'vue-drag-drop' +import VueAxe from 'vue-axe' +import VueRouter from 'vue-router' +import Vuex from 'vuex' + +Vue.use(Vuex) +Vue.use(VueRouter) +Vue.use(VueEvents) +Vue.use(VueScrollTo) +Vue.use(MediaSource) +Vue.use(WebPlugin) +Vue.use(VueResize) +Vue.use(VueMeta, { + refreshOnceOnNavigation: true +}) +Vue.use(ChunkedUpload) +Vue.use(Vue2TouchEvents) +Vue.use(PortalVue) +Vue.use(AsyncComputed) + +if (process.env.NODE_ENV === 'development') { + Vue.use(VueAxe, { + allowConsoleClears: false + }) +} + +Vue.component('drag', Drag) +Vue.component('drop', Drop) +Vue.component('avatar-image', Avatar) + +Vue.mixin(focusMixin) +Vue.mixin(lifecycleMixin) + +Vue.directive('click-outside', ClickOutsideDirective) + +// externalize Vue - this is not the Vue instance but the class +window.Vue = Vue + +export default Vue diff --git a/packages/web-runtime/src/helpers/theme.js b/packages/web-runtime/src/helpers/theme.js index 67e3a4c6bad..20bc0bde52e 100644 --- a/packages/web-runtime/src/helpers/theme.js +++ b/packages/web-runtime/src/helpers/theme.js @@ -1,10 +1,13 @@ +import { isEqual } from 'lodash-es' import defaultTheme from 'web-runtime/themes/owncloud/theme.json' export const loadTheme = async (location = '') => { const defaults = { theme: defaultTheme } if (location.split('.').pop() !== 'json') { - console.error(`Theme '${location}' does not specify a json file, using default theme.`) + if (isEqual(process.env.NODE_ENV, 'development')) { + console.info(`Theme '${location}' does not specify a json file, using default theme.`) + } return defaults } diff --git a/packages/web-runtime/src/index.js b/packages/web-runtime/src/index.js deleted file mode 100644 index 9ff3530d4de..00000000000 --- a/packages/web-runtime/src/index.js +++ /dev/null @@ -1,317 +0,0 @@ -// --- Styles --- -import 'vue-resize/dist/vue-resize.css' - -// --- Libraries and Plugins --- -import Vue from './vue' -import Vuex from 'vuex' -import PortalVue, { Wormhole } from 'portal-vue' -import { createStore } from 'vuex-extensions' -import AsyncComputed from 'vue-async-computed' - -// --- Components --- -import App from './App.vue' -import missingOrInvalidConfigPage from './pages/missingOrInvalidConfig.vue' - -// --- Adding global libraries --- -import OwnCloud from 'owncloud-sdk' - -import { sync } from 'vuex-router-sync' -import Store from './store' -import router from './router' - -// --- Plugins ---- -import VueResize from 'vue-resize' -import VueEvents from 'vue-events' -import VueRouter from 'vue-router' -import VueScrollTo from 'vue-scrollto' -import VueMeta from 'vue-meta' -import Vue2TouchEvents from 'vue2-touch-events' -import VueAxe from 'vue-axe' - -// --- Mixins ---- -import focusMixin from './mixins/focusMixin' -import lifecycleMixin from './mixins/lifecycleMixin' - -// --- Directive --- -import ClickOutsideDirective from './directives/clickOutside' - -// --- Gettext ---- -import GetTextPlugin from 'vue-gettext' -import coreTranslations from '../l10n/translations.json' -import odsTranslations from 'owncloud-design-system/dist/system/translations.json' - -// --- Image source ---- -import MediaSource from './plugins/mediaSource.js' -import WebPlugin from './plugins/web' -import ChunkedUpload from './plugins/upload' - -// --- Drag Drop ---- -import { Drag, Drop } from 'vue-drag-drop' - -// Import the Design System -import DesignSystem from 'owncloud-design-system' -import 'owncloud-design-system/dist/system/system.css' - -import Avatar from './components/Avatar.vue' - -import { registerClient } from './services/clientRegistration' -import { Api } from './services/api' - -import { loadConfig } from './helpers/config' -import { loadTheme } from './helpers/theme' - -import merge from 'lodash-es/merge' - -Vue.prototype.$client = new OwnCloud() - -Vue.use(VueEvents) -Vue.use(Vuex) -Vue.use(VueRouter) -Vue.use(VueScrollTo) -Vue.use(MediaSource) -Vue.use(WebPlugin) -Vue.use(VueResize) -Vue.use(VueMeta, { - // optional pluginOptions - refreshOnceOnNavigation: true -}) -Vue.use(ChunkedUpload) -Vue.use(Vue2TouchEvents) -Vue.use(PortalVue) -Vue.use(AsyncComputed) - -Vue.component('drag', Drag) -Vue.component('drop', Drop) -Vue.component('avatar-image', Avatar) - -Vue.mixin(focusMixin) -Vue.mixin(lifecycleMixin) - -Vue.directive('click-outside', ClickOutsideDirective) - -// --- DEV only ---- -if (process.env.NODE_ENV === 'development') { - Vue.use(VueAxe, { - allowConsoleClears: false - }) -} - -/* --- Store --- */ -const store = createStore(Vuex.Store, { ...Store }) - -// --- Router ---- - -let config -const supportedLanguages = { - en: 'English', - de: 'Deutsch', - es: 'Español', - cs: 'Czech', - fr: 'Français', - it: 'Italiano', - gl: 'Galego' -} - -const translations = merge({}, coreTranslations, odsTranslations) -const appRegistry = [] -const api = new Api(store) - -const loadApp = async path => { - const app = await new Promise((resolve, reject) => - requirejs( - [path], - app => resolve(app), - err => reject(err) - ) - ) - - if (!app.appInfo) { - throw new Error('Skipping without appInfo for app ' + path) - } - - appRegistry.push(app) - - if (app.routes) { - // rewrite relative app routes by prefixing their corresponding appId - app.routes = app.routes.map(route => { - route.name = app.appInfo.id === route.name ? route.name : `${app.appInfo.id}-${route.name}` - route.path = `/${encodeURI(app.appInfo.id)}${route.path}` - - if (route.children) { - route.children = route.children.map(child => { - child.name = `${app.appInfo.id}-${child.name}` - - return child - }) - } - - return route - }) - - router.addRoutes(app.routes) - } - - if (app.navItems) { - store.commit('SET_NAV_ITEMS_FROM_CONFIG', { - extension: app.appInfo.id, - navItems: app.navItems - }) - } - - if (app.translations) { - Object.keys(supportedLanguages).forEach(lang => { - if (translations[lang] && app.translations[lang]) { - Object.assign(translations[lang], app.translations[lang]) - } - }) - } - - if (app.quickActions) { - store.commit('ADD_QUICK_ACTIONS', app.quickActions) - } - - if (app.store) { - store.registerModule(app.appInfo.name, app.store.default || app.store) - } - - await store.dispatch('registerApp', app.appInfo) -} - -const finalizeInit = async () => { - // set home route - const appIds = store.getters.appIds - let defaultExtensionId = store.getters.configuration.options.defaultExtension - if (!defaultExtensionId || appIds.indexOf(defaultExtensionId) < 0) { - defaultExtensionId = appIds[0] - } - - router.addRoutes([ - { - path: '/', - redirect: () => store.getters.getNavItemsByExtension(defaultExtensionId)[0].route - } - ]) - - // sync router into store - sync(store, router) - - // inject custom config into vuex - await store.dispatch('loadConfig', config) - - // basic init of ownCloud sdk - Vue.prototype.$client.init({ baseUrl: config.server || window.location.origin }) - - // inject custom theme config into vuex - await fetchTheme(config.theme) - - loadTranslations() - - // TODO: Find a different way to access store from withit JS files - Vue.$store = store - - const portal = (instance, fromApp) => { - const open = (toApp, toPortal, order, components) => - Wormhole.open({ - to: ['app', toApp, toPortal].filter(Boolean).join('.'), - from: ['app', fromApp, toPortal, order].filter(Boolean).join('.'), - order: order, - passengers: components.map(instance.$createElement) - }) - - return { open } - } - - // eslint-disable-next-line no-new - new Vue({ - el: '#owncloud', - store, - router, - render: h => h(App), - mounted() { - appRegistry.forEach( - app => - app.mounted && - app.mounted({ store, router, runtime: this, portal: portal(this, app.id), api }) - ) - } - }) -} - -const fetchTheme = async location => { - const { theme } = await loadTheme(location) - await store.dispatch('loadTheme', { theme: theme.default }) - - Vue.use(DesignSystem, { - tokens: store.getters.theme.designTokens - }) -} - -const missingOrInvalidConfig = async () => { - loadTranslations() - - // inject custom theme config into vuex - await fetchTheme() - - // eslint-disable-next-line no-new - new Vue({ - el: '#owncloud', - store, - render: h => h(missingOrInvalidConfigPage) - }) -} - -const loadTranslations = () => { - Vue.use(GetTextPlugin, { - availableLanguages: supportedLanguages, - defaultLanguage: navigator.language.substring(0, 2), - translations, - silent: true - }) -} - -export const exec = async () => { - try { - config = await loadConfig() - } catch (err) { - console.error(err) - router.push('missing-config') - missingOrInvalidConfig(err) - return - } - - try { - // if dynamic client registration is necessary - do this here now - if (config.openIdConnect && config.openIdConnect.dynamic) { - const clientData = await registerClient(config.openIdConnect) - config.openIdConnect.client_id = clientData.client_id - config.openIdConnect.client_secret = clientData.client_secret - } - - // Collect internal app paths - const apps = config.apps.map(app => `web-app-${app}`) - - // Collect external app paths - if (config.external_apps) { - apps.push(...config.external_apps.map(app => app.path)) - } - - // requirejs.config({waitSeconds:200}) is not really working ... reason unknown - // we are manipulating requirejs directly - requirejs.s.contexts._.config.waitSeconds = 200 - - // Boot apps - for (const path of apps) { - // please note that we have to go through apps one by one for now, to not break e.g. translations loading (race conditions) - try { - await loadApp(path) - } catch (err) { - console.error('failed to load app ' + path, err) - } - } - - // Finalize loading core and apps - await finalizeInit() - } catch (err) { - console.error(err) - } -} diff --git a/packages/web-runtime/src/index.ts b/packages/web-runtime/src/index.ts new file mode 100644 index 00000000000..3b586fdc9e3 --- /dev/null +++ b/packages/web-runtime/src/index.ts @@ -0,0 +1,58 @@ +import { + DesignSystem as designSystem, + pages, + translations, + supportedLanguages, + store, + Router as router, + Vue +} from './defaults' +import { + requestConfiguration, + announceApplications, + announceClient, + announceTheme, + announceTranslations, + announceOwncloudSDK, + announceDefaults, + applicationStore +} from './container' + +export const bootstrap = async (configurationPath: string): Promise => { + const runtimeConfiguration = await requestConfiguration(configurationPath) + await announceClient(runtimeConfiguration) + await announceApplications({ + runtimeConfiguration, + store, + supportedLanguages, + router, + translations + }) + await announceOwncloudSDK({ vue: Vue, runtimeConfiguration }) + await announceTranslations({ vue: Vue, supportedLanguages, translations }) + await announceTheme({ store, vue: Vue, designSystem }) + await announceDefaults({ vue: Vue, store, router, runtimeConfiguration }) +} + +export const renderSuccess = (): void => { + new Vue({ + el: '#owncloud', + store, + router, + render: h => h(pages.success), + mounted() { + Array.from(applicationStore.values()).map(application => application.mounted(this)) + } + }) +} + +export const renderFailure = async (err: Error): Promise => { + await announceTranslations({ vue: Vue, supportedLanguages, translations }) + await announceTheme({ store, vue: Vue, designSystem }) + console.error(err) + new Vue({ + el: '#owncloud', + store, + render: h => h(pages.failure) + }) +} diff --git a/packages/web-runtime/src/vue.js b/packages/web-runtime/src/vue.js deleted file mode 100644 index 76567524290..00000000000 --- a/packages/web-runtime/src/vue.js +++ /dev/null @@ -1,6 +0,0 @@ -import Vue from 'vue' - -// externalize Vue - this is not the Vue instance but the class -window.Vue = Vue - -export default Vue From a573d4493d9119d6c83a12045ec3a0e5e6336ab1 Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Fri, 3 Sep 2021 15:19:26 +0200 Subject: [PATCH 4/7] use foreach to announce application mounted hook --- packages/web-runtime/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web-runtime/src/index.ts b/packages/web-runtime/src/index.ts index 3b586fdc9e3..51dcbc5df07 100644 --- a/packages/web-runtime/src/index.ts +++ b/packages/web-runtime/src/index.ts @@ -41,7 +41,7 @@ export const renderSuccess = (): void => { router, render: h => h(pages.success), mounted() { - Array.from(applicationStore.values()).map(application => application.mounted(this)) + Array.from(applicationStore.values()).forEach(application => application.mounted(this)) } }) } From 5a9c412e76b3d14f1fd420115ee06d7b8867cde5 Mon Sep 17 00:00:00 2001 From: Benedikt Kulmann Date: Wed, 15 Sep 2021 16:13:12 +0200 Subject: [PATCH 5/7] Make quick action usage more clear --- packages/web-runtime/src/container/types.ts | 39 ++++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/packages/web-runtime/src/container/types.ts b/packages/web-runtime/src/container/types.ts index 54ba576ba07..a7c09dd48e1 100644 --- a/packages/web-runtime/src/container/types.ts +++ b/packages/web-runtime/src/container/types.ts @@ -5,7 +5,7 @@ import Vue, { Component } from 'vue' /** shim configuration for now, should be typed in a later step */ export type RuntimeConfiguration = any -/** application information describes needed information from a application */ +/** ApplicationInformation describes required information of an application */ export interface ApplicationInformation { id?: string name?: string @@ -16,8 +16,8 @@ export interface ApplicationInformation { } /** - * application navigation item describes a application navigation item. - * a example use for this is the registration of application specific navigation items in the runtime + * ApplicationNavigationItem describes an application navigation item. + * an example use for this is the registration of application specific navigation items in the runtime */ export interface ApplicationNavigationItem { name?: string @@ -28,18 +28,31 @@ export interface ApplicationNavigationItem { } } -/** application quick action describes a application action that is used in the runtime */ +/** + * ApplicationQuickAction describes an application action that is used in the runtime. + * + * @deprecated In the future quick actions should be registered just like any other extension. Fine + * to use this interface for now, but it will be changed in the near future. + */ +export interface ApplicationQuickAction { + id?: string + label?: string + icon?: string + handler?: () => Promise + displayed?: boolean +} + +/** + * ApplicationQuickActions describes a map of application actions that are used in the runtime + * + * @deprecated In the future quick actions should be registered just like any other extension. Fine + * to use this interface for now, but it will be changed in the near future. + */ export interface ApplicationQuickActions { - [key: string]: { - id?: string - label?: string - icon?: string - handler?: () => Promise - displayed?: boolean - } + [key: string]: ApplicationQuickAction } -/** reflects classic application script structure */ +/** ClassicApplicationScript reflects classic application script structure */ export interface ClassicApplicationScript { appInfo?: ApplicationInformation store?: Store @@ -51,7 +64,7 @@ export interface ClassicApplicationScript { mounted?: () => void } -/** definition of public available runtime api */ +/** RuntimeApi defines the publicly available runtime api */ export interface RuntimeApi { announceRoutes: (routes: RouteConfig[]) => void announceNavigationItems: (navigationItems: ApplicationNavigationItem[]) => void From 90504ccbad255e6d5b1b1ae5646dda7efde727c1 Mon Sep 17 00:00:00 2001 From: Benedikt Kulmann Date: Wed, 15 Sep 2021 16:28:37 +0200 Subject: [PATCH 6/7] Remove ClassicApplicationScript references from api --- packages/web-runtime/src/container/api.ts | 12 ++++++------ packages/web-runtime/src/container/bootstrap.ts | 8 +++++++- packages/web-runtime/src/container/types.ts | 13 +++++++++++-- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/web-runtime/src/container/api.ts b/packages/web-runtime/src/container/api.ts index 0392a41b7a4..6dff85d97a8 100644 --- a/packages/web-runtime/src/container/api.ts +++ b/packages/web-runtime/src/container/api.ts @@ -1,10 +1,10 @@ import VueRouter, { RouteConfig } from 'vue-router' import clone from 'lodash-es/clone' import { - ClassicApplicationScript, RuntimeApi, ApplicationNavigationItem, - ApplicationQuickActions + ApplicationQuickActions, + ApplicationTranslations } from './types' import { ApiError } from './error' import { get, isEqual, isObject, isArray } from 'lodash-es' @@ -61,7 +61,7 @@ const announceRoutes = (applicationId: string, router: VueRouter, routes: RouteC const announceNavigationItems = ( applicationId: string, store: Store, - navigationItems: ClassicApplicationScript['navItems'] + navigationItems: ApplicationNavigationItem[] ): void => { if (!isObject(navigationItems)) { throw new ApiError("navigationItems can't be blank") @@ -98,7 +98,7 @@ const announceExtension = ( const announceTranslations = ( supportedLanguages: { [key: string]: string }, translations: unknown, - appTranslations: ClassicApplicationScript['translations'] + appTranslations: ApplicationTranslations ): void => { if (!isObject(translations)) { throw new ApiError("translations can't be blank") @@ -119,7 +119,7 @@ const announceTranslations = ( */ const announceQuickActions = ( store: Store, - quickActions: ClassicApplicationScript['quickActions'] + quickActions: ApplicationQuickActions ): void => { if (!isObject(quickActions)) { throw new ApiError("quickActions can't be blank") @@ -245,7 +245,7 @@ export const buildRuntimeApi = ({ announceRoutes: (routes: RouteConfig[]): void => announceRoutes(applicationId, router, routes), announceNavigationItems: (navigationItems: ApplicationNavigationItem[]): void => announceNavigationItems(applicationId, store, navigationItems), - announceTranslations: (appTranslations: unknown): void => + announceTranslations: (appTranslations: ApplicationTranslations): void => announceTranslations(supportedLanguages, translations, appTranslations), announceQuickActions: (quickActions: ApplicationQuickActions): void => announceQuickActions(store, quickActions), diff --git a/packages/web-runtime/src/container/bootstrap.ts b/packages/web-runtime/src/container/bootstrap.ts index 4d081ef1e6f..bbaaff5dc21 100644 --- a/packages/web-runtime/src/container/bootstrap.ts +++ b/packages/web-runtime/src/container/bootstrap.ts @@ -84,7 +84,13 @@ export const announceApplications = async ({ const applicationImplementations = await Promise.all( allApplications.map(applicationPath => - buildApplication({ applicationPath, store, supportedLanguages, router, translations }) + buildApplication({ + applicationPath, + store, + supportedLanguages, + router, + translations + }) ) ) await Promise.all(applicationImplementations.map(application => application.initialize())) diff --git a/packages/web-runtime/src/container/types.ts b/packages/web-runtime/src/container/types.ts index a7c09dd48e1..8f3bc7cccdb 100644 --- a/packages/web-runtime/src/container/types.ts +++ b/packages/web-runtime/src/container/types.ts @@ -52,6 +52,15 @@ export interface ApplicationQuickActions { [key: string]: ApplicationQuickAction } +/** + * ApplicationTranslations is a map of language keys to translations + */ +export interface ApplicationTranslations { + [lang: string]: { + key: string + } +} + /** ClassicApplicationScript reflects classic application script structure */ export interface ClassicApplicationScript { appInfo?: ApplicationInformation @@ -59,7 +68,7 @@ export interface ClassicApplicationScript { routes?: RouteConfig[] navItems?: ApplicationNavigationItem[] quickActions?: ApplicationQuickActions - translations?: unknown + translations?: ApplicationTranslations ready?: () => void mounted?: () => void } @@ -68,7 +77,7 @@ export interface ClassicApplicationScript { export interface RuntimeApi { announceRoutes: (routes: RouteConfig[]) => void announceNavigationItems: (navigationItems: ApplicationNavigationItem[]) => void - announceTranslations: (appTranslations: unknown) => void + announceTranslations: (appTranslations: ApplicationTranslations) => void announceQuickActions: (quickActions: ApplicationQuickActions) => void announceStore: (applicationStore: Store) => void announceExtension: (extension: { [key: string]: unknown }) => void From a445a3dd206b2837f8a374fa3d71a5636011ca3a Mon Sep 17 00:00:00 2001 From: Benedikt Kulmann Date: Thu, 16 Sep 2021 11:36:29 +0200 Subject: [PATCH 7/7] Fix code smells --- packages/web-runtime/src/container/api.ts | 6 +++--- packages/web-runtime/src/container/bootstrap.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/web-runtime/src/container/api.ts b/packages/web-runtime/src/container/api.ts index 6dff85d97a8..f3d1ed26f3e 100644 --- a/packages/web-runtime/src/container/api.ts +++ b/packages/web-runtime/src/container/api.ts @@ -39,9 +39,9 @@ const announceRoutes = (applicationId: string, router: VueRouter, routes: RouteC throw new ApiError("route children can't be blank", applicationRoute, childRoute) } - const route = clone(childRoute) - route.name = `${applicationId}-${childRoute.name}` - return route + const r = clone(childRoute) + r.name = `${applicationId}-${childRoute.name}` + return r }) } diff --git a/packages/web-runtime/src/container/bootstrap.ts b/packages/web-runtime/src/container/bootstrap.ts index bbaaff5dc21..0fe1f593da4 100644 --- a/packages/web-runtime/src/container/bootstrap.ts +++ b/packages/web-runtime/src/container/bootstrap.ts @@ -25,7 +25,7 @@ export const requestConfiguration = async (path: string): Promise { + return request.json().catch(error => { throw new Error(`config could not be parsed. ${error}`) }) } @@ -79,7 +79,7 @@ export const announceApplications = async ({ const allApplications = [ ...applications.map(application => `web-app-${application}`), - ...externalApplications.map(applications => applications.path) + ...externalApplications.map(application => application.path) ].filter(Boolean) const applicationImplementations = await Promise.all(