feature (#79): media view, infinite scroll and persistent user settings
@@ -35,19 +35,17 @@ a{color: inherit; text-decoration: none;}
|
||||
|
||||
select{-moz-appearance: none;}
|
||||
select:-moz-focusring {
|
||||
color: inherit;
|
||||
color: transparent;
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
select::-ms-expand {
|
||||
display: none;
|
||||
}
|
||||
select::-ms-expand { display: none; }
|
||||
|
||||
|
||||
button::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
|
||||
input, textarea{
|
||||
transition: border 0.2s;
|
||||
outline: none;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
id="svg4334"
|
||||
sodipodi:docname="check.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<metadata
|
||||
id="metadata4340">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs4338" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1894"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview4336"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.4609375"
|
||||
inkscape:cx="-208.27119"
|
||||
inkscape:cy="256"
|
||||
inkscape:window-x="10"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg4334" />
|
||||
<path
|
||||
d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"
|
||||
id="path4332"
|
||||
style="fill:#909090;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -40,12 +40,20 @@
|
||||
id="namedview4654"
|
||||
showgrid="false"
|
||||
inkscape:zoom="49.75"
|
||||
inkscape:cx="8.4924623"
|
||||
inkscape:cx="7.4070352"
|
||||
inkscape:cy="8"
|
||||
inkscape:window-x="10"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg4652" />
|
||||
inkscape:current-layer="svg4652"
|
||||
showguides="false"
|
||||
inkscape:guide-bbox="true">
|
||||
<sodipodi:guide
|
||||
position="-5.5075377,9.4874372"
|
||||
orientation="1,0"
|
||||
id="guide7161"
|
||||
inkscape:locked="false" />
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;text-indent:0;text-transform:none;fill:#8c8c8c;fill-opacity:1;stroke-width:0.98481041"
|
||||
@@ -60,8 +68,8 @@
|
||||
sodipodi:nodetypes="ccccccccccccc" />
|
||||
<path
|
||||
style="display:inline;fill:#8c8c8c;fill-opacity:1;stroke:#9e7575;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 10.63457,1.0286114 c -0.0416,3.1491307 -0.01814,3.1380869 3.3121,3.0978656 z"
|
||||
d="m 10.63457,1.0286114 c -0.0416,3.7119448 -0.476797,3.1977577 3.3121,3.0978656 z"
|
||||
id="path5283"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csc" />
|
||||
sodipodi:nodetypes="ccc" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
id="svg875"
|
||||
sodipodi:docname="grid.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<metadata
|
||||
id="metadata881">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs879" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1894"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview877"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.6035156"
|
||||
inkscape:cx="184.79579"
|
||||
inkscape:cy="276.62026"
|
||||
inkscape:window-x="10"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg875" />
|
||||
<path
|
||||
d="m 141.333,76 v 128 c 0,13.255 -10.745,24 -24,24 H 24 C 10.745,228 0,217.255 0,204 V 76 C 0,62.745 10.745,52 24,52 h 93.333 c 13.255,0 24,10.745 24,24 z m 229.334,0 v 128 c 0,13.255 10.745,24 24,24 H 488 c 13.255,0 24,-10.745 24,-24 V 76 C 512,62.745 501.255,52 488,52 h -93.333 c -13.255,0 -24,10.745 -24,24 z m -44,128 V 76 c 0,-13.255 -10.745,-24 -24,-24 H 201.333 c -13.255,0 -24,10.745 -24,24 v 128 c 0,13.255 10.745,24 24,24 h 101.333 c 13.256,0 24.001,-10.745 24.001,-24 z M 0,316 v 120 c 0,13.255 10.745,24 24,24 h 93.333 c 13.255,0 24,-10.745 24,-24 V 316 c 0,-13.255 -10.745,-24 -24,-24 H 24 C 10.745,292 0,302.745 0,316 Z M 394.667,460 H 488 c 13.255,0 24,-10.745 24,-24 V 316 c 0,-13.255 -10.745,-24 -24,-24 h -93.333 c -13.255,0 -24,10.745 -24,24 v 120 c 0,13.255 10.745,24 24,24 z M 177.333,316 v 120 c 0,13.255 10.745,24 24,24 h 101.333 c 13.255,0 24,-10.745 24,-24 V 316 c 0,-13.255 -10.745,-24 -24,-24 H 201.333 c -13.255,0 -24,10.745 -24,24 z"
|
||||
id="path873"
|
||||
style="fill:#626469;fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ssssssssssssssssssssssssssssssssssssssssssssssssssssss" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
id="svg875"
|
||||
sodipodi:docname="list.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<metadata
|
||||
id="metadata881">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs879" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1894"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview877"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.6035156"
|
||||
inkscape:cx="181.16443"
|
||||
inkscape:cy="252.08427"
|
||||
inkscape:window-x="10"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg875" />
|
||||
<path
|
||||
d="m 133.333,56 v 64 c 0,13.255 -10.745,24 -24,24 H 24 C 10.745,144 0,133.255 0,120 V 56 C 0,42.745 10.745,32 24,32 h 85.333 c 13.255,0 24,10.745 24,24 z m 379.334,232 v -64 c 0,-13.255 -10.745,-24 -24,-24 H 213.333 c -13.255,0 -24,10.745 -24,24 v 64 c 0,13.255 10.745,24 24,24 h 275.333 c 13.256,0 24.001,-10.745 24.001,-24 z m 0,-168 V 56 c 0,-13.255 -10.745,-24 -24,-24 H 213.333 c -13.255,0 -24,10.745 -24,24 v 64 c 0,13.255 10.745,24 24,24 h 275.333 c 13.256,0 24.001,-10.745 24.001,-24 z M 109.333,200 H 24 C 10.745,200 0,210.745 0,224 v 64 c 0,13.255 10.745,24 24,24 h 85.333 c 13.255,0 24,-10.745 24,-24 v -64 c 0,-13.255 -10.745,-24 -24,-24 z M 0,392 v 64 c 0,13.255 10.745,24 24,24 h 85.333 c 13.255,0 24,-10.745 24,-24 v -64 c 0,-13.255 -10.745,-24 -24,-24 H 24 C 10.745,368 0,378.745 0,392 Z m 189.333,0 v 64 c 0,13.255 10.745,24 24,24 h 275.333 c 13.255,0 24,-10.745 24,-24 v -64 c 0,-13.255 -10.745,-24 -24,-24 H 213.333 c -13.255,0 -24,10.745 -24,24 z"
|
||||
id="path873"
|
||||
style="fill:#626469;fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ssssssssssssssssssssssssssssssssssssssssssssssssssssss" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 320 512"
|
||||
version="1.1"
|
||||
id="svg3752"
|
||||
sodipodi:docname="sort.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<metadata
|
||||
id="metadata3758">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs3756" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="940"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview3754"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.4609375"
|
||||
inkscape:cx="-43.932203"
|
||||
inkscape:cy="256"
|
||||
inkscape:window-x="964"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg3752" />
|
||||
<path
|
||||
d="M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41zm255-105L177 64c-9.4-9.4-24.6-9.4-33.9 0L24 183c-15.1 15.1-4.4 41 17 41h238c21.4 0 32.1-25.9 17-41z"
|
||||
id="path3750"
|
||||
style="fill:#626469;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -62,7 +62,7 @@
|
||||
font-size: 18px;
|
||||
display: inline-block;
|
||||
|
||||
a{border-radius: 2px;}
|
||||
a{border-radius: 2px; letter-spacing: -0.5px;}
|
||||
&.highlight a{
|
||||
background: var(--emphasis-primary);
|
||||
border: 2px solid var(--primary);
|
||||
|
||||
@@ -59,9 +59,11 @@ export class Dropdown extends React.Component {
|
||||
const button = this.props.children[0];
|
||||
|
||||
const dropdown = React.cloneElement(this.props.children[1], {onSelect: this.onSelect.bind(this)});
|
||||
|
||||
let className = "component_dropdown ";
|
||||
className += this.props.className ? this.props.className+" " : "";
|
||||
className += this.state.button ? " active" : "";
|
||||
return (
|
||||
<div className={"component_dropdown"+(this.state.button ? " active" : "")}>
|
||||
<div className={className}>
|
||||
{ button }
|
||||
{ dropdown }
|
||||
</div>
|
||||
|
||||
@@ -29,8 +29,13 @@ import img_arrow_down from '../assets/img/arrow-down.svg';
|
||||
import img_arrow_up_double from '../assets/img/arrow-up-double.svg';
|
||||
import img_arrow_down_double from '../assets/img/arrow-down-double.svg';
|
||||
import img_search from '../assets/img/search.svg';
|
||||
import img_grid from '../assets/img/grid.svg';
|
||||
import img_list from '../assets/img/list.svg';
|
||||
import img_sort from '../assets/img/sort.svg';
|
||||
import img_check from '../assets/img/check.svg';
|
||||
|
||||
export const Icon = (props) => {
|
||||
if(props.name === null) return null;
|
||||
let img;
|
||||
if(props.name === 'directory'){
|
||||
img = img_folder;
|
||||
@@ -84,6 +89,14 @@ export const Icon = (props) => {
|
||||
img = img_arrow_down;
|
||||
}else if(props.name === 'search'){
|
||||
img = img_search;
|
||||
}else if(props.name === 'grid'){
|
||||
img = img_grid;
|
||||
}else if(props.name === 'list'){
|
||||
img = img_list;
|
||||
}else if(props.name === 'sort'){
|
||||
img = img_sort;
|
||||
}else if(props.name === 'check'){
|
||||
img = img_check;
|
||||
}else{
|
||||
throw('unknown icon');
|
||||
}
|
||||
|
||||
@@ -12,3 +12,5 @@ export { prompt } from './prompt';
|
||||
export { notify } from './notify';
|
||||
export { gid } from './random';
|
||||
export { leftPad } from './common';
|
||||
export { getMimeType } from './mimetype';
|
||||
export { settings_get, settings_put } from './settings';
|
||||
|
||||
@@ -9,6 +9,9 @@ function Memory(){
|
||||
set: function(key, value){
|
||||
if(!data[key]) data[key] = {};
|
||||
data[key] = value;
|
||||
},
|
||||
all: function(){
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,13 @@ export function opener(file){
|
||||
return 'image';
|
||||
}else if(['application/javascript', 'application/xml', 'application/json', 'application/x-perl'].indexOf(mime) !== -1){
|
||||
return 'editor';
|
||||
}else if(['audio/wav', 'audio/mp3', 'audio/flac'].indexOf(mime) !== -1){
|
||||
}else if(['audio/wav', 'audio/mp3', 'audio/flac', 'audio/ogg'].indexOf(mime) !== -1){
|
||||
return 'audio';
|
||||
}else if(['video/webm', 'video/mp4', 'application/ogg'].indexOf(mime) !== -1){
|
||||
}else if(['video/webm', 'video/mp4'].indexOf(mime) !== -1){
|
||||
return 'video';
|
||||
}else{
|
||||
}else if(mime.split('/')[0] === "application")
|
||||
return 'download';
|
||||
else{
|
||||
return 'editor';
|
||||
}
|
||||
}
|
||||
@@ -55,7 +57,6 @@ const db = {
|
||||
'mp4': 'video/mp4',
|
||||
'mov': 'video/quicktime',
|
||||
'avi': 'video/x-msvideo',
|
||||
'ogg': 'application/ogg',
|
||||
'ogv': 'application/ogg',
|
||||
'js': 'application/javascript',
|
||||
'xml': 'application/xml',
|
||||
@@ -166,7 +167,6 @@ const db = {
|
||||
"mid": "audio/midi",
|
||||
"kar": "audio/midi",
|
||||
"midi": "audio/midi",
|
||||
"ogg": "audio/ogg",
|
||||
"m4a": "audio/x-m4a",
|
||||
"ra": "audio/x-realaudio",
|
||||
"swf": "application/x-shockwave-flash",
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
let settings = JSON.parse(window.localStorage.getItem("settings")) || {};
|
||||
|
||||
export function settings_get(key){
|
||||
return settings[key];
|
||||
}
|
||||
|
||||
export function settings_put(key, value){
|
||||
settings[key] = value;
|
||||
save(settings);
|
||||
}
|
||||
|
||||
function save(d){
|
||||
setInterval(() => {
|
||||
window.localStorage.setItem("settings", JSON.stringify(d));
|
||||
}, 500);
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
import { Files } from '../model/';
|
||||
import { notify } from '../helpers/';
|
||||
import Path from 'path';
|
||||
|
||||
export const sort = function(files, type){
|
||||
if(type === 'name'){
|
||||
return sortByName(files);
|
||||
}else if(type === 'date'){
|
||||
return sortByDate(files);
|
||||
}else{
|
||||
return sortByType(files);
|
||||
}
|
||||
function sortByType(files){
|
||||
return files.sort((fileA, fileB) => {
|
||||
if(fileA.icon === 'loading' && fileB.icon !== 'loading'){return +1;}
|
||||
else if(fileA.icon !== 'loading' && fileB.icon === 'loading'){return -1;}
|
||||
else{
|
||||
if(['directory', 'link'].indexOf(fileA.type) === -1 && ['directory', 'link'].indexOf(fileB.type) !== -1){
|
||||
return +1;
|
||||
}else if(['directory', 'link'].indexOf(fileA.type) !== -1 && ['directory', 'link'].indexOf(fileB.type) === -1){
|
||||
return -1;
|
||||
}else{
|
||||
if(fileA.name[0] === "." && fileB.name[0] !== ".") return +1;
|
||||
else if(fileA.name[0] !== "." && fileB.name[0] === ".") return -1;
|
||||
else{
|
||||
if(fileA.name.toLowerCase() === fileB.name.toLowerCase()){
|
||||
return fileA.name > fileB.name ? +1 : -1;
|
||||
}
|
||||
return fileA.name.toLowerCase() > fileB.name.toLowerCase() ? +1 : -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function sortByName(files){
|
||||
return files.sort((fileA, fileB) => {
|
||||
if(fileA.icon === 'loading' && fileB.icon !== 'loading'){return +1;}
|
||||
else if(fileA.icon !== 'loading' && fileB.icon === 'loading'){return -1;}
|
||||
else{
|
||||
if(fileA.name[0] === "." && fileB.name[0] !== ".") return +1;
|
||||
else if(fileA.name[0] !== "." && fileB.name[0] === ".") return -1;
|
||||
else{
|
||||
if(fileA.name.toLowerCase() === fileB.name.toLowerCase()){
|
||||
return fileA.name > fileB.name ? +1 : -1;
|
||||
}
|
||||
return fileA.name.toLowerCase() > fileB.name.toLowerCase() ? +1 : -1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function sortByDate(files){
|
||||
return files.sort((fileA, fileB) => {
|
||||
if(fileA.icon === 'loading' && fileB.icon !== 'loading'){return +1;}
|
||||
else if(fileA.icon !== 'loading' && fileB.icon === 'loading'){return -1;}
|
||||
else{
|
||||
if(fileB.time === fileA.time){
|
||||
return fileA.name > fileB.name ? +1 : -1;
|
||||
}
|
||||
return fileB.time - fileA.time;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const onCreate = function(path, type, file){
|
||||
if(type === 'file'){
|
||||
return Files.touch(path, file)
|
||||
.then(() => {
|
||||
notify.send('A file named "'+Path.basename(path)+'" was created', 'success');
|
||||
return Promise.resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
notify.send(err, 'error');
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}else if(type === 'directory'){
|
||||
return Files.mkdir(path)
|
||||
.then(() => notify.send('A folder named "'+Path.basename(path)+'" was created', 'success'))
|
||||
.catch((err) => notify.send(err, 'error'));
|
||||
}else{
|
||||
return Promise.reject({message: 'internal error: can\'t create a '+type.toString(), code: 'UNKNOWN_TYPE'});
|
||||
}
|
||||
};
|
||||
|
||||
export const onRename = function(from, to, type){
|
||||
return Files.mv(from, to, type)
|
||||
.then(() => notify.send('The file "'+Path.basename(from)+'" was renamed', 'success'))
|
||||
.catch((err) => notify.send(err, 'error'));
|
||||
};
|
||||
|
||||
export const onDelete = function(path, type){
|
||||
return Files.rm(path, type)
|
||||
.then(() => notify.send('The file "'+Path.basename(path)+'" was deleted', 'success'))
|
||||
.catch((err) => notify.send(err, 'error'));
|
||||
};
|
||||
|
||||
export const onUpload = function(path, e){
|
||||
const MAX_POOL_SIZE = 10;
|
||||
let PRIOR_STATUS = {};
|
||||
extract_upload_directory_the_way_that_works_but_non_official(e.dataTransfer.items || [], [])
|
||||
.then((files) => {
|
||||
if(files.length === 0){
|
||||
return extract_upload_crappy_hack_but_official_way(e.dataTransfer);
|
||||
}
|
||||
return Promise.resolve(files);
|
||||
})
|
||||
.then((files) => {
|
||||
const processes = files.map((file) => {
|
||||
file.path = Path.join(path, file.path);
|
||||
if(file.type === 'file'){
|
||||
Files.touch(file.path, file.file, 'prepare_only');
|
||||
return {
|
||||
parent: file._prior || null,
|
||||
fn: Files.touch.bind(Files, file.path, file.file, 'execute_only')
|
||||
};
|
||||
}else{
|
||||
Files.mkdir(file.path, 'prepare_only');
|
||||
return {
|
||||
id: file._id || null,
|
||||
parent: file._prior || null,
|
||||
fn: Files.mkdir.bind(Files, file.path, 'execute_only')
|
||||
};
|
||||
}
|
||||
});
|
||||
function runner(id){
|
||||
let current_process = null;
|
||||
if(processes.length === 0) return Promise.resolve();
|
||||
|
||||
for(let i=0; i<processes.length; i++){
|
||||
if(processes[i].parent === null || PRIOR_STATUS[processes[i].parent] === true){
|
||||
current_process = processes[i];
|
||||
processes.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(current_process){
|
||||
return current_process.fn(id)
|
||||
.then(() => {
|
||||
if(current_process.id) PRIOR_STATUS[current_process.id] = true;
|
||||
return runner(id);
|
||||
})
|
||||
.catch((err) => {
|
||||
notify.send(err, 'error');
|
||||
return runner(id);
|
||||
});
|
||||
}else{
|
||||
return waitABit()
|
||||
.then(() => runner(id));
|
||||
function waitABit(){
|
||||
return new Promise((done) => {
|
||||
window.setTimeout(() => {
|
||||
requestAnimationFrame(() => {
|
||||
done();
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Promise.all(Array.apply(null, Array(MAX_POOL_SIZE)).map((process,index) => {
|
||||
return runner();
|
||||
})).then(() => {
|
||||
notify.send('Upload completed', 'success');
|
||||
}).catch((err) => {
|
||||
notify.send(err, 'error');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
// adapted from: https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
|
||||
function _rand_id(){
|
||||
function s4() {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
}
|
||||
return s4() + s4() + s4() + s4();
|
||||
}
|
||||
|
||||
function extract_upload_directory_the_way_that_works_but_non_official(items, files = []){
|
||||
const traverseDirectory = (item, _files, parent_id) => {
|
||||
let file = {
|
||||
path: item.fullPath
|
||||
};
|
||||
if(item.isFile){
|
||||
return new Promise((done, err) => {
|
||||
file.type = "file";
|
||||
item.file((_file, _err) => {
|
||||
if(!_err){
|
||||
file.file = _file;
|
||||
if(parent_id) file._prior = parent_id;
|
||||
_files.push(file);
|
||||
}
|
||||
done(_files);
|
||||
});
|
||||
});
|
||||
}else if(item.isDirectory){
|
||||
file.type = "directory";
|
||||
file.path += "/";
|
||||
file._id = _rand_id();
|
||||
if(parent_id) file._prior = parent_id;
|
||||
_files.push(file);
|
||||
|
||||
return new Promise((done, err) => {
|
||||
item.createReader().readEntries(function(entries){
|
||||
Promise.all(entries.map((entry) => {
|
||||
return traverseDirectory(entry, _files, file._id);
|
||||
})).then(() => done(_files));
|
||||
});
|
||||
});
|
||||
}else{
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
return Promise.all(
|
||||
Array.prototype.slice.call(items).map((item) => {
|
||||
if(typeof item.webkitGetAsEntry === 'function'){
|
||||
return traverseDirectory(item.webkitGetAsEntry(), files.slice(0));
|
||||
}
|
||||
}).filter((e) => e)
|
||||
).then((res) => Promise.resolve([].concat.apply([], res)));
|
||||
}
|
||||
|
||||
function extract_upload_crappy_hack_but_official_way(data){
|
||||
const _files = data.files;
|
||||
return Promise.all(
|
||||
Array.prototype.slice.call(_files).map((_file) => {
|
||||
return detectType(_file)
|
||||
.then(transform);
|
||||
function detectType(_f){
|
||||
// the 4096 is an heuristic I've observed and taken from: https://stackoverflow.com/questions/25016442/how-to-distinguish-if-a-file-or-folder-is-being-dragged-prior-to-it-being-droppe
|
||||
// however the proposed answer is just wrong as it doesn't consider folder with name such as: test.png
|
||||
// and as Stackoverflow favor consanguinity with their point system, I couldn't rectify the proposed answer.
|
||||
// The following code is actually working as expected
|
||||
if(_file.size % 4096 !== 0){
|
||||
return Promise.resolve('file');
|
||||
}
|
||||
return new Promise((done, err) => {
|
||||
let reader = new window.FileReader();
|
||||
reader.onload = function() {
|
||||
done('file');
|
||||
};
|
||||
reader.onerror = function() {
|
||||
done('directory');
|
||||
};
|
||||
reader.readAsText(_f);
|
||||
});
|
||||
}
|
||||
|
||||
function transform(_type){
|
||||
let file = {
|
||||
type: _type,
|
||||
path: _file.name
|
||||
};
|
||||
if(file.type === 'file'){
|
||||
file.file = _file;
|
||||
}else{
|
||||
file.path += "/";
|
||||
}
|
||||
return Promise.resolve(file);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,14 +1,17 @@
|
||||
import React from 'react';
|
||||
import { DragDropContext } from 'react-dnd';
|
||||
import HTML5Backend from 'react-dnd-html5-backend-filedrop';
|
||||
import Path from 'path';
|
||||
|
||||
import './filespage.scss';
|
||||
import './error.scss';
|
||||
import { Files } from '../model/';
|
||||
import { sort, onCreate, onRename, onDelete, onUpload } from './filespage.helper';
|
||||
import { NgIf, Loader, Uploader, EventReceiver } from '../components/';
|
||||
import { notify, debounce, goToFiles, goToViewer, event } from '../helpers/';
|
||||
import { notify, debounce, goToFiles, goToViewer, event, settings_get, settings_put } from '../helpers/';
|
||||
import { BreadCrumb, FileSystem, FrequentlyAccess } from './filespage/';
|
||||
import InfiniteScroll from 'react-infinite-scroller';
|
||||
|
||||
const PAGE_NUMBER_INIT = 3;
|
||||
|
||||
@EventReceiver
|
||||
@DragDropContext(('ontouchstart' in window)? HTML5Backend : HTML5Backend)
|
||||
@@ -17,11 +20,13 @@ export class FilesPage extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
path: props.match.url.replace('/files', '') || '/',
|
||||
sort: settings_get('filespage_sort') || 'type',
|
||||
view: settings_get('filespage_view') || 'grid',
|
||||
files: [],
|
||||
frequents: [],
|
||||
page_number: PAGE_NUMBER_INIT,
|
||||
loading: true,
|
||||
error: null,
|
||||
height: null
|
||||
error: null
|
||||
};
|
||||
|
||||
this.goToFiles = goToFiles.bind(null, this.props.history);
|
||||
@@ -33,10 +38,10 @@ export class FilesPage extends React.Component {
|
||||
this.onRefresh(this.state.path, 'directory');
|
||||
|
||||
// subscriptions
|
||||
this.props.subscribe('file.upload', this.onUpload.bind(this));
|
||||
this.props.subscribe('file.create', this.onCreate.bind(this));
|
||||
this.props.subscribe('file.rename', this.onRename.bind(this));
|
||||
this.props.subscribe('file.delete', this.onDelete.bind(this));
|
||||
this.props.subscribe('file.upload', onUpload.bind(this));
|
||||
this.props.subscribe('file.create', onCreate.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));
|
||||
|
||||
this.hideError();
|
||||
@@ -79,7 +84,7 @@ export class FilesPage extends React.Component {
|
||||
file.link = file.type === "file" ? "/view"+path : "/files"+path+"/";
|
||||
return file;
|
||||
});
|
||||
this.setState({files: files, loading: false});
|
||||
this.setState({files: sort(files, this.state.sort), loading: false, page_number: PAGE_NUMBER_INIT});
|
||||
}else{
|
||||
notify.send(res, 'error');
|
||||
}
|
||||
@@ -100,209 +105,40 @@ export class FilesPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onCreate(path, type, file){
|
||||
if(type === 'file'){
|
||||
return Files.touch(path, file)
|
||||
.then(() => {
|
||||
notify.send('A file named "'+Path.basename(path)+'" was created', 'success');
|
||||
return Promise.resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
notify.send(err, 'error');
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}else if(type === 'directory'){
|
||||
return Files.mkdir(path)
|
||||
.then(() => notify.send('A folder named "'+Path.basename(path)+'" was created', 'success'))
|
||||
.catch((err) => notify.send(err, 'error'));
|
||||
}else{
|
||||
return Promise.reject({message: 'internal error: can\'t create a '+type.toString(), code: 'UNKNOWN_TYPE'});
|
||||
}
|
||||
}
|
||||
onRename(from, to, type){
|
||||
return Files.mv(from, to, type)
|
||||
.then(() => notify.send('The file "'+Path.basename(from)+'" was renamed', 'success'))
|
||||
.catch((err) => notify.send(err, 'error'));
|
||||
}
|
||||
onDelete(path, type){
|
||||
return Files.rm(path, type)
|
||||
.then(() => notify.send('The file "'+Path.basename(path)+'" was deleted', 'success'))
|
||||
.catch((err) => notify.send(err, 'error'));
|
||||
}
|
||||
|
||||
onUpload(path, e){
|
||||
const MAX_POOL_SIZE = 10;
|
||||
let PRIOR_STATUS = {};
|
||||
extract_upload_directory_the_way_that_works_but_non_official(e.dataTransfer.items || [], [])
|
||||
.then((files) => {
|
||||
if(files.length === 0){
|
||||
return extract_upload_crappy_hack_but_official_way(e.dataTransfer);
|
||||
}
|
||||
return Promise.resolve(files);
|
||||
})
|
||||
.then((files) => {
|
||||
const processes = files.map((file) => {
|
||||
file.path = Path.join(path, file.path);
|
||||
if(file.type === 'file'){
|
||||
Files.touch(file.path, file.file, 'prepare_only');
|
||||
return {
|
||||
parent: file._prior || null,
|
||||
fn: Files.touch.bind(Files, file.path, file.file, 'execute_only')
|
||||
};
|
||||
}else{
|
||||
Files.mkdir(file.path, 'prepare_only');
|
||||
return {
|
||||
id: file._id || null,
|
||||
parent: file._prior || null,
|
||||
fn: Files.mkdir.bind(Files, file.path, 'execute_only')
|
||||
};
|
||||
}
|
||||
});
|
||||
function runner(id){
|
||||
let current_process = null;
|
||||
if(processes.length === 0) return Promise.resolve();
|
||||
|
||||
for(let i=0; i<processes.length; i++){
|
||||
if(processes[i].parent === null || PRIOR_STATUS[processes[i].parent] === true){
|
||||
current_process = processes[i];
|
||||
processes.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(current_process){
|
||||
return current_process.fn(id)
|
||||
.then(() => {
|
||||
if(current_process.id) PRIOR_STATUS[current_process.id] = true;
|
||||
return runner(id);
|
||||
})
|
||||
.catch((err) => {
|
||||
notify.send(err, 'error');
|
||||
return runner(id);
|
||||
});
|
||||
}else{
|
||||
return waitABit()
|
||||
.then(() => runner(id));
|
||||
function waitABit(){
|
||||
return new Promise((done) => {
|
||||
window.setTimeout(() => {
|
||||
requestAnimationFrame(() => {
|
||||
done();
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Promise.all(Array.apply(null, Array(MAX_POOL_SIZE)).map((process,index) => {
|
||||
return runner();
|
||||
})).then(() => {
|
||||
notify.send('Upload completed', 'success');
|
||||
}).catch((err) => {
|
||||
notify.send(err, 'error');
|
||||
onSort(_sort){
|
||||
settings_put('filespage_sort', _sort);
|
||||
this.setState({
|
||||
sort: _sort
|
||||
}, () => {
|
||||
requestAnimationFrame(() => {
|
||||
this.setState({
|
||||
page_number: PAGE_NUMBER_INIT,
|
||||
files: sort(this.state.files, _sort)
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
// adapted from: https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
|
||||
function _rand_id(){
|
||||
function s4() {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
}
|
||||
return s4() + s4() + s4() + s4();
|
||||
}
|
||||
|
||||
function extract_upload_directory_the_way_that_works_but_non_official(items, files = []){
|
||||
const traverseDirectory = (item, _files, parent_id) => {
|
||||
let file = {
|
||||
path: item.fullPath
|
||||
};
|
||||
if(item.isFile){
|
||||
return new Promise((done, err) => {
|
||||
file.type = "file";
|
||||
item.file((_file, _err) => {
|
||||
if(!_err){
|
||||
file.file = _file;
|
||||
if(parent_id) file._prior = parent_id;
|
||||
_files.push(file);
|
||||
}
|
||||
done(_files);
|
||||
});
|
||||
});
|
||||
}else if(item.isDirectory){
|
||||
file.type = "directory";
|
||||
file.path += "/";
|
||||
file._id = _rand_id();
|
||||
if(parent_id) file._prior = parent_id;
|
||||
_files.push(file);
|
||||
|
||||
return new Promise((done, err) => {
|
||||
item.createReader().readEntries(function(entries){
|
||||
Promise.all(entries.map((entry) => {
|
||||
return traverseDirectory(entry, _files, file._id);
|
||||
})).then(() => done(_files));
|
||||
});
|
||||
});
|
||||
}else{
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
return Promise.all(
|
||||
Array.prototype.slice.call(items).map((item) => {
|
||||
if(typeof item.webkitGetAsEntry === 'function'){
|
||||
return traverseDirectory(item.webkitGetAsEntry(), files.slice(0));
|
||||
}
|
||||
}).filter((e) => e)
|
||||
).then((res) => Promise.resolve([].concat.apply([], res)));
|
||||
}
|
||||
|
||||
function extract_upload_crappy_hack_but_official_way(data){
|
||||
const _files = data.files;
|
||||
return Promise.all(
|
||||
Array.prototype.slice.call(_files).map((_file) => {
|
||||
return detectType(_file)
|
||||
.then(transform);
|
||||
function detectType(_f){
|
||||
// the 4096 is an heuristic I've observed and taken from: https://stackoverflow.com/questions/25016442/how-to-distinguish-if-a-file-or-folder-is-being-dragged-prior-to-it-being-droppe
|
||||
// however the proposed answer is just wrong as it doesn't consider folder with name such as: test.png
|
||||
// and as Stackoverflow favor consanguinity with their point system, I couldn't rectify the proposed answer.
|
||||
// The following code is actually working as expected
|
||||
if(_file.size % 4096 !== 0){
|
||||
return Promise.resolve('file');
|
||||
}
|
||||
return new Promise((done, err) => {
|
||||
let reader = new window.FileReader();
|
||||
reader.onload = function() {
|
||||
done('file');
|
||||
};
|
||||
reader.onerror = function() {
|
||||
done('directory');
|
||||
};
|
||||
reader.readAsText(_f);
|
||||
});
|
||||
}
|
||||
|
||||
function transform(_type){
|
||||
let file = {
|
||||
type: _type,
|
||||
path: _file.name
|
||||
};
|
||||
if(file.type === 'file'){
|
||||
file.file = _file;
|
||||
}else{
|
||||
file.path += "/";
|
||||
}
|
||||
return Promise.resolve(file);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onView(){
|
||||
const _view = this.state.view === "list" ? "grid" : "list";
|
||||
settings_put('filespage_view', _view);
|
||||
this.setState({
|
||||
view: _view
|
||||
}, () => {
|
||||
requestAnimationFrame(() => {
|
||||
this.setState({
|
||||
page_number: PAGE_NUMBER_INIT
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
loadMore(){
|
||||
requestAnimationFrame(() => {
|
||||
let page_number = this.state.page_number + 1;
|
||||
this.setState({page_number: page_number});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
@@ -310,13 +146,15 @@ export class FilesPage extends React.Component {
|
||||
<BreadCrumb className="breadcrumb" path={this.state.path} />
|
||||
<div className="page_container">
|
||||
<div className="scroll-y">
|
||||
<NgIf className="container" cond={this.state.loading === false && this.state.error === null}>
|
||||
<NgIf cond={this.state.path === '/'}>
|
||||
<FrequentlyAccess files={this.state.frequents}/>
|
||||
<InfiniteScroll pageStart={0} hasMore={this.state.files.length > 70} initialLoad={false} useWindow={false} loadMore={this.loadMore.bind(this)} threshold={40} loader={<div className="infinite_scroll_loading" key={-1}><Loader/></div>}>
|
||||
<NgIf className="container" cond={this.state.loading === false && this.state.error === null}>
|
||||
<NgIf cond={this.state.path === '/'}>
|
||||
<FrequentlyAccess files={this.state.frequents}/>
|
||||
</NgIf>
|
||||
<FileSystem path={this.state.path} sort={this.state.sort} view={this.state.view} onSort={this.onSort.bind(this)} onView={this.onView.bind(this)} files={this.state.files.slice(0, this.state.page_number * 24)} />
|
||||
<Uploader path={this.state.path} />
|
||||
</NgIf>
|
||||
<FileSystem path={this.state.path} files={this.state.files} />
|
||||
<Uploader path={this.state.path} />
|
||||
</NgIf>
|
||||
</InfiniteScroll>
|
||||
<NgIf cond={this.state.loading && this.state.error === null}>
|
||||
<Loader/>
|
||||
</NgIf>
|
||||
|
||||
@@ -15,6 +15,16 @@
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.infinite_scroll_loading{
|
||||
text-align: center;
|
||||
.component_loader{
|
||||
margin-top: -50px;
|
||||
svg{
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-y{
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||
import { DropTarget } from 'react-dnd';
|
||||
|
||||
import Path from 'path';
|
||||
|
||||
import "./filesystem.scss";
|
||||
@@ -19,8 +20,7 @@ export class FileSystem extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
creating: null,
|
||||
access_right: this._findAccessRight(props.files),
|
||||
sort: 'type'
|
||||
access_right: this._findAccessRight(props.files)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -34,66 +34,6 @@ export class FileSystem extends React.Component {
|
||||
return {can_create_file: true, can_create_directory: true};
|
||||
}
|
||||
|
||||
sort(files, type){
|
||||
if(type === 'name'){
|
||||
return sortByName(files);
|
||||
}else if(type === 'date'){
|
||||
return sortByDate(files);
|
||||
}else{
|
||||
return sortByType(files);
|
||||
}
|
||||
function sortByType(files){
|
||||
return files.sort((fileA, fileB) => {
|
||||
if(fileA.icon === 'loading' && fileB.icon !== 'loading'){return +1;}
|
||||
else if(fileA.icon !== 'loading' && fileB.icon === 'loading'){return -1;}
|
||||
else{
|
||||
if(['directory', 'link'].indexOf(fileA.type) === -1 && ['directory', 'link'].indexOf(fileB.type) !== -1){
|
||||
return +1;
|
||||
}else if(['directory', 'link'].indexOf(fileA.type) !== -1 && ['directory', 'link'].indexOf(fileB.type) === -1){
|
||||
return -1;
|
||||
}else{
|
||||
if(fileA.name[0] === "." && fileB.name[0] !== ".") return +1;
|
||||
else if(fileA.name[0] !== "." && fileB.name[0] === ".") return -1;
|
||||
else{
|
||||
if(fileA.name.toLowerCase() === fileB.name.toLowerCase()){
|
||||
return fileA.name > fileB.name ? +1 : -1;
|
||||
}
|
||||
return fileA.name.toLowerCase() > fileB.name.toLowerCase() ? +1 : -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function sortByName(files){
|
||||
return files.sort((fileA, fileB) => {
|
||||
if(fileA.icon === 'loading' && fileB.icon !== 'loading'){return +1;}
|
||||
else if(fileA.icon !== 'loading' && fileB.icon === 'loading'){return -1;}
|
||||
else{
|
||||
if(fileA.name[0] === "." && fileB.name[0] !== ".") return +1;
|
||||
else if(fileA.name[0] !== "." && fileB.name[0] === ".") return -1;
|
||||
else{
|
||||
if(fileA.name.toLowerCase() === fileB.name.toLowerCase()){
|
||||
return fileA.name > fileB.name ? +1 : -1;
|
||||
}
|
||||
return fileA.name.toLowerCase() > fileB.name.toLowerCase() ? +1 : -1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function sortByDate(files){
|
||||
return files.sort((fileA, fileB) => {
|
||||
if(fileA.icon === 'loading' && fileB.icon !== 'loading'){return +1;}
|
||||
else if(fileA.icon !== 'loading' && fileB.icon === 'loading'){return -1;}
|
||||
else{
|
||||
if(fileB.time === fileA.time){
|
||||
return fileA.name > fileB.name ? +1 : -1;
|
||||
}
|
||||
return fileB.time - fileA.time;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onComponentPropsUpdate(props){
|
||||
this.setState({access_right: this._findAccessRight(props.files)});
|
||||
}
|
||||
@@ -102,16 +42,16 @@ export class FileSystem extends React.Component {
|
||||
return this.props.connectDropFile(
|
||||
<div className="component_filesystem">
|
||||
<Container>
|
||||
<NewThing path={this.props.path} sort={this.state.sort} onSortUpdate={(value) => {this.setState({sort: value})}} accessRight={this.state.access_right}></NewThing>
|
||||
<NewThing path={this.props.path} sort={this.props.sort} view={this.props.view} onViewUpdate={(value) => this.props.onView(value)} onSortUpdate={(value) => {this.props.onSort(value);}} accessRight={this.state.access_right}></NewThing>
|
||||
<NgIf cond={this.props.fileIsOver}>
|
||||
<FileZone path={this.props.path} />
|
||||
</NgIf>
|
||||
<NgIf className="list" cond={this.props.files.length > 0}>
|
||||
<ReactCSSTransitionGroup transitionName="filelist-item" transitionLeave={false} transitionEnter={false} transitionAppear={true} transitionAppearTimeout={200}>
|
||||
{
|
||||
this.sort(this.props.files, this.state.sort).map((file, index) => {
|
||||
this.props.files.map((file, index) => {
|
||||
if(file.type === 'directory' || file.type === 'file' || file.type === 'link' || file.type === 'bucket'){
|
||||
return <ExistingThing key={file.name+(file.icon || '')} file={file} path={this.props.path} />
|
||||
return ( <ExistingThing view={this.props.view} key={file.name+(file.icon || '')} file={file} path={this.props.path} /> );
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import React from 'react';
|
||||
import path from 'path';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { DragSource, DropTarget } from 'react-dnd';
|
||||
|
||||
import './thing.scss';
|
||||
import { Card, NgIf, Icon, EventEmitter } from '../../components/';
|
||||
import { pathBuilder, prompt, leftPad } from '../../helpers/';
|
||||
import { pathBuilder, prompt, leftPad, getMimeType, debounce, memory } from '../../helpers/';
|
||||
import { Files } from '../../model/';
|
||||
import img_placeholder from '../../assets/img/placeholder.png';
|
||||
|
||||
const fileSource = {
|
||||
beginDrag(props, monitor, component) {
|
||||
@@ -80,22 +83,67 @@ export class ExistingThing extends React.Component {
|
||||
hover: null,
|
||||
message: null,
|
||||
filename: props.file.name,
|
||||
is_renaming: false
|
||||
is_renaming: false,
|
||||
preview: null
|
||||
};
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState){
|
||||
if(nextState.hover !== this.state.hover ||
|
||||
nextState.message !== this.state.message ||
|
||||
nextState.is_renaming !== this.state.is_renaming ||
|
||||
nextProps.view !== this.props.view ||
|
||||
this.state.preview !== nextState.preview ||
|
||||
this.props.fileIsOver !== nextProps.fileIsOver ||
|
||||
this.props.canDropFile !== nextProps.canDropFile ||
|
||||
this.props.nativeFileIsOver !== nextProps.nativeFileIsOver ||
|
||||
this.props.canDropNativeFile !== nextProps.canDropNativeFile
|
||||
){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
this.updateThumbnail(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props){
|
||||
if(props.view !== this.props.view){
|
||||
this.updateThumbnail(props);
|
||||
}
|
||||
}
|
||||
|
||||
updateThumbnail(props){
|
||||
if(props.view === "grid" && props.icon !== "loading"){
|
||||
const _path = path.join(props.path, props.file.name);
|
||||
const type = getMimeType(_path).split("/")[0];
|
||||
if(type === "image"){
|
||||
Files.url(_path).then((url) => {
|
||||
this.setState({preview: url+"&size=250"});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onRename(newFilename){
|
||||
this.props.emit(
|
||||
'file.rename',
|
||||
pathBuilder(this.props.path, this.props.file.name, this.props.file.type),
|
||||
pathBuilder(this.props.path, newFilename, this.props.file.type),
|
||||
this.props.file.type
|
||||
);
|
||||
if(typeof newFilename === "string"){
|
||||
this.props.emit(
|
||||
'file.rename',
|
||||
pathBuilder(this.props.path, this.props.file.name, this.props.file.type),
|
||||
pathBuilder(this.props.path, newFilename, this.props.file.type),
|
||||
this.props.file.type
|
||||
);
|
||||
}
|
||||
this.setState({is_renaming: false});
|
||||
}
|
||||
|
||||
onRenameRequest(){
|
||||
this.setState({is_renaming: !this.state.is_renaming});
|
||||
onRenameRequest(force){
|
||||
let new_state = !this.state.is_renaming;
|
||||
if(typeof force === "boolean"){
|
||||
new_state = force;
|
||||
}
|
||||
this.setState({is_renaming: new_state});
|
||||
}
|
||||
|
||||
onDeleteRequest(filename){
|
||||
@@ -150,18 +198,20 @@ export class ExistingThing extends React.Component {
|
||||
if(this.props.file.icon === 'loading'){
|
||||
className += "loading ";
|
||||
}
|
||||
|
||||
if(this.state.preview){
|
||||
className += "preview ";
|
||||
}
|
||||
className = className.trim();
|
||||
|
||||
return connectDragSource(connectDropNativeFile(connectDropFile(
|
||||
<div className="component_thing">
|
||||
<div className={"component_thing view-"+this.props.view}>
|
||||
<Link to={this.props.file.link || "#"}>
|
||||
<Card className={this.state.hover} className={className}>
|
||||
<Icon name={this.props.file.icon || this.props.file.type} />
|
||||
<Filename filename={this.props.file.name} filesize={this.props.file.size} filetype={this.props.file.type} onRename={this.onRename.bind(this)} is_renaming={this.state.is_renaming} />
|
||||
<Image preview={this.state.preview} icon={this.props.file.icon || this.props.file.type} view={this.props.view} path={path.join(this.props.path, this.props.file.name)} />
|
||||
<Filename filename={this.props.file.name} filesize={this.props.file.size} filetype={this.props.file.type} onRename={this.onRename.bind(this)} is_renaming={this.state.is_renaming} onRenameCancel={this.onRenameRequest.bind(this, false)}/>
|
||||
<Message message={this.state.message} />
|
||||
<DateTime show={this.state.icon !== 'loading'} timestamp={this.props.file.time} />
|
||||
<ActionButton onClickRename={this.onRenameRequest.bind(this)} onClickDelete={this.onDeleteRequest.bind(this)} can_move={this.props.file.can_move !== false} can_delete={this.props.file.can_delete !== false} />
|
||||
<ActionButton onClickRename={this.onRenameRequest.bind(this)} onClickDelete={this.onDeleteRequest.bind(this)} is_renaming={this.state.is_renaming} can_move={this.props.file.can_move !== false} can_delete={this.props.file.can_delete !== false} />
|
||||
</Card>
|
||||
</Link>
|
||||
</div>
|
||||
@@ -185,12 +235,22 @@ class Filename extends React.Component {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
moveCaretAtEnd(e){
|
||||
const length = e.target.value.length;
|
||||
e.target.setSelectionRange(length, length);
|
||||
}
|
||||
|
||||
onRename(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onRename(this.state.filename);
|
||||
}
|
||||
|
||||
onCancel(){
|
||||
this.props.onRenameCancel();
|
||||
}
|
||||
|
||||
preventSelect(e){
|
||||
e.preventDefault();
|
||||
}
|
||||
@@ -204,7 +264,7 @@ class Filename extends React.Component {
|
||||
</NgIf>
|
||||
<NgIf cond={this.props.is_renaming === true} type='inline'>
|
||||
<form onClick={this.preventSelect} onSubmit={this.onRename.bind(this)}>
|
||||
<input value={this.state.filename} onChange={(e) => this.setState({filename: e.target.value})} autoFocus />
|
||||
<input value={this.state.filename} onChange={(e) => this.setState({filename: e.target.value})} onBlur={this.onCancel.bind(this)} onFocus={this.moveCaretAtEnd.bind(this)} autoFocus />
|
||||
</form>
|
||||
</NgIf>
|
||||
</span>
|
||||
@@ -226,7 +286,7 @@ const ActionButton = (props) => {
|
||||
|
||||
return (
|
||||
<div className="component_action">
|
||||
<NgIf cond={props.can_move === true} type="inline">
|
||||
<NgIf cond={props.can_move === true && props.is_renaming === false} type="inline">
|
||||
<Icon name="edit" onClick={onRename} className="component_updater--icon" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.can_delete === true} type="inline">
|
||||
@@ -292,3 +352,73 @@ const Message = (props) => {
|
||||
</NgIf>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
class Image extends React.Component{
|
||||
constructor(props){
|
||||
super(props);
|
||||
}
|
||||
|
||||
render(){
|
||||
if(this.props.preview && this.props.view === "grid"){
|
||||
return (
|
||||
<span>
|
||||
<div className="image_layer"></div>
|
||||
<LazyLoadImage scroller=".scroll-y" className="thumbnail" src={this.props.preview} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const ext = path.extname(this.props.path).replace(/^\./, "");
|
||||
return (
|
||||
<span>
|
||||
<Icon name={this.props.icon} />
|
||||
<NgIf cond={!!ext && this.props.view === "grid" && this.props.icon === "file"} className="info_extension">
|
||||
<span>{ext}</span>
|
||||
</NgIf>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class LazyLoadImage extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
appear: false
|
||||
};
|
||||
this.$scroll = document.querySelector(props.scroller);
|
||||
this.onScroll = debounce(this.onScroll.bind(this), 100);
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
if(!this.$scroll){ throw("No scroll detected on LazyLoadImage"); }
|
||||
this.$scroll.addEventListener("scroll", this.onScroll, {passive: true});
|
||||
this.onScroll();
|
||||
}
|
||||
componentWillUnmount(){
|
||||
this.$scroll.removeEventListener("scroll", this.onScroll);
|
||||
}
|
||||
|
||||
onScroll(){
|
||||
if(!this.refs.$el) return this.componentWillUnmount();
|
||||
const dim_el = this.refs.$el.getBoundingClientRect();
|
||||
if(dim_el.top + dim_el.height > 0 && dim_el.top < window.innerHeight){
|
||||
this.componentWillUnmount();
|
||||
memory.set(this.props.src, true);
|
||||
this.setState({appear: true});
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
if(this.props.preview || memory.get(this.props.src) === null){
|
||||
return (
|
||||
<img ref="$el" className={this.props.className} src={img_placeholder} />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<img className={this.props.className} src={this.props.src} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Card, NgIf, Icon, EventEmitter } from '../../components/';
|
||||
import { Card, NgIf, Icon, EventEmitter, Dropdown, DropdownButton, DropdownList, DropdownItem } from '../../components/';
|
||||
import { pathBuilder } from '../../helpers/';
|
||||
import "./thing.scss";
|
||||
|
||||
@@ -18,7 +18,7 @@ export class NewThing extends React.Component {
|
||||
|
||||
this._onEscapeKeyPress = (e) => {
|
||||
if(e.keyCode === 27) this.onDelete();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
@@ -48,8 +48,12 @@ export class NewThing extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onSortUpdate(e){
|
||||
this.props.onSortUpdate(e.target.value);
|
||||
onViewChange(e){
|
||||
this.props.onViewUpdate();
|
||||
}
|
||||
|
||||
onSortChange(e){
|
||||
this.props.onSortUpdate(e);
|
||||
}
|
||||
|
||||
render(){
|
||||
@@ -58,11 +62,17 @@ export class NewThing extends React.Component {
|
||||
<div className="menubar no-select">
|
||||
<NgIf cond={this.props.accessRight.can_create_file === true} onClick={this.onNew.bind(this, 'file')} type="inline">New File</NgIf>
|
||||
<NgIf cond={this.props.accessRight.can_create_directory === true} onClick={this.onNew.bind(this, 'directory')} type="inline">New Directory</NgIf>
|
||||
<select value={this.props.sort} onChange={this.onSortUpdate.bind(this)}>
|
||||
<option value="type">Sort By Type</option>
|
||||
<option value="date">Sort By Date</option>
|
||||
<option value="name">Sort By Name</option>
|
||||
</select>
|
||||
<Dropdown className="view sort" onChange={this.onSortChange.bind(this)}>
|
||||
<DropdownButton>
|
||||
<Icon name="sort"/>
|
||||
</DropdownButton>
|
||||
<DropdownList>
|
||||
<DropdownItem name="type" icon={this.props.sort === "type" ? "check" : null}> Sort By Type </DropdownItem>
|
||||
<DropdownItem name="date" icon={this.props.sort === "date" ? "check" : null}> Sort By Date </DropdownItem>
|
||||
<DropdownItem name="name" icon={this.props.sort === "name" ? "check" : null}> Sort By Name </DropdownItem>
|
||||
</DropdownList>
|
||||
</Dropdown>
|
||||
<div className="view list-grid" onClick={this.onViewChange.bind(this)}><Icon name={this.props.view === "grid" ? "list" : "grid"}/></div>
|
||||
</div>
|
||||
<NgIf cond={this.state.type !== null} className="component_thing">
|
||||
<Card className="mouse-is-hover highlight">
|
||||
@@ -94,4 +104,4 @@ NewThing.PropTypes = {
|
||||
onCreate: PropTypes.func.isRequired,
|
||||
onSortUpdate: PropTypes.func.isRequired,
|
||||
sort: PropTypes.string.isRequired
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,17 +5,41 @@
|
||||
height: 15px;
|
||||
margin-top: 5px;
|
||||
color: var(--light);
|
||||
margin: 0 0 10px 0;
|
||||
margin: 5px 0 10px 0;
|
||||
|
||||
> span{cursor: pointer; margin-right: 15px;}
|
||||
select{
|
||||
> div, > span{cursor: pointer; margin-right: 15px;}
|
||||
.view{
|
||||
float: right;
|
||||
color: var(--light);
|
||||
background: none;
|
||||
border-radius: 3px;
|
||||
outline: none;
|
||||
border: 1px solid var(--light);
|
||||
font-size: 12px;
|
||||
padding: 2px;
|
||||
transition: 0.15s ease-out background;
|
||||
margin-right: 0px;
|
||||
margin-left: 0px;
|
||||
.component_icon{
|
||||
min-width: 20px;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
}
|
||||
&.list-grid, > .dropdown_button{
|
||||
border-radius: 2px;
|
||||
padding: 5px;
|
||||
margin: 2px 0 2px 0;
|
||||
&:hover, &:focus{
|
||||
background: white;
|
||||
border-color: var(--bg-color);
|
||||
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
&.active > .dropdown_button{
|
||||
background: white;
|
||||
border-color: var(--bg-color);
|
||||
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.dropdown_container .component_icon{
|
||||
box-sizing: border-box;
|
||||
border: 2px solid rgba(0,0,0,0);
|
||||
}
|
||||
&.list-grid{margin-top: -5px!important;}
|
||||
&.component_dropdown { margin-top: -10px; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,3 +131,137 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* GRID & LIST VIEW */
|
||||
.list > span{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
> .component_thing{
|
||||
width: 100%;
|
||||
.box{margin: 0px 0 2px 0;}
|
||||
}
|
||||
|
||||
// GRID VIEW
|
||||
> .component_thing.view-grid{
|
||||
width: 25%; .box{ height: 160px;}
|
||||
@media (max-width: 800px){width: 33.33%; .box{height: 200px} }
|
||||
@media (max-width: 670px){.box{height: 170px}}
|
||||
@media (max-width: 550px){width: 50%; .box{height: 200px} }
|
||||
@media (max-width: 480px){.box{height: 170px}}
|
||||
@media (max-width: 400px){.box{height: 140px; .info_extension{font-size: 0.85em; padding: 3px 10px;}}}
|
||||
@media (max-width: 340px){.box{height: 130px}}
|
||||
text-align: center;
|
||||
.box{
|
||||
margin: 2px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
> span > img{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: block;
|
||||
}
|
||||
> span > img.component_icon{
|
||||
padding: 30px;
|
||||
box-sizing: border-box;
|
||||
width: auto;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
z-index: 0;
|
||||
}
|
||||
> span > img.thumbnail{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: 50% 50%;
|
||||
background: var(--dark);
|
||||
z-index: 0;
|
||||
}
|
||||
.info_extension{
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
text-align: right;
|
||||
left: 0; right: 20%;
|
||||
margin: 0 auto;
|
||||
span{
|
||||
background: var(--color);
|
||||
color: var(--bg-color);
|
||||
border-radius: 4px;
|
||||
padding: 4px 12px;
|
||||
}
|
||||
}
|
||||
.component_filename{
|
||||
letter-spacing: -0.5px;
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
left: 2px;
|
||||
right: 2px;
|
||||
border-radius: 2px;
|
||||
padding: 3px 0px;
|
||||
.file-details{
|
||||
width: calc(100% - 10px);
|
||||
display: block;
|
||||
> span{
|
||||
width: 100%;
|
||||
form input{
|
||||
letter-spacing: -0.5px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.image_layer{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
background: rgba(0,0,0,0);
|
||||
transition: 0.2s ease-out background;
|
||||
}
|
||||
&:hover{
|
||||
.component_action{
|
||||
transition-delay: 0.1s;
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
img.thumbnail{
|
||||
transform: scale(0.6);
|
||||
}
|
||||
.image_layer{
|
||||
background: rgba(242, 242, 242, 0.7);
|
||||
}
|
||||
}
|
||||
&.preview{
|
||||
&:hover{
|
||||
.component_filename{
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.component_filename{
|
||||
transition: 0.2s ease-out opacity;
|
||||
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;}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,16 @@ import React from 'react';
|
||||
import { MenuBar } from './menubar';
|
||||
|
||||
export const ImageViewer = (props) => {
|
||||
const image_url = (size) => {
|
||||
return props.data+"&meta=true&size="+parseInt((window.innerWidth - 40)*size);
|
||||
};
|
||||
return (
|
||||
<div style={{height: '100%'}}>
|
||||
<MenuBar title={props.filename} download={props.data} />
|
||||
<div style={{textAlign: 'center', background: '#525659', height: 'calc(100% - 34px)', overflow: 'hidden', padding: '20px', boxSizing: 'border-box'}}>
|
||||
<img src={props.data} style={{maxHeight: '100%', maxWidth: '100%', minHeight: '100px', background: '#f1f1f1', boxShadow: 'rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px, rgba(0, 0, 0, 0.2) 0px 2px 4px -1px'}} />
|
||||
<img src={image_url(1)}
|
||||
srcSet={image_url(1)+", "+image_url(3/2)+" 1.5x, "+image_url(2)+" 2x"}
|
||||
style={{maxHeight: '100%', maxWidth: '100%', minHeight: '100px', background: '#f1f1f1', boxShadow: 'rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px, rgba(0, 0, 0, 0.2) 0px 2px 4px -1px'}} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -22,5 +22,12 @@ module.exports = {
|
||||
clientID: process.env.DROPBOX_CLIENT_ID,
|
||||
redirectURI: process.env.APPLICATION_URL+"/login"
|
||||
},
|
||||
transcoder: {
|
||||
url: process.env.TRANSCODER_URL || null,
|
||||
audio_bitrate: 128, // in kbps
|
||||
audio_quality: 0,
|
||||
video_codec: 'libx264',
|
||||
video_bitrate: 1000 // in kbps
|
||||
},
|
||||
secret_key: process.env.SECRET_KEY || (Math.random()*Math.pow(10,16)).toString(32)
|
||||
};
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
"react-dnd-html5-backend-filedrop": "^1.0.0",
|
||||
"react-dom": "^15.3.2",
|
||||
"react-draggable": "^2.2.6",
|
||||
"react-infinite-scroller": "^1.1.4",
|
||||
"react-router": "^4.1.1",
|
||||
"react-router-dom": "^4.1.1",
|
||||
"requirejs": "^2.3.5",
|
||||
|
||||
@@ -3,7 +3,10 @@ var express = require('express'),
|
||||
path = require('path'),
|
||||
crypto = require('../utils/crypto'),
|
||||
Files = require('../model/files'),
|
||||
config = require('../../config_server'),
|
||||
multiparty = require('multiparty'),
|
||||
request = require('request'),
|
||||
FormData = require('form-data'),
|
||||
mime = require('../utils/mimetype.js');
|
||||
|
||||
app.use(function(req, res, next){
|
||||
@@ -35,8 +38,8 @@ app.get('/cat', function(req, res){
|
||||
res.clearCookie("download");
|
||||
if(path){
|
||||
Files.cat(path, req.cookies.auth, res)
|
||||
.then(function(stream){
|
||||
stream = stream.on('error', function (error) {
|
||||
.then(function(_stream){
|
||||
_stream = _stream.on('error', function (error) {
|
||||
let status = 404;
|
||||
if(error && typeof error.status === "number"){
|
||||
status = error.status;
|
||||
@@ -44,8 +47,33 @@ app.get('/cat', function(req, res){
|
||||
res.status(status).send({status: status, message: "There's nothing here"});
|
||||
if(typeof this.end === "function") this.end();
|
||||
});
|
||||
res.set('Content-Type', mime.getMimeType(path));
|
||||
stream.pipe(res);
|
||||
const mType = mime.getMimeType(path);
|
||||
res.set('Content-Type', mType);
|
||||
|
||||
if((/^image\//.test(mType) || /^video\//.test(mType) ) && config.transcoder.url){
|
||||
const form = new FormData();
|
||||
form.append('video', _stream, {
|
||||
filename: 'tmp',
|
||||
contentType: mType,
|
||||
});
|
||||
|
||||
let endpoint = config.transcoder.url;
|
||||
if(req.query.size){
|
||||
endpoint += "?size="+req.query.size;
|
||||
}
|
||||
const post_request = request({
|
||||
method: "POST",
|
||||
url: endpoint,
|
||||
headers: form.getHeaders()
|
||||
});
|
||||
return form.pipe(post_request)
|
||||
.on('error', (err) => {
|
||||
console.log(err);
|
||||
res.status(500).end();
|
||||
})
|
||||
.pipe(res);
|
||||
}
|
||||
return _stream.pipe(res);
|
||||
})
|
||||
.catch(function(err){ errorHandler(res, err, 'couldn\'t read the file'); });
|
||||
}else{
|
||||
|
||||