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.
This commit is contained in:
@@ -13,8 +13,10 @@ const TEMPLATE =`
|
||||
:project="project"
|
||||
:selectedIds="selectedIds"
|
||||
:canChangeSelectionCB="canChangeSelectionCB"
|
||||
:componentState="initialTableState"
|
||||
@selectItemsChanged="onSelectItemsChanged"
|
||||
@isInitialized="onTableInitialized"
|
||||
@componentStateChanged="onTableStateChanged"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-splitter"/>
|
||||
@@ -27,6 +29,23 @@ const TEMPLATE =`
|
||||
</div>
|
||||
`;
|
||||
|
||||
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?");
|
||||
|
@@ -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 =`
|
||||
<div class="pillar-table-actions">
|
||||
@@ -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: {
|
||||
|
@@ -1,98 +0,0 @@
|
||||
let RowFilterBase = pillar.vuecomponents.table.filter.RowFilter;
|
||||
const TEMPLATE =`
|
||||
<div class="pillar-table-row-filter">
|
||||
<input
|
||||
placeholder="Filter by name"
|
||||
v-model="nameQuery"
|
||||
/>
|
||||
<pillar-dropdown>
|
||||
<i class="pi-filter"
|
||||
slot="button"
|
||||
title="Row filter"
|
||||
/>
|
||||
|
||||
<ul class="settings-menu"
|
||||
slot="menu"
|
||||
>
|
||||
<li>
|
||||
Status:
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox"
|
||||
v-model="showAssetStatus.final"
|
||||
/> Final
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox"
|
||||
v-model="showAssetStatus.approved"
|
||||
/> Approved
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox"
|
||||
v-model="showAssetStatus.cbb"
|
||||
/> Cbb
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox"
|
||||
v-model="showAssetStatus.review"
|
||||
/> Review
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox"
|
||||
v-model="showAssetStatus.in_progress"
|
||||
/> In Progress
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox"
|
||||
v-model="showAssetStatus.todo"
|
||||
/> Todo
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox"
|
||||
v-model="showAssetStatus.on_hold"
|
||||
/> On Hold
|
||||
</li>
|
||||
</ul>
|
||||
</pillar-dropdown>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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 }
|
@@ -9,6 +9,10 @@ class AttractRowBase extends RowBase {
|
||||
onRowUpdated(event) {
|
||||
this.underlyingObject = event.detail;
|
||||
}
|
||||
|
||||
getStatus() {
|
||||
return this.underlyingObject.properties.status;
|
||||
}
|
||||
}
|
||||
|
||||
export { AttractRowBase }
|
||||
|
@@ -0,0 +1,68 @@
|
||||
let NameFilter = pillar.vuecomponents.table.rows.filter.NameFilter;
|
||||
let StatusFilter = pillar.vuecomponents.table.rows.filter.StatusFilter;
|
||||
|
||||
const TEMPLATE =`
|
||||
<div class="pillar-table-row-filter">
|
||||
<name-filter
|
||||
:rowObjects="rowObjects"
|
||||
:componentState="(componentState || {}).nameFilter"
|
||||
@visibleRowObjectsChanged="onNameFiltered"
|
||||
@componentStateChanged="onNameFilterStateChanged"
|
||||
/>
|
||||
<status-filter
|
||||
:availableStatuses="availableStatuses"
|
||||
:rowObjects="nameFilteredRowObjects"
|
||||
:componentState="(componentState || {}).statusFilter"
|
||||
@visibleRowObjectsChanged="$emit('visibleRowObjectsChanged', ...arguments)"
|
||||
@componentStateChanged="onStatusFilterStateChanged"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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 }
|
||||
|
@@ -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: {
|
||||
|
@@ -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 =`
|
||||
<div class="pillar-table-actions">
|
||||
@@ -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: {
|
||||
|
Reference in New Issue
Block a user