Vue Attract: Sort/filterable table based on Vue
Initial commit implementing sortable and filterable tables for attract using Vue.
This commit is contained in:
9
src/scripts/js/es6/common/api/assets.js
Normal file
9
src/scripts/js/es6/common/api/assets.js
Normal file
@@ -0,0 +1,9 @@
|
||||
function thenGetProjectAssets(projectId) {
|
||||
let where = {
|
||||
project: projectId,
|
||||
node_type: 'attract_asset'
|
||||
}
|
||||
return pillar.api.thenGetNodes(where);
|
||||
}
|
||||
|
||||
export { thenGetProjectAssets }
|
3
src/scripts/js/es6/common/api/init.js
Normal file
3
src/scripts/js/es6/common/api/init.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export {thenGetProjectAssets} from './assets'
|
||||
export {thenGetProjectShots} from './shots'
|
||||
export {thenGetTasks, thenGetProjectTasks} from './tasks'
|
9
src/scripts/js/es6/common/api/shots.js
Normal file
9
src/scripts/js/es6/common/api/shots.js
Normal file
@@ -0,0 +1,9 @@
|
||||
function thenGetProjectShots(projectId) {
|
||||
let where = {
|
||||
project: projectId,
|
||||
node_type: 'attract_shot'
|
||||
}
|
||||
return pillar.api.thenGetNodes(where);
|
||||
}
|
||||
|
||||
export { thenGetProjectShots }
|
20
src/scripts/js/es6/common/api/tasks.js
Normal file
20
src/scripts/js/es6/common/api/tasks.js
Normal file
@@ -0,0 +1,20 @@
|
||||
function thenGetTasks(parentId) {
|
||||
let where = {
|
||||
parent: parentId,
|
||||
node_type: 'attract_task'
|
||||
};
|
||||
return pillar.api.thenGetNodes(where);
|
||||
}
|
||||
|
||||
function thenGetProjectTasks(projectId) {
|
||||
let where = {
|
||||
project: projectId,
|
||||
node_type: 'attract_task'
|
||||
}
|
||||
let embedded = {
|
||||
parent: 1
|
||||
}
|
||||
return pillar.api.thenGetNodes(where, embedded);
|
||||
}
|
||||
|
||||
export { thenGetTasks, thenGetProjectTasks }
|
41
src/scripts/js/es6/common/auth/auth.js
Normal file
41
src/scripts/js/es6/common/auth/auth.js
Normal file
@@ -0,0 +1,41 @@
|
||||
class ProjectAuth {
|
||||
constructor() {
|
||||
this.canCreateTask = false;
|
||||
this.canCreateAsset = false;
|
||||
}
|
||||
}
|
||||
|
||||
class Auth {
|
||||
constructor() {
|
||||
this.perProjectAuth = {}
|
||||
}
|
||||
|
||||
canUserCreateTask(projectId) {
|
||||
let projectAuth = this.getProjectAuth(projectId);
|
||||
return projectAuth.canCreateTask;
|
||||
}
|
||||
|
||||
canUserCreateAsset(projectId) {
|
||||
let projectAuth = this.getProjectAuth(projectId);
|
||||
return projectAuth.canCreateAsset;
|
||||
}
|
||||
|
||||
setUserCanCreateTask(projectId, canCreateTask) {
|
||||
let projectAuth = this.getProjectAuth(projectId);
|
||||
projectAuth.canCreateTask = canCreateTask;
|
||||
}
|
||||
|
||||
setUserCanCreateAsset(projectId, canCreateAsset) {
|
||||
let projectAuth = this.getProjectAuth(projectId);
|
||||
projectAuth.canCreateAsset = canCreateAsset;
|
||||
}
|
||||
|
||||
getProjectAuth(projectId) {
|
||||
this.perProjectAuth[projectId] = this.perProjectAuth[projectId] || new ProjectAuth();
|
||||
return this.perProjectAuth[projectId];
|
||||
}
|
||||
}
|
||||
|
||||
let AttractAuth = new Auth();
|
||||
|
||||
export {AttractAuth}
|
1
src/scripts/js/es6/common/auth/init.js
Normal file
1
src/scripts/js/es6/common/auth/init.js
Normal file
@@ -0,0 +1 @@
|
||||
export { AttractAuth } from './auth'
|
40
src/scripts/js/es6/common/vuecomponents/assetstable/Table.js
Normal file
40
src/scripts/js/es6/common/vuecomponents/assetstable/Table.js
Normal file
@@ -0,0 +1,40 @@
|
||||
let PillarTable = pillar.vuecomponents.table.PillarTable;
|
||||
import {AssetColumnFactory} from './columns/AssetColumnFactory'
|
||||
import {AssetRowsSource} from './rows/AssetRowsSource'
|
||||
import {RowFilter} from '../attracttable/filter/RowFilter'
|
||||
|
||||
const TEMPLATE =`
|
||||
<div class="pillar-table-actions">
|
||||
<button class="action"
|
||||
v-if="canAddAsset"
|
||||
@click="createNewAsset"
|
||||
>
|
||||
<i class="pi-plus">New Asset</i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let TableActions = {
|
||||
template: TEMPLATE,
|
||||
computed: {
|
||||
canAddAsset() {
|
||||
let projectId = ProjectUtils.projectId();
|
||||
return attract.auth.AttractAuth.canUserCreateAsset(projectId);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
createNewAsset() {
|
||||
asset_create(ProjectUtils.projectUrl());
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Vue.component('attract-assets-table', {
|
||||
extends: PillarTable,
|
||||
columnFactory: AssetColumnFactory,
|
||||
rowsSource: AssetRowsSource,
|
||||
components: {
|
||||
'pillar-table-actions': TableActions,
|
||||
'pillar-table-row-filter': RowFilter,
|
||||
}
|
||||
});
|
@@ -0,0 +1,24 @@
|
||||
import { TaskColumn } from '../../attracttable/columns/Tasks';
|
||||
import { FirstTaskDueDate, NextTaskDueDate, LastTaskDueDate } from '../../attracttable/columns/TaskDueDate';
|
||||
import { Status } from '../../attracttable/columns/Status';
|
||||
import { RowObject } from '../../attracttable/columns/RowObject'
|
||||
let ColumnFactoryBase = pillar.vuecomponents.table.columns.ColumnFactoryBase;
|
||||
|
||||
|
||||
class AssetColumnFactory extends ColumnFactoryBase{
|
||||
thenGetColumns() {
|
||||
return this.thenGetProject()
|
||||
.then((project) => {
|
||||
let taskTypes = project.extension_props.attract.task_types.attract_asset;
|
||||
let taskColumns = taskTypes.map((tType) => {
|
||||
return new TaskColumn(tType, 'asset-task');
|
||||
})
|
||||
|
||||
return [new Status(), new RowObject()]
|
||||
.concat(taskColumns)
|
||||
.concat([new NextTaskDueDate(),]);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export { AssetColumnFactory }
|
@@ -0,0 +1,30 @@
|
||||
import { AttractRowBase } from '../../attracttable/rows/AttractRowBase'
|
||||
import { TaskEventListener } from '../../attracttable/rows/TaskEventListener';
|
||||
|
||||
class AssetRow extends AttractRowBase {
|
||||
constructor(asset) {
|
||||
super(asset);
|
||||
this.tasks = [];
|
||||
}
|
||||
|
||||
thenInit() {
|
||||
return attract.api.thenGetTasks(this.getId())
|
||||
.then((response) => {
|
||||
this.tasks = response._items;
|
||||
this.registerTaskEventListeners();
|
||||
this.isInitialized = true;
|
||||
});
|
||||
}
|
||||
|
||||
registerTaskEventListeners() {
|
||||
new TaskEventListener(this).register();
|
||||
}
|
||||
|
||||
getTasksOfType(taskType) {
|
||||
return this.tasks.filter((t) => {
|
||||
return t.properties.task_type === taskType;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export { AssetRow }
|
@@ -0,0 +1,18 @@
|
||||
import { AttractRowsSourceBase } from '../../attracttable/rows/AttractRowsSourceBase'
|
||||
import { AssetRow } from './AssetRow'
|
||||
|
||||
class AssetRowsSource extends AttractRowsSourceBase {
|
||||
constructor(projectId) {
|
||||
super(projectId, 'attract_asset', AssetRow);
|
||||
}
|
||||
|
||||
thenInit() {
|
||||
return attract.api.thenGetProjectAssets(this.projectId)
|
||||
.then((result) => {
|
||||
let assets = result._items;
|
||||
this.initRowObjects(assets);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { AssetRowsSource }
|
@@ -0,0 +1,38 @@
|
||||
let CellDefault = pillar.vuecomponents.table.cells.renderer.CellDefault;
|
||||
|
||||
const TEMPLATE =`
|
||||
<div>
|
||||
<a
|
||||
@click.prevent="onClick()"
|
||||
:href="cellLink"
|
||||
>
|
||||
{{ cellValue }}
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let CellRowObject = Vue.component('pillar-cell-row-object', {
|
||||
extends: CellDefault,
|
||||
template: TEMPLATE,
|
||||
computed: {
|
||||
cellLink() {
|
||||
let project_url = ProjectUtils.projectUrl();
|
||||
let item_type = this.itemType();
|
||||
return `/attract/${project_url}/${item_type}s/${this.rowObject.getId()}`;
|
||||
},
|
||||
embededLink() {
|
||||
return this.cellLink;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
item_open(this.rowObject.getId(), this.itemType(), true, ProjectUtils.projectUrl());
|
||||
},
|
||||
itemType() {
|
||||
let node_type = this.rowObject.underlyingObject.node_type;
|
||||
return node_type.replace('attract_', ''); // eg. attract_task to tasks
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export { CellRowObject }
|
@@ -0,0 +1,12 @@
|
||||
let CellDefault = pillar.vuecomponents.table.cells.renderer.CellDefault;
|
||||
|
||||
let CellStatus = Vue.component('attract-cell-Status', {
|
||||
extends: CellDefault,
|
||||
computed: {
|
||||
cellValue() {
|
||||
return '';
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export { CellStatus }
|
@@ -0,0 +1,59 @@
|
||||
let CellDefault = pillar.vuecomponents.table.cells.renderer.CellDefault;
|
||||
|
||||
const TEMPLATE =`
|
||||
<div>
|
||||
<div class="tasks">
|
||||
<a
|
||||
v-for="t in tasks"
|
||||
:class="taskClass(t)"
|
||||
:href="taskLink(t)"
|
||||
:key="t._id"
|
||||
:title="taskTitle(t)"
|
||||
@click.prevent="onTaskClicked(t)"
|
||||
/>
|
||||
</div>
|
||||
<button class="add-task-link"
|
||||
v-if="canAddTask"
|
||||
@click.prevent="onAddTask"
|
||||
>
|
||||
<i class="pi-plus">Task</i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let CellTasks = Vue.component('attract-cell-tasks', {
|
||||
extends: CellDefault,
|
||||
template: TEMPLATE,
|
||||
computed: {
|
||||
tasks() {
|
||||
return this.rawCellValue;
|
||||
},
|
||||
canAddTask() {
|
||||
let projectId = ProjectUtils.projectId();
|
||||
return attract.auth.AttractAuth.canUserCreateTask(projectId);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
taskClass(task) {
|
||||
return `task status-${task.properties.status}`
|
||||
},
|
||||
taskLink(task) {
|
||||
let project_url = ProjectUtils.projectUrl();
|
||||
let node_type = this.rowObject.underlyingObject.node_type;
|
||||
let item_type = node_type.replace('attract_', '') + 's'; // eg. attract_asset to assets
|
||||
return `/attract/${project_url}/${item_type}/with-task/${task._id}`;
|
||||
},
|
||||
taskTitle(task) {
|
||||
let status = (task.properties.status || '').replace('_', ' ');
|
||||
return `Task: ${task.name}\nStatus: ${status}`
|
||||
},
|
||||
onTaskClicked(task) {
|
||||
task_open(task._id, ProjectUtils.projectUrl());
|
||||
},
|
||||
onAddTask(event) {
|
||||
task_create(this.rowObject.getId(), this.column.taskType);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export { CellTasks }
|
@@ -0,0 +1,19 @@
|
||||
let ColumnBase = pillar.vuecomponents.table.columns.ColumnBase;
|
||||
import { CellRowObject } from '../cells/renderer/CellRowObject'
|
||||
|
||||
class RowObject extends ColumnBase {
|
||||
constructor() {
|
||||
super('Name', 'row-object');
|
||||
this.isMandatory = true;
|
||||
}
|
||||
|
||||
getCellRenderer(rowObject) {
|
||||
return CellRowObject.options.name;
|
||||
}
|
||||
|
||||
getRawCellValue(rowObject) {
|
||||
return rowObject.getName() || '<No Name>';
|
||||
}
|
||||
}
|
||||
|
||||
export { RowObject }
|
@@ -0,0 +1,47 @@
|
||||
import {CellStatus} from '../cells/renderer/CellStatus'
|
||||
let ColumnBase = pillar.vuecomponents.table.columns.ColumnBase;
|
||||
|
||||
export class Status extends ColumnBase {
|
||||
constructor() {
|
||||
super('', 'attract-status');
|
||||
this.isMandatory = true;
|
||||
}
|
||||
getCellRenderer(rowObject) {
|
||||
return CellStatus.options.name;
|
||||
}
|
||||
getRawCellValue(rowObject) {
|
||||
return rowObject.getProperties().status;
|
||||
}
|
||||
getCellTitle(rawCellValue, rowObject) {
|
||||
function capitalize(str) {
|
||||
if(str.length === 0) return str;
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
let formatedStatus = capitalize(rawCellValue).replace('_', ' ');
|
||||
return `Status: ${formatedStatus}`;
|
||||
}
|
||||
getCellClasses(rawCellValue, rowObject) {
|
||||
let classes = super.getCellClasses(rawCellValue, rowObject);
|
||||
classes[`status-${rawCellValue}`] = true;
|
||||
return classes;
|
||||
}
|
||||
compareRows(rowObject1, rowObject2) {
|
||||
let sortNbr1 = this.getSortNumber(rowObject1);
|
||||
let sortNbr2 = this.getSortNumber(rowObject2);
|
||||
if (sortNbr1 === sortNbr2) return 0;
|
||||
return sortNbr1 < sortNbr2 ? -1 : 1;
|
||||
}
|
||||
getSortNumber(rowObject) {
|
||||
let statusStr = rowObject.getProperties().status;
|
||||
switch (statusStr) {
|
||||
case 'on_hold': return 10;
|
||||
case 'todo': return 20;
|
||||
case 'in_progress': return 30;
|
||||
case 'review': return 40;
|
||||
case 'cbb': return 50;
|
||||
case 'approved': return 60;
|
||||
case 'final': return 70;
|
||||
default: return 9999; // invalid status
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,100 @@
|
||||
let ColumnBase = pillar.vuecomponents.table.columns.ColumnBase;
|
||||
let CellPrettyDate = pillar.vuecomponents.table.cells.renderer.CellPrettyDate;
|
||||
|
||||
function firstDate(prevDate, task) {
|
||||
let candidate = task.properties.due_date;
|
||||
if (prevDate && candidate) {
|
||||
if (prevDate !== candidate) {
|
||||
return new Date(candidate) < new Date(prevDate) ? candidate : prevDate;
|
||||
}
|
||||
}
|
||||
return prevDate || candidate;
|
||||
}
|
||||
|
||||
function lastDate(prevDate, task) {
|
||||
let candidate = task.properties.due_date;
|
||||
if (prevDate && candidate) {
|
||||
if (prevDate !== candidate) {
|
||||
return new Date(candidate) > new Date(prevDate) ? candidate : prevDate;
|
||||
}
|
||||
}
|
||||
return prevDate || candidate;
|
||||
}
|
||||
|
||||
function nextDate(prevDate, task) {
|
||||
let candidate = task.properties.due_date;
|
||||
if(candidate && new Date(candidate) >= new Date()) {
|
||||
return firstDate(prevDate, task);
|
||||
}
|
||||
return prevDate;
|
||||
}
|
||||
|
||||
class DueDate extends ColumnBase {
|
||||
getCellRenderer(rowObject) {
|
||||
return CellPrettyDate.options.name;
|
||||
}
|
||||
|
||||
getCellClasses(dueDate, rowObject) {
|
||||
let classes = super.getCellClasses(dueDate, rowObject);
|
||||
let isPostDueDate = false;
|
||||
if (dueDate) {
|
||||
isPostDueDate = new Date(dueDate) < new Date();
|
||||
}
|
||||
classes['warning'] = isPostDueDate;
|
||||
return classes;
|
||||
}
|
||||
|
||||
compareRows(rowObject1, rowObject2) {
|
||||
let dueDateStr1 = this.getRawCellValue(rowObject1);
|
||||
let dueDateStr2 = this.getRawCellValue(rowObject2);
|
||||
if (dueDateStr1 === dueDateStr2) return 0;
|
||||
if (dueDateStr1 && dueDateStr2) {
|
||||
return new Date(dueDateStr1) < new Date(dueDateStr2) ? -1 : 1;
|
||||
}
|
||||
return dueDateStr1 ? -1 : 1;
|
||||
}
|
||||
|
||||
getCellTitle(rawCellValue, rowObject) {
|
||||
return rawCellValue;
|
||||
}
|
||||
}
|
||||
|
||||
export class FirstTaskDueDate extends DueDate {
|
||||
constructor() {
|
||||
super('First Due Date', 'first-duedate');
|
||||
}
|
||||
getRawCellValue(rowObject) {
|
||||
let tasks = rowObject.tasks || [];
|
||||
return tasks.reduce(firstDate, undefined) || '';
|
||||
}
|
||||
}
|
||||
|
||||
export class LastTaskDueDate extends DueDate {
|
||||
constructor() {
|
||||
super('Last Due Date', 'last-duedate');
|
||||
}
|
||||
getRawCellValue(rowObject) {
|
||||
let tasks = rowObject.tasks || [];
|
||||
return tasks.reduce(lastDate, undefined) || '';
|
||||
}
|
||||
}
|
||||
|
||||
export class NextTaskDueDate extends DueDate {
|
||||
constructor() {
|
||||
super('Next Due Date', 'next-duedate');
|
||||
}
|
||||
getRawCellValue(rowObject) {
|
||||
let tasks = rowObject.tasks || [];
|
||||
return tasks.reduce(nextDate, undefined) || '';
|
||||
}
|
||||
}
|
||||
|
||||
export class TaskDueDate extends DueDate {
|
||||
constructor() {
|
||||
super('Due Date', 'duedate');
|
||||
}
|
||||
getRawCellValue(rowObject) {
|
||||
let task = rowObject.getTask();
|
||||
return task.properties.due_date || '';
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
let ColumnBase = pillar.vuecomponents.table.columns.ColumnBase;
|
||||
import { CellTasks } from '../cells/renderer/CellTasks'
|
||||
|
||||
export class TaskColumn extends ColumnBase {
|
||||
constructor(taskType, columnType) {
|
||||
super(taskType, columnType);
|
||||
this.taskType = taskType;
|
||||
}
|
||||
|
||||
getCellRenderer(rowObject) {
|
||||
return CellTasks.options.name;
|
||||
}
|
||||
|
||||
getRawCellValue(rowObject) {
|
||||
return rowObject.getTasksOfType(this.taskType);
|
||||
}
|
||||
|
||||
compareRows(rowObject1, rowObject2) {
|
||||
let numTasks1 = this.getRawCellValue(rowObject1).length;
|
||||
let numTasks2 = this.getRawCellValue(rowObject2).length;
|
||||
if (numTasks1 === numTasks2) return 0;
|
||||
return numTasks1 < numTasks2 ? -1 : 1;
|
||||
}
|
||||
}
|
@@ -0,0 +1,98 @@
|
||||
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 }
|
@@ -0,0 +1,14 @@
|
||||
let RowBase = pillar.vuecomponents.table.rows.RowBase;
|
||||
|
||||
class AttractRowBase extends RowBase {
|
||||
constructor(underlyingObject) {
|
||||
super(underlyingObject);
|
||||
pillar.events.Nodes.onUpdated(this.getId(), this.onRowUpdated.bind(this));
|
||||
}
|
||||
|
||||
onRowUpdated(event, updatedObj) {
|
||||
this.underlyingObject = updatedObj;
|
||||
}
|
||||
}
|
||||
|
||||
export { AttractRowBase }
|
@@ -0,0 +1,38 @@
|
||||
let RowObjectsSourceBase = pillar.vuecomponents.table.rows.RowObjectsSourceBase;
|
||||
|
||||
class AttractRowsSourceBase extends RowObjectsSourceBase {
|
||||
constructor(projectId, node_type, rowClass) {
|
||||
super(projectId);
|
||||
this.node_type = node_type;
|
||||
this.rowClass = rowClass;
|
||||
}
|
||||
|
||||
createRow(node) {
|
||||
let row = new this.rowClass(node);
|
||||
row.thenInit();
|
||||
this.registerListeners(row);
|
||||
return row;
|
||||
}
|
||||
|
||||
initRowObjects(nodes) {
|
||||
this.rowObjects = nodes.map(this.createRow.bind(this));
|
||||
pillar.events.Nodes.onCreated(this.node_type, this.onNodeCreated.bind(this));
|
||||
}
|
||||
|
||||
registerListeners(rowObject) {
|
||||
pillar.events.Nodes.onDeleted(rowObject.getId(), this.onNodeDeleted.bind(this));
|
||||
}
|
||||
|
||||
onNodeDeleted(event, nodeId) {
|
||||
this.rowObjects = this.rowObjects.filter((rowObj) => {
|
||||
return rowObj.getId() !== nodeId;
|
||||
});
|
||||
}
|
||||
|
||||
onNodeCreated(event, node) {
|
||||
let rowObj = this.createRow(node);
|
||||
this.rowObjects = this.rowObjects.concat(rowObj);
|
||||
}
|
||||
}
|
||||
|
||||
export { AttractRowsSourceBase }
|
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Helper class that listens to events triggered when a RowObject task is updated/created/deleted and keep the tasks
|
||||
* array of a RowObject up to date accordingly.
|
||||
*/
|
||||
|
||||
export class TaskEventListener {
|
||||
constructor(rowWithTasks) {
|
||||
this.rowObject = rowWithTasks;
|
||||
}
|
||||
|
||||
register() {
|
||||
pillar.events.Nodes.onParentCreated(this.rowObject.getId(), 'attract_task', this.onTaskCreated.bind(this));
|
||||
this.rowObject.tasks.forEach(this.registerEventListeners.bind(this));
|
||||
}
|
||||
|
||||
registerEventListeners(task) {
|
||||
pillar.events.Nodes.onUpdated(task._id, this.onTaskUpdated.bind(this));
|
||||
pillar.events.Nodes.onDeleted(task._id, this.onTaskDeleted.bind(this));
|
||||
}
|
||||
|
||||
onTaskCreated(event, newTask) {
|
||||
this.registerEventListeners(newTask);
|
||||
this.rowObject.tasks = this.rowObject.tasks.concat(newTask);
|
||||
}
|
||||
|
||||
onTaskUpdated(event, updatedTask) {
|
||||
this.rowObject.tasks = this.rowObject.tasks.map((t) => {
|
||||
return t._id === updatedTask._id ? updatedTask : t;
|
||||
});
|
||||
}
|
||||
|
||||
onTaskDeleted(event, taskId) {
|
||||
this.rowObject.tasks = this.rowObject.tasks.filter((t) => {
|
||||
return t._id !== taskId;
|
||||
});
|
||||
}
|
||||
}
|
3
src/scripts/js/es6/common/vuecomponents/init.js
Normal file
3
src/scripts/js/es6/common/vuecomponents/init.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import './assetstable/Table'
|
||||
import './taskstable/Table'
|
||||
import './shotstable/Table'
|
13
src/scripts/js/es6/common/vuecomponents/shotstable/Table.js
Normal file
13
src/scripts/js/es6/common/vuecomponents/shotstable/Table.js
Normal file
@@ -0,0 +1,13 @@
|
||||
let PillarTable = pillar.vuecomponents.table.PillarTable;
|
||||
import {ShotsColumnFactory} from './columns/ShotsColumnFactory'
|
||||
import {ShotRowsSource} from './rows/ShotRowsSource'
|
||||
import {RowFilter} from '../attracttable/filter/RowFilter'
|
||||
|
||||
Vue.component('attract-shots-table', {
|
||||
extends: PillarTable,
|
||||
columnFactory: ShotsColumnFactory,
|
||||
rowsSource: ShotRowsSource,
|
||||
components: {
|
||||
'pillar-table-row-filter': RowFilter,
|
||||
},
|
||||
});
|
@@ -0,0 +1,63 @@
|
||||
let CellDefault = pillar.vuecomponents.table.cells.renderer.CellDefault;
|
||||
|
||||
const TEMPLATE =`
|
||||
<div>
|
||||
<img
|
||||
v-if="img.src"
|
||||
:src="img.src"
|
||||
:alt="img.alt"
|
||||
:height="img.height"
|
||||
:width="img.width"
|
||||
/>
|
||||
<generic-placeholder
|
||||
v-if="isLoading"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let CellPicture = Vue.component('pillar-cell-picture', {
|
||||
extends: CellDefault,
|
||||
template: TEMPLATE,
|
||||
data() {
|
||||
return {
|
||||
img: {},
|
||||
failed: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isLoading() {
|
||||
if(!this.failed) {
|
||||
return !!this.rawCellValue && !this.img.src;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.rawCellValue) {
|
||||
this.loadThumbnail(this.rawCellValue);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
rawCellValue(newValue) {
|
||||
this.loadThumbnail(newValue);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadThumbnail(imgId) {
|
||||
this.img = {};
|
||||
pillar.utils.thenLoadImage(imgId, 't')
|
||||
.then(fileDoc => {
|
||||
this.img = {
|
||||
src: fileDoc.link,
|
||||
alt: fileDoc.name,
|
||||
width: fileDoc.width,
|
||||
height: fileDoc.height,
|
||||
}
|
||||
}).fail(() => {
|
||||
this.failed = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export { CellPicture }
|
@@ -0,0 +1,15 @@
|
||||
import {CellPicture} from '../cells/renderer/Picture'
|
||||
let ColumnBase = pillar.vuecomponents.table.columns.ColumnBase;
|
||||
|
||||
export class Picture extends ColumnBase {
|
||||
constructor() {
|
||||
super('Thumbnail', 'thumbnail');
|
||||
this.isSortable = false;
|
||||
}
|
||||
getCellRenderer(rowObject) {
|
||||
return CellPicture.options.name;
|
||||
}
|
||||
getRawCellValue(rowObject) {
|
||||
return rowObject.underlyingObject.picture;
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
import { TaskColumn } from '../../attracttable/columns/Tasks';
|
||||
import { FirstTaskDueDate, NextTaskDueDate, LastTaskDueDate } from '../../attracttable/columns/TaskDueDate';
|
||||
import { Status } from '../../attracttable/columns/Status';
|
||||
import { Picture } from '../columns/Picture'
|
||||
import { RowObject } from '../../attracttable/columns/RowObject'
|
||||
let ColumnFactoryBase = pillar.vuecomponents.table.columns.ColumnFactoryBase;
|
||||
|
||||
|
||||
class ShotsColumnFactory extends ColumnFactoryBase{
|
||||
thenGetColumns() {
|
||||
return this.thenGetProject()
|
||||
.then((project) => {
|
||||
let taskTypes = project.extension_props.attract.task_types.attract_shot;
|
||||
let taskColumns = taskTypes.map((tType) => {
|
||||
return new TaskColumn(tType, 'shot-task');
|
||||
})
|
||||
|
||||
return [new Status(), new Picture(), new RowObject()]
|
||||
.concat(taskColumns)
|
||||
.concat([new NextTaskDueDate(),]);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export { ShotsColumnFactory }
|
@@ -0,0 +1,38 @@
|
||||
import {AttractRowBase} from '../../attracttable/rows/AttractRowBase'
|
||||
import { TaskEventListener } from '../../attracttable/rows/TaskEventListener';
|
||||
|
||||
class ShotRow extends AttractRowBase {
|
||||
constructor(shot) {
|
||||
super(shot);
|
||||
this.tasks = [];
|
||||
}
|
||||
|
||||
thenInit() {
|
||||
return attract.api.thenGetTasks(this.getId())
|
||||
.then((response) => {
|
||||
this.tasks = response._items;
|
||||
this.registerTaskEventListeners();
|
||||
this.isInitialized = true;
|
||||
})
|
||||
}
|
||||
|
||||
registerTaskEventListeners() {
|
||||
new TaskEventListener(this).register();
|
||||
}
|
||||
|
||||
getTasksOfType(taskType) {
|
||||
return this.tasks.filter((t) => {
|
||||
return t.properties.task_type === taskType;
|
||||
})
|
||||
}
|
||||
|
||||
getRowClasses() {
|
||||
let classes = super.getRowClasses()
|
||||
if(this.isInitialized) {
|
||||
classes['shot-not-in-edit'] = !this.underlyingObject.properties.used_in_edit;
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
}
|
||||
|
||||
export { ShotRow }
|
@@ -0,0 +1,18 @@
|
||||
import { AttractRowsSourceBase } from '../../attracttable/rows/AttractRowsSourceBase'
|
||||
import { ShotRow } from './ShotRow'
|
||||
|
||||
class ShotRowsSource extends AttractRowsSourceBase {
|
||||
constructor(projectId) {
|
||||
super(projectId, 'attract_asset', ShotRow);
|
||||
}
|
||||
|
||||
thenInit() {
|
||||
return attract.api.thenGetProjectShots(this.projectId)
|
||||
.then((result) => {
|
||||
let shots = result._items;
|
||||
this.initRowObjects(shots);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { ShotRowsSource }
|
40
src/scripts/js/es6/common/vuecomponents/taskstable/Table.js
Normal file
40
src/scripts/js/es6/common/vuecomponents/taskstable/Table.js
Normal file
@@ -0,0 +1,40 @@
|
||||
let PillarTable = pillar.vuecomponents.table.PillarTable;
|
||||
import {TasksColumnFactory} from './columns/TasksColumnFactory'
|
||||
import {TaskRowsSource} from './rows/TaskRowsSource'
|
||||
import {RowFilter} from '../attracttable/filter/RowFilter'
|
||||
|
||||
const TEMPLATE =`
|
||||
<div class="pillar-table-actions">
|
||||
<button class="action"
|
||||
v-if="canAddTask"
|
||||
@click="createNewTask"
|
||||
>
|
||||
<i class="pi-plus">New Task</i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let TableActions = {
|
||||
template: TEMPLATE,
|
||||
computed: {
|
||||
canAddTask() {
|
||||
let projectId = ProjectUtils.projectId();
|
||||
return attract.auth.AttractAuth.canUserCreateTask(projectId);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
createNewTask() {
|
||||
task_create(undefined, 'generic');
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Vue.component('attract-tasks-table', {
|
||||
extends: PillarTable,
|
||||
columnFactory: TasksColumnFactory,
|
||||
rowsSource: TaskRowsSource,
|
||||
components: {
|
||||
'pillar-table-actions': TableActions,
|
||||
'pillar-table-row-filter': RowFilter,
|
||||
}
|
||||
});
|
@@ -0,0 +1,42 @@
|
||||
let CellDefault = pillar.vuecomponents.table.cells.renderer.CellDefault;
|
||||
|
||||
const TEMPLATE =`
|
||||
<div>
|
||||
<a
|
||||
v-if="rawCellValue"
|
||||
@click.prevent="onClick()"
|
||||
:href="cellLink"
|
||||
>
|
||||
{{ cellValue }}
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let ParentNameCell = Vue.component('pillar-cell-parent-name', {
|
||||
extends: CellDefault,
|
||||
template: TEMPLATE,
|
||||
computed: {
|
||||
cellTitle() {
|
||||
return this.rawCellValue;
|
||||
},
|
||||
cellLink() {
|
||||
let project_url = ProjectUtils.projectUrl();
|
||||
let item_type = this.itemType();
|
||||
return `/attract/${project_url}/${item_type}s/${this.rowObject.getParent()._id}`;
|
||||
},
|
||||
embededLink() {
|
||||
return this.cellLink;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
item_open(this.rowObject.getParent()._id, this.itemType(), false, ProjectUtils.projectUrl());
|
||||
},
|
||||
itemType() {
|
||||
let node_type = this.rowObject.getParent().node_type;
|
||||
return node_type.replace('attract_', ''); // eg. attract_task to tasks
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export { ParentNameCell }
|
@@ -0,0 +1,30 @@
|
||||
let ColumnBase = pillar.vuecomponents.table.columns.ColumnBase;
|
||||
import {ParentNameCell} from '../cells/ParentName'
|
||||
|
||||
class ParentName extends ColumnBase {
|
||||
constructor() {
|
||||
super('Parent', 'parent-name');
|
||||
}
|
||||
|
||||
getCellRenderer(rowObject) {
|
||||
return ParentNameCell.options.name;
|
||||
}
|
||||
|
||||
getRawCellValue(rowObject) {
|
||||
if(!rowObject.getParent()) return '';
|
||||
return rowObject.getParent().name || '<No Name>';
|
||||
}
|
||||
|
||||
compareRows(rowObject1, rowObject2) {
|
||||
let parent1 = rowObject1.getParent();
|
||||
let parent2 = rowObject2.getParent();
|
||||
if (parent1 && parent2) {
|
||||
if (parent1.name === parent2.name) {
|
||||
return parent1._id < parent2._id ? -1 : 1;
|
||||
}
|
||||
}
|
||||
return super.compareRows(rowObject1, rowObject2);
|
||||
}
|
||||
}
|
||||
|
||||
export { ParentName }
|
@@ -0,0 +1,13 @@
|
||||
let ColumnBase = pillar.vuecomponents.table.columns.ColumnBase;
|
||||
|
||||
class ShortCode extends ColumnBase {
|
||||
constructor() {
|
||||
super('Short Code', 'short-code');
|
||||
}
|
||||
|
||||
getRawCellValue(rowObject) {
|
||||
return rowObject.getTask().properties.shortcode || '';
|
||||
}
|
||||
}
|
||||
|
||||
export { ShortCode }
|
@@ -0,0 +1,13 @@
|
||||
let ColumnBase = pillar.vuecomponents.table.columns.ColumnBase;
|
||||
|
||||
class TaskType extends ColumnBase {
|
||||
constructor() {
|
||||
super('Type', 'task-type');
|
||||
}
|
||||
|
||||
getRawCellValue(rowObject) {
|
||||
return rowObject.getTask().properties.task_type || '';
|
||||
}
|
||||
}
|
||||
|
||||
export { TaskType }
|
@@ -0,0 +1,27 @@
|
||||
import { Status } from '../../attracttable/columns/Status'
|
||||
import { RowObject } from '../../attracttable/columns/RowObject'
|
||||
import { TaskDueDate } from '../../attracttable/columns/TaskDueDate'
|
||||
import { TaskType } from './TaskType'
|
||||
import { ShortCode } from './ShortCode'
|
||||
import { ParentName } from './ParentName'
|
||||
|
||||
let ColumnFactoryBase = pillar.vuecomponents.table.columns.ColumnFactoryBase;
|
||||
|
||||
|
||||
class TasksColumnFactory extends ColumnFactoryBase{
|
||||
thenGetColumns() {
|
||||
return this.thenGetProject()
|
||||
.then((project) => {
|
||||
return [
|
||||
new Status(),
|
||||
new ParentName(),
|
||||
new RowObject(),
|
||||
new ShortCode(),
|
||||
new TaskType(),
|
||||
new TaskDueDate(),
|
||||
];
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export { TasksColumnFactory }
|
@@ -0,0 +1,29 @@
|
||||
import {AttractRowBase} from '../../attracttable/rows/AttractRowBase'
|
||||
|
||||
class TaskRow extends AttractRowBase {
|
||||
constructor(task) {
|
||||
super(task);
|
||||
this.parent = undefined;
|
||||
if (task.parent) {
|
||||
// Deattach parent from task to avoid parent to be overwritten when task is updated
|
||||
let parentId = task.parent._id;
|
||||
this.parent = task.parent;
|
||||
task.parent = parentId;
|
||||
pillar.events.Nodes.onUpdated(parentId, this.onParentUpdated.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
getTask() {
|
||||
return this.underlyingObject;
|
||||
}
|
||||
|
||||
getParent() {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
onParentUpdated(event, updatedObj) {
|
||||
this.parent = updatedObj;
|
||||
}
|
||||
}
|
||||
|
||||
export { TaskRow }
|
@@ -0,0 +1,18 @@
|
||||
import { AttractRowsSourceBase } from '../../attracttable/rows/AttractRowsSourceBase'
|
||||
import { TaskRow } from './TaskRow'
|
||||
|
||||
class TaskRowsSource extends AttractRowsSourceBase {
|
||||
constructor(projectId) {
|
||||
super(projectId, 'attract_task', TaskRow);
|
||||
}
|
||||
|
||||
thenInit() {
|
||||
return attract.api.thenGetProjectTasks(this.projectId)
|
||||
.then((result) => {
|
||||
let tasks = result._items;
|
||||
this.initRowObjects(tasks);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { TaskRowsSource }
|
Reference in New Issue
Block a user