diff --git a/.gitignore b/.gitignore index e495a5a8..15d6e86c 100644 --- a/.gitignore +++ b/.gitignore @@ -270,4 +270,6 @@ Temp/ **/Assets/**/setup.* **/Assets/**/*.js deps/*.dll -zero.Commerce/ \ No newline at end of file +zero.Commerce/ +zero.Web.UI/package.json +zero.Web.UI/package-lock.json \ No newline at end of file diff --git a/zero.Web.UI/App/app.vue b/zero.Web.UI/App/app.vue index 720743ec..4cb0616f 100644 --- a/zero.Web.UI/App/app.vue +++ b/zero.Web.UI/App/app.vue @@ -19,16 +19,15 @@ import AppLogin from 'zero/pages/login/login.vue' import AppOverlays from 'zero/components/overlays/overlay-holder.vue' import AppNotifications from 'zero/components/notifications/notification-holder.vue' - import Router from 'zero/router.config.js' + //import Router from 'zero/router.config.js' import AuthApi from 'zero/services/auth.js' - import 'zero/vue.config.js' - import 'zero/axios.config.js' - import 'zero/zero.config.js' + import 'zero/config/vue.config.js' + import 'zero/config/axios.config.js' export default { name: 'app', - router: Router, + // router: Router, components: { AppNavigation, AppOverlays, AppLogin, AppNotifications }, @@ -38,6 +37,7 @@ created() { + console.info(this.zero); AuthApi.$on('authenticated', isAuthenticated => { this.isAuthenticated = isAuthenticated; @@ -52,7 +52,7 @@ getClassList() { return { - 'is-preview': this.$route.name === 'preview' + 'is-preview': false //this.$route.name === 'preview' }; } } diff --git a/zero.Web.UI/App/axios.config.js b/zero.Web.UI/App/config/axios.config.js similarity index 100% rename from zero.Web.UI/App/axios.config.js rename to zero.Web.UI/App/config/axios.config.js diff --git a/zero.Web.UI/App/config/router.config.js b/zero.Web.UI/App/config/router.config.js new file mode 100644 index 00000000..f4c00c8d --- /dev/null +++ b/zero.Web.UI/App/config/router.config.js @@ -0,0 +1,75 @@ +import Localization from 'zero/services/localization'; + + +// set meta title before routing +const beforeEach = () => (to, from, next) => +{ + let isGuarded = false; + + // set document title + call next + let callback = () => + { + let title = Localization.localize('@zero.name'); + + if (to.meta.name && to.meta.alias !== zero.alias.sections.dashboard) + { + let name = to.meta.name; + let nameParts = _isArray(name) ? name : [name]; + let translations = []; + + nameParts.forEach(part => + { + if (part) + { + translations.push(Localization.localize(part)); + } + }); + + title += ': ' + translations.join(' - '); + } + + document.title = title; + + next(); + }; + + // dirty form guard + if (from.matched.length && from.matched[0].instances) + { + let instance = from.matched[0].instances.default; + + if (instance.$refs['form'] && typeof instance.$refs.form.beforeRouteLeave === 'function') + { + isGuarded = true; + instance.$refs.form.beforeRouteLeave(to, from, res => + { + if (res === false) + { + next(false); + } + else + { + callback(); + } + }); + } + } + + if (!isGuarded) + { + callback(); + } +}; + + +export default { + mode: 'history', + base: zero.path, + linkActiveClass: 'is-active', + linkExactActiveClass: 'is-active-exact', + beforeEach: beforeEach, + scrollBehavior(to, from, savedPosition) + { + return savedPosition ? savedPosition : { x: 0, y: 0 }; + } +}; \ No newline at end of file diff --git a/zero.Web.UI/App/router.config.js b/zero.Web.UI/App/config/routes.js similarity index 60% rename from zero.Web.UI/App/router.config.js rename to zero.Web.UI/App/config/routes.js index afe89f0a..5c9a3e84 100644 --- a/zero.Web.UI/App/router.config.js +++ b/zero.Web.UI/App/config/routes.js @@ -1,25 +1,20 @@ -import Vue from 'vue'; -import VueRouter from 'vue-router'; -import Localization from 'zero/services/localization'; + import { isArray as _isArray, find as _find, map as _map, filter as _filter } from 'underscore'; import { warn } from 'zero/services/debug'; -Vue.use(VueRouter); - -const routes = []; - +let routes = []; // add defined backoffice sections (with their children) to the router -let addSection = (section, component, parent) => +const addSection = (section, component, parent) => { let route = { - path: section.url, + path: section.url, component: component, name: (parent ? parent.alias + '-' : '') + section.alias, meta: { - name: [ section.name, (parent ? parent.name : null) ], + name: [section.name, (parent ? parent.name : null)], alias: section.alias, section: section, parent: parent @@ -93,7 +88,7 @@ let addRoutesPerContext = (context, isPlugin) => } parentRoute.children.push(route); } - // add routes to root + // add routes to root else { routes.push(route); @@ -120,92 +115,9 @@ catch (exc) - // add fallback route (this should probably by 404 page) routes.push({ name: '404', path: '*', component: () => import('zero/pages/notfound') }); - - -// create the router with history mode - -const router = new VueRouter({ - mode: 'history', - routes: routes, - base: zero.path, - linkActiveClass: 'is-active', - linkExactActiveClass: 'is-active-exact', - scrollBehavior(to, from, savedPosition) - { - return savedPosition ? savedPosition : { x: 0, y: 0 }; - } -}); - - - - -// set meta title before routing - -router.beforeEach((to, from, next) => -{ - let isGuarded = false; - - - // set document title + call next - let callback = () => - { - let title = Localization.localize('@zero.name'); - - if (to.meta.name && to.meta.alias !== zero.alias.sections.dashboard) - { - let name = to.meta.name; - let nameParts = _isArray(name) ? name : [name]; - let translations = []; - - nameParts.forEach(part => - { - if (part) - { - translations.push(Localization.localize(part)); - } - }); - - title += ': ' + translations.join(' - '); - } - - document.title = title; - - next(); - }; - - // dirty form guard - if (from.matched.length && from.matched[0].instances) - { - let instance = from.matched[0].instances.default; - - if (instance.$refs['form'] && typeof instance.$refs.form.beforeRouteLeave === 'function') - { - isGuarded = true; - instance.$refs.form.beforeRouteLeave(to, from, res => - { - if (res === false) - { - next(false); - } - else - { - callback(); - } - }); - } - } - - if (!isGuarded) - { - callback(); - } -}); - - -export default router; \ No newline at end of file +export default routes; \ No newline at end of file diff --git a/zero.Web.UI/App/vue.config.js b/zero.Web.UI/App/config/vue.config.js similarity index 100% rename from zero.Web.UI/App/vue.config.js rename to zero.Web.UI/App/config/vue.config.js diff --git a/zero.Web.UI/App/config/zero.plugins.js b/zero.Web.UI/App/config/zero.plugins.js new file mode 100644 index 00000000..4cdeccd0 --- /dev/null +++ b/zero.Web.UI/App/config/zero.plugins.js @@ -0,0 +1,36 @@ + +//zero.plugins = zero.plugins || []; + +//// TODO correct path +//let plugins = require.context('@/../zero.Commerce/Plugins/zero.Commerce', true, /plugin\.js$/); // TODO dynPATH + +//plugins.keys().forEach(path => +//{ +// const routesDefinition = plugins(path); +// const definition = routesDefinition.default || routesDefinition; + +// definition.name = 'commerce'; // TODO +// zero.plugins.push(definition); +//}); + +////plugins = require.context('@/../zero.Debug', true, /plugin\.js$/); // TODO dynPATH + +////plugins.keys().forEach(path => +////{ +//// const routesDefinition = plugins(path); +//// const definition = routesDefinition.default || routesDefinition; + +//// definition.name = 'project'; // TODO +//// zero.plugins.push(definition); +////}); + + +//// run setup action directly + +//zero.plugins.forEach(plugin => +//{ +// if (typeof plugin.setup === 'function') +// { +// plugin.setup(); +// } +//}); \ No newline at end of file diff --git a/zero.Web.UI/App/core/options.js b/zero.Web.UI/App/core/options.js new file mode 100644 index 00000000..46df02cc --- /dev/null +++ b/zero.Web.UI/App/core/options.js @@ -0,0 +1,16 @@ + +export default { + version: '0.1.0', + + apiPath: '/zero/api/', + + path: '/zero/', + + media: { + defaults: { + images: ['.jpg', '.jpeg', '.png', '.webp', '.svg'], + images_natural: ['.jpg', '.jpeg', '.webp'], + images_artificial: ['.png', '.webp', '.svg'] + } + } +} \ No newline at end of file diff --git a/zero.Web.UI/App/core/plugin.js b/zero.Web.UI/App/core/plugin.js new file mode 100644 index 00000000..64b38e39 --- /dev/null +++ b/zero.Web.UI/App/core/plugin.js @@ -0,0 +1,68 @@ + +class Plugin +{ + #name; + #onInstall; + + routes = []; + renderers = {}; + + + constructor(name) + { + this.#name = name; + } + + + get name() + { + return this.#name; + } + + get install() + { + return this.#onInstall; + } + + set install(callback) + { + this.#onInstall = callback; + } + + + /* + * Add a new vue form renderer to the global configuration + */ + addRenderer(alias, config) + { + this.renderers[alias] = config; + } + + /* + * Adds new form renderers to the global configuration + * Can either by an array (where a key of the child object is `alias`) or an object where the key is the renderer alias + */ + addRenderers(renderers) + { + let items = !Array.isArray(renderers) ? Object.values(renderers) : renderers; + items.forEach(item => this.addRenderer(item.alias, item)); + } + + /* + * Add a new route to vue-router + */ + addRoute(route) + { + this.routes.push(route); + } + + /* + * Adds new routes to vue-router + */ + addRoutes(routes) + { + this.routes.forEach(route => this.addRoute(route)); + } +}; + +export default Plugin; \ No newline at end of file diff --git a/zero.Web.UI/App/core/plugin.zero.js b/zero.Web.UI/App/core/plugin.zero.js new file mode 100644 index 00000000..77647fea --- /dev/null +++ b/zero.Web.UI/App/core/plugin.zero.js @@ -0,0 +1,14 @@ + +import Plugin from './plugin.js'; +import renderers from '../renderers/all.js'; +import routes from '../config/routes.js'; + +const plugin = new Plugin('zero'); + +// add renderers +plugin.addRenderers(renderers); + +// add routes +plugin.addRoutes(routes); + +export default plugin; \ No newline at end of file diff --git a/zero.Web.UI/App/core/zero.js b/zero.Web.UI/App/core/zero.js new file mode 100644 index 00000000..31e63d80 --- /dev/null +++ b/zero.Web.UI/App/core/zero.js @@ -0,0 +1,86 @@ + +// ref: +// https://github.com/vuejs/vue-router/blob/dev/src/index.js + +import ZeroPlugin from './plugin.zero.js'; +import CommercePlugin from '../../../zero.Commerce/Plugins/zero.Commerce/plugin.js'; // TODO dynPath +import options from './options.js'; + +class Zero +{ + static install; + + config = { ...options }; + + #vue = null; + #plugins = []; + + + constructor(vue, opts) + { + this.config = { ...options, ...opts }; + this.#vue = vue; + + this.use(ZeroPlugin); + this.use(CommercePlugin); + } + + + /* + * Get the currently installed version of the zero frontend + */ + get version() + { + return this.config.version; + } + + /* + * Get all installed zero plugins + */ + get plugins() + { + return this.#plugins; + } + + + /* + * Locates zero plugins and install them + */ + setup() + { + + } + + + /* + * Installs a zero plugin + * Each plugin is a superset of a vue (client) plugin and can therefore hook into the vue system + */ + use(plugin) + { + if (typeof plugin.install === 'function') + { + plugin.install(this.#vue, this); + } + + this.#plugins.push(plugin); + + console.log(`[zero] Installed %c${plugin.name}%cplugin`, 'font-style:italic;'); + } +}; + + +/* + * Registers zero within the vue system + */ +Zero.install = (vue, opts) => +{ + const zero = new Zero(vue, opts); + + Object.defineProperty(vue.prototype, 'zero', { + get: () => zero + }); +}; + + +export default Zero; \ No newline at end of file diff --git a/zero.Web.UI/App/renderers/all.js b/zero.Web.UI/App/renderers/all.js new file mode 100644 index 00000000..c364b0ae --- /dev/null +++ b/zero.Web.UI/App/renderers/all.js @@ -0,0 +1,18 @@ + +import application from './application.js'; +import country from './country.js'; +import language from './language.js'; +import media from './media.js'; +import translation from './translation.js'; +import user from './user.js'; +import userRole from './userRole.js'; + +export default { + application, + country, + language, + media, + translation, + user, + userRole +}; \ No newline at end of file diff --git a/zero.Web.UI/App/test.js b/zero.Web.UI/App/test.js new file mode 100644 index 00000000..6f3fefb9 --- /dev/null +++ b/zero.Web.UI/App/test.js @@ -0,0 +1 @@ +//# sourceMappingURL=test.js.map \ No newline at end of file diff --git a/zero.Web.UI/App/test.js.map b/zero.Web.UI/App/test.js.map new file mode 100644 index 00000000..3a8a8aaf --- /dev/null +++ b/zero.Web.UI/App/test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"test.js","sourceRoot":"","sources":["test.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/zero.Web.UI/App/zero.config.js b/zero.Web.UI/App/zero.config.js deleted file mode 100644 index d574a55b..00000000 --- a/zero.Web.UI/App/zero.config.js +++ /dev/null @@ -1,11 +0,0 @@ - -window.zero = window.zero || {}; -window.zero.config = window.zero.config || {}; - -window.zero.config.media = { - defaults: { - images: ['.jpg', '.jpeg', '.png', '.webp', '.svg'], - images_natural: ['.jpg', '.jpeg', '.webp'], - images_artificial: ['.png', '.webp', '.svg'] - } -}; \ No newline at end of file diff --git a/zero.Web.UI/App/zero.plugins.js b/zero.Web.UI/App/zero.plugins.js deleted file mode 100644 index 5da14fbf..00000000 --- a/zero.Web.UI/App/zero.plugins.js +++ /dev/null @@ -1,36 +0,0 @@ - -zero.plugins = zero.plugins || []; - -// TODO correct path -let plugins = require.context('@/../zero.Commerce/Plugins/zero.Commerce', true, /plugin\.js$/); // TODO dynPATH - -plugins.keys().forEach(path => -{ - const routesDefinition = plugins(path); - const definition = routesDefinition.default || routesDefinition; - - definition.name = 'commerce'; // TODO - zero.plugins.push(definition); -}); - -//plugins = require.context('@/../zero.Debug', true, /plugin\.js$/); // TODO dynPATH - -//plugins.keys().forEach(path => -//{ -// const routesDefinition = plugins(path); -// const definition = routesDefinition.default || routesDefinition; - -// definition.name = 'project'; // TODO -// zero.plugins.push(definition); -//}); - - -// run setup action directly - -zero.plugins.forEach(plugin => -{ - if (typeof plugin.setup === 'function') - { - plugin.setup(); - } -}); \ No newline at end of file diff --git a/zero.Web.UI/app.js b/zero.Web.UI/app.js index 13c865d8..009f853a 100644 --- a/zero.Web.UI/app.js +++ b/zero.Web.UI/app.js @@ -1,16 +1,22 @@ import Vue from 'vue'; +import VueRouter from 'vue-router'; +import Zero from 'zero/core/zero.js'; import App from 'zero/app'; +import 'zero/components/globals'; +import 'zero/directives/globals'; +import 'zero/filters/globals'; zero.apps = { shared: true }; -import 'zero/components/globals'; -import 'zero/directives/globals'; -import 'zero/filters/globals'; -import 'zero/renderers/globals'; //import 'zero/pages/register'; -import 'zero/zero.plugins.js'; +//import ZeroPlugin from 'zero/config/zero.plugin.js'; + +Vue.use(VueRouter); +Vue.use(Zero); + +import 'zero/config/zero.plugins.js'; new Vue(App).$mount('#app'); \ No newline at end of file diff --git a/zero.Web.UI/package-base.json b/zero.Web.UI/package-base.json new file mode 100644 index 00000000..1354b83b --- /dev/null +++ b/zero.Web.UI/package-base.json @@ -0,0 +1,37 @@ +{ + // TODO + // create tool which installs node, npm + other global dependencies (npm-deps(1)) needed to run the application + // (see umbraco for details) + // maybe this can be done via the create dotnet-tool + + "name": "zero", + "version": "0.1.0", + "private": true, + "scripts": { + "install": "", + "serve": "vue-cli-service serve", + "build": "vue-cli-service build", + "build-dev": "vue-cli-service build --mode development", + "watch": "vue-cli-service build --mode development --watch" + }, + "dependencies": { + "core-js": "^3.6.5", + "axios": "^0.19.2", + "dayjs": "^1.8.26", + "flatpickr": "^4.6.3", + "lodash": "*", + "sortablejs": "^1.10.2", + "underscore": "^1.10.2", + "vue": "^2.6.11", + "vue-router": "^3.2.0" + }, + "devDependencies": { + "@vue/cli-plugin-babel": "~4.5.0", + "@vue/cli-plugin-router": "~4.5.0", + "@vue/cli-service": "~4.5.0", + "sass": "^1.26.5", + "sass-loader": "^8.0.2", + "syncpack": "^5.6.10", + "vue-template-compiler": "^2.6.11" + } +} diff --git a/zero.Web.UI/package.json b/zero.Web.UI/package.json index 1d8c111e..8cdebe06 100644 --- a/zero.Web.UI/package.json +++ b/zero.Web.UI/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { + "install": "", "serve": "vue-cli-service serve", "build": "vue-cli-service build", "build-dev": "vue-cli-service build --mode development", @@ -25,6 +26,7 @@ "@vue/cli-service": "~4.5.0", "sass": "^1.26.5", "sass-loader": "^8.0.2", + "syncpack": "^5.6.10", "vue-template-compiler": "^2.6.11" } }