diff --git a/zero.Core/Api/MediaApi.cs b/zero.Core/Api/MediaApi.cs index de128717..12d3a70c 100644 --- a/zero.Core/Api/MediaApi.cs +++ b/zero.Core/Api/MediaApi.cs @@ -46,13 +46,13 @@ namespace zero.Core.Api /// - public async Task GetSourceById(string id, bool thumb = false, bool enforceAppId = true) + public async Task GetSourceById(string id, bool thumb = false, bool isCoreDatabase = false) { - using IAsyncDocumentSession session = Store.OpenAsyncSession(); + using IAsyncDocumentSession session = isCoreDatabase ? Store.OpenCoreSession() : Store.OpenAsyncSession(); IMedia media = await session.LoadAsync(id); - if (media == null) //|| (enforceAppId && media.AppId != Constants.Database.SharedAppId && media.AppId != Scope.AppId)) // TODO appx fix + if (media == null) { return null; } @@ -280,7 +280,7 @@ namespace zero.Core.Api /// /// Get media source by Id /// - Task GetSourceById(string id, bool thumb = false, bool enforceAppId = true); + Task GetSourceById(string id, bool thumb = false, bool isCoreDatabase = false); /// /// Get media by ids diff --git a/zero.Web.UI/App/components/tables/table-filter.vue b/zero.Web.UI/App/components/tables/table-filter.vue index 0b589aef..763af953 100644 --- a/zero.Web.UI/App/components/tables/table-filter.vue +++ b/zero.Web.UI/App/components/tables/table-filter.vue @@ -134,6 +134,7 @@ else { this.currentFilter = JSON.parse(JSON.stringify(value)); + this.currentFilter.filter.id = this.currentFilter.id; this.$emit('filter', this.currentFilter.filter); this.attach.setFilter(this.currentFilter.filter); } @@ -157,7 +158,7 @@ // persist a filter in local storage saveFilter(name, filter, id) { - id = id || Strings.guid(); + id = id || Strings.guid(4); let savedFilter = this.storedFilters.find(x => x.id === id); let index = this.storedFilters.indexOf(savedFilter); diff --git a/zero.Web.UI/App/components/tables/table.vue b/zero.Web.UI/App/components/tables/table.vue index 4f1129bb..d0639623 100644 --- a/zero.Web.UI/App/components/tables/table.vue +++ b/zero.Web.UI/App/components/tables/table.vue @@ -4,7 +4,7 @@
-
@@ -42,7 +42,8 @@ import './table.scss' import UiPagination from 'zero/components/pagination.vue'; import TableValue from './table-value.js'; - import { debounce as _debounce } from 'underscore'; + import { debounce as _debounce, isEqual as _isEqual } from 'underscore'; + import Qs from 'qs'; export default { name: 'uiTable', @@ -88,12 +89,9 @@ }, watch: { - query: { - deep: true, - handler: function (val) - { - if (this.loaded) this.debouncedUpdate(); - } + 'query.search': function (val) + { + if (this.loaded) this.onChange(); }, $route(to, from) { @@ -116,7 +114,7 @@ flex: column.options.width ? { 'flex': '0 1 ' + column.options.width + 'px' } : {} }; }); - this.query = { ...this.listConfig.query }; + this.query = { ...this.listConfig.query, ...this.listConfig.queryToParams(this.$route.query) }; this.component = typeof !!this.listConfig.link ? 'router-link' : 'div'; this.filter = { ...this.listConfig.filterOptions }; this.$nextTick(() => @@ -167,6 +165,17 @@ }, + // a property has changed and therefore we update the URL and table content + onChange() + { + const query = Qs.stringify(this.listConfig.paramsToQuery(this.query)); + const path = this.$route.path + (query ? '?' + query : ''); + + history.replaceState(null, null, path); + this.debouncedUpdate(); + }, + + getLink(item) { if (!this.listConfig.link) @@ -190,14 +199,14 @@ setPage(index) { this.query.page = index; - this.debouncedUpdate(); + this.onChange(); }, // set a new filter setFilter(filter) { this.query.filter = filter; - this.debouncedUpdate(); + this.onChange(); }, // sort by a column @@ -217,7 +226,10 @@ this.query.orderIsDescending = true; } - this.debouncedUpdate(); + // reset the page on sorting change + this.query.page = 1; + + this.onChange(); }, // toggle selection of an item diff --git a/zero.Web.UI/App/editor/editor.vue b/zero.Web.UI/App/editor/editor.vue index 755eed9a..50611785 100644 --- a/zero.Web.UI/App/editor/editor.vue +++ b/zero.Web.UI/App/editor/editor.vue @@ -70,6 +70,7 @@ loaded: false, hasTabs: false, asBoxes: false, + coreDatabase: false, tabs: [] }), @@ -101,6 +102,7 @@ } this.asBoxes = this.editorConfig.options.boxes; + this.coreDatabase = this.editorConfig.options.coreDatabase; this.onConfigure(this); this.loaded = true; diff --git a/zero.Web.UI/App/navigation.vue b/zero.Web.UI/App/navigation.vue index 4f9c0f4e..e92dd5ec 100644 --- a/zero.Web.UI/App/navigation.vue +++ b/zero.Web.UI/App/navigation.vue @@ -105,7 +105,7 @@ if (user && user.avatarId) { - this.userAvatar = MediaApi.getImageSource(user.avatarId, false, false); + this.userAvatar = MediaApi.getImageSource(user.avatarId, false, true); } else { diff --git a/zero.Web.UI/App/renderers/editors/application.js b/zero.Web.UI/App/renderers/editors/application.js index fa7069d2..5996bd36 100644 --- a/zero.Web.UI/App/renderers/editors/application.js +++ b/zero.Web.UI/App/renderers/editors/application.js @@ -3,6 +3,7 @@ import Editor from 'zero/core/editor.ts'; import ApplicationFeatures from 'zero/pages/settings/application-features.vue'; const editor = new Editor('application', '@application.fields.'); +editor.options.coreDatabase = true; const general = editor.tab('general', '@ui.tab_general'); const domains = editor.tab('domains', '@application.tab_domains', x => x.domains.length); diff --git a/zero.Web.UI/App/renderers/editors/user.js b/zero.Web.UI/App/renderers/editors/user.js index faa108e7..cadba146 100644 --- a/zero.Web.UI/App/renderers/editors/user.js +++ b/zero.Web.UI/App/renderers/editors/user.js @@ -3,6 +3,7 @@ import Editor from 'zero/core/editor.ts'; import Permissions from 'zero/components/permissions.vue'; const editor = new Editor('user', '@user.fields.'); +editor.options.coreDatabase = true; const permissionsCount = x => (x.claims || []).filter(claim => { diff --git a/zero.Web.UI/App/renderers/editors/userRole.js b/zero.Web.UI/App/renderers/editors/userRole.js index 5beec5fd..e65dd9e9 100644 --- a/zero.Web.UI/App/renderers/editors/userRole.js +++ b/zero.Web.UI/App/renderers/editors/userRole.js @@ -3,6 +3,7 @@ import Editor from 'zero/core/editor.ts'; import Permissions from 'zero/components/permissions.vue'; const editor = new Editor('userRole', '@role.fields.'); +editor.options.coreDatabase = true; const permissionsCount = x => (x.claims || []).filter(claim => { diff --git a/zero.Web.UI/App/resources/media.js b/zero.Web.UI/App/resources/media.js index 3df0ae0f..6f76eab0 100644 --- a/zero.Web.UI/App/resources/media.js +++ b/zero.Web.UI/App/resources/media.js @@ -35,7 +35,7 @@ var api = base => }, // get path to thumbnail source - getImageSource(id, thumb, enforceAppId) + getImageSource(id, thumb, core) { if (!id || id.indexOf('http') === 0) { @@ -45,7 +45,7 @@ var api = base => { return id.substring(6) + "?preset=productListing"; } - return zero.apiPath + base + 'streamThumbnail/?id=' + id + (typeof thumb === 'boolean' ? '&thumb=' + (thumb ? 'true' : 'false') : '') + (enforceAppId === false ? '&enforceAppId=false' : ''); + return zero.apiPath + base + 'streamThumbnail/?id=' + id + (typeof thumb === 'boolean' ? '&thumb=' + (thumb ? 'true' : 'false') : '') + (core === true ? '&core=true' : ''); }, // save a media diff --git a/zero.Web.UI/App/services/strings.js b/zero.Web.UI/App/services/strings.js index 30c550f0..b72e34b0 100644 --- a/zero.Web.UI/App/services/strings.js +++ b/zero.Web.UI/App/services/strings.js @@ -10,11 +10,18 @@ export default { /// /// Generate a GUID /// - guid() + guid(length) { - return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => + var guid = ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) ); + + if (length > 0) + { + return guid.replace(/-/g, '').substring(0, length); + } + + return guid; }, /// diff --git a/zero.Web.UI/app/core/editor-field.ts b/zero.Web.UI/app/core/editor-field.ts index dd3f8c82..90e22200 100644 --- a/zero.Web.UI/app/core/editor-field.ts +++ b/zero.Web.UI/app/core/editor-field.ts @@ -38,6 +38,7 @@ class EditorField condition: null, disabled: false, tab: null, + coreDatabase: false, class: '' }; diff --git a/zero.Web.UI/app/core/editor.ts b/zero.Web.UI/app/core/editor.ts index ec4b93e6..9b44f520 100644 --- a/zero.Web.UI/app/core/editor.ts +++ b/zero.Web.UI/app/core/editor.ts @@ -20,7 +20,8 @@ class Editor options = { disabled: false, - boxes: false + boxes: false, + coreDatabase: true }; @@ -90,22 +91,29 @@ class Editor * @param {string} [options.helpText] - Display a help text below the field * @param {boolean|function} [options.condition] - Conditionally hide the field * @param {boolean|function} [options.disabled=false] - Conditionally disable the field + * @param {boolean} [options.coreDatabase] - Operate on the core database for this field (default is set by Editor.options.coreDatabase) * @param {string|object} [options.tab] - Add this field to a tab (by passing the alias or the tab instance) * @param {string} [options.class] - Append HTML class to the generated property * @returns {EditorField} */ field(path, options) { + options = options || {}; + let field = this.fields.find(x => x.path === path); if (!field) { + if (typeof options.coreDatabase === 'undefined') + { + options.coreDatabase = this.options.coreDatabase; + } field = new EditorField(path, options); this.fields.push(field); } else { - field.options = { ...field.options, ...(options || {}) }; + field.options = { ...field.options, ...options }; } return field; diff --git a/zero.Web.UI/app/core/list.ts b/zero.Web.UI/app/core/list.ts index a5882a96..8907bdc4 100644 --- a/zero.Web.UI/app/core/list.ts +++ b/zero.Web.UI/app/core/list.ts @@ -42,6 +42,67 @@ class List actions = []; + /** + * Converts the list parameters (like page, search, filter, ...) to a vue router query + */ + paramsToQuery = params => + { + let values: any = {}; + + if (params.page !== this.query.page) + { + values.page = params.page; + } + if (params.isDescending !== this.query.isDescending || params.orderBy !== this.query.orderBy) + { + values.by = params.orderBy || this.query.orderBy; + values.desc = params.isDescending || this.query.isDescending; + } + if (!!params.search) + { + values.search = params.search; + } + if (params.filter && params.filter.id) + { + values.filter = params.filter.id; + } + return values; + }; + + /** + * Converts an URL query string to list parameters + */ + queryToParams = query => + { + if (!query) + { + return {}; + } + let values = JSON.parse(JSON.stringify(this.query)); + if (query.page) + { + values.page = +query.page || this.query.page; + } + if (query.by) + { + values.orderBy = query.by; + } + if (query.desc) + { + values.isDescending = query.desc === "true" || query.desc === true; + } + if (query.search) + { + values.search = query.search; + } + if (query.filter) + { + values.filter = values.filter || {}; + values.filter.id = query.filter; + } + return values; + }; + constructor(alias) { diff --git a/zero.Web/Controllers/MediaController.cs b/zero.Web/Controllers/MediaController.cs index 1db57943..d6fe7c29 100644 --- a/zero.Web/Controllers/MediaController.cs +++ b/zero.Web/Controllers/MediaController.cs @@ -81,9 +81,9 @@ namespace zero.Web.Controllers [HttpGet] - public async Task StreamThumbnail([FromQuery] string id, [FromQuery] bool thumb = true, [FromQuery] bool enforceAppId = true) + public async Task StreamThumbnail([FromQuery] string id, [FromQuery] bool thumb = true, [FromQuery] bool core = false) { - string path = await Api.GetSourceById(id, thumb, enforceAppId); + string path = await Api.GetSourceById(id, thumb, core); if (path == null) {