Files
attract/src/scripts/js/es6/common/vuecomponents/App.js
Tobias Johansson a7c1f5aa39 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.
2019-03-28 10:29:13 +01:00

211 lines
6.9 KiB
JavaScript

import { AssetsTable } from './assetstable/Table'
import { TasksTable } from './taskstable/Table'
import { ShotsTable } from './shotstable/Table'
import './detailedview/Viewer'
const BrowserHistoryState = pillar.vuecomponents.mixins.BrowserHistoryState;
const StateSaveMode = pillar.vuecomponents.mixins.StateSaveMode;
const TEMPLATE =`
<div class="attract-app">
<div id="col_main">
<component
:is="tableComponentName"
:project="project"
:selectedIds="selectedIds"
:canChangeSelectionCB="canChangeSelectionCB"
:componentState="initialTableState"
@selectItemsChanged="onSelectItemsChanged"
@isInitialized="onTableInitialized"
@componentStateChanged="onTableStateChanged"
/>
</div>
<div class="col-splitter"/>
<attract-detailed-view id="col_right"
:items="selectedItems"
:project="project"
:contextType="contextType"
@objects-are-edited="onEditingObjects"
/>
</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],
props: {
projectId: String,
selectedIds: {
type: Array,
default: []
},
contextType: {
type: String,
default: 'shots',
}
},
data() {
return {
selectedItems: [],
isEditing: false,
isTableInited: false,
project: null
}
},
created() {
pillar.api.thenGetProject(this.projectId)
.then((project) =>{
this.project = project;
});
},
computed: {
selectedNames() {
return this.selectedItems.map(it => it.name);
},
tableComponentName() {
if(!this.project) return '';
switch (this.contextType) {
case 'assets': return AssetsTable.options.name;
case 'tasks': return TasksTable.options.name;
case 'shots': return ShotsTable.options.name;
default:
console.log('Unknown context type', this.contextType);
return ShotsTable.$options.name;
}
},
/**
* @override BrowserHistoryState
*/
browserHistoryState() {
if(this.isTableInited) {
return {
'selectedIds': this.selectedIds
};
} else {
return {};
}
},
/**
* @override BrowserHistoryState
*/
historyStateUrl() {
let projectUrl = ProjectUtils.projectUrl();
if(this.selectedItems.length !== 1) {
return `/attract/${projectUrl}/${this.contextType}/`;
} else {
let selected = this.selectedItems[0];
let node_type = selected.node_type;
if (node_type === 'attract_task' && this.contextType !== 'tasks') {
return `/attract/${projectUrl}/${this.contextType}/with-task/${selected._id}`;
} else {
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: {
selectedItems(newValue) {
function equals(arrA, arrB) {
if (arrA.length === arrB.length) {
return arrA.every(it => arrB.includes(it)) &&
arrB.every(it => arrA.includes(it))
}
return false;
}
let newSelectedIds = newValue.map(item => item._id);
// They will be equal for instance when we pop browser history
if (equals(newSelectedIds, this.selectedIds)) return;
this.selectedIds = newSelectedIds;
}
},
methods: {
onSelectItemsChanged(selectedItems) {
this.selectedItems = selectedItems;
},
onEditingObjects(isEditing) {
this.isEditing = !!isEditing;
},
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?");
return retval;
}
return true
},
/**
* @override BrowserHistoryState
*/
stateSaveMode(newState, oldState) {
if (!this.isTableInited) {
return StateSaveMode.IGNORE;
}
if (!oldState) {
// Initial state. Replace what we have so we can go back to this state
return StateSaveMode.REPLACE;
}
if (newState.selectedIds.length > 1 && oldState.selectedIds.length > 1) {
// To not spam history when multiselecting items
return StateSaveMode.REPLACE;
}
return StateSaveMode.PUSH;
},
/**
* @override BrowserHistoryState
*/
applyHistoryState(newState) {
this.selectedIds = newState.selectedIds || this.selectedIds;
}
},
});