From a7c1f5aa391029d14413441c1e04b77a86f44b19 Mon Sep 17 00:00:00 2001 From: Tobias Johansson Date: Thu, 28 Mar 2019 10:29:13 +0100 Subject: [PATCH] Store filter/column settings in localStorage The filter and column settings in tables are stored per project and context in the browsers localStorage. This makes the table keep the settings even if the browser is refreshed or restarted. The table emits a "componentStateChanged" event containing the tables current state (filter/column settings) which then is saved by the top level component. --- .../js/es6/common/vuecomponents/App.js | 50 +++++++++- .../common/vuecomponents/assetstable/Table.js | 14 ++- .../attracttable/filter/RowFilter.js | 98 ------------------- .../attracttable/rows/AttractRowBase.js | 4 + .../attracttable/rows/filter/RowFilter.js | 68 +++++++++++++ .../common/vuecomponents/shotstable/Table.js | 14 ++- .../common/vuecomponents/taskstable/Table.js | 14 ++- 7 files changed, 160 insertions(+), 102 deletions(-) delete mode 100644 src/scripts/js/es6/common/vuecomponents/attracttable/filter/RowFilter.js create mode 100644 src/scripts/js/es6/common/vuecomponents/attracttable/rows/filter/RowFilter.js diff --git a/src/scripts/js/es6/common/vuecomponents/App.js b/src/scripts/js/es6/common/vuecomponents/App.js index 550b74d..dd060c2 100644 --- a/src/scripts/js/es6/common/vuecomponents/App.js +++ b/src/scripts/js/es6/common/vuecomponents/App.js @@ -13,8 +13,10 @@ const TEMPLATE =` :project="project" :selectedIds="selectedIds" :canChangeSelectionCB="canChangeSelectionCB" + :componentState="initialTableState" @selectItemsChanged="onSelectItemsChanged" @isInitialized="onTableInitialized" + @componentStateChanged="onTableStateChanged" />
@@ -27,6 +29,23 @@ const TEMPLATE =`
`; +class ComponentState { + /** + * Serializable state of this component. + * + * @param {Object} tableState + */ + constructor(tableState) { + this.tableState = tableState; + } +} +/** + * Component wrapping a table for selecting attract_task/asset/shot nodes, and a editor to edit the selected node(s). + * Selected row filters and visible columns are stored in localStorage per project/context. This makes the settings + * sticky between sessions in the same browser. + * Selected nodes are stored in window.history. This makes it possible to move back/forward in browser and the selection + * will change accordingly. + */ Vue.component('attract-app', { template: TEMPLATE, mixins: [BrowserHistoryState], @@ -98,7 +117,22 @@ Vue.component('attract-app', { return `/attract/${projectUrl}/${this.contextType}/${selected._id}`; } } - + }, + stateStorageKey() { + return `attract.${this.projectId}.${this.contextType}`; + }, + initialAppState() { + let stateJsonStr; + try { + stateJsonStr = localStorage.getItem(this.stateStorageKey); + } catch (error) { + // Log and ignore. + console.warn('Unable to restore state:', error); + } + return stateJsonStr ? JSON.parse(stateJsonStr) : undefined; + }, + initialTableState() { + return this.initialAppState ? this.initialAppState.tableState : undefined; } }, watch: { @@ -127,6 +161,20 @@ Vue.component('attract-app', { onTableInitialized() { this.isTableInited = true; }, + /** + * Save table state to localStorage per project and context + * @param {Object} newState + */ + onTableStateChanged(newState) { + let appState = new ComponentState(newState); + let stateJsonStr = JSON.stringify(appState); + try { + localStorage.setItem(this.stateStorageKey, stateJsonStr); + } catch (error) { + // Log and ignore. + console.warn('Unable to save state:', error); + } + }, canChangeSelectionCB() { if(this.isEditing) { let retval = confirm("You have unsaved data. Do you want to discard it?"); diff --git a/src/scripts/js/es6/common/vuecomponents/assetstable/Table.js b/src/scripts/js/es6/common/vuecomponents/assetstable/Table.js index f035459..f7ab580 100644 --- a/src/scripts/js/es6/common/vuecomponents/assetstable/Table.js +++ b/src/scripts/js/es6/common/vuecomponents/assetstable/Table.js @@ -1,7 +1,7 @@ let PillarTable = pillar.vuecomponents.table.PillarTable; import {AssetColumnFactory} from './columns/AssetColumnFactory' import {AssetRowsSource} from './rows/AssetRowsSource' -import {RowFilter} from '../attracttable/filter/RowFilter' +import {RowFilter} from '../attracttable/rows/filter/RowFilter' const TEMPLATE =`
@@ -41,6 +41,18 @@ let AssetsTable = Vue.component('attract-assets-table', { return { columnFactory: new AssetColumnFactory(this.project), rowsSource: new AssetRowsSource(this.project._id), + rowFilterConfig: {validStatuses: this.getValidStatuses()} + } + }, + methods: { + getValidStatuses() { + for (const it of this.project.node_types) { + if(it.name === 'attract_asset'){ + return it.dyn_schema.status.allowed; + } + } + console.warn('Did not find allowed statuses for node type attract_shot'); + return []; } }, components: { diff --git a/src/scripts/js/es6/common/vuecomponents/attracttable/filter/RowFilter.js b/src/scripts/js/es6/common/vuecomponents/attracttable/filter/RowFilter.js deleted file mode 100644 index eeeb095..0000000 --- a/src/scripts/js/es6/common/vuecomponents/attracttable/filter/RowFilter.js +++ /dev/null @@ -1,98 +0,0 @@ -let RowFilterBase = pillar.vuecomponents.table.filter.RowFilter; -const TEMPLATE =` -
- - - - -
    -
  • - Status: -
  • -
  • - Final -
  • -
  • - Approved -
  • -
  • - Cbb -
  • -
  • - Review -
  • -
  • - In Progress -
  • -
  • - Todo -
  • -
  • - On Hold -
  • -
-
-
-`; - -let RowFilter = { - extends: RowFilterBase, - template: TEMPLATE, - props: { - rowObjects: Array - }, - data() { - return { - showAssetStatus: { - todo: true, - in_progress: true, - on_hold: true, - approved: true, - cbb: true, - final: true, - review: true, - }, - } - }, - computed: { - nameQueryLoweCase() { - return this.nameQuery.toLowerCase(); - }, - visibleRowObjects() { - return this.rowObjects.filter((row) => { - if (!this.hasShowStatus(row)) return false; - return this.filterByName(row); - }); - } - }, - methods: { - hasShowStatus(rowObject) { - let status = rowObject.getProperties().status; - return !(this.showAssetStatus[status] === false); // To handle invalid statuses - }, - }, -}; - -export { RowFilter } diff --git a/src/scripts/js/es6/common/vuecomponents/attracttable/rows/AttractRowBase.js b/src/scripts/js/es6/common/vuecomponents/attracttable/rows/AttractRowBase.js index ce45608..6deca7a 100644 --- a/src/scripts/js/es6/common/vuecomponents/attracttable/rows/AttractRowBase.js +++ b/src/scripts/js/es6/common/vuecomponents/attracttable/rows/AttractRowBase.js @@ -9,6 +9,10 @@ class AttractRowBase extends RowBase { onRowUpdated(event) { this.underlyingObject = event.detail; } + + getStatus() { + return this.underlyingObject.properties.status; + } } export { AttractRowBase } diff --git a/src/scripts/js/es6/common/vuecomponents/attracttable/rows/filter/RowFilter.js b/src/scripts/js/es6/common/vuecomponents/attracttable/rows/filter/RowFilter.js new file mode 100644 index 0000000..60b66ce --- /dev/null +++ b/src/scripts/js/es6/common/vuecomponents/attracttable/rows/filter/RowFilter.js @@ -0,0 +1,68 @@ +let NameFilter = pillar.vuecomponents.table.rows.filter.NameFilter; +let StatusFilter = pillar.vuecomponents.table.rows.filter.StatusFilter; + +const TEMPLATE =` +
+ + +
+`; + +let RowFilter = { + template: TEMPLATE, + props: { + rowObjects: Array, + componentState: Object, + config: Object + }, + data() { + return { + availableStatuses: this.config.validStatuses, + nameFilteredRowObjects: [], + nameFilterState: (this.componentState || {}).nameFilter, + statusFilterState: (this.componentState || {}).statusFilter, + } + }, + methods: { + onNameFiltered(visibleRowObjects) { + this.nameFilteredRowObjects = visibleRowObjects; + }, + onNameFilterStateChanged(stateObj) { + this.nameFilterState = stateObj; + }, + onStatusFilterStateChanged(stateObj) { + this.statusFilterState = stateObj; + } + }, + computed: { + currentComponentState() { + return { + nameFilter: this.nameFilterState, + statusFilter: this.statusFilterState, + }; + } + }, + watch: { + currentComponentState(newValue) { + this.$emit('componentStateChanged', newValue); + } + }, + components: { + 'name-filter': NameFilter, + 'status-filter': StatusFilter + } +}; + +export { RowFilter } + diff --git a/src/scripts/js/es6/common/vuecomponents/shotstable/Table.js b/src/scripts/js/es6/common/vuecomponents/shotstable/Table.js index 7c3f94d..6f6bcd0 100644 --- a/src/scripts/js/es6/common/vuecomponents/shotstable/Table.js +++ b/src/scripts/js/es6/common/vuecomponents/shotstable/Table.js @@ -1,7 +1,7 @@ let PillarTable = pillar.vuecomponents.table.PillarTable; import {ShotsColumnFactory} from './columns/ShotsColumnFactory' import {ShotRowsSource} from './rows/ShotRowsSource' -import {RowFilter} from '../attracttable/filter/RowFilter' +import {RowFilter} from '../attracttable/rows/filter/RowFilter' let ShotsTable = Vue.component('attract-shots-table', { extends: PillarTable, @@ -12,6 +12,18 @@ let ShotsTable = Vue.component('attract-shots-table', { return { columnFactory: new ShotsColumnFactory(this.project), rowsSource: new ShotRowsSource(this.project._id), + rowFilterConfig: {validStatuses: this.getValidStatuses()} + } + }, + methods: { + getValidStatuses() { + for (const it of this.project.node_types) { + if(it.name === 'attract_shot'){ + return it.dyn_schema.status.allowed; + } + } + console.warn('Did not find allowed statuses for node type attract_shot'); + return []; } }, components: { diff --git a/src/scripts/js/es6/common/vuecomponents/taskstable/Table.js b/src/scripts/js/es6/common/vuecomponents/taskstable/Table.js index 269d81f..a937b44 100644 --- a/src/scripts/js/es6/common/vuecomponents/taskstable/Table.js +++ b/src/scripts/js/es6/common/vuecomponents/taskstable/Table.js @@ -1,7 +1,7 @@ let PillarTable = pillar.vuecomponents.table.PillarTable; import {TasksColumnFactory} from './columns/TasksColumnFactory' import {TaskRowsSource} from './rows/TaskRowsSource' -import {RowFilter} from '../attracttable/filter/RowFilter' +import {RowFilter} from '../attracttable/rows/filter/RowFilter' const TEMPLATE =`
@@ -41,6 +41,18 @@ let TasksTable = Vue.component('attract-tasks-table', { return { columnFactory: new TasksColumnFactory(this.project), rowsSource: new TaskRowsSource(this.project._id), + rowFilterConfig: {validStatuses: this.getValidStatuses()} + } + }, + methods: { + getValidStatuses() { + for (const it of this.project.node_types) { + if(it.name === 'attract_task'){ + return it.dyn_schema.status.allowed; + } + } + console.warn('Did not find allowed statuses for node type attract_task'); + return []; } }, components: {