mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2025-11-03 04:50:14 +08:00
feature (batch): create selections of files/folder to batch delete and move actions
This commit is contained in:
@ -56,7 +56,7 @@ export class BreadCrumb extends React.Component {
|
||||
{
|
||||
this.state.path.map((path, index) => {
|
||||
return (
|
||||
<Path key={"breadcrumb_"+index} path={path} isLast={this.state.path.length === index + 1} needSaving={this.props.needSaving} />
|
||||
<Path key={"breadcrumb_"+index} currentSelection={this.props.currentSelection} path={path} isLast={this.state.path.length === index + 1} needSaving={this.props.needSaving} />
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
@ -102,6 +102,18 @@ export const onDelete = function(path, type){
|
||||
.catch((err) => notify.send(err, 'error'));
|
||||
};
|
||||
|
||||
export const onMultiDelete = function(arrOfPath){
|
||||
return Promise.all(arrOfPath.map((p) => Files.rm(p)))
|
||||
.then(() => notify.send('All done!', 'success'))
|
||||
.catch((err) => notify.send(err, 'error'));
|
||||
}
|
||||
|
||||
export const onMultiRename = function(arrOfPath){
|
||||
return Promise.all(arrOfPath.map((p) => Files.mv(p[0], p[1])))
|
||||
.then(() => notify.send('All done!', 'success'))
|
||||
.catch((err) => notify.send(err, 'error'));
|
||||
}
|
||||
|
||||
/*
|
||||
* The upload method has a few strategies:
|
||||
* 1. user is coming from drag and drop + browser provides support to read entire folders
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import React from 'react';
|
||||
import { DragDropContext } from 'react-dnd';
|
||||
import HTML5Backend from 'react-dnd-html5-backend-filedrop';
|
||||
import { SelectableGroup } from 'react-selectable';
|
||||
|
||||
import './filespage.scss';
|
||||
import './error.scss';
|
||||
import { Files } from '../model/';
|
||||
import { sort, onCreate, onRename, onDelete, onUpload, onSearch, createLink } from './filespage.helper';
|
||||
import { sort, onCreate, onRename, onMultiRename, onDelete, onMultiDelete, onUpload, onSearch, createLink } from './filespage.helper';
|
||||
import { NgIf, Loader, EventReceiver, LoggedInOnly, ErrorPage } from '../components/';
|
||||
import { notify, debounce, goToFiles, goToViewer, event, settings_get, settings_put } from '../helpers/';
|
||||
import { BreadCrumb, FileSystem, FrequentlyAccess, Submenu } from './filespage/';
|
||||
@ -33,13 +34,14 @@ export class FilesPage extends React.Component {
|
||||
this.props.history.push(props.match.url + "/");
|
||||
}
|
||||
this.state = {
|
||||
path: props.match.url.replace('/files', '').replace(/%23/g, "#") || '/',
|
||||
sort: settings_get('filespage_sort') || 'type',
|
||||
path: props.match.url.replace("/files", "").replace(/%23/g, "#") || "/",
|
||||
sort: settings_get("filespage_sort") || "type",
|
||||
sort_reverse: true,
|
||||
show_hidden: settings_get('filespage_show_hidden') || CONFIG["display_hidden"],
|
||||
view: settings_get('filespage_view') || 'grid',
|
||||
show_hidden: settings_get("filespage_show_hidden") || CONFIG["display_hidden"],
|
||||
view: settings_get("filespage_view") || "grid",
|
||||
is_search: false,
|
||||
files: [],
|
||||
search_loading: false,
|
||||
selected: [],
|
||||
metadata: null,
|
||||
frequents: null,
|
||||
page_number: PAGE_NUMBER_INIT,
|
||||
@ -51,31 +53,36 @@ export class FilesPage extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
this.onRefresh(this.state.path, 'directory');
|
||||
this.onRefresh(this.state.path, "directory");
|
||||
|
||||
// subscriptions
|
||||
this.props.subscribe('file.create', function(){
|
||||
this.props.subscribe("file.create", function(){
|
||||
return onCreate.apply(this, arguments).then(() => {
|
||||
if(this.state.metadata && this.state.metadata.refresh_on_create === true){
|
||||
this.onRefresh(this.state.path, 'directory')
|
||||
this.onRefresh(this.state.path, "directory");
|
||||
}
|
||||
return Promise.resolve()
|
||||
return Promise.resolve();
|
||||
});
|
||||
}.bind(this));
|
||||
this.props.subscribe('file.upload', onUpload.bind(this));
|
||||
this.props.subscribe('file.rename', onRename.bind(this));
|
||||
this.props.subscribe('file.delete', onDelete.bind(this));
|
||||
this.props.subscribe('file.refresh', this.onRefresh.bind(this));
|
||||
window.addEventListener('keydown', this.toggleHiddenFilesVisibilityonCtrlK);
|
||||
this.props.subscribe("file.upload", onUpload.bind(this));
|
||||
this.props.subscribe("file.rename", onRename.bind(this));
|
||||
this.props.subscribe("file.rename.multiple", onMultiRename.bind(this));
|
||||
this.props.subscribe("file.delete", onDelete.bind(this));
|
||||
this.props.subscribe("file.delete.multiple", onMultiDelete.bind(this));
|
||||
this.props.subscribe("file.refresh", this.onRefresh.bind(this));
|
||||
this.props.subscribe("file.select", this.toggleSelect.bind(this));
|
||||
window.addEventListener("keydown", this.toggleHiddenFilesVisibilityonCtrlK);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.unsubscribe('file.upload');
|
||||
this.props.unsubscribe('file.create');
|
||||
this.props.unsubscribe('file.rename');
|
||||
this.props.unsubscribe('file.delete');
|
||||
this.props.unsubscribe('file.refresh');
|
||||
window.removeEventListener('keydown', this.toggleHiddenFilesVisibilityonCtrlK);
|
||||
this.props.unsubscribe("file.upload");
|
||||
this.props.unsubscribe("file.create");
|
||||
this.props.unsubscribe("file.rename");
|
||||
this.props.unsubscribe("file.delete");
|
||||
this.props.unsubscribe("file.delete.multiple");
|
||||
this.props.unsubscribe("file.refresh");
|
||||
this.props.unsubscribe("file.select");
|
||||
window.removeEventListener("keydown", this.toggleHiddenFilesVisibilityonCtrlK);
|
||||
this._cleanupListeners();
|
||||
|
||||
LAST_PAGE_PARAMS.path = this.state.path;
|
||||
@ -100,11 +107,11 @@ export class FilesPage extends React.Component {
|
||||
if(e.keyCode === 72 && e.ctrlKey === true){
|
||||
e.preventDefault();
|
||||
this.setState({show_hidden: !this.state.show_hidden}, () => {
|
||||
settings_put('filespage_show_hidden', this.state.show_hidden);
|
||||
settings_put("filespage_show_hidden", this.state.show_hidden);
|
||||
if(!!this.state.show_hidden){
|
||||
notify.send("Display hidden files", 'info');
|
||||
notify.send("Display hidden files", "info");
|
||||
}else{
|
||||
notify.send("Hide hidden files", 'info');
|
||||
notify.send("Hide hidden files", "info");
|
||||
}
|
||||
});
|
||||
this.onRefresh();
|
||||
@ -114,7 +121,7 @@ export class FilesPage extends React.Component {
|
||||
onRefresh(path = this.state.path){
|
||||
this._cleanupListeners();
|
||||
const observer = Files.ls(path).subscribe((res) => {
|
||||
if(res.status === 'ok'){
|
||||
if(res.status === "ok"){
|
||||
let files = new Array(res.results.length);
|
||||
for(let i=0,l=res.results.length; i<l; i++){
|
||||
let path = this.state.path+res.results[i].name;
|
||||
@ -123,12 +130,14 @@ export class FilesPage extends React.Component {
|
||||
continue;
|
||||
}
|
||||
files[i] = res.results[i];
|
||||
files[i].link = createLink(res.results[i].type, res.results[i].path)
|
||||
files[i].link = createLink(res.results[i].type, res.results[i].path);
|
||||
}
|
||||
this.setState({
|
||||
metadata: res.metadata,
|
||||
files: sort(files, this.state.sort),
|
||||
selected: [],
|
||||
loading: false,
|
||||
is_search: false,
|
||||
page_number: function(){
|
||||
if(this.state.path === LAST_PAGE_PARAMS.path){
|
||||
return LAST_PAGE_PARAMS.page_number;
|
||||
@ -141,7 +150,7 @@ export class FilesPage extends React.Component {
|
||||
}
|
||||
});
|
||||
}else{
|
||||
notify.send(res, 'error');
|
||||
notify.send(res, "error");
|
||||
}
|
||||
}, (error) => {
|
||||
this.props.error(error);
|
||||
@ -162,7 +171,7 @@ export class FilesPage extends React.Component {
|
||||
}
|
||||
|
||||
onSort(_sort){
|
||||
settings_put('filespage_sort', _sort);
|
||||
settings_put("filespage_sort", _sort);
|
||||
const same_sort = _sort === this.state.sort;
|
||||
this.setState({
|
||||
sort: _sort
|
||||
@ -181,7 +190,7 @@ export class FilesPage extends React.Component {
|
||||
|
||||
onView(){
|
||||
const _view = this.state.view === "list" ? "grid" : "list";
|
||||
settings_put('filespage_view', _view);
|
||||
settings_put("filespage_view", _view);
|
||||
this.setState({
|
||||
view: _view
|
||||
}, () => {
|
||||
@ -210,6 +219,7 @@ export class FilesPage extends React.Component {
|
||||
f.link = createLink(f.type, f.path);
|
||||
return f;
|
||||
}) || [],
|
||||
is_search: true,
|
||||
metadata: {
|
||||
can_rename: false,
|
||||
can_delete: false,
|
||||
@ -226,6 +236,19 @@ export class FilesPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
handleMultiSelect(selectedKeys, e){
|
||||
this.setState({selected: selectedKeys});
|
||||
}
|
||||
toggleSelect(path){
|
||||
const idx = this.state.selected.indexOf(path);
|
||||
if(idx == -1){
|
||||
this.setState({ selected: this.state.selected.concat([path]) });
|
||||
} else {
|
||||
this.state.selected.splice(idx, 1);
|
||||
this.setState({ selected: this.state.selected });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let $moreLoading = ( <div className="infinite_scroll_loading" key={-1}><Loader/></div> );
|
||||
if(this.state.files.length <= this.state.page_number * LOAD_PER_SCROLL){
|
||||
@ -233,19 +256,20 @@ export class FilesPage extends React.Component {
|
||||
}
|
||||
return (
|
||||
<div className="component_page_filespage">
|
||||
<BreadCrumb className="breadcrumb" path={this.state.path} />
|
||||
<BreadCrumb className="breadcrumb" path={this.state.path} currentSelection={this.state.selected} />
|
||||
<SelectableGroup onSelection={this.handleMultiSelect.bind(this)} tolerance={2} onNonItemClick={this.handleMultiSelect.bind(this, [])} preventDefault={true} enabled={this.state.is_search === false} className="selectablegroup">
|
||||
<div className="page_container">
|
||||
<div ref="$scroll" className="scroll-y">
|
||||
<InfiniteScroll pageStart={0} loader={$moreLoading} hasMore={this.state.files.length > 70}
|
||||
initialLoad={false} useWindow={false} loadMore={this.loadMore.bind(this)} threshold={100}>
|
||||
<NgIf className="container" cond={!this.state.loading}>
|
||||
<NgIf cond={this.state.path === '/'}>
|
||||
<NgIf cond={this.state.path === "/"}>
|
||||
<FrequentlyAccess files={this.state.frequents} />
|
||||
</NgIf>
|
||||
<Submenu path={this.state.path} sort={this.state.sort} view={this.state.view} onSearch={this.onSearch.bind(this)} onViewUpdate={(value) => this.onView(value)} onSortUpdate={(value) => {this.onSort(value);}} accessRight={this.state.metadata || {}}></Submenu>
|
||||
<Submenu path={this.state.path} sort={this.state.sort} view={this.state.view} onSearch={this.onSearch.bind(this)} onViewUpdate={(value) => this.onView(value)} onSortUpdate={(value) => {this.onSort(value);}} accessRight={this.state.metadata || {}} selected={this.state.selected}></Submenu>
|
||||
<NgIf cond={true}>
|
||||
<FileSystem path={this.state.path} sort={this.state.sort} view={this.state.view}
|
||||
files={this.state.files.slice(0, this.state.page_number * LOAD_PER_SCROLL)}
|
||||
<FileSystem path={this.state.path} sort={this.state.sort} view={this.state.view} selected={this.state.selected}
|
||||
files={this.state.files.slice(0, this.state.page_number * LOAD_PER_SCROLL)} isSearch={this.state.is_search}
|
||||
metadata={this.state.metadata || {}} onSort={this.onSort.bind(this)} onView={this.onView.bind(this)} />
|
||||
</NgIf>
|
||||
</NgIf>
|
||||
@ -259,6 +283,7 @@ export class FilesPage extends React.Component {
|
||||
<div className="upload-footer">
|
||||
<div className="bar"></div>
|
||||
</div>
|
||||
</SelectableGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -10,11 +10,17 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.page_container{
|
||||
.page_container, .selectablegroup{
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
.selectablegroup{
|
||||
overflow: hidden!important;
|
||||
> div[style] span {
|
||||
border: 1px dashed var(--light)!important;
|
||||
}
|
||||
}
|
||||
|
||||
.infinite_scroll_loading{
|
||||
text-align: center;
|
||||
|
||||
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { DropTarget } from 'react-dnd';
|
||||
|
||||
import { EventEmitter, BreadCrumb, PathElement } from '../../components/';
|
||||
import { pathBuilder } from '../../helpers/';
|
||||
import { pathBuilder, filetype, basename } from '../../helpers/';
|
||||
|
||||
export class BreadCrumbTargettable extends BreadCrumb {
|
||||
constructor(props){
|
||||
@ -21,9 +21,21 @@ const fileTarget = {
|
||||
},
|
||||
drop(props, monitor, component){
|
||||
let src = monitor.getItem();
|
||||
let from = pathBuilder(src.path, src.name, src.type);
|
||||
let to = pathBuilder(props.path.full, src.name, src.type);
|
||||
return {action: 'rename', args: [from, to, src.type], ctx: 'breadcrumb'}
|
||||
if(props.currentSelection.length === 0){
|
||||
const from = pathBuilder(src.path, src.name, src.type);
|
||||
const to = pathBuilder(props.path.full, src.name, src.type);
|
||||
return {action: 'rename', args: [from, to, src.type], ctx: 'breadcrumb'};
|
||||
} else {
|
||||
return {action: 'rename.multiple', args: props.currentSelection.map((selectionPath) => {
|
||||
const from = selectionPath;
|
||||
const to = pathBuilder(
|
||||
props.path.full,
|
||||
"./"+basename(selectionPath),
|
||||
filetype(selectionPath)
|
||||
);
|
||||
return [from, to];
|
||||
})};
|
||||
}
|
||||
}
|
||||
}
|
||||
const nativeFileTarget = {
|
||||
@ -34,7 +46,7 @@ const nativeFileTarget = {
|
||||
let files = monitor.getItem();
|
||||
props.emit('file.upload', props.path.full, files);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@EventEmitter
|
||||
@DropTarget('__NATIVE_FILE__', nativeFileTarget, (connect, monitor) => ({
|
||||
|
||||
@ -29,7 +29,7 @@ export class FileSystem extends React.PureComponent {
|
||||
{
|
||||
this.props.files.map((file, index) => {
|
||||
if(file.type === 'directory' || file.type === 'file' || file.type === 'link' || file.type === 'bucket'){
|
||||
return ( <ExistingThing view={this.props.view} key={file.name+file.path+(file.icon || '')} file={file} path={this.props.path} metadata={this.props.metadata || {}} /> );
|
||||
return ( <ExistingThing view={this.props.view} key={file.name+file.path+(file.icon || '')} file={file} path={this.props.path} metadata={this.props.metadata || {}} selectableKey={file.path} selected={this.props.selected.indexOf(file.path) !== -1} currentSelection={this.props.selected} /> );
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -37,7 +37,7 @@ export class FileSystem extends React.PureComponent {
|
||||
</NgIf>
|
||||
<NgIf className="error" cond={this.props.files.length === 0}>
|
||||
<p className="empty_image">
|
||||
<Icon name="file"/>
|
||||
<Icon name={this.props.isSearch ? "search" : "file"}/>
|
||||
</p>
|
||||
<p>There is nothing here</p>
|
||||
</NgIf>
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||
|
||||
import { Card, NgIf, Icon, EventEmitter, Dropdown, DropdownButton, DropdownList, DropdownItem, Container } from '../../components/';
|
||||
import { pathBuilder, debounce } from '../../helpers/';
|
||||
import { pathBuilder, debounce, prompt } from '../../helpers/';
|
||||
import "./submenu.scss";
|
||||
|
||||
@EventEmitter
|
||||
@ -49,6 +50,19 @@ export class Submenu extends React.Component {
|
||||
onNew(type){
|
||||
this.props.emit("new::"+type);
|
||||
}
|
||||
onDelete(arrayOfPaths){
|
||||
prompt.now(
|
||||
"Confirm by typing \"remove\"",
|
||||
(answer) => {
|
||||
if(answer !== "remove"){
|
||||
return Promise.resolve();
|
||||
}
|
||||
this.props.emit("file.delete.multiple", arrayOfPaths);
|
||||
return Promise.resolve();
|
||||
},
|
||||
() => { /* click on cancel */ }
|
||||
);
|
||||
}
|
||||
|
||||
onViewChange(){
|
||||
requestAnimationFrame(() => this.props.onViewUpdate());
|
||||
@ -104,12 +118,18 @@ export class Submenu extends React.Component {
|
||||
<div className="component_submenu">
|
||||
<Container>
|
||||
<div className={"menubar no-select "+(this.state.search_input_visible ? "search_focus" : "")}>
|
||||
<NgIf cond={this.props.accessRight.can_create_file !== false} onClick={this.onNew.bind(this, 'file')} type="inline">
|
||||
<NgIf cond={this.props.accessRight.can_create_file !== false && this.props.selected.length === 0} onClick={this.onNew.bind(this, 'file')} type="inline">
|
||||
New File
|
||||
</NgIf>
|
||||
<NgIf cond={this.props.accessRight.can_create_directory !== false} onClick={this.onNew.bind(this, 'directory')} type="inline">
|
||||
<NgIf cond={this.props.accessRight.can_create_directory !== false && this.props.selected.length === 0} onClick={this.onNew.bind(this, 'directory')} type="inline">
|
||||
New Directory
|
||||
</NgIf>
|
||||
<NgIf cond={this.props.selected.length > 0} type="inline" onMouseDown={this.onDelete.bind(this, this.props.selected)}>
|
||||
<ReactCSSTransitionGroup transitionName="submenuwithSelection" transitionLeave={false} transitionEnter={false} transitionAppear={true} transitionAppearTimeout={10000}>
|
||||
<span>Remove</span>
|
||||
</ReactCSSTransitionGroup>
|
||||
</NgIf>
|
||||
|
||||
<Dropdown className="view sort" onChange={this.onSortChange.bind(this)}>
|
||||
<DropdownButton>
|
||||
<Icon name="sort"/>
|
||||
|
||||
@ -86,4 +86,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submenuwithSelection-appear{
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
transform: translateY(3px);
|
||||
transition: opacity 0.2s ease, transform 0.4s ease;
|
||||
}
|
||||
.submenuwithSelection-appear.submenuwithSelection-appear-active{
|
||||
opacity: 1;
|
||||
transform: translateY(0px);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,10 +3,11 @@ import path from 'path';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { DragSource, DropTarget } from 'react-dnd';
|
||||
import { createSelectable } from 'react-selectable';
|
||||
|
||||
import './thing.scss';
|
||||
import { Card, NgIf, Icon, EventEmitter, Button } from '../../components/';
|
||||
import { pathBuilder, prompt, alert, leftPad, getMimeType, debounce, memory } from '../../helpers/';
|
||||
import { pathBuilder, basename, filetype, prompt, alert, leftPad, getMimeType, debounce, memory } from '../../helpers/';
|
||||
import { Files } from '../../model/';
|
||||
import { ShareComponent } from './share';
|
||||
import img_placeholder from '../../assets/img/placeholder.png';
|
||||
@ -30,6 +31,8 @@ const fileSource = {
|
||||
let result = monitor.getDropResult();
|
||||
if(result.action === 'rename'){
|
||||
props.emit.apply(component, ['file.rename'].concat(result.args));
|
||||
}else if(result.action === "rename.multiple"){
|
||||
props.emit.call(component, "file.rename.multiple", result.args);
|
||||
}else{
|
||||
throw 'unknown action';
|
||||
}
|
||||
@ -40,19 +43,31 @@ const fileSource = {
|
||||
const fileTarget = {
|
||||
canDrop(props, monitor){
|
||||
let file = monitor.getItem();
|
||||
if(props.file.type === 'directory' && file.name !== props.file.name && props.file.icon !== 'loading'){
|
||||
if(props.file.type !== "directory") return false;
|
||||
else if(file.name === props.file.name) return false;
|
||||
else if(props.file.icon === "loading") return false;
|
||||
else if(props.selected === true) return false;
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
},
|
||||
drop(props, monitor, component){
|
||||
let src = monitor.getItem();
|
||||
let dest = props.file;
|
||||
|
||||
let from = pathBuilder(props.path, src.name, src.type);
|
||||
let to = pathBuilder(props.path, './'+dest.name+'/'+src.name, src.type);
|
||||
if(props.currentSelection.length === 0){
|
||||
const from = pathBuilder(props.path, src.name, src.type);
|
||||
const to = pathBuilder(props.path, './'+dest.name+'/'+src.name, src.type);
|
||||
return {action: 'rename', args: [from, to, src.type], ctx: 'existingfile'};
|
||||
} else {
|
||||
return {action: 'rename.multiple', args: props.currentSelection.map((selectionPath) => {
|
||||
const from = selectionPath;
|
||||
const to = pathBuilder(
|
||||
props.path,
|
||||
"./"+dest.name+"/"+basename(selectionPath),
|
||||
filetype(selectionPath)
|
||||
);
|
||||
return [ from, to ];
|
||||
})};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -62,9 +77,10 @@ const nativeFileTarget = {
|
||||
let path = pathBuilder(props.path, props.file.name, 'directory');
|
||||
props.emit('file.upload', path, monitor.getItem());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@createSelectable
|
||||
@EventEmitter
|
||||
@DropTarget('__NATIVE_FILE__', nativeFileTarget, (connect, monitor) => ({
|
||||
connectDropNativeFile: connect.dropTarget(),
|
||||
@ -99,7 +115,8 @@ export class ExistingThing extends React.Component {
|
||||
this.props.fileIsOver !== nextProps.fileIsOver ||
|
||||
this.props.canDropFile !== nextProps.canDropFile ||
|
||||
this.props.nativeFileIsOver !== nextProps.nativeFileIsOver ||
|
||||
this.props.canDropNativeFile !== nextProps.canDropNativeFile
|
||||
this.props.canDropNativeFile !== nextProps.canDropNativeFile ||
|
||||
this.props.selected !== nextProps.selected
|
||||
){
|
||||
return true;
|
||||
}
|
||||
@ -188,6 +205,16 @@ export class ExistingThing extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
onThingClick(e){
|
||||
if(e.ctrlKey === true){
|
||||
e.preventDefault();
|
||||
this.props.emit(
|
||||
"file.select",
|
||||
pathBuilder(this.props.path, this.props.file.name, this.props.file.type)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_confirm_delete_text(){
|
||||
return this.props.file.name.length > 16? this.props.file.name.substring(0, 10).toLowerCase() : this.props.file.name;
|
||||
}
|
||||
@ -213,8 +240,8 @@ export class ExistingThing extends React.Component {
|
||||
className = className.trim();
|
||||
|
||||
return connectDragSource(connectDropNativeFile(connectDropFile(
|
||||
<div className={"component_thing view-"+this.props.view}>
|
||||
<ToggleableLink to={this.props.file.link + window.location.search} disabled={this.props.file.icon === "loading"}>
|
||||
<div className={"component_thing view-"+this.props.view+(this.props.selected === true ? " selected" : " not-selected")}>
|
||||
<ToggleableLink onClick={this.onThingClick.bind(this)} to={this.props.file.link + window.location.search} disabled={this.props.file.icon === "loading"}>
|
||||
<Card ref="$card"className={this.state.hover} className={className}>
|
||||
<Image preview={this.state.preview}
|
||||
icon={this.props.file.icon || this.props.file.type}
|
||||
@ -231,6 +258,7 @@ export class ExistingThing extends React.Component {
|
||||
<DateTime show={this.state.icon !== 'loading'} timestamp={this.props.file.time} />
|
||||
<ActionButton onClickRename={this.onRenameRequest.bind(this)} onClickDelete={this.onDeleteRequest.bind(this)} onClickShare={this.onShareRequest.bind(this)} is_renaming={this.state.is_renaming}
|
||||
can_rename={this.props.metadata.can_rename !== false} can_delete={this.props.metadata.can_delete !== false} can_share={this.props.metadata.can_share !== false && window.CONFIG.enable_share === true} />
|
||||
<div className="selectionOverlay"></div>
|
||||
</Card>
|
||||
</ToggleableLink>
|
||||
</div>
|
||||
|
||||
@ -82,6 +82,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.selectionOverlay{ display: none; }
|
||||
&.selected .selectionOverlay{
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--primary);
|
||||
z-index: 2;
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -104,6 +117,7 @@
|
||||
@media (max-width: 400px){.box{height: 140px; .info_extension{font-size: 0.8em!important; padding: 3px 10px;}}}
|
||||
@media (max-width: 340px){.box{height: 130px}}
|
||||
text-align: center;
|
||||
|
||||
.box{
|
||||
margin: 2px;
|
||||
padding: 0;
|
||||
@ -190,6 +204,23 @@
|
||||
background: rgba(0,0,0,0);
|
||||
transition: 0.2s ease-out background;
|
||||
}
|
||||
.component_filesize, .component_datetime{ display: none; }
|
||||
.component_action{
|
||||
opacity: 0;
|
||||
transform: translateX(5px);
|
||||
transition: 0.15s ease-out all;
|
||||
z-index: 2;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
border-radius: 5px;
|
||||
margin-right: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
img.thumbnail{transition: 0.2s ease-out transform;}
|
||||
}
|
||||
&.not-selected .box{
|
||||
&:hover{
|
||||
.component_action{
|
||||
transition-delay: 0.1s;
|
||||
@ -214,21 +245,11 @@
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.component_filesize, .component_datetime{ display: none; }
|
||||
.component_action{
|
||||
opacity: 0;
|
||||
transform: translateX(5px);
|
||||
transition: 0.15s ease-out all;
|
||||
z-index: 2;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
border-radius: 5px;
|
||||
margin-right: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
img.thumbnail{transition: 0.2s ease-out transform;}
|
||||
&.selected .box{
|
||||
img.thumbnail{
|
||||
transform: scale(0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,8 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"bcryptjs": "^2.4.3"
|
||||
"bcryptjs": "^2.4.3",
|
||||
"react-selectable": "^2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.13.2",
|
||||
|
||||
Reference in New Issue
Block a user