API Docs for: 0.25.0
Show:

File: addon/components/file-browser/component.js

import Ember from 'ember';
import layout from './template';

import loadAll from 'ember-osf/utils/load-relationship';
import outsideClick from 'ember-osf/utils/outside-click';
import Analytics from '../../mixins/analytics';
import pathJoin from 'ember-osf/utils/path-join';
import permissions from 'ember-osf/const/permissions';

/**
 * File browser widget
 *
 * Sample usage:
 * ```handlebars
 * {{file-browser}}
 * ```
 * @class file-browser
 */
export default Ember.Component.extend(Analytics, {
    // TODO: Improve documentation in the future
    layout,
    //Can be overwritten to have a trimmed down display, these are all the options available to be displayed
    display: Ember.A(['header', 'size-column', 'version-column', 'downloads-column', 'modified-column', 'delete-button', 'move-button', 'rename-button', 'download-button', 'view-button', 'info-button', 'upload-button', 'share-button']),
    i18n: Ember.inject.service(),
    store: Ember.inject.service(),
    toast: Ember.inject.service(),
    classNames: ['file-browser'],
    dropzoneOptions: {
        createImageThumbnails: false,
        method: 'PUT',
        withCredentials: true,
        preventMultipleFiles: true,
        acceptDirectories: false
    },
    multiple: true,
    unselect: true,
    openOnSelect: false,
    projectList: Ember.A(),
    isLoadingProjects: null,
    selectedFile: null,
    node: null,
    nodeTitle: null,
    newProject: false,
    projectSelectState: 'main',
    isInputInvalid: true,
    nodeLink: Ember.computed.alias('node.links.html'),
    isMoving: false,

    init() {
        this._super(...arguments);
        this.set('_items', Ember.A());
        outsideClick(function() {
            this.send('dismissPop');
        }.bind(this));
        Ember.$(window).resize(function() {
            this.send('dismissPop');
        }.bind(this));
        this._loadProjects(this.get('user'));
    },
    currentUser: Ember.inject.service('current-user'),
    dropzone: Ember.computed.alias('edit'),
    edit: Ember.computed('user', function() {
        return this.get('user.id') === this.get('currentUser.currentUserId');
    }),
    _loadFiles(user) {
        //pagination? when?
        loadAll(user, 'quickfiles', this.get('_items')).then(() => {
            this.set('loaded', true);
            this.set('_items', this.get('_items').sortBy('itemName'));
            this.get('_items').forEach(item => {
                if (this.get('selectedFile.id') && this.get('selectedFile.id') === item.id) {
                    item.isSelected = true;
                }
            });
        });
    },
    _loadProjects(user) {
        loadAll(user, 'nodes', this.get('projectList')).then(() => {
            let onlyWriteNodes = this.get('projectList').filter((item) => item.get('currentUserPermissions').includes(permissions.WRITE));
            this.set('projectList', onlyWriteNodes);
            this.set('isLoadingProjects', false);
        });
    },
    _addNewNodeToList(user, node) {
        this.get('projectList').unshiftObject(node);
    },
    _loadUser:  Ember.on('init', Ember.observer('user', function() {
        let user = this.get('user');
        if (!user || this.get('loaded')) {
            return;
        }
        //items need to be reloaded when attrs are received
        //TODO: think about replacing _items with user.items, provided it's loaded properly
        let _load = user_ => {
            Ember.run(() => {
                this.set('_items', Ember.A());
                Ember.run.next(() => {
                    this._loadFiles(user_);
                });
            });
        };
        if (user.then) {
            user.then(user_ => {
                _load(user_);
            })
        } else {
            _load(user);
        }
    })),
    uploadUrl: Ember.computed('user', function() {
        return this.get('user.links.relationships.quickfiles.links.upload.href');
    }),
    downloadUrl: Ember.computed('user', function() {
        return this.get('user.links.relationships.quickfiles.links.download.href');
    }),
    loaded: false,
    filtering: false,
    renaming: false,
    sortingBy: 'itemName',
    sortingOrder: 'asc',
    uploading: Ember.A(),
    isUploading: Ember.computed.notEmpty('uploading'),
    filter: null,
    modalOpen: false,
    popupOpen: false,
    itemsLoaded: true,
    selectedItems: Ember.computed.filterBy('items', 'isSelected', true),
    loadedChanged: Ember.observer('itemsLoaded', function() {
        let containerWidth = this.$().width();
        this.set('itemWidth', containerWidth);
    }),
    link: Ember.computed('selectedItems.firstObject.guid', function() {
        let guid = this.get('selectedItems.firstObject.guid');
        return guid ? pathJoin(window.location.origin, guid) : undefined;
    }),
    flash(item, message, type, time) {
        item.set('flash', {
            message,
            type: type || 'success'
        });
        Ember.run.later(() => item.set('flash', null), time || 2000);
    },
    items: Ember.computed('_items', 'textValue', 'filtering', 'sortingBy', 'sortingOrder', function() {
        //look at ways to use the api to filter
        let items = this.get('textValue') && this.get('filtering') ? this.get('_items').filter(i => i.get('name').toLowerCase().indexOf(this.get('textValue').toLowerCase()) !== -1) : this.get('_items');
        let sorted = items.sortBy(this.get('sortingBy'));
        return this.get('sortingOrder') === 'des' ? sorted.reverse() : sorted;
    }),
    textFieldOpen: Ember.computed('filtering', 'renaming', function() {
        return this.get('filtering') ? 'filtering' : (this.get('renaming') ? 'renaming' : false);
    }),
    nameColumnWidth: Ember.computed('display', function() {
        let display = this.get('display');
        let width = 6 + !display.includes('share-link-column') + !display.includes('size-column') + !display.includes('version-column') + !display.includes('downloads-column') + 2 * !display.includes('modified-column');
        if (!display.includes('header')) { //Allows scrollable elements to use extra space occupied by header
            let height = Ember.$('.file-browser-list').height();
            Ember.$('.file-browser-list').height(height + 50);
        }
        return width;
    }),
    browserState: Ember.computed('loaded', '_items', function() {
        return this.get('loaded') ? (this.get('_items').length ? (this.get('items').length ? 'show' : 'filtered') : 'empty') : 'loading';
    }),
    clickable: Ember.computed('browserState', function() {
        let clickable = ['.dz-upload-button'];
        if (this.get('browserState') == 'empty') {
            clickable.push('.file-browser-list');
        }
        return clickable;
    }),
    actions: {
        //dropzone listeners
        addedFile(_, __, file) {
            this.get('uploading').pushObject(file);
        },
        uploadProgress(_, __, file, progress) {
            Ember.$('#uploading-' + file.size).css('width', progress + '%');
        },
        dragStart() {
            this.set('dropping', true);
        },
        dragEnd() {
            this.set('dropping', false);
        },
        error(_, __, file, response) {
            this.get('uploading').removeObject(file);
            this.get('toast').error(response.message_long || response.message || response);
        },
        success(_, __, file, response) {
            this.send('track', 'upload', 'track', 'Quick Files - Upload');
            this.get('uploading').removeObject(file);
            let data = response.data.attributes;
            //OPTIONS (some not researched)
            //Ember store method for passing updated attributes (either with a query for the object, or iterating to find matching)
            //Manually updating the object based on new attrs
            //Making an additional request anytime success is done, finding the file detail page based on path
            let path = data.path; //THERE SHOULD BE A BETTER WAY OF DOING THIS
            let conflictingItem = false;
            for (let file of this.get('_items')) {
                if (path === file.get('path')) {
                    conflictingItem = file;
                    break;
                }
            }
            if (conflictingItem) {
                conflictingItem.setProperties({
                    size: data.size,
                    currentVersion: data.extra.version,
                    dateModified: data.modified_utc
                });
                return;
            }
            response.data.type = 'file'; //
            response.data.attributes.currentVersion = '1';
            // Strip prefix.
            response.data.id = response.data.id.replace(/^[^\/]*\//, '');
            let item = this.get('store').push(response);
            item.set('links', response.data.links); //Push doesnt pass it links
            this.get('_items').unshiftObject(item);
            this.notifyPropertyChange('_items');
            item.getGuid();
            Ember.run.next(() => {
                this.flash(item, 'This file has been added.');
                this.get('toast').success('A file has been added');
            });
        },
        buildUrl(files) {
            let name = files[0].name;
            let conflictingItem = false;
            for (let file of this.get('_items')) {
                if (name === file.get('itemName')) {
                    conflictingItem = file;
                    break;
                }
            }
            if (conflictingItem) {
                return conflictingItem.get('links.upload');
            }
            return this.get('uploadUrl') + '?' + Ember.$.param({
                name: files[0].name
            });
        },
        selectItem(item) {
            if (this.get('openOnSelect')) {
                this.openFile(item, 'view');
            }
            this.set('renaming', false);
            this.set('popupOpen', false);
            if (this.get('selectedItems.length') > 1) {
                for (var item_ of this.get('selectedItems')) {
                    item_.set('isSelected', item_ === item);
                }
            } else if (this.get('selectedItems.length') ===  1) {
                if (item.get('isSelected') && this.get('unselect')) {
                    item.set('isSelected', false);
                    return;
                }
                let otherItem = this.get('selectedItems.firstObject');
                otherItem.set('isSelected', false);
            }
            item.set('isSelected', true);
            this.set('shiftAnchor', item);
        },
        selectMultiple(item, toggle) {
            if (!this.get('multiple')) {
                return;
            }
            this.set('renaming', false);
            this.set('popupOpen', false);
            if (toggle) {
                item.toggleProperty('isSelected');
            } else {
                let items = this.get('items');
                let anchor = this.get('shiftAnchor');
                if (anchor) {
                    let max = Math.max(items.indexOf(anchor), items.indexOf(item));
                    let min = Math.min(items.indexOf(anchor), items.indexOf(item));
                    for (var item_ of this.get('items')) {
                        item_.set('isSelected', item_ === item || item_ === anchor || (items.indexOf(item_) > min && items.indexOf(item_) < max));
                    }
                }
                item.set('isSelected', true);
            }
            Ember.run.next(this, function(){
                if (this.get('selectedItems.length') === 1) {
                    this.set('shiftAnchor', item)
                }
            });
        },
        viewItem() {
            let item = this.get('selectedItems.firstObject');
            this.openFile(item, 'view');
        },
        openItem(item, qparams) {
            this.openFile(item, qparams);
        },
        downloadItem() {
            let downloadLink = this.get('selectedItems.firstObject.links.download');
            window.location = downloadLink;
        },
        _deleteItem(item) {
            item.destroyRecord().then(() => {
                this.flash(item, 'This file has been deleted.', 'danger');
                Ember.run.later(() => {
                    this.get('_items').removeObject(item);
                    this.notifyPropertyChange('_items');
                }, 1800);
            }).catch(() => this.flash(item, 'Delete failed.', 'danger'));
        },
        deleteItem(){
            this.send('_deleteItem', this.get('selectedItems.firstObject'));
            this.set('modalOpen', false);
        },
        deleteItems() {
            for (let item_ of this.get('selectedItems')) {
                this.send('_deleteItem', item_);
            }
            this.set('modalOpen', false);
        },
        _rename(conflict) {
            let item = this.get('selectedItems.firstObject');
            this.set('modalOpen', false);
            item.rename(this.get('textValue'), conflict).then(() => {
                this.flash(item, 'Successfully renamed');
                if (conflict === 'replace') {
                    const replacedItem  = this.get('_conflictingItem');
                    if (!replacedItem) {
                        return;
                    }
                    this.flash(replacedItem, 'This file has been replaced', 'danger');
                    setTimeout(() => {
                        this.get('_items').removeObject(replacedItem);
                        this.notifyPropertyChange('_items');
                    }, 1800);
                    // Later to avoid flash() trying to set on a destroyed item.
                    Ember.run.later(() => replacedItem.unloadRecord(), 2200);
                }
            }).catch(() => this.flash(item, 'Failed to rename item', 'danger'));
            this.set('textValue', null);
            this.toggleProperty('renaming');
        },
        rename() {
            let rename = this.get('textValue');
            let conflict = false;
            for (let item of this.get('_items')) {
                if (item.get('itemName') === rename) {
                    if (item === this.get('selectedItems.firstObject')) {
                        this.set('textValue', null);
                        this.toggleProperty('renaming');
                        return;
                    }
                    conflict = true;
                    this.set('_conflictingItem', item);
                    break;
                }
            }
            if (conflict) {
                this.set('modalOpen', 'renameConflict');
            } else {
                this.send('_rename');
            }
        },
        cancelRename() {
            this.set('textValue', null);
            this.toggleProperty('renaming')
            this.set('modalOpen', false);
        },
        sort(by, order) {
            this.$('.sorting').removeClass('active');
            this.$(`.${by}${order}`).addClass('active');
            this.setProperties({
                sortingBy: by,
                sortingOrder: order
            })
        },
        toggleText(which) {
            this.set('textValue', which === 'renaming' ? this.get('selectedItems.firstObject.itemName') : null);
            this.toggleProperty(which);
        },
        openModal(modalType) {
            this.set('modalOpen', modalType);
        },
        closeModal() {
            if (this.get('modalOpen') == 'move') {
                this.set('projectSelectState', 'main');
            }
            this.set('modalOpen', false);
        },
        textValueKeypress() {
            if (this.get('renaming')) {
                this.send('rename');
            }
        },
        copyLink() {
            this.set('popupOpen', true);
            if (this.get('link')) {
                return;
            }
            this.get('selectedItems.firstObject').getGuid();
        },
        dismissPop() {
            this.set('popupOpen', false);
        },
        checkNodeTitleKeypress(value) {
            this.set('nodeTitle', value);
            this.set('isInputInvalid', Ember.isBlank(value));
        },
        changeProjectSelectState(state) {
            this.set('projectSelectState', state);
            this.set('isInputInvalid', true);
        },
        setSelectedNode(node, isChild) {
            this.set('node', node);
            this.set('isChildNode', isChild);
            this.set('isInputInvalid', false);
        },
        setMoveFile() {
            this.set('isMoving', true);
            let selectedItem = this.get('selectedItems.firstObject');
            let title = this.get('nodeTitle');

            if (this.get('projectSelectState') == 'newProject') {
                this.set('newProject', true);
                this._createProject(title).then(project => {
                    this.set('node', project);
                    this._addNewNodeToList(this.get('user'), project);
                    this._moveFile(selectedItem, project);
                });
            } else if (this.get('projectSelectState') == 'existingProject') {
                this._moveFile(selectedItem, this.get('node'));
                this.set('newProject', false);
            }
        },
    },
    _createProject(title) {
        return this.get('store').createRecord('node', {
            public: true,
            category: 'project',
            title: title,
        }).save()
            .catch(() => this.get('toast').error(
                this.get('i18n').t('eosf.components.moveToProject.couldNotCreateProject')
            ));
    },
    _moveFile(item, node) {
        item.move(node)
            .then(() => {
                this.flash(item, 'Successfully moved');
                Ember.run.later(() => {
                    this.get('_items').removeObject(item);
                    this.notifyPropertyChange('_items');
                }, 1800);
                this.set('modalOpen', 'successMove');
                this.set('projectSelectState', 'main');
                this.set('willCreateComponent', false);
                this.send('track', 'move', 'track', 'Quick Files - Move to project');
            })
            .catch(() => this.get('toast').error(
                this.get('i18n').t('eosf.components.moveToProject.couldNotMoveFile')
            ))
            .then(() => this.set('isMoving', false));
    },
});