Vue Attract: Sort/filterable table based on Vue
Initial commit implementing sortable and filterable tables for attract using Vue.
This commit is contained in:
@@ -110,7 +110,7 @@ class ShotAssetManager(object):
|
|||||||
|
|
||||||
node = pillarsdk.Node(node_props)
|
node = pillarsdk.Node(node_props)
|
||||||
node.create(api=api)
|
node.create(api=api)
|
||||||
return node
|
return pillarsdk.Node.find(node._id, api=api)
|
||||||
|
|
||||||
def create_shot(self, project):
|
def create_shot(self, project):
|
||||||
"""Creates a new shot, owned by the current user.
|
"""Creates a new shot, owned by the current user.
|
||||||
|
@@ -24,25 +24,14 @@ log = logging.getLogger(__name__)
|
|||||||
@perproject_blueprint.route('/with-task/<task_id>', endpoint='with_task')
|
@perproject_blueprint.route('/with-task/<task_id>', endpoint='with_task')
|
||||||
@attract_project_view(extension_props=True)
|
@attract_project_view(extension_props=True)
|
||||||
def for_project(project, attract_props, task_id=None, asset_id=None):
|
def for_project(project, attract_props, task_id=None, asset_id=None):
|
||||||
node_type_name = node_type_asset['name']
|
|
||||||
|
|
||||||
assets, tasks_for_assets, task_types_for_template = routes_common.for_project(
|
|
||||||
node_type_name,
|
|
||||||
attract_props['task_types'][node_type_name],
|
|
||||||
project, attract_props, task_id, asset_id)
|
|
||||||
|
|
||||||
can_create = current_attract.auth.current_user_may(current_attract.auth.Actions.USE)
|
can_create = current_attract.auth.current_user_may(current_attract.auth.Actions.USE)
|
||||||
navigation_links = project_navigation_links(project, pillar_api())
|
navigation_links = project_navigation_links(project, pillar_api())
|
||||||
extension_sidebar_links = current_app.extension_sidebar_links(project)
|
extension_sidebar_links = current_app.extension_sidebar_links(project)
|
||||||
|
|
||||||
return render_template('attract/assets/for_project.html',
|
return render_template('attract/assets/for_project.html',
|
||||||
assets=assets,
|
|
||||||
tasks_for_assets=tasks_for_assets,
|
|
||||||
task_types=task_types_for_template,
|
|
||||||
open_task_id=task_id,
|
open_task_id=task_id,
|
||||||
open_asset_id=asset_id,
|
open_asset_id=asset_id,
|
||||||
project=project,
|
project=project,
|
||||||
attract_props=attract_props,
|
|
||||||
can_create_task=can_create,
|
can_create_task=can_create,
|
||||||
can_create_asset=can_create,
|
can_create_asset=can_create,
|
||||||
navigation_links=navigation_links,
|
navigation_links=navigation_links,
|
||||||
@@ -94,7 +83,7 @@ def create_asset(project):
|
|||||||
project_url=project['url'],
|
project_url=project['url'],
|
||||||
asset_id=asset['_id'])
|
asset_id=asset['_id'])
|
||||||
resp.status_code = 201
|
resp.status_code = 201
|
||||||
return flask.make_response(flask.jsonify({'asset_id': asset['_id']}), 201)
|
return flask.make_response(flask.jsonify(asset.to_dict()), 201)
|
||||||
|
|
||||||
|
|
||||||
@perproject_blueprint.route('/<asset_id>/activities')
|
@perproject_blueprint.route('/<asset_id>/activities')
|
||||||
|
@@ -49,7 +49,7 @@ class TaskManager(object):
|
|||||||
|
|
||||||
task = pillarsdk.Node(node_props)
|
task = pillarsdk.Node(node_props)
|
||||||
task.create(api=api)
|
task.create(api=api)
|
||||||
return task
|
return pillarsdk.Node.find(task._id, api=api)
|
||||||
|
|
||||||
def edit_task(self, task_id, **fields):
|
def edit_task(self, task_id, **fields):
|
||||||
"""Edits a task.
|
"""Edits a task.
|
||||||
|
@@ -178,7 +178,7 @@ def create_task(project):
|
|||||||
task_id=task['_id'])
|
task_id=task['_id'])
|
||||||
resp.status_code = 201
|
resp.status_code = 201
|
||||||
|
|
||||||
return flask.make_response(flask.jsonify({'task_id': task['_id']}), 201)
|
return flask.make_response(flask.jsonify(task.to_dict()), 201)
|
||||||
|
|
||||||
|
|
||||||
@perproject_blueprint.route('/<task_id>/activities')
|
@perproject_blueprint.route('/<task_id>/activities')
|
||||||
|
56
gulpfile.js
56
gulpfile.js
@@ -13,6 +13,13 @@ var rename = require('gulp-rename');
|
|||||||
var sass = require('gulp-sass');
|
var sass = require('gulp-sass');
|
||||||
var sourcemaps = require('gulp-sourcemaps');
|
var sourcemaps = require('gulp-sourcemaps');
|
||||||
var uglify = require('gulp-uglify-es').default;
|
var uglify = require('gulp-uglify-es').default;
|
||||||
|
var browserify = require('browserify');
|
||||||
|
var babelify = require('babelify');
|
||||||
|
var sourceStream = require('vinyl-source-stream');
|
||||||
|
var glob = require('glob');
|
||||||
|
var es = require('event-stream');
|
||||||
|
var path = require('path');
|
||||||
|
var buffer = require('vinyl-buffer');
|
||||||
|
|
||||||
var enabled = {
|
var enabled = {
|
||||||
chmod: argv.production,
|
chmod: argv.production,
|
||||||
@@ -73,11 +80,51 @@ gulp.task('scripts', function() {
|
|||||||
.pipe(gulpif(enabled.liveReload, livereload()));
|
.pipe(gulpif(enabled.liveReload, livereload()));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function browserify_base(entry) {
|
||||||
|
let pathSplited = path.dirname(entry).split(path.sep);
|
||||||
|
let moduleName = pathSplited[pathSplited.length - 1];
|
||||||
|
return browserify({
|
||||||
|
entries: [entry],
|
||||||
|
standalone: 'attract.' + moduleName,
|
||||||
|
})
|
||||||
|
.transform(babelify, { "presets": ["@babel/preset-env"] })
|
||||||
|
.bundle()
|
||||||
|
.pipe(gulpif(enabled.failCheck, plumber()))
|
||||||
|
.pipe(sourceStream(path.basename(entry)))
|
||||||
|
.pipe(buffer())
|
||||||
|
.pipe(rename({
|
||||||
|
basename: moduleName,
|
||||||
|
extname: '.min.js'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function browserify_common() {
|
||||||
|
return glob.sync('src/scripts/js/es6/common/**/init.js').map(browserify_base);
|
||||||
|
}
|
||||||
|
|
||||||
|
gulp.task('scripts_browserify', function(done) {
|
||||||
|
glob('src/scripts/js/es6/individual/**/init.js', function(err, files) {
|
||||||
|
if(err) done(err);
|
||||||
|
|
||||||
|
var tasks = files.map(function(entry) {
|
||||||
|
return browserify_base(entry)
|
||||||
|
.pipe(gulpif(enabled.maps, sourcemaps.init()))
|
||||||
|
.pipe(gulpif(enabled.uglify, uglify()))
|
||||||
|
.pipe(gulpif(enabled.maps, sourcemaps.write(".")))
|
||||||
|
.pipe(gulp.dest(destination.js));
|
||||||
|
});
|
||||||
|
|
||||||
|
es.merge(tasks).on('end', done);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
/* Collection of scripts in src/scripts/tutti/ to merge into tutti.min.js */
|
/* Collection of scripts in src/scripts/tutti/ to merge into tutti.min.js */
|
||||||
/* Since it's always loaded, it's only for functions that we want site-wide */
|
/* Since it's always loaded, it's only for functions that we want site-wide */
|
||||||
gulp.task('scripts_tutti', function() {
|
gulp.task('scripts_tutti', function(done) {
|
||||||
gulp.src('src/scripts/tutti/**/*.js')
|
let toUglify = ['src/scripts/tutti/**/*.js']
|
||||||
|
|
||||||
|
es.merge(gulp.src(toUglify), ...browserify_common())
|
||||||
.pipe(gulpif(enabled.failCheck, plumber()))
|
.pipe(gulpif(enabled.failCheck, plumber()))
|
||||||
.pipe(gulpif(enabled.maps, sourcemaps.init()))
|
.pipe(gulpif(enabled.maps, sourcemaps.init()))
|
||||||
.pipe(concat("tutti.min.js"))
|
.pipe(concat("tutti.min.js"))
|
||||||
@@ -86,11 +133,12 @@ gulp.task('scripts_tutti', function() {
|
|||||||
.pipe(gulpif(enabled.chmod, chmod(0o644)))
|
.pipe(gulpif(enabled.chmod, chmod(0o644)))
|
||||||
.pipe(gulp.dest(destination.js))
|
.pipe(gulp.dest(destination.js))
|
||||||
.pipe(gulpif(enabled.liveReload, livereload()));
|
.pipe(gulpif(enabled.liveReload, livereload()));
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// While developing, run 'gulp watch'
|
// While developing, run 'gulp watch'
|
||||||
gulp.task('watch',function() {
|
gulp.task('watch',function(done) {
|
||||||
// Only listen for live reloads if ran with --livereload
|
// Only listen for live reloads if ran with --livereload
|
||||||
if (argv.livereload){
|
if (argv.livereload){
|
||||||
livereload.listen();
|
livereload.listen();
|
||||||
@@ -100,6 +148,8 @@ gulp.task('watch',function() {
|
|||||||
gulp.watch('src/templates/**/*.pug',['templates']);
|
gulp.watch('src/templates/**/*.pug',['templates']);
|
||||||
gulp.watch('src/scripts/*.js',['scripts']);
|
gulp.watch('src/scripts/*.js',['scripts']);
|
||||||
gulp.watch('src/scripts/tutti/*.js',['scripts_tutti']);
|
gulp.watch('src/scripts/tutti/*.js',['scripts_tutti']);
|
||||||
|
gulp.watch('src/scripts/js/**/*.js', ['scripts_browserify', 'scripts_tutti']);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Erases all generated files in output directories.
|
// Erases all generated files in output directories.
|
||||||
|
10
package.json
10
package.json
@@ -7,6 +7,12 @@
|
|||||||
"url": "git://git.blender.org/attract.git"
|
"url": "git://git.blender.org/attract.git"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babel/core": "7.1.6",
|
||||||
|
"@babel/preset-env": "7.1.6",
|
||||||
|
"acorn": "5.7.3",
|
||||||
|
"babel-core": "7.0.0-bridge.0",
|
||||||
|
"babelify": "10.0.0",
|
||||||
|
"browserify": "16.2.3",
|
||||||
"gulp": "^3.9.1",
|
"gulp": "^3.9.1",
|
||||||
"gulp-autoprefixer": "^6.0.0",
|
"gulp-autoprefixer": "^6.0.0",
|
||||||
"gulp-cached": "^1.1.1",
|
"gulp-cached": "^1.1.1",
|
||||||
@@ -21,7 +27,9 @@
|
|||||||
"gulp-sass": "^4.0.1",
|
"gulp-sass": "^4.0.1",
|
||||||
"gulp-sourcemaps": "^2.6.4",
|
"gulp-sourcemaps": "^2.6.4",
|
||||||
"gulp-uglify-es": "^1.0.4",
|
"gulp-uglify-es": "^1.0.4",
|
||||||
"minimist": "^1.2.0"
|
"minimist": "^1.2.0",
|
||||||
|
"vinyl-buffer": "1.0.1",
|
||||||
|
"vinyl-source-stream": "2.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "^4.1.3",
|
"bootstrap": "^4.1.3",
|
||||||
|
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 }
|
@@ -1,28 +1,3 @@
|
|||||||
/**
|
|
||||||
* Removes the task from the task list and shot list, and show the 'task-add-link'
|
|
||||||
* when this was the last task in its category.
|
|
||||||
*/
|
|
||||||
function _remove_task_from_list(task_id) {
|
|
||||||
var $task_link = $('#task-' + task_id)
|
|
||||||
var $task_link_parent = $task_link.parent();
|
|
||||||
$task_link.hideAndRemove(300, function() {
|
|
||||||
if ($task_link_parent.children('.task-link').length == 0) {
|
|
||||||
$task_link_parent.find('.task-add-link').removeClass('hidden');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the 'active' class from any element whose ID starts with
|
|
||||||
* shot-, asset-, or task-.
|
|
||||||
*/
|
|
||||||
function deactivateItemLinks()
|
|
||||||
{
|
|
||||||
$('[id^="shot-"]').removeClass('active');
|
|
||||||
$('[id^="asset-"]').removeClass('active');
|
|
||||||
$('[id^="task-"]').removeClass('active');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open an item such as tasks/shots in the #item-details div
|
* Open an item such as tasks/shots in the #item-details div
|
||||||
*/
|
*/
|
||||||
@@ -39,19 +14,9 @@ function item_open(item_id, item_type, pushState, project_url)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Style elements starting with item_type and dash, e.g. "#shot-uuid"
|
|
||||||
deactivateItemLinks();
|
|
||||||
var current_item = $('#' + item_type + '-' + item_id);
|
|
||||||
current_item.addClass('processing');
|
|
||||||
|
|
||||||
// Special case to highlight the shot row when opening task in shot or asset context
|
// Special case to highlight the shot row when opening task in shot or asset context
|
||||||
var pu_ctx = ProjectUtils.context();
|
var pu_ctx = ProjectUtils.context();
|
||||||
var pc_ctx_shot_asset = (pu_ctx == 'shot' || pu_ctx == 'asset');
|
var pc_ctx_shot_asset = (pu_ctx == 'shot' || pu_ctx == 'asset');
|
||||||
if (pc_ctx_shot_asset && item_type == 'task'){
|
|
||||||
$('[id^="shot-"]').removeClass('active');
|
|
||||||
$('[id^="asset-"]').removeClass('active');
|
|
||||||
$('#task-' + item_id).closest('.table-row').addClass('active');
|
|
||||||
}
|
|
||||||
|
|
||||||
var item_url = '/attract/' + project_url + '/' + item_type + 's/' + item_id;
|
var item_url = '/attract/' + project_url + '/' + item_type + 's/' + item_id;
|
||||||
var push_url = item_url;
|
var push_url = item_url;
|
||||||
@@ -60,22 +25,14 @@ function item_open(item_id, item_type, pushState, project_url)
|
|||||||
}
|
}
|
||||||
item_url += '?context=' + pu_ctx;
|
item_url += '?context=' + pu_ctx;
|
||||||
|
|
||||||
statusBarSet('default', 'Loading ' + item_type + '…');
|
|
||||||
|
|
||||||
$.get(item_url, function(item_data) {
|
$.get(item_url, function(item_data) {
|
||||||
statusBarClear();
|
|
||||||
$('#item-details').html(item_data);
|
$('#item-details').html(item_data);
|
||||||
$('#col_right .col_header span.header_text').text(item_type + ' details');
|
$('#col_right .col_header span.header_text').text(item_type + ' details');
|
||||||
current_item
|
|
||||||
.removeClass('processing newborn')
|
|
||||||
.addClass('active');
|
|
||||||
|
|
||||||
}).fail(function(xhr) {
|
}).fail(function(xhr) {
|
||||||
if (console) {
|
if (console) {
|
||||||
console.log('Error fetching task', item_id, 'from', item_url);
|
console.log('Error fetching task', item_id, 'from', item_url);
|
||||||
console.log('XHR:', xhr);
|
console.log('XHR:', xhr);
|
||||||
current_item
|
|
||||||
.removeClass('processing')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toastr.error('Failed to open ' + item_type);
|
toastr.error('Failed to open ' + item_type);
|
||||||
@@ -141,7 +98,9 @@ function asset_create(project_url)
|
|||||||
};
|
};
|
||||||
|
|
||||||
$.post(url, data, function(asset_data) {
|
$.post(url, data, function(asset_data) {
|
||||||
window.location.href = asset_data.asset_id;
|
/* window.location.href = asset_data.asset_id; */
|
||||||
|
pillar.events.Nodes.triggerCreated(asset_data);
|
||||||
|
asset_open(asset_data._id);
|
||||||
})
|
})
|
||||||
.fail(function(xhr) {
|
.fail(function(xhr) {
|
||||||
if (console) {
|
if (console) {
|
||||||
@@ -152,58 +111,6 @@ function asset_create(project_url)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the task item to the shots/tasks list.
|
|
||||||
*
|
|
||||||
* 'shot_id' can be undefined if the task isn't attached to a shot.
|
|
||||||
*/
|
|
||||||
function task_add(shot_id, task_id, task_type)
|
|
||||||
{
|
|
||||||
if (task_id === undefined || task_type === undefined) {
|
|
||||||
throw new ReferenceError("task_add(" + shot_id + ", " + task_id + ", " + task_type + ") called.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var project_url = ProjectUtils.projectUrl();
|
|
||||||
var url = '/attract/' + project_url + '/tasks/' + task_id;
|
|
||||||
var context = ProjectUtils.context();
|
|
||||||
|
|
||||||
if (context == 'task') {
|
|
||||||
/* WARNING: This is a copy of an element of attract/tasks/for_project .item-list.col-list
|
|
||||||
* If that changes, change this too. */
|
|
||||||
$('.item-list.task').append('\
|
|
||||||
<a class="col-list-item task-list-item status-todo task-link active"\
|
|
||||||
href="' + url + '"\
|
|
||||||
data-task-id="' + task_id + '"\
|
|
||||||
id="task-' + task_id + '">\
|
|
||||||
<span class="status-indicator"></span>\
|
|
||||||
<span class="name">-save your task first-</span>\
|
|
||||||
<span class="due_date">-</span>\
|
|
||||||
</a>\
|
|
||||||
');
|
|
||||||
} else if (context == 'shot' || context == 'asset') {
|
|
||||||
if (shot_id === undefined) {
|
|
||||||
throw new ReferenceError("task_add(" + shot_id + ", " + task_id + ", " + task_type + ") called in " + context + " context.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var $list_cell = $('#' + context + '-' + shot_id + ' .table-cell.task-type.' + task_type);
|
|
||||||
var url = '/attract/' + project_url + '/' + context + 's/with-task/' + task_id;
|
|
||||||
|
|
||||||
/* WARNING: This is a copy of an element of attract/shots/for_project .item-list.col-list
|
|
||||||
* If that changes, change this too. */
|
|
||||||
$list_cell.append('\
|
|
||||||
<a class="status-todo task-link active newborn"\
|
|
||||||
title="-save your task first-"\
|
|
||||||
href="' + url + '"\
|
|
||||||
data-task-id="' + task_id + '"\
|
|
||||||
id="task-' + task_id + '">\
|
|
||||||
</a>\
|
|
||||||
');
|
|
||||||
|
|
||||||
$list_cell.find('.task-add.task-add-link').addClass('hidden');
|
|
||||||
} else {
|
|
||||||
if (console) console.log('task_add: not doing much in context', context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a task and show it in the #item-details div.
|
* Create a task and show it in the #item-details div.
|
||||||
@@ -228,8 +135,8 @@ function task_create(shot_id, task_type)
|
|||||||
|
|
||||||
$.post(url, data, function(task_data) {
|
$.post(url, data, function(task_data) {
|
||||||
if (console) console.log('Task created:', task_data);
|
if (console) console.log('Task created:', task_data);
|
||||||
task_open(task_data.task_id);
|
pillar.events.Nodes.triggerCreated(task_data);
|
||||||
task_add(shot_id, task_data.task_id, task_type);
|
task_open(task_data._id);
|
||||||
})
|
})
|
||||||
.fail(function(xhr) {
|
.fail(function(xhr) {
|
||||||
if (console) {
|
if (console) {
|
||||||
@@ -257,18 +164,15 @@ function attract_form_save(form_id, item_id, item_save_url, options)
|
|||||||
var $item = $('#' + item_id);
|
var $item = $('#' + item_id);
|
||||||
|
|
||||||
$button.attr('disabled', true);
|
$button.attr('disabled', true);
|
||||||
$item.addClass('processing');
|
|
||||||
|
|
||||||
statusBarSet('', 'Saving ' + options.type + '…');
|
|
||||||
|
|
||||||
if (console) console.log('Sending:', payload);
|
if (console) console.log('Sending:', payload);
|
||||||
|
|
||||||
$.post(item_save_url, payload)
|
$.post(item_save_url, payload)
|
||||||
.done(function(saved_item) {
|
.done(function(saved_item) {
|
||||||
if (console) console.log('Done saving', saved_item);
|
if (console) console.log('Done saving', saved_item);
|
||||||
|
toastr.success('Saved ' + options.type + '. ' + saved_item._updated);
|
||||||
|
|
||||||
statusBarSet('success', 'Saved ' + options.type + '. ' + saved_item._updated, 'pi-check');
|
pillar.events.Nodes.triggerUpdated(saved_item);
|
||||||
|
|
||||||
$form.find("input[name='_etag']").val(saved_item._etag);
|
$form.find("input[name='_etag']").val(saved_item._etag);
|
||||||
|
|
||||||
if (options.done) options.done($item, saved_item);
|
if (options.done) options.done($item, saved_item);
|
||||||
@@ -279,13 +183,12 @@ function attract_form_save(form_id, item_id, item_save_url, options)
|
|||||||
|
|
||||||
$button.removeClass('btn-outline-success').addClass('btn-danger');
|
$button.removeClass('btn-outline-success').addClass('btn-danger');
|
||||||
|
|
||||||
statusBarSet('error', 'Failed saving. ' + xhr_or_response_data.status, 'pi-warning');
|
toastr.error('Failed saving. ' + xhr_or_response_data.status);
|
||||||
|
|
||||||
if (options.fail) options.fail($item, xhr_or_response_data);
|
if (options.fail) options.fail($item, xhr_or_response_data);
|
||||||
})
|
})
|
||||||
.always(function() {
|
.always(function() {
|
||||||
$button.attr('disabled', false);
|
$button.attr('disabled', false);
|
||||||
$item.removeClass('processing');
|
|
||||||
|
|
||||||
if (options.always) options.always($item);
|
if (options.always) options.always($item);
|
||||||
})
|
})
|
||||||
@@ -297,21 +200,6 @@ function attract_form_save(form_id, item_id, item_save_url, options)
|
|||||||
function task_save(task_id, task_url) {
|
function task_save(task_id, task_url) {
|
||||||
return attract_form_save('item_form', 'task-' + task_id, task_url, {
|
return attract_form_save('item_form', 'task-' + task_id, task_url, {
|
||||||
done: function($task, saved_task) {
|
done: function($task, saved_task) {
|
||||||
// Update the task list.
|
|
||||||
// NOTE: this is tightly linked to the HTML of the task list in for_project.jade.
|
|
||||||
$('.task-name-' + saved_task._id).text(saved_task.name).flashOnce();
|
|
||||||
$task.find('span.name').text(saved_task.name);
|
|
||||||
$task.find('span.status').text(saved_task.properties.status.replace('_', ' '));
|
|
||||||
if (saved_task.properties.due_date){
|
|
||||||
$task.find('span.due_date').text(moment().to(saved_task.properties.due_date));
|
|
||||||
}
|
|
||||||
|
|
||||||
$task
|
|
||||||
.removeClassPrefix('status-')
|
|
||||||
.addClass('status-' + saved_task.properties.status)
|
|
||||||
.flashOnce()
|
|
||||||
;
|
|
||||||
|
|
||||||
task_open(task_id);
|
task_open(task_id);
|
||||||
},
|
},
|
||||||
fail: function($item, xhr_or_response_data) {
|
fail: function($item, xhr_or_response_data) {
|
||||||
@@ -329,13 +217,6 @@ function task_save(task_id, task_url) {
|
|||||||
function shot_save(shot_id, shot_url) {
|
function shot_save(shot_id, shot_url) {
|
||||||
return attract_form_save('item_form', 'shot-' + shot_id, shot_url, {
|
return attract_form_save('item_form', 'shot-' + shot_id, shot_url, {
|
||||||
done: function($shot, saved_shot) {
|
done: function($shot, saved_shot) {
|
||||||
// Update the shot list.
|
|
||||||
$('.shot-name-' + saved_shot._id).text(saved_shot.name);
|
|
||||||
$shot
|
|
||||||
.removeClassPrefix('status-')
|
|
||||||
.addClass('status-' + saved_shot.properties.status)
|
|
||||||
.flashOnce()
|
|
||||||
;
|
|
||||||
shot_open(shot_id);
|
shot_open(shot_id);
|
||||||
},
|
},
|
||||||
fail: function($item, xhr_or_response_data) {
|
fail: function($item, xhr_or_response_data) {
|
||||||
@@ -353,19 +234,6 @@ function shot_save(shot_id, shot_url) {
|
|||||||
function asset_save(asset_id, asset_url) {
|
function asset_save(asset_id, asset_url) {
|
||||||
return attract_form_save('item_form', 'asset-' + asset_id, asset_url, {
|
return attract_form_save('item_form', 'asset-' + asset_id, asset_url, {
|
||||||
done: function($asset, saved_asset) {
|
done: function($asset, saved_asset) {
|
||||||
// Update the asset list.
|
|
||||||
// NOTE: this is tightly linked to the HTML of the asset list in for_project.jade.
|
|
||||||
$('.item-name-' + saved_asset._id).text(saved_asset.name).flashOnce();
|
|
||||||
$asset.find('span.name').text(saved_asset.name);
|
|
||||||
$asset.find('span.due_date').text(moment().to(saved_asset.properties.due_date));
|
|
||||||
$asset.find('span.status').text(saved_asset.properties.status.replace('_', ' '));
|
|
||||||
|
|
||||||
$asset
|
|
||||||
.removeClassPrefix('status-')
|
|
||||||
.addClass('status-' + saved_asset.properties.status)
|
|
||||||
.flashOnce()
|
|
||||||
;
|
|
||||||
|
|
||||||
asset_open(asset_id);
|
asset_open(asset_id);
|
||||||
},
|
},
|
||||||
fail: function($item, xhr_or_response_data) {
|
fail: function($item, xhr_or_response_data) {
|
||||||
@@ -384,9 +252,6 @@ function task_delete(task_id, task_etag, task_delete_url) {
|
|||||||
if (task_id === undefined || task_etag === undefined || task_delete_url === undefined) {
|
if (task_id === undefined || task_etag === undefined || task_delete_url === undefined) {
|
||||||
throw new ReferenceError("task_delete(" + task_id + ", " + task_etag + ", " + task_delete_url + ") called.");
|
throw new ReferenceError("task_delete(" + task_id + ", " + task_etag + ", " + task_delete_url + ") called.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#task-' + task_id).addClass('processing');
|
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'DELETE',
|
type: 'DELETE',
|
||||||
url: task_delete_url,
|
url: task_delete_url,
|
||||||
@@ -395,13 +260,12 @@ function task_delete(task_id, task_etag, task_delete_url) {
|
|||||||
.done(function(e) {
|
.done(function(e) {
|
||||||
if (console) console.log('Task', task_id, 'was deleted.');
|
if (console) console.log('Task', task_id, 'was deleted.');
|
||||||
$('#item-details').fadeOutAndClear();
|
$('#item-details').fadeOutAndClear();
|
||||||
_remove_task_from_list(task_id);
|
pillar.events.Nodes.triggerDeleted(task_id);
|
||||||
|
|
||||||
statusBarSet('success', 'Task deleted successfully', 'pi-check');
|
toastr.success('Task deleted');
|
||||||
})
|
})
|
||||||
.fail(function(xhr) {
|
.fail(function(xhr) {
|
||||||
|
toastr.error('Unable to delete task, code ' + xhr.status);
|
||||||
statusBarSet('error', 'Unable to delete task, code ' + xhr.status, 'pi-warning');
|
|
||||||
|
|
||||||
if (xhr.status == 412) {
|
if (xhr.status == 412) {
|
||||||
alert('Someone else edited this task before you deleted it; refresh to try again.');
|
alert('Someone else edited this task before you deleted it; refresh to try again.');
|
||||||
@@ -429,7 +293,7 @@ function loadActivities(url)
|
|||||||
console.log('XHR:', xhr);
|
console.log('XHR:', xhr);
|
||||||
}
|
}
|
||||||
|
|
||||||
statusBarSet('error', 'Opening activity log failed.', 'pi-warning');
|
toastr.error('Opening activity log failed.');
|
||||||
|
|
||||||
if (xhr.status) {
|
if (xhr.status) {
|
||||||
$('#activities').html(xhr.responseText);
|
$('#activities').html(xhr.responseText);
|
||||||
@@ -441,31 +305,6 @@ function loadActivities(url)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$(function() {
|
|
||||||
$("a.shot-link[data-shot-id]").click(function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
// delegateTarget is the thing the event hander was attached to,
|
|
||||||
// rather than the thing we clicked on.
|
|
||||||
var shot_id = e.delegateTarget.dataset.shotId;
|
|
||||||
shot_open(shot_id);
|
|
||||||
});
|
|
||||||
|
|
||||||
$("a.asset-link[data-asset-id]").click(function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
// delegateTarget is the thing the event hander was attached to,
|
|
||||||
// rather than the thing we clicked on.
|
|
||||||
var asset_id = e.delegateTarget.dataset.assetId;
|
|
||||||
asset_open(asset_id);
|
|
||||||
});
|
|
||||||
|
|
||||||
$("a.task-link[data-task-id]").click(function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
var task_id = e.delegateTarget.dataset.taskId;
|
|
||||||
var project_url = e.delegateTarget.dataset.projectUrl; // fine if undefined
|
|
||||||
task_open(task_id, project_url);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var save_on_ctrl_enter = ['shot', 'asset', 'task'];
|
var save_on_ctrl_enter = ['shot', 'asset', 'task'];
|
||||||
$(document).on('keyup', function(e){
|
$(document).on('keyup', function(e){
|
||||||
if ($.inArray(save_on_ctrl_enter, ProjectUtils.context())) {
|
if ($.inArray(save_on_ctrl_enter, ProjectUtils.context())) {
|
||||||
|
188
src/styles/components/_attract_table.sass
Normal file
188
src/styles/components/_attract_table.sass
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
$thumbnail-max-width: 110px
|
||||||
|
$thumbnail-max-height: calc(110px * (9/16))
|
||||||
|
|
||||||
|
.pillar-table-container
|
||||||
|
background-color: white
|
||||||
|
height: 100%
|
||||||
|
|
||||||
|
.pillar-table
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
width: 100%
|
||||||
|
height: 95% // TODO: Investigate why some rows are outside screen if 100%
|
||||||
|
|
||||||
|
.pillar-table-head
|
||||||
|
display: flex
|
||||||
|
flex-direction: row
|
||||||
|
position: relative
|
||||||
|
box-shadow: 0 5 $color-background-dark
|
||||||
|
|
||||||
|
.cell-content
|
||||||
|
display: flex
|
||||||
|
flex-direction: row
|
||||||
|
align-items: center
|
||||||
|
height: 100%
|
||||||
|
font-size: .9em
|
||||||
|
|
||||||
|
.column-sort
|
||||||
|
display: flex
|
||||||
|
opacity: 0
|
||||||
|
flex-direction: column
|
||||||
|
|
||||||
|
.sort-action
|
||||||
|
&:hover
|
||||||
|
background-color: $color-background-active
|
||||||
|
&:hover
|
||||||
|
.column-sort
|
||||||
|
opacity: 1
|
||||||
|
|
||||||
|
.pillar-table-row-group
|
||||||
|
display: block
|
||||||
|
overflow-y: auto
|
||||||
|
height: 100%
|
||||||
|
|
||||||
|
.pillar-table-row:nth-child(odd)
|
||||||
|
background-color: $color-background-dark
|
||||||
|
|
||||||
|
.pillar-table-row
|
||||||
|
display: flex
|
||||||
|
flex-direction: row
|
||||||
|
transition: all 250ms ease-in-out
|
||||||
|
background-color: $color-background-light
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background-color: $color-background-active
|
||||||
|
|
||||||
|
&.is-busy
|
||||||
|
+stripes-animate
|
||||||
|
+stripes(transparent, rgba($color-background-active, .6), -45deg, 4em)
|
||||||
|
animation-duration: 4s
|
||||||
|
|
||||||
|
.pillar-table-container.attract-tasks-table
|
||||||
|
.pillar-table-row
|
||||||
|
min-height: 1.4em
|
||||||
|
|
||||||
|
.pillar-table-container.attract-shots-table
|
||||||
|
.pillar-table-row
|
||||||
|
min-height: $thumbnail-max-height
|
||||||
|
|
||||||
|
&.shot-not-in-edit
|
||||||
|
+stripes(transparent, rgba($color-warning, .2), -45deg, 4em)
|
||||||
|
|
||||||
|
.pillar-table-container.attract-assets-table
|
||||||
|
.pillar-table-row
|
||||||
|
min-height: 2.6em
|
||||||
|
|
||||||
|
.pillar-cell
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
flex-grow: 1
|
||||||
|
flex-basis: 0
|
||||||
|
overflow: hidden
|
||||||
|
white-space: nowrap
|
||||||
|
text-overflow: ellipsis
|
||||||
|
justify-content: center
|
||||||
|
|
||||||
|
&.highlight
|
||||||
|
background-color: rgba($color-background-active, .4)
|
||||||
|
|
||||||
|
&.warning
|
||||||
|
background-color: rgba($color-warning, .4)
|
||||||
|
|
||||||
|
&.header-cell
|
||||||
|
text-transform: capitalize
|
||||||
|
color: $color-text-dark-secondary
|
||||||
|
|
||||||
|
&.attract-status
|
||||||
|
flex: 0
|
||||||
|
flex-basis: 1em
|
||||||
|
|
||||||
|
&.thumbnail
|
||||||
|
flex: 0
|
||||||
|
flex-basis: $thumbnail-max-width
|
||||||
|
text-align: center
|
||||||
|
|
||||||
|
img
|
||||||
|
max-width: $thumbnail-max-width
|
||||||
|
height: auto
|
||||||
|
|
||||||
|
&.task-type
|
||||||
|
text-transform: capitalize
|
||||||
|
|
||||||
|
a
|
||||||
|
overflow: hidden
|
||||||
|
text-overflow: ellipsis
|
||||||
|
|
||||||
|
@include status-color-property(background-color, '', '')
|
||||||
|
|
||||||
|
.add-task-link
|
||||||
|
opacity: 0
|
||||||
|
cursor: pointer
|
||||||
|
vertical-align: middle
|
||||||
|
color: $color-primary
|
||||||
|
border: none
|
||||||
|
background: none
|
||||||
|
width: max-content
|
||||||
|
padding: 0
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
text-decoration-line: underline
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
.add-task-link
|
||||||
|
opacity: 1
|
||||||
|
.tasks
|
||||||
|
display: flex
|
||||||
|
.task
|
||||||
|
@include status-color-property(background-color, '', '')
|
||||||
|
width: 1em
|
||||||
|
height: 1em
|
||||||
|
border-radius: 1em
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
box-shadow: inset 0px 0px 5px $color-background-active-dark
|
||||||
|
|
||||||
|
|
||||||
|
.pillar-table-menu
|
||||||
|
display: flex
|
||||||
|
flex-direction: row
|
||||||
|
|
||||||
|
.settings-menu
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
position: absolute
|
||||||
|
background-color: white
|
||||||
|
list-style: none
|
||||||
|
margin: 0
|
||||||
|
padding: 0
|
||||||
|
text-transform: capitalize
|
||||||
|
z-index: $z-index-base + 1
|
||||||
|
box-shadow: 0 2px 5px rgba(black, .4)
|
||||||
|
|
||||||
|
.pillar-table-row-filter
|
||||||
|
display: flex
|
||||||
|
flex-direction: row
|
||||||
|
|
||||||
|
.pillar-table-actions
|
||||||
|
margin-left: auto
|
||||||
|
|
||||||
|
.action
|
||||||
|
cursor: pointer
|
||||||
|
vertical-align: middle
|
||||||
|
color: $color-primary
|
||||||
|
border: none
|
||||||
|
background: none
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
text-decoration-line: underline
|
||||||
|
|
||||||
|
.pillar-table-column-filter
|
||||||
|
margin-left: auto
|
||||||
|
.settings-menu
|
||||||
|
right: 0em
|
||||||
|
|
||||||
|
.pillar-table-row-item
|
||||||
|
display: inline-block
|
||||||
|
|
||||||
|
.pillar-table-row-enter, .pillar-table-row-leave-to
|
||||||
|
opacity: 0
|
@@ -55,6 +55,9 @@
|
|||||||
@import _app_utils
|
@import _app_utils
|
||||||
@import _app_base
|
@import _app_base
|
||||||
|
|
||||||
|
// Attract components
|
||||||
|
@import "components/attract_table"
|
||||||
|
|
||||||
@import _tasks
|
@import _tasks
|
||||||
@import _shots
|
@import _shots
|
||||||
@import _dashboard
|
@import _dashboard
|
||||||
|
@@ -3,77 +3,9 @@
|
|||||||
| {% block page_title %}Assets - {{ project.name }}{% endblock %}
|
| {% block page_title %}Assets - {{ project.name }}{% endblock %}
|
||||||
| {% block attractbody %}
|
| {% block attractbody %}
|
||||||
#col_main
|
#col_main
|
||||||
.col_header.item-list-header
|
attract-assets-table#table(
|
||||||
a.item-project(href="{{url_for('projects.view', project_url=project.url)}}") {{ project.name }}
|
project-id="{{ project._id}}"
|
||||||
span.item-extra Assets ({{ assets | count }})
|
)
|
||||||
| {% if can_create_asset %}
|
|
||||||
a#item-add(href="javascript:asset_create('{{ project.url }}');") + Create Asset
|
|
||||||
| {% endif %}
|
|
||||||
|
|
||||||
.item-list.asset.col-scrollable
|
|
||||||
.table
|
|
||||||
.table-head.is-fixed
|
|
||||||
.table-head.original
|
|
||||||
.table-row
|
|
||||||
.table-cell.asset-status
|
|
||||||
| {# disabled until users can actually upload a thumbnail
|
|
||||||
.table-cell.item-thumbnail
|
|
||||||
span.collapser.thumbnails(title="Collapse thumbnails") Thumbnail
|
|
||||||
| #}
|
|
||||||
.table-cell.item-name
|
|
||||||
span.collapser(title="Collapse name column") Name
|
|
||||||
| {% for task_type in task_types %}
|
|
||||||
.table-cell.task-type(class="{{ task_type }}")
|
|
||||||
span.collapser(title="Collapse {{ task_type or 'Other' }} column") {{ task_type or 'other' }}
|
|
||||||
| {% endfor %}
|
|
||||||
|
|
||||||
.table-body
|
|
||||||
| {% for asset in assets %}
|
|
||||||
.table-row(
|
|
||||||
id="asset-{{ asset._id }}",
|
|
||||||
class="status-{{ asset.properties.status }} {{ asset.properties.used_in_edit | yesno(' ,not-in-edit, ') }}")
|
|
||||||
.table-cell.item-status(
|
|
||||||
title="Status: {{ asset.properties.status | undertitle }}")
|
|
||||||
| {# disabled until users can actually upload a thumbnail
|
|
||||||
.table-cell.item-thumbnail
|
|
||||||
a(
|
|
||||||
data-asset-id="{{ asset._id }}",
|
|
||||||
href="{{ url_for('attract.assets.perproject.view_asset', project_url=project.url, asset_id=asset._id) }}",
|
|
||||||
class="status-{{ asset.properties.status }} asset-link")
|
|
||||||
img(src="{{ asset._thumbnail }}",
|
|
||||||
alt="Thumbnail",
|
|
||||||
style='width: 110px; height: {{ asset._thumbnail_height }}')
|
|
||||||
| #}
|
|
||||||
.table-cell.item-name
|
|
||||||
a(
|
|
||||||
data-asset-id="{{ asset._id }}",
|
|
||||||
href="{{ url_for('attract.assets.perproject.view_asset', project_url=project.url, asset_id=asset._id) }}",
|
|
||||||
class="status-{{ asset.properties.status }} asset-link")
|
|
||||||
span(class="item-name-{{ asset._id }}") {{ asset.name }}
|
|
||||||
| {% for task_type in task_types %}
|
|
||||||
.table-cell.task-type(class="{{ task_type }}")
|
|
||||||
| {% for task in tasks_for_assets[asset._id][task_type] %}
|
|
||||||
a(
|
|
||||||
data-task-id="{{ task._id }}",
|
|
||||||
id="task-{{ task._id }}",
|
|
||||||
href="{{ url_for('attract.assets.perproject.with_task', project_url=project.url, task_id=task._id) }}",
|
|
||||||
class="status-{{ task.properties.status }} task-link",
|
|
||||||
title="{{ task.properties.status | undertitle }} task: {{ task.name }}")
|
|
||||||
| {# First letter of the status. Disabled until we provide the user setting to turn it off
|
|
||||||
span {{ task.properties.status[0] }}
|
|
||||||
| #}
|
|
||||||
| {% endfor %}
|
|
||||||
| {% if can_create_task %}
|
|
||||||
button.task-add(
|
|
||||||
title="Add a new '{{ task_type }}' task",
|
|
||||||
class="task-add-link {% if tasks_for_assets[asset._id][task_type] %}hidden{% endif %}",
|
|
||||||
data-parent-id='{{ asset._id }}',
|
|
||||||
data-task-type='{{ task_type }}')
|
|
||||||
i.pi-plus
|
|
||||||
| Task
|
|
||||||
| {% endif %}
|
|
||||||
| {% endfor %}
|
|
||||||
| {% endfor %}
|
|
||||||
|
|
||||||
.col-splitter
|
.col-splitter
|
||||||
|
|
||||||
@@ -83,10 +15,19 @@
|
|||||||
#item-details.col-scrollable
|
#item-details.col-scrollable
|
||||||
.item-details-empty
|
.item-details-empty
|
||||||
| Select an Asset or Task
|
| Select an Asset or Task
|
||||||
|
|
||||||
| {% endblock %}
|
| {% endblock %}
|
||||||
|
|
||||||
| {% block footer_scripts %}
|
| {% block footer_scripts %}
|
||||||
|
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/clipboard.min.js')}}")
|
||||||
|
script(src="{{ url_for('static_attract', filename='assets/js/vendor/jquery-resizable-0.20.min.js')}}")
|
||||||
|
|
||||||
script.
|
script.
|
||||||
|
{% if can_create_task %}
|
||||||
|
attract.auth.AttractAuth.setUserCanCreateTask('{{project._id}}', true);
|
||||||
|
{% endif %}
|
||||||
|
{% if can_create_asset %}
|
||||||
|
attract.auth.AttractAuth.setUserCanCreateAsset('{{project._id}}', true);
|
||||||
|
{% endif %}
|
||||||
{% if open_task_id %}
|
{% if open_task_id %}
|
||||||
$(function() { item_open('{{ open_task_id }}', 'task', false); });
|
$(function() { item_open('{{ open_task_id }}', 'task', false); });
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -94,73 +35,10 @@ script.
|
|||||||
$(function() { item_open('{{ open_asset_id }}', 'asset', false); });
|
$(function() { item_open('{{ open_asset_id }}', 'asset', false); });
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
$('button.task-add').on('click', function(e){
|
|
||||||
e.preventDefault();
|
|
||||||
var parent_id = $(this).attr('data-parent-id');
|
|
||||||
var task_type = $(this).attr('data-task-type');
|
|
||||||
|
|
||||||
task_create(parent_id, task_type);
|
|
||||||
});
|
|
||||||
|
|
||||||
var same_cells;
|
|
||||||
|
|
||||||
/* Collapse columns by clicking on the title */
|
|
||||||
$('.table-head .table-cell span.collapser').on('click', function(e){
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
/* We need to find every cell matching the same classes */
|
|
||||||
same_cells = '.' + $(this).parent().attr('class').split(' ').join('.');
|
|
||||||
$(same_cells).hide();
|
|
||||||
/* Add the spacer which we later click to expand */
|
|
||||||
$('<div class="table-cell-spacer ' + $(this).text() + '" title="Expand ' + $(this).text() + '"></div>').insertAfter(same_cells);
|
|
||||||
});
|
|
||||||
|
|
||||||
$('body').on('click', '.table-cell-spacer', function(){
|
|
||||||
|
|
||||||
/* We need to find every cell matching the same classes */
|
|
||||||
same_cells = '.' + $(this).prev().attr('class').split(' ').join('.');
|
|
||||||
$(same_cells).show();
|
|
||||||
$(same_cells).next().remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.table-body .table-cell').mouseenter(function(){
|
|
||||||
same_cells = '.' + $(this).attr('class').split(' ').join('.');
|
|
||||||
$('.table-head ' + same_cells).addClass('highlight');
|
|
||||||
}).mouseleave(function(){
|
|
||||||
same_cells = '.' + $(this).attr('class').split(' ').join('.');
|
|
||||||
$('.table-head ' + same_cells).removeClass('highlight');
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.table-head .table-cell').mouseenter(function(){
|
|
||||||
same_cells = '.' + $(this).attr('class').split(' ').join('.');
|
|
||||||
$('.table-body ' + same_cells).addClass('highlight');
|
|
||||||
}).mouseleave(function(){
|
|
||||||
same_cells = '.' + $(this).attr('class').split(' ').join('.');
|
|
||||||
$('.table-body ' + same_cells).removeClass('highlight');
|
|
||||||
});
|
|
||||||
|
|
||||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/clipboard.min.js')}}")
|
|
||||||
script(src="{{ url_for('static_attract', filename='assets/js/vendor/jquery-resizable-0.20.min.js')}}")
|
|
||||||
script.
|
script.
|
||||||
// The fixed table header situation, check out 00_utils.js
|
new Vue({el:'#table'})
|
||||||
cloneChildren('.item-list .table-head .table-row', '.item-list .table-head.is-fixed');
|
|
||||||
scrollHeaderHorizontal('.item-list', '.item-list .table-head.is-fixed', 50);
|
|
||||||
// For every column, set the width of the fixed header using the original columns width
|
|
||||||
setHeaderCellsWidth('.item-list .table-head.original .table-row', '.item-list .table-head.is-fixed .table-row');
|
|
||||||
|
|
||||||
$("#col_main").resizable({
|
$("#col_main").resizable({
|
||||||
handleSelector: ".col-splitter",
|
handleSelector: ".col-splitter",
|
||||||
resizeHeight: false,
|
resizeHeight: false,
|
||||||
onDrag: function () {
|
|
||||||
setHeaderCellsWidth('.item-list .table-head.original .table-row', '.item-list .table-head.is-fixed .table-row');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set height of asset-list and item details so we can scroll inside them
|
|
||||||
$(window).on('load resize', function(){
|
|
||||||
var window_height = $(window).height() - 50; // header is 50px
|
|
||||||
$('#shot-list').css({'height': window_height});
|
|
||||||
$('#item-details').css({'height': window_height});
|
|
||||||
});
|
|
||||||
|
|
||||||
| {% endblock footer_scripts %}
|
| {% endblock footer_scripts %}
|
||||||
|
@@ -92,7 +92,7 @@ script.
|
|||||||
var clipboard = new Clipboard('.copy-to-clipboard');
|
var clipboard = new Clipboard('.copy-to-clipboard');
|
||||||
|
|
||||||
clipboard.on('success', function(e) {
|
clipboard.on('success', function(e) {
|
||||||
statusBarSet('info', 'Copied asset ID to clipboard', 'pi-check');
|
toastr.info('Copied asset ID to clipboard')
|
||||||
});
|
});
|
||||||
|
|
||||||
var activities_url = "{{ url_for('.activities', project_url=project.url, asset_id=asset['_id']) }}";
|
var activities_url = "{{ url_for('.activities', project_url=project.url, asset_id=asset['_id']) }}";
|
||||||
|
@@ -3,71 +3,9 @@
|
|||||||
| {% block page_title %}Shots - {{ project.name }}{% endblock %}
|
| {% block page_title %}Shots - {{ project.name }}{% endblock %}
|
||||||
| {% block attractbody %}
|
| {% block attractbody %}
|
||||||
#col_main
|
#col_main
|
||||||
.col_header.item-list-header
|
attract-shots-table#table(
|
||||||
a.item-project(href="{{url_for('projects.view', project_url=project.url)}}") {{ project.name }}
|
project-id="{{ project._id}}"
|
||||||
span.item-extra(title='In the edit') {{ stats.nr_of_shots }} shots | {{ stats.total_frame_count }} frames
|
)
|
||||||
|
|
||||||
.item-list.shot.col-scrollable
|
|
||||||
.table
|
|
||||||
.table-head.is-fixed
|
|
||||||
.table-head.original
|
|
||||||
.table-row
|
|
||||||
.table-cell.item-status
|
|
||||||
.table-cell.item-thumbnail
|
|
||||||
span.collapser.thumbnails(title="Collapse thumbnails") Thumbnail
|
|
||||||
.table-cell.item-name
|
|
||||||
span.collapser(title="Collapse name column") Name
|
|
||||||
| {% for task_type in task_types %}
|
|
||||||
.table-cell.task-type(class="{{ task_type }}")
|
|
||||||
span.collapser(title="Collapse {{ task_type or 'Other' }} column") {{ task_type or 'other' }}
|
|
||||||
| {% endfor %}
|
|
||||||
|
|
||||||
.table-body
|
|
||||||
| {% for shot in shots %}
|
|
||||||
.table-row(
|
|
||||||
id="shot-{{ shot._id }}",
|
|
||||||
class="status-{{ shot.properties.status }} {{ shot.properties.used_in_edit | yesno(' ,not-in-edit, ') }}")
|
|
||||||
.table-cell.item-status(
|
|
||||||
title="Status: {{ shot.properties.status | undertitle }}")
|
|
||||||
.table-cell.item-thumbnail
|
|
||||||
a(
|
|
||||||
data-shot-id="{{ shot._id }}",
|
|
||||||
href="{{ url_for('attract.shots.perproject.view_shot', project_url=project.url, shot_id=shot._id) }}",
|
|
||||||
class="status-{{ shot.properties.status }} shot-link")
|
|
||||||
img(src="{{ shot._thumbnail }}",
|
|
||||||
alt="Thumbnail",
|
|
||||||
style='width: 110px; height: {{ shot._thumbnail_height }}')
|
|
||||||
.table-cell.item-name
|
|
||||||
a(
|
|
||||||
data-shot-id="{{ shot._id }}",
|
|
||||||
href="{{ url_for('attract.shots.perproject.view_shot', project_url=project.url, shot_id=shot._id) }}",
|
|
||||||
class="status-{{ shot.properties.status }} shot-link")
|
|
||||||
span(class="item-name-{{ shot._id }}") {{ shot.name }}
|
|
||||||
| {% for task_type in task_types %}
|
|
||||||
.table-cell.task-type(class="{{ task_type }}")
|
|
||||||
| {% for task in tasks_for_shots[shot._id][task_type] %}
|
|
||||||
a(
|
|
||||||
data-task-id="{{ task._id }}",
|
|
||||||
id="task-{{ task._id }}",
|
|
||||||
href="{{ url_for('attract.shots.perproject.with_task', project_url=project.url, task_id=task._id) }}",
|
|
||||||
class="status-{{ task.properties.status }} task-link",
|
|
||||||
title="{{ task.properties.status | undertitle }} task: {{ task.name }}")
|
|
||||||
| {# First letter of the status. Disabled until we provide the user setting to turn it off
|
|
||||||
span {{ task.properties.status[0] }}
|
|
||||||
| #}
|
|
||||||
| {% endfor %}
|
|
||||||
| {% if can_create_task %}
|
|
||||||
button.task-add(
|
|
||||||
title="Add a new '{{ task_type }}' task",
|
|
||||||
class="task-add-link {% if tasks_for_shots[shot._id][task_type] %}hidden{% endif %}"
|
|
||||||
data-parent-id='{{ shot._id }}',
|
|
||||||
data-task-type='{{ task_type }}')
|
|
||||||
i.pi-plus
|
|
||||||
| Task
|
|
||||||
| {% endif %}
|
|
||||||
| {% endfor %}
|
|
||||||
| {% endfor %}
|
|
||||||
|
|
||||||
|
|
||||||
.col-splitter
|
.col-splitter
|
||||||
|
|
||||||
@@ -77,13 +15,16 @@
|
|||||||
#item-details.col-scrollable
|
#item-details.col-scrollable
|
||||||
.item-details-empty
|
.item-details-empty
|
||||||
| Select a Shot or Task
|
| Select a Shot or Task
|
||||||
|
|
||||||
| {% endblock %}
|
| {% endblock %}
|
||||||
|
|
||||||
| {% block footer_scripts %}
|
| {% block footer_scripts %}
|
||||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/clipboard.min.js')}}")
|
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/clipboard.min.js')}}")
|
||||||
script(src="{{ url_for('static_attract', filename='assets/js/vendor/jquery-resizable-0.20.min.js')}}")
|
script(src="{{ url_for('static_attract', filename='assets/js/vendor/jquery-resizable-0.20.min.js')}}")
|
||||||
script.
|
|
||||||
|
|
||||||
|
script.
|
||||||
|
{% if can_create_task %}
|
||||||
|
attract.auth.AttractAuth.setUserCanCreateTask('{{project._id}}', true);
|
||||||
|
{% endif %}
|
||||||
// Open task or shot on load
|
// Open task or shot on load
|
||||||
{% if open_task_id %}
|
{% if open_task_id %}
|
||||||
$(function() { item_open('{{ open_task_id }}', 'task', false); });
|
$(function() { item_open('{{ open_task_id }}', 'task', false); });
|
||||||
@@ -92,70 +33,10 @@ script.
|
|||||||
$(function() { item_open('{{ open_shot_id }}', 'shot', false); });
|
$(function() { item_open('{{ open_shot_id }}', 'shot', false); });
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
script.
|
||||||
// Create tasks
|
new Vue({el:'#table'})
|
||||||
$('button.task-add').on('click', function(e){
|
|
||||||
e.preventDefault();
|
|
||||||
var parent_id = $(this).attr('data-parent-id');
|
|
||||||
var task_type = $(this).attr('data-task-type');
|
|
||||||
|
|
||||||
task_create(parent_id, task_type);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/* Collapse columns by clicking on the title */
|
|
||||||
var same_cells;
|
|
||||||
|
|
||||||
$('.table-head .table-cell span.collapser').on('click', function(e){
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
/* We need to find every cell matching the same classes */
|
|
||||||
same_cells = '.' + $(this).parent().attr('class').split(' ').join('.');
|
|
||||||
$(same_cells).hide();
|
|
||||||
/* Add the spacer which we later click to expand */
|
|
||||||
$('<div class="table-cell-spacer ' + $(this).text() + '" title="Expand ' + $(this).text() + '"></div>').insertAfter(same_cells);
|
|
||||||
|
|
||||||
// For every column, set the width of the fixed header using the original columns width
|
|
||||||
setHeaderCellsWidth('.item-list .table-head.original .table-row', '.item-list .table-head.is-fixed .table-row');
|
|
||||||
});
|
|
||||||
|
|
||||||
$('body').on('click', '.table-cell-spacer', function(){
|
|
||||||
|
|
||||||
/* We need to find every cell matching the same classes */
|
|
||||||
same_cells = '.' + $(this).prev().attr('class').split(' ').join('.');
|
|
||||||
$(same_cells).show();
|
|
||||||
$(same_cells).next().remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.table-body .table-cell').mouseenter(function(){
|
|
||||||
same_cells = '.' + $(this).attr('class').split(' ').join('.');
|
|
||||||
$('.table-head ' + same_cells).addClass('highlight');
|
|
||||||
}).mouseleave(function(){
|
|
||||||
same_cells = '.' + $(this).attr('class').split(' ').join('.');
|
|
||||||
$('.table-head ' + same_cells).removeClass('highlight');
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.table-head .table-cell').mouseenter(function(){
|
|
||||||
same_cells = '.' + $(this).attr('class').split(' ').join('.');
|
|
||||||
$('.table-body ' + same_cells).addClass('highlight');
|
|
||||||
}).mouseleave(function(){
|
|
||||||
same_cells = '.' + $(this).attr('class').split(' ').join('.');
|
|
||||||
$('.table-body ' + same_cells).removeClass('highlight');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// The fixed table header situation, check out 00_utils.js
|
|
||||||
cloneChildren('.item-list .table-head .table-row', '.item-list .table-head.is-fixed');
|
|
||||||
scrollHeaderHorizontal('.item-list', '.item-list .table-head.is-fixed', 50);
|
|
||||||
// For every column, set the width of the fixed header using the original columns width
|
|
||||||
setHeaderCellsWidth('.item-list .table-head.original .table-row', '.item-list .table-head.is-fixed .table-row');
|
|
||||||
|
|
||||||
$("#col_main").resizable({
|
$("#col_main").resizable({
|
||||||
handleSelector: ".col-splitter",
|
handleSelector: ".col-splitter",
|
||||||
resizeHeight: false,
|
resizeHeight: false,
|
||||||
onDrag: function () {
|
|
||||||
setHeaderCellsWidth('.item-list .table-head.original .table-row', '.item-list .table-head.is-fixed .table-row');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
| {% endblock footer_scripts %}
|
| {% endblock footer_scripts %}
|
||||||
|
@@ -124,7 +124,7 @@ script.
|
|||||||
var clipboard = new Clipboard('.copy-to-clipboard');
|
var clipboard = new Clipboard('.copy-to-clipboard');
|
||||||
|
|
||||||
clipboard.on('success', function(e) {
|
clipboard.on('success', function(e) {
|
||||||
statusBarSet('info', 'Copied shot ID to clipboard', 'pi-check');
|
toastr.info('Copied shot ID to clipboard')
|
||||||
});
|
});
|
||||||
|
|
||||||
var activities_url = "{{ url_for('.activities', project_url=project.url, shot_id=shot['_id']) }}";
|
var activities_url = "{{ url_for('.activities', project_url=project.url, shot_id=shot['_id']) }}";
|
||||||
|
@@ -4,27 +4,9 @@
|
|||||||
| {% block attractbody %}
|
| {% block attractbody %}
|
||||||
|
|
||||||
#col_main
|
#col_main
|
||||||
.col_header.item-list-header
|
attract-tasks-table#table(
|
||||||
a.item-project(href="{{url_for('projects.view', project_url=project.url)}}") {{ project.name }}
|
project-id="{{ project._id}}"
|
||||||
span.item-extra Tasks ({{ tasks | count }})
|
)
|
||||||
| {% if can_create_task %}
|
|
||||||
a#item-add(href="javascript:task_create(undefined, 'generic');") + Create Task
|
|
||||||
| {% endif %}
|
|
||||||
.item-list.task.col-list.col-scrollable
|
|
||||||
| {% for task in tasks %}
|
|
||||||
//- NOTE: this is tightly linked to the JS in tasks.js, function task_add()
|
|
||||||
a.col-list-item.task-list-item(
|
|
||||||
id="task-{{task._id}}",
|
|
||||||
data-task-id="{{task._id}}",
|
|
||||||
class="status-{{ task.properties.status }} task-link",
|
|
||||||
href="{{ url_for('attract.tasks.perproject.view_task', project_url=project.url, task_id=task._id) }}")
|
|
||||||
span.status-indicator(title="Status: {{ task.properties.status | undertitle }}")
|
|
||||||
| {% if task._parent_info %}
|
|
||||||
span.shotname(title="Shot {{ task._parent_info.name }}") {{ task._parent_info.name }}
|
|
||||||
| {% endif %}
|
|
||||||
span.name {{ task.name }}
|
|
||||||
span.due_date {{ task.properties.due_date | pretty_date | hide_none }}
|
|
||||||
| {% endfor %}
|
|
||||||
|
|
||||||
.col-splitter
|
.col-splitter
|
||||||
|
|
||||||
@@ -41,13 +23,17 @@ script.
|
|||||||
{% if open_task_id %}
|
{% if open_task_id %}
|
||||||
$(function() { item_open('{{ open_task_id }}', 'task', false); });
|
$(function() { item_open('{{ open_task_id }}', 'task', false); });
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/clipboard.min.js')}}")
|
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/clipboard.min.js')}}")
|
||||||
script(src="{{ url_for('static_attract', filename='assets/js/vendor/jquery-resizable-0.20.min.js')}}")
|
script(src="{{ url_for('static_attract', filename='assets/js/vendor/jquery-resizable-0.20.min.js')}}")
|
||||||
|
|
||||||
script.
|
script.
|
||||||
|
{% if can_create_task %}
|
||||||
|
attract.auth.AttractAuth.setUserCanCreateTask('{{project._id}}', true);
|
||||||
|
{% endif %}
|
||||||
|
new Vue({el:'#table'})
|
||||||
$("#col_main").resizable({
|
$("#col_main").resizable({
|
||||||
handleSelector: ".col-splitter",
|
handleSelector: ".col-splitter",
|
||||||
resizeHeight: false
|
resizeHeight: false,
|
||||||
});
|
});
|
||||||
| {% endblock %}
|
| {% endblock footer_scripts %}
|
||||||
|
@@ -195,12 +195,12 @@ script.
|
|||||||
|
|
||||||
new Clipboard('.copy-to-clipboard-id')
|
new Clipboard('.copy-to-clipboard-id')
|
||||||
.on('success', function(e) {
|
.on('success', function(e) {
|
||||||
statusBarSet('info', 'Copied task ID to clipboard', 'pi-check');
|
toastr.info('Copied task ID to clipboard');
|
||||||
});
|
});
|
||||||
|
|
||||||
new Clipboard('.copy-to-clipboard-shortcode')
|
new Clipboard('.copy-to-clipboard-shortcode')
|
||||||
.on('success', function(e) {
|
.on('success', function(e) {
|
||||||
statusBarSet('info', 'Copied task shortcode to clipboard', 'pi-check');
|
toastr.info('Copied task shortcode to clipboard');
|
||||||
});
|
});
|
||||||
|
|
||||||
loadActivities("{{ url_for('.activities', project_url=project.url, task_id=task['_id']) }}"); // from 10_tasks.js
|
loadActivities("{{ url_for('.activities', project_url=project.url, task_id=task['_id']) }}"); // from 10_tasks.js
|
||||||
|
Reference in New Issue
Block a user