mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-30 12:16:07 +01:00
maintain (eslint): linting on frontend
This commit is contained in:
parent
48a6763380
commit
5156432b52
62 changed files with 1920 additions and 1655 deletions
|
|
@ -53,6 +53,12 @@ steps:
|
|||
- git clone https://mickael-kerjean:$PASSWORD@github.com/mickael-kerjean/filestash-test test
|
||||
- chmod -R 777 ./test/
|
||||
|
||||
- name: lint_frontend
|
||||
image: node:8-alpine
|
||||
depends_on: [ test_prepare, build_frontend ]
|
||||
commands:
|
||||
- npx eslint ./client/
|
||||
|
||||
- name: test_frontend
|
||||
image: node:8-alpine
|
||||
depends_on: [ test_prepare, build_frontend ]
|
||||
|
|
|
|||
|
|
@ -4,21 +4,38 @@
|
|||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended"
|
||||
"plugin:react/recommended",
|
||||
"google"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"semi": ["error", "always"],
|
||||
"quotes": ["error", "double"]
|
||||
"indent": [2, 4],
|
||||
"quotes": [2, "double"],
|
||||
"object-curly-spacing": [2, "always"],
|
||||
"require-jsdoc": [0],
|
||||
"camelcase": [0],
|
||||
"max-len": ["error", 100],
|
||||
"prefer-promise-reject-errors": [0],
|
||||
"guard-for-in": [0],
|
||||
"react/prop-types": [0],
|
||||
"new-cap": [0],
|
||||
"prefer-spread": [0],
|
||||
|
||||
"no-throw-literal": [1],
|
||||
"react/no-find-dom-node": [1],
|
||||
"no-invalid-this": [1],
|
||||
"react/no-string-refs": [1],
|
||||
"react/display-name": [1],
|
||||
"prefer-rest-params": [1],
|
||||
"react/no-deprecated": [1]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,37 +8,40 @@ import { t } from "../locales/";
|
|||
import "./alert.scss";
|
||||
|
||||
export class ModalAlert extends Popup {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
componentDidMount() {
|
||||
alert.subscribe((Component, okCallback) => {
|
||||
this.setState({
|
||||
appear: true,
|
||||
value: Component,
|
||||
fn: okCallback
|
||||
fn: okCallback,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit(){
|
||||
this.setState({appear: false}, () => {
|
||||
onSubmit() {
|
||||
this.setState({ appear: false }, () => {
|
||||
requestAnimationFrame(() => this.state.fn && this.state.fn());
|
||||
});
|
||||
}
|
||||
|
||||
modalContentBody(){
|
||||
modalContentBody() {
|
||||
return (
|
||||
<div className="modal-message">
|
||||
{this.state.value}
|
||||
{this.state.value}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
modalContentFooter(){
|
||||
modalContentFooter() {
|
||||
return (
|
||||
<Button type="submit" theme="emphasis" onClick={this.onSubmit.bind(this)}>{ t("OK") }</Button>
|
||||
<Button type="submit" theme="emphasis"
|
||||
onClick={this.onSubmit.bind(this)}>
|
||||
{ t("OK") }
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -46,7 +49,7 @@ export class ModalAlert extends Popup {
|
|||
export function Alert({ children = null, className = null }) {
|
||||
return (
|
||||
<div className={"alert" + (className ? ` ${className}`: "")}>
|
||||
{ children }
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
|
||||
/**
|
||||
/*
|
||||
* Back when I started this project, there used to be a library for animation made by facebook
|
||||
* named: react-addons-css-transition-group
|
||||
* Facebook got away from it and things haven't stayed backward compatible at the point where I got
|
||||
* to realise it would be easier to write this simpler wrapper than migrate things over
|
||||
*/
|
||||
export function CSSTransition({ transitionName = "animate", children = null, transitionAppearTimeout = 300 }) {
|
||||
export function CSSTransition({
|
||||
transitionName = "animate", children = null, transitionAppearTimeout = 300,
|
||||
}) {
|
||||
const [className, setClassName] = useState(`${transitionName} ${transitionName}-appear`);
|
||||
|
||||
useEffect(() => {
|
||||
setClassName(`${transitionName} ${transitionName}-appear ${transitionName}-appear-active`)
|
||||
setClassName(`${transitionName} ${transitionName}-appear ${transitionName}-appear-active`);
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
setClassName(`${transitionName}`)
|
||||
setClassName(`${transitionName}`);
|
||||
}, transitionAppearTimeout);
|
||||
return () => clearTimeout(timeout);
|
||||
}, []);
|
||||
|
|
@ -22,5 +24,5 @@ export function CSSTransition({ transitionName = "animate", children = null, tra
|
|||
<div className={className}>
|
||||
{ children }
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +1,50 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { NgIf, Icon, EventEmitter, EventReceiver } from './';
|
||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||
// eslint-disable
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Link } from "react-router-dom";
|
||||
import { NgIf, Icon, EventEmitter } from "./";
|
||||
import ReactCSSTransitionGroup from "react-addons-css-transition-group";
|
||||
|
||||
import './breadcrumb.scss';
|
||||
import "./breadcrumb.scss";
|
||||
|
||||
export class BreadCrumb extends React.Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
path: this._formatPath(props.path)
|
||||
path: this._formatPath(props.path),
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props){
|
||||
this.setState({path: this._formatPath(props.path)});
|
||||
UNSAFE_componentWillReceiveProps(props) {
|
||||
this.setState({ path: this._formatPath(props.path) });
|
||||
}
|
||||
|
||||
_formatPath(full_path){
|
||||
_formatPath(full_path) {
|
||||
let paths = full_path.split("/");
|
||||
if(paths.slice(-1)[0] === ''){
|
||||
if (paths.slice(-1)[0] === "") {
|
||||
paths.pop();
|
||||
}
|
||||
paths = paths.map((path, index) => {
|
||||
let sub_path = paths.slice(0, index+1).join('/'),
|
||||
label = path === '' ? (CONFIG.name || 'Filestash') : path;
|
||||
if(index === paths.length - 1){
|
||||
return {full: null, label: label};
|
||||
}else{
|
||||
const sub_path = paths.slice(0, index+1).join("/");
|
||||
const label = path === "" ? (CONFIG.name || "Filestash") : path;
|
||||
if (index === paths.length - 1) {
|
||||
return { full: null, label: label };
|
||||
} else {
|
||||
return {
|
||||
full: sub_path+'/',
|
||||
full: sub_path + "/",
|
||||
label: label,
|
||||
minify: function(){
|
||||
if(index === 0){ return false; }
|
||||
if(paths.length <= (document.body.clientWidth > 800 ? 5 : 4)){ return false; }
|
||||
if(index > paths.length - (document.body.clientWidth > 1000? 4 : 3)) return false;
|
||||
minify: function() {
|
||||
if (index === 0) {
|
||||
return false;
|
||||
}
|
||||
if (paths.length <= (document.body.clientWidth > 800 ? 5 : 4)) {
|
||||
return false;
|
||||
}
|
||||
if (index > paths.length - (document.body.clientWidth > 1000? 4 : 3)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}()
|
||||
}(),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
@ -45,43 +52,48 @@ export class BreadCrumb extends React.Component {
|
|||
}
|
||||
|
||||
render(Element) {
|
||||
if(new window.URL(location.href).searchParams.get("nav") === "false") return null;
|
||||
if (new window.URL(location.href).searchParams.get("nav") === "false") return null;
|
||||
|
||||
const Path = Element? Element : PathElement;
|
||||
return (
|
||||
<div className="component_breadcrumb" role="navigation">
|
||||
<BreadCrumbContainer className={this.props.className+' no-select'}>
|
||||
<Logout />
|
||||
<ReactCSSTransitionGroup transitionName="breadcrumb" transitionLeave={true} transitionEnter={true} transitionLeaveTimeout={150} transitionEnterTimeout={200} transitionAppear={false}>
|
||||
{
|
||||
this.state.path.map((path, index) => {
|
||||
return (
|
||||
<Path key={"breadcrumb_"+index} currentSelection={this.props.currentSelection} path={path} isLast={this.state.path.length === index + 1} needSaving={this.props.needSaving} />
|
||||
);
|
||||
})
|
||||
}
|
||||
</ReactCSSTransitionGroup>
|
||||
</BreadCrumbContainer>
|
||||
</div>
|
||||
<BreadCrumbContainer className={this.props.className + " no-select"}>
|
||||
<Logout />
|
||||
<ReactCSSTransitionGroup transitionName="breadcrumb" transitionLeave={true}
|
||||
transitionEnter={true} transitionLeaveTimeout={150}
|
||||
transitionEnterTimeout={200} transitionAppear={false}>
|
||||
{
|
||||
this.state.path.map((path, index) => {
|
||||
return (
|
||||
<Path key={"breadcrumb_"+index}
|
||||
currentSelection={this.props.currentSelection} path={path}
|
||||
isLast={this.state.path.length === index + 1}
|
||||
needSaving={this.props.needSaving} />
|
||||
);
|
||||
})
|
||||
}
|
||||
</ReactCSSTransitionGroup>
|
||||
</BreadCrumbContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BreadCrumb.propTypes = {
|
||||
path: PropTypes.string.isRequired,
|
||||
needSaving: PropTypes.bool
|
||||
}
|
||||
|
||||
needSaving: PropTypes.bool,
|
||||
};
|
||||
|
||||
const BreadCrumbContainer = (props) => {
|
||||
return (
|
||||
<div className={props.className}>
|
||||
<div className="ul">
|
||||
{props.children}
|
||||
</div>
|
||||
<div className="ul">
|
||||
{ props.children }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const Logout = (props) => {
|
||||
const isRunningFromAnIframe = window.self !== window.top;
|
||||
return (
|
||||
|
|
@ -95,99 +107,108 @@ const Logout = (props) => {
|
|||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const Saving = (props) => {
|
||||
return (
|
||||
<ReactCSSTransitionGroup transitionName="saving_indicator" transitionLeave={true} transitionEnter={true} transitionAppear={true} transitionLeaveTimeout={200} transitionEnterTimeout={500} transitionAppearTimeout={500}>
|
||||
<NgIf key={props.needSaving} className="component_saving" cond={props.needSaving === true}>
|
||||
*
|
||||
</NgIf>
|
||||
<ReactCSSTransitionGroup transitionName="saving_indicator"
|
||||
transitionLeave={true} transitionEnter={true} transitionAppear={true}
|
||||
transitionLeaveTimeout={200} transitionEnterTimeout={500}
|
||||
transitionAppearTimeout={500}>
|
||||
<NgIf key={props.needSaving} className="component_saving"
|
||||
cond={props.needSaving === true}>
|
||||
*
|
||||
</NgIf>
|
||||
</ReactCSSTransitionGroup>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const Separator = (props) => {
|
||||
/* eslint-disable-next-line max-len */
|
||||
const src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAYAAAA7bUf6AAAA30lEQVQ4T63T7Q2CMBAG4OuVPdQNcAPdBCYwDdclCAQ3ACfRDXQDZQMHgNRcAoYApfWjv0jIPX3b3gn4wxJjI03TUAhRBkGwV0o9ffaYIEVRrJumuQHA3ReaILxzl+bCkNZ660ozi/QQIl4BoCKieAmyIlyU53lkjCld0CIyhIwxSmt9nEvkRLgoyzIuPggh4iRJqjHkhXTQAwBWUsqNUoq/38sL+TlJf7lf38ngdU5EFNme2adPFgGGrR2LiGcAqIko/LhjeXbatuVOraWUO58hnJ1iRKx8AetxXPHH/1+y62USursaSgAAAABJRU5ErkJggg==";
|
||||
return (
|
||||
<div className="component_separator">
|
||||
<img alt="path_separator" width="16" height="16" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAYAAAA7bUf6AAAA30lEQVQ4T63T7Q2CMBAG4OuVPdQNcAPdBCYwDdclCAQ3ACfRDXQDZQMHgNRcAoYApfWjv0jIPX3b3gn4wxJjI03TUAhRBkGwV0o9ffaYIEVRrJumuQHA3ReaILxzl+bCkNZ660ozi/QQIl4BoCKieAmyIlyU53lkjCld0CIyhIwxSmt9nEvkRLgoyzIuPggh4iRJqjHkhXTQAwBWUsqNUoq/38sL+TlJf7lf38ngdU5EFNme2adPFgGGrR2LiGcAqIko/LhjeXbatuVOraWUO58hnJ1iRKx8AetxXPHH/1+y62USursaSgAAAABJRU5ErkJggg=="/>
|
||||
<img alt="path_separator" width="16" height="16" src={src} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@EventEmitter
|
||||
export class PathElementWrapper extends React.Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
limitSize(str, is_highlight = false){
|
||||
if(is_highlight === true){
|
||||
if(str.length > 30){
|
||||
return str.substring(0,12).trim()+'...'+str.substring(str.length - 10, str.length).trim();
|
||||
limitSize(str, is_highlight = false) {
|
||||
if (is_highlight === true) {
|
||||
if (str.length > 30) {
|
||||
return str.substring(0, 12).trim() + "..." +
|
||||
str.substring(str.length - 10, str.length).trim();
|
||||
}
|
||||
}else{
|
||||
if(str.length > 27){
|
||||
return str.substring(0,20).trim()+'...';
|
||||
} else {
|
||||
if (str.length > 27) {
|
||||
return str.substring(0, 20).trim() + "...";
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
render(){
|
||||
render() {
|
||||
let className = "component_path-element-wrapper";
|
||||
if(this.props.highlight) { className += " highlight"; }
|
||||
if (this.props.highlight) {
|
||||
className += " highlight";
|
||||
}
|
||||
|
||||
let href = "/files" + (this.props.path.full || "")
|
||||
let href = "/files" + (this.props.path.full || "");
|
||||
href = href
|
||||
.replace(/\%/g, "%2525") // Hack to get the Link Component to work
|
||||
// See ExistingThing in 'thing-existing.js'
|
||||
.replace(/#/g, "%23")
|
||||
.replace(/\?/g, "%3F");
|
||||
href = href || "/"
|
||||
href = href || "/";
|
||||
href += location.search;
|
||||
|
||||
return (
|
||||
<div className={"li "+className}>
|
||||
<NgIf cond={this.props.isLast === false}>
|
||||
<Link to={href} className="label">
|
||||
<NgIf cond={this.props.path.minify !== true}>
|
||||
<NgIf cond={this.props.isLast === false}>
|
||||
<Link to={href} className="label">
|
||||
<NgIf cond={this.props.path.minify !== true}>
|
||||
{ this.limitSize(this.props.path.label) }
|
||||
</NgIf>
|
||||
<NgIf cond={this.props.path.minify === true}>
|
||||
...
|
||||
<span className="title">
|
||||
{ this.limitSize(this.props.path.label, true) }
|
||||
</span>
|
||||
</NgIf>
|
||||
</Link>
|
||||
<Separator/>
|
||||
</NgIf>
|
||||
<NgIf cond={this.props.isLast === true} className="label">
|
||||
{this.limitSize(this.props.path.label)}
|
||||
</NgIf>
|
||||
<NgIf cond={this.props.path.minify === true}>
|
||||
...
|
||||
<span className="title">
|
||||
{this.limitSize(this.props.path.label, true)}
|
||||
</span>
|
||||
</NgIf>
|
||||
</Link>
|
||||
<Separator/>
|
||||
</NgIf>
|
||||
<NgIf cond={this.props.isLast === true} className="label">
|
||||
{this.limitSize(this.props.path.label)}
|
||||
<Saving needSaving={this.props.needSaving} />
|
||||
</NgIf>
|
||||
<Saving needSaving={this.props.needSaving} />
|
||||
</NgIf>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// just a hack to make it play nicely with react-dnd as it refuses to use our custom component if it's not wrap by something it knows ...
|
||||
// just a hack to make it play nicely with react-dnd as it refuses to use our
|
||||
// custom component if it's not wrap by something it knows ...
|
||||
export class PathElement extends PathElementWrapper {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render(highlight = false){
|
||||
render(highlight = false) {
|
||||
let className = "component_path-element";
|
||||
if(this.props.isLast){
|
||||
if (this.props.isLast) {
|
||||
className += " is-last";
|
||||
}
|
||||
return (
|
||||
<div className={className}>
|
||||
<PathElementWrapper highlight={highlight} {...this.props} />
|
||||
<PathElementWrapper highlight={highlight} {...this.props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,59 +1,63 @@
|
|||
// taken from https://reacttraining.com/react-router/web/guides/code-splitting
|
||||
import React from 'react';
|
||||
import Path from 'path';
|
||||
import React from "react";
|
||||
import load from "little-loader";
|
||||
|
||||
export class Bundle extends React.Component {
|
||||
state = { mod: null };
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { mod: null };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.load(this.props)
|
||||
this.load(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(nextProps.load !== this.props.load){ this.load(nextProps) }
|
||||
if (nextProps.load !== this.props.load) {
|
||||
this.load(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
load(props) {
|
||||
this.setState({
|
||||
mod: null
|
||||
mod: null,
|
||||
});
|
||||
Promise.all(
|
||||
[props.loader].concat(
|
||||
(props.overrides || []).map((src) => loadAsPromise(src))
|
||||
)
|
||||
(props.overrides || []).map((src) => loadAsPromise(src)),
|
||||
),
|
||||
).then((m) => {
|
||||
const _mod = m[0];
|
||||
this.setState({
|
||||
mod: function(mod){
|
||||
if(mod['default']){
|
||||
mod: function(mod) {
|
||||
if (mod["default"]) {
|
||||
return mod.default;
|
||||
}else if(mod['__esModule'] === true){
|
||||
} else if (mod["__esModule"] === true) {
|
||||
return mod[props.symbol] ? mod[props.symbol] : null;
|
||||
}else{
|
||||
} else {
|
||||
return mod;
|
||||
}
|
||||
}(_mod)
|
||||
}(_mod),
|
||||
});
|
||||
}).catch((err) => {
|
||||
console.warn(err)
|
||||
console.warn(err);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.state.mod ? this.props.children(this.state.mod) : null
|
||||
return this.state.mod ? this.props.children(this.state.mod) : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function loadAsPromise(src){
|
||||
function loadAsPromise(src) {
|
||||
return new Promise((done, error) => {
|
||||
load(src, function(err){
|
||||
if(err){
|
||||
load(src, function(err) {
|
||||
if (err) {
|
||||
error(err);
|
||||
return;
|
||||
}
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,27 +2,27 @@ import React from "react";
|
|||
import "./card.scss";
|
||||
|
||||
export class Card extends React.Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
dragging: false
|
||||
dragging: false,
|
||||
};
|
||||
}
|
||||
|
||||
onClick(){
|
||||
if(this.props.onClick){
|
||||
onClick() {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
}
|
||||
}
|
||||
|
||||
onMouseEnter(){
|
||||
if(this.props.onMouseEnter){
|
||||
onMouseEnter() {
|
||||
if (this.props.onMouseEnter) {
|
||||
this.props.onMouseEnter();
|
||||
}
|
||||
}
|
||||
|
||||
onMouseLeave(){
|
||||
if(this.props.onMouseLeave){
|
||||
onMouseLeave() {
|
||||
if (this.props.onMouseLeave) {
|
||||
this.props.onMouseLeave();
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ export class Card extends React.Component {
|
|||
const _className = this.props.className ? this.props.className+" box" : "box";
|
||||
return (
|
||||
<div {...this.props} className={_className}>
|
||||
{this.props.children}
|
||||
{ this.props.children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,47 +5,49 @@ import { confirm } from "../helpers/";
|
|||
import { Popup } from "./popup";
|
||||
import { t } from "../locales/";
|
||||
|
||||
export class ModalConfirm extends Popup{
|
||||
constructor(props){
|
||||
export class ModalConfirm extends Popup {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
componentDidMount() {
|
||||
confirm.subscribe((Component, yesCallback, noCallback) => {
|
||||
this.setState({
|
||||
appear: true,
|
||||
value: Component,
|
||||
fns: {yes: yesCallback, no: noCallback}
|
||||
fns: { yes: yesCallback, no: noCallback },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
modalContentBody(){
|
||||
modalContentBody() {
|
||||
return (
|
||||
<div className="modal-message">
|
||||
{this.state.value}
|
||||
{this.state.value}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
yes(){
|
||||
if(this.state.fns && typeof this.state.fns.yes === "function"){
|
||||
yes() {
|
||||
if (this.state.fns && typeof this.state.fns.yes === "function") {
|
||||
this.state.fns.yes();
|
||||
}
|
||||
this.setState({appear: false});
|
||||
this.setState({ appear: false });
|
||||
}
|
||||
no(){
|
||||
if(this.state.fns && typeof this.state.fns.no === "function"){
|
||||
no() {
|
||||
if (this.state.fns && typeof this.state.fns.no === "function") {
|
||||
this.state.fns.no();
|
||||
}
|
||||
this.setState({appear: false});
|
||||
this.setState({ appear: false });
|
||||
}
|
||||
|
||||
modalContentFooter(){
|
||||
modalContentFooter() {
|
||||
return (
|
||||
<div>
|
||||
<Button type="button" onClick={this.no.bind(this)}>{ t("NO") } </Button>
|
||||
<Button type="submit" theme="emphasis" onClick={this.yes.bind(this)}>{ t("YES") }</Button>
|
||||
<Button type="button" onClick={this.no.bind(this)}>{ t("NO") } </Button>
|
||||
<Button type="submit" theme="emphasis" onClick={this.yes.bind(this)}>
|
||||
{ t("YES") }
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,16 +2,16 @@ import React from "react";
|
|||
import "./container.scss";
|
||||
|
||||
export class Container extends React.Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
const style = this.props.maxWidth ? {maxWidth: this.props.maxWidth} : {};
|
||||
const style = this.props.maxWidth ? { maxWidth: this.props.maxWidth } : {};
|
||||
let className = "component_container";
|
||||
if(this.props.className) className += " "+this.props.className;
|
||||
if (this.props.className) className += " " + this.props.className;
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
{this.props.children}
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,29 +8,29 @@ import { t } from "../locales/";
|
|||
|
||||
import "../pages/error.scss";
|
||||
|
||||
export function LoggedInOnly(WrappedComponent){
|
||||
export function LoggedInOnly(WrappedComponent) {
|
||||
memory.set("user::authenticated", false);
|
||||
|
||||
return class extends React.Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
is_logged_in: memory.get("user::authenticated")
|
||||
is_logged_in: memory.get("user::authenticated"),
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
if(this.state.is_logged_in === false && currentShare() === null){
|
||||
componentDidMount() {
|
||||
if (this.state.is_logged_in === false && currentShare() === null) {
|
||||
Session.currentUser().then((res) => {
|
||||
if(res.is_authenticated === false){
|
||||
this.props.error({message: "Authentication Required"});
|
||||
if (res.is_authenticated === false) {
|
||||
this.props.error({ message: "Authentication Required" });
|
||||
return;
|
||||
}
|
||||
memory.set("user::authenticated", true);
|
||||
this.setState({is_logged_in: true});
|
||||
this.setState({ is_logged_in: true });
|
||||
}).catch((err) => {
|
||||
if(err.code === "NO_INTERNET"){
|
||||
this.setState({is_logged_in: true});
|
||||
if (err.code === "NO_INTERNET") {
|
||||
this.setState({ is_logged_in: true });
|
||||
return;
|
||||
}
|
||||
this.props.error(err);
|
||||
|
|
@ -38,8 +38,8 @@ export function LoggedInOnly(WrappedComponent){
|
|||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
if(this.state.is_logged_in === true || currentShare() !== null){
|
||||
render() {
|
||||
if (this.state.is_logged_in === true || currentShare() !== null) {
|
||||
return <WrappedComponent {...this.props} />;
|
||||
}
|
||||
return null;
|
||||
|
|
@ -47,49 +47,53 @@ export function LoggedInOnly(WrappedComponent){
|
|||
};
|
||||
}
|
||||
|
||||
export function ErrorPage(WrappedComponent){
|
||||
export function ErrorPage(WrappedComponent) {
|
||||
return class extends React.Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
error: null,
|
||||
has_back_button: false
|
||||
has_back_button: false,
|
||||
};
|
||||
this.unlisten = this.props.history.listen(() => {
|
||||
this.setState({has_back_button: false});
|
||||
this.setState({ has_back_button: false });
|
||||
this.unlisten();
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
if(this.unlisten) this.unlisten();
|
||||
componentWillUnmount() {
|
||||
if (this.unlisten) this.unlisten();
|
||||
}
|
||||
|
||||
update(obj){
|
||||
this.setState({error: obj});
|
||||
update(obj) {
|
||||
this.setState({ error: obj });
|
||||
}
|
||||
|
||||
navigate(e) {
|
||||
if(this.state.has_back_button){
|
||||
if (this.state.has_back_button) {
|
||||
e.preventDefault();
|
||||
this.props.history.goBack();
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
if(this.state.error !== null){
|
||||
render() {
|
||||
if (this.state.error !== null) {
|
||||
const message = this.state.error.message || t("There is nothing in here");
|
||||
return (
|
||||
<div>
|
||||
<Link onClick={this.navigate.bind(this)} to={`/${window.location.search}`} className="backnav">
|
||||
<Icon name="arrow_left" />{ this.state.has_back_button ? "back" : "home" }
|
||||
</Link>
|
||||
<Container>
|
||||
<div className="error-page">
|
||||
<h1>{ t("Oops!") }</h1>
|
||||
<h2>{ message }</h2>
|
||||
</div>
|
||||
</Container>
|
||||
<Link to={`/${window.location.search}`}
|
||||
className="backnav" onClick={this.navigate.bind(this)}
|
||||
>
|
||||
<Icon name="arrow_left" />{
|
||||
this.state.has_back_button ? "back" : "home"
|
||||
}
|
||||
</Link>
|
||||
<Container>
|
||||
<div className="error-page">
|
||||
<h1>{ t("Oops!") }</h1>
|
||||
<h2>{ message }</h2>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -102,8 +106,8 @@ export function ErrorPage(WrappedComponent){
|
|||
|
||||
export const LoadingPage = () => {
|
||||
return (
|
||||
<div style={{marginTop: parseInt(window.innerHeight / 3)+"px"}}>
|
||||
<Loader />
|
||||
<div style={{ marginTop: parseInt(window.innerHeight / 3) + "px" }}>
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,17 +10,17 @@ import { Icon, NgIf } from "./";
|
|||
import "./dropdown.scss";
|
||||
|
||||
export class Dropdown extends React.Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
button: false
|
||||
button: false,
|
||||
};
|
||||
this.$dropdown = null;
|
||||
this.closeDropdown = this.closeDropdown.bind(this);
|
||||
this.toggleDropdown = this.toggleDropdown.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
componentDidMount() {
|
||||
this.$dropdown = ReactDOM.findDOMNode(this).querySelector(".dropdown_button");
|
||||
// This is not really the "react" way of doing things but we needed to use both a
|
||||
// click on the button and on the body (to exit the dropdown). we had issues
|
||||
|
|
@ -29,27 +29,27 @@ export class Dropdown extends React.Component {
|
|||
this.$dropdown.addEventListener("click", this.toggleDropdown);
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
componentWillUnmount() {
|
||||
this.$dropdown.removeEventListener("click", this.toggleDropdown);
|
||||
document.body.removeEventListener("click", this.closeDropdown);
|
||||
}
|
||||
|
||||
onSelect(name){
|
||||
onSelect(name) {
|
||||
this.props.onChange(name);
|
||||
}
|
||||
|
||||
closeDropdown(){
|
||||
closeDropdown() {
|
||||
document.body.removeEventListener("click", this.closeDropdown);
|
||||
this.setState({button: false});
|
||||
this.setState({ button: false });
|
||||
}
|
||||
|
||||
toggleDropdown(){
|
||||
if(this.props.enable === false){
|
||||
toggleDropdown() {
|
||||
if (this.props.enable === false) {
|
||||
return;
|
||||
}
|
||||
document.body.removeEventListener("click", this.closeDropdown);
|
||||
this.setState({button: !this.state.button}, () => {
|
||||
if(this.state.button === true){
|
||||
this.setState({ button: !this.state.button }, () => {
|
||||
if (this.state.button === true) {
|
||||
requestAnimationFrame(() => {
|
||||
document.body.addEventListener("click", this.closeDropdown);
|
||||
});
|
||||
|
|
@ -57,17 +57,19 @@ export class Dropdown extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
render(){
|
||||
render() {
|
||||
const button = this.props.children[0];
|
||||
|
||||
const dropdown = React.cloneElement(this.props.children[1], {onSelect: this.onSelect.bind(this)});
|
||||
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={className}>
|
||||
{ button }
|
||||
{ dropdown }
|
||||
{ button }
|
||||
{ dropdown }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -77,7 +79,7 @@ export class Dropdown extends React.Component {
|
|||
export const DropdownButton = (props) => {
|
||||
return (
|
||||
<div className="dropdown_button">
|
||||
{ props.children }
|
||||
{ props.children }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -87,16 +89,16 @@ export const DropdownList = (props) => {
|
|||
const childrens = Array.isArray(props.children) ? props.children : [props.children];
|
||||
return (
|
||||
<div className="dropdown_container">
|
||||
<ul>
|
||||
{
|
||||
childrens.map((children, index) => {
|
||||
const child = React.cloneElement(children, {onSelect: props.onSelect });
|
||||
return (
|
||||
<li key={index}>{child}</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
<ul>
|
||||
{
|
||||
childrens.map((children, index) => {
|
||||
const child = React.cloneElement(children, { onSelect: props.onSelect });
|
||||
return (
|
||||
<li key={index}>{child}</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -104,12 +106,12 @@ export const DropdownList = (props) => {
|
|||
export const DropdownItem = (props) => {
|
||||
return (
|
||||
<div onClick={props.onSelect.bind(null, props.name)}>
|
||||
{props.children}
|
||||
<NgIf cond={!!props.icon} type="inline">
|
||||
<span style={{float: "right"}}>
|
||||
<Icon name={props.icon} />
|
||||
</span>
|
||||
</NgIf>
|
||||
{props.children}
|
||||
<NgIf cond={!!props.icon} type="inline">
|
||||
<span style={{ float: "right" }}>
|
||||
<Icon name={props.icon} />
|
||||
</span>
|
||||
</NgIf>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,71 +1,74 @@
|
|||
// cheap event system that handle subscription, unsubscriptions and event emitions
|
||||
import React from "react";
|
||||
let emitters = {};
|
||||
const emitters = {};
|
||||
|
||||
function subscribe(key, event, fn){
|
||||
if(emitters[event]){
|
||||
function subscribe(key, event, fn) {
|
||||
if (emitters[event]) {
|
||||
emitters[event][key] = fn;
|
||||
}else{
|
||||
} else {
|
||||
emitters[event] = {};
|
||||
emitters[event][key] = fn;
|
||||
}
|
||||
}
|
||||
|
||||
function unsubscribe(key, event){
|
||||
if(emitters[event]){
|
||||
if(key){
|
||||
function unsubscribe(key, event) {
|
||||
if (emitters[event]) {
|
||||
if (key) {
|
||||
delete emitters[event][key];
|
||||
}else{
|
||||
} else {
|
||||
delete emitters[event];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function emit(event, payload){
|
||||
function emit(event, payload) {
|
||||
// trigger events if needed
|
||||
if(emitters[event]){
|
||||
if (emitters[event]) {
|
||||
return Promise.all(Object.keys(emitters[event]).map((key) => {
|
||||
return emitters[event][key].apply(null, payload);
|
||||
})).then((res) => {
|
||||
return emitters[event] ?
|
||||
Promise.resolve(res) :
|
||||
Promise.reject({message: "do not exist", code: "CANCELLED"});
|
||||
Promise.reject({ message: "do not exist", code: "CANCELLED" });
|
||||
});
|
||||
}else{
|
||||
return Promise.reject({message: "oups, something went wrong", code: "NO_LISTENERS"});
|
||||
} else {
|
||||
return Promise.reject({ message: "oups, something went wrong", code: "NO_LISTENERS" });
|
||||
}
|
||||
}
|
||||
|
||||
export function EventReceiver(WrappedComponent){
|
||||
let id = Math.random().toString();
|
||||
export function EventReceiver(WrappedComponent) {
|
||||
const id = Math.random().toString();
|
||||
|
||||
return class extends React.Component {
|
||||
subscribe(event, callback){
|
||||
subscribe(event, callback) {
|
||||
subscribe(id, event, callback);
|
||||
}
|
||||
|
||||
unsubscribe(event){
|
||||
unsubscribe(event) {
|
||||
unsubscribe(id, event);
|
||||
}
|
||||
|
||||
render(){
|
||||
return <WrappedComponent subscribe={this.subscribe} unsubscribe={this.unsubscribe} {...this.props} />;
|
||||
render() {
|
||||
return (
|
||||
<WrappedComponent subscribe={this.subscribe} unsubscribe={this.unsubscribe}
|
||||
{...this.props} />
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function EventEmitter(WrappedComponent) {
|
||||
return class extends React.Component {
|
||||
emit(){
|
||||
emit() {
|
||||
// reconstruct arguments
|
||||
let args = Array.prototype.slice.call(arguments);
|
||||
let event = args.shift();
|
||||
let payload = args;
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
const event = args.shift();
|
||||
const payload = args;
|
||||
|
||||
let res = emit(event, payload);
|
||||
if(res.then){
|
||||
const res = emit(event, payload);
|
||||
if (res.then) {
|
||||
return res;
|
||||
}else{
|
||||
} else {
|
||||
return Promise.resolve(res);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import React from "react";
|
||||
import "./fab.scss";
|
||||
|
||||
export function Fab(props){
|
||||
export function Fab(props) {
|
||||
return (
|
||||
<div className="component_fab" onClick={props.onClick}>
|
||||
<div className="content">
|
||||
{props.children}
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,147 +8,161 @@ import { t } from "../locales/";
|
|||
import "./formbuilder.scss";
|
||||
|
||||
export class FormBuilder extends React.Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
section(struct, key, level = 0){
|
||||
if(struct == null) struct = "";
|
||||
const isALeaf = function(struct){
|
||||
if("label" in struct && "type" in struct &&
|
||||
"value" in struct && "default" in struct){
|
||||
section(struct, key, level = 0) {
|
||||
if (struct == null) struct = "";
|
||||
const isALeaf = function(struct) {
|
||||
if ("label" in struct && "type" in struct &&
|
||||
"value" in struct && "default" in struct) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if(Array.isArray(struct)) return null;
|
||||
else if(isALeaf(struct) === false){
|
||||
const [normal, advanced] = function(s){
|
||||
let _normal = [];
|
||||
let _advanced = [];
|
||||
for (let key in s){
|
||||
const tmp = {key: key, data: s[key]};
|
||||
if (Array.isArray(struct)) return null;
|
||||
else if (isALeaf(struct) === false) {
|
||||
const [normal, advanced] = function(s) {
|
||||
const _normal = [];
|
||||
const _advanced = [];
|
||||
for (const key in s) {
|
||||
const tmp = { key: key, data: s[key] };
|
||||
"id" in s[key] ? _advanced.push(tmp) : _normal.push(tmp);
|
||||
}
|
||||
return [_normal, _advanced];
|
||||
}(struct);
|
||||
if(level <= 1){
|
||||
if (level <= 1) {
|
||||
return (
|
||||
<div className="formbuilder">
|
||||
{
|
||||
key ? <h2 className="no-select">{ format(key) }</h2> : ""
|
||||
}
|
||||
{
|
||||
normal.map((s, index) => {
|
||||
return (
|
||||
<div key={s.key+"-"+index}>
|
||||
{ this.section(s.data, s.key, level + 1) }
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
<div className="advanced_form">
|
||||
{
|
||||
advanced.map((s, index) => {
|
||||
key ? <h2 className="no-select">{ format(key) }</h2> : ""
|
||||
}
|
||||
{
|
||||
normal.map((s, index) => {
|
||||
return (
|
||||
<div key={s.key+"-"+index}>
|
||||
{ this.section(s.data, s.key, level + 1) }
|
||||
{ this.section(s.data, s.key, level + 1) }
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
<div className="advanced_form">
|
||||
{
|
||||
advanced.map((s, index) => {
|
||||
return (
|
||||
<div key={s.key+"-"+index}>
|
||||
{ this.section(s.data, s.key, level + 1) }
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<fieldset>
|
||||
<legend className="no-select">{ format(key) }</legend>
|
||||
{
|
||||
Object.keys(struct).map((key, index) => {
|
||||
return (
|
||||
<div key={key+"-"+index}>
|
||||
{ this.section(struct[key], key, level + 1) }
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend className="no-select">{ format(key) }</legend>
|
||||
{
|
||||
Object.keys(struct).map((key, index) => {
|
||||
return (
|
||||
<div key={key+"-"+index}>
|
||||
{ this.section(struct[key], key, level + 1) }
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</fieldset>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let id = {};
|
||||
const id = {};
|
||||
let target = [];
|
||||
if(struct.id !== undefined){
|
||||
if (struct.id !== undefined) {
|
||||
id.id = this.props.idx === undefined ? struct.id : struct.id + "_" + this.props.idx;
|
||||
}
|
||||
if(struct.type === "enable"){
|
||||
if (struct.type === "enable") {
|
||||
target = struct.target.map((target) => {
|
||||
return this.props.idx === undefined ? target : target + "_" + this.props.idx;
|
||||
});
|
||||
}
|
||||
|
||||
const onChange = function(e, fn){
|
||||
const onChange = function(e, fn) {
|
||||
struct.value = e;
|
||||
if(typeof fn === "function"){
|
||||
if (typeof fn === "function") {
|
||||
fn(struct);
|
||||
}
|
||||
this.props.onChange.call(
|
||||
this,
|
||||
FormObjToJSON(this.props.form)
|
||||
FormObjToJSON(this.props.form),
|
||||
);
|
||||
};
|
||||
|
||||
return ( <FormElement render={this.props.render} onChange={onChange.bind(this)} {...id} params={struct} target={target} name={ format(struct.label) } autoComplete={ this.props.autoComplete || "off" }/> );
|
||||
return (
|
||||
<FormElement render={this.props.render}
|
||||
onChange={onChange.bind(this)} {...id}
|
||||
params={struct} target={target} name={ format(struct.label) }
|
||||
autoComplete={ this.props.autoComplete || "off" } />
|
||||
);
|
||||
}
|
||||
|
||||
render(){
|
||||
render() {
|
||||
return this.section(this.props.form || {});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const FormElement = (props) => {
|
||||
const id = props.id !== undefined ? {id: props.id} : {};
|
||||
let struct = props.params;
|
||||
let autoCompleteProp = props.autoComplete || "off"
|
||||
let $input = ( <Input onChange={(e) => props.onChange(e.target.value)} {...id} name={struct.label} type="text" defaultValue={struct.value} placeholder={ t(struct.placeholder) } /> );
|
||||
switch(props.params["type"]){
|
||||
const id = props.id !== undefined ? { id: props.id } : {};
|
||||
const struct = props.params;
|
||||
const autoCompleteProp = props.autoComplete || "off";
|
||||
let $input = (
|
||||
<Input onChange={(e) => props.onChange(e.target.value)} {...id} name={struct.label}
|
||||
type="text" defaultValue={struct.value} placeholder={ t(struct.placeholder) } />
|
||||
);
|
||||
switch (props.params["type"]) {
|
||||
case "text": {
|
||||
const onTextChange = (value) => {
|
||||
if(value === ""){
|
||||
if (value === "") {
|
||||
value = null;
|
||||
}
|
||||
props.onChange(value);
|
||||
};
|
||||
|
||||
const list_id = struct.datalist ? "list_"+Math.random() : null;
|
||||
$input = ( <Input list={list_id} onChange={(e) => onTextChange(e.target.value)} {...id} name={struct.label} type="text" value={struct.value || ""} placeholder={ t(struct.placeholder) } readOnly={struct.readonly} autoComplete={autoCompleteProp} autoCorrect="off" autoCapitalize="off" spellCheck="false" /> );
|
||||
if(list_id != null){
|
||||
const filtered = function(multi, datalist, currentValue){
|
||||
if(multi !== true || currentValue == null) return datalist;
|
||||
$input = (
|
||||
<Input list={list_id} onChange={(e) => onTextChange(e.target.value)} {...id}
|
||||
name={struct.label} type="text" value={struct.value || ""}
|
||||
placeholder={ t(struct.placeholder) } readOnly={struct.readonly}
|
||||
autoComplete={autoCompleteProp} autoCorrect="off" autoCapitalize="off"
|
||||
spellCheck="false" />
|
||||
);
|
||||
if (list_id != null) {
|
||||
const filtered = function(multi, datalist, currentValue) {
|
||||
if (multi !== true || currentValue == null) return datalist;
|
||||
|
||||
return autocomplete(
|
||||
currentValue
|
||||
.split(",")
|
||||
.map((t) => t.trim())
|
||||
.filter((t) => t),
|
||||
datalist
|
||||
datalist,
|
||||
);
|
||||
};
|
||||
$input = (
|
||||
<span>
|
||||
{ $input }
|
||||
<datalist id={list_id}>
|
||||
{
|
||||
filtered(struct.multi, struct.datalist, struct.value).map((item,i) => {
|
||||
return ( <option key={i} value={item} /> );
|
||||
})
|
||||
}
|
||||
</datalist>
|
||||
{ $input }
|
||||
<datalist id={list_id}>
|
||||
{
|
||||
filtered(struct.multi, struct.datalist, struct.value).map((item, i) => {
|
||||
return ( <option key={i} value={item} /> );
|
||||
})
|
||||
}
|
||||
</datalist>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
@ -159,37 +173,56 @@ const FormElement = (props) => {
|
|||
value = value === "" ? null : parseInt(value);
|
||||
props.onChange(value);
|
||||
};
|
||||
$input = ( <Input onChange={(e) => onNumberChange(e.target.value)} {...id} name={struct.label} type="number" value={struct.value === null ? "" : struct.value} placeholder={ t(struct.placeholder) } /> );
|
||||
$input = (
|
||||
<Input onChange={(e) => onNumberChange(e.target.value)} {...id} name={struct.label}
|
||||
type="number" value={struct.value === null ? "" : struct.value}
|
||||
placeholder={ t(struct.placeholder) } />
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "password": {
|
||||
const onPasswordChange = (value) => {
|
||||
if(value === ""){
|
||||
value = null;
|
||||
}
|
||||
props.onChange(value);
|
||||
};
|
||||
$input = ( <Input onChange={(e) => onPasswordChange(e.target.value)} {...id} name={struct.label} type="password" value={struct.value || ""} placeholder={ t(struct.placeholder) } autoComplete={autoCompleteProp} autoCorrect="off" autoCapitalize="off" spellCheck="false"/> );
|
||||
break;
|
||||
}
|
||||
case "long_password": {
|
||||
const onLongPasswordChange = (value) => {
|
||||
if(value === ""){
|
||||
if (value === "") {
|
||||
value = null;
|
||||
}
|
||||
props.onChange(value);
|
||||
};
|
||||
$input = (
|
||||
<Textarea {...id} disabledEnter={true} value={struct.value || ""} onChange={(e) => onLongPasswordChange(e.target.value)} type="text" rows="1" name={struct.label} placeholder={ t(struct.placeholder) } autoComplete={autoCompleteProp} autoCorrect="off" autoCapitalize="off" spellCheck="false" />
|
||||
<Input onChange={(e) => onPasswordChange(e.target.value)} {...id} name={struct.label}
|
||||
type="password" value={struct.value || ""} placeholder={ t(struct.placeholder) }
|
||||
autoComplete={autoCompleteProp} autoCorrect="off" autoCapitalize="off"
|
||||
spellCheck="false"/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "long_password": {
|
||||
const onLongPasswordChange = (value) => {
|
||||
if (value === "") {
|
||||
value = null;
|
||||
}
|
||||
props.onChange(value);
|
||||
};
|
||||
$input = (
|
||||
<Textarea {...id} disabledEnter={true} value={struct.value || ""}
|
||||
onChange={(e) => onLongPasswordChange(e.target.value)} type="text" rows="1"
|
||||
name={struct.label} placeholder={ t(struct.placeholder) }
|
||||
autoComplete={autoCompleteProp} autoCorrect="off" autoCapitalize="off"
|
||||
spellCheck="false" />
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "long_text":
|
||||
$input = ( <Textarea {...id} disabledEnter={true} value={struct.value || ""} onChange={(e) => props.onChange(e.target.value)} type="text" rows="3" name={struct.label} placeholder={ t(struct.placeholder) } autoComplete={autoCompleteProp} autoCorrect="off" autoCapitalize="off" spellCheck="false" /> );
|
||||
$input = (
|
||||
<Textarea {...id} disabledEnter={true} value={struct.value || ""}
|
||||
onChange={(e) => props.onChange(e.target.value)}
|
||||
type="text" rows="3" name={struct.label} placeholder={ t(struct.placeholder) }
|
||||
autoComplete={autoCompleteProp} autoCorrect="off" autoCapitalize="off"
|
||||
spellCheck="false" />
|
||||
);
|
||||
break;
|
||||
case "bcrypt": {
|
||||
const onBcryptChange = (value) => {
|
||||
if(value === ""){
|
||||
if (value === "") {
|
||||
return props.onChange(null);
|
||||
}
|
||||
return import(/* webpackChunkName: "bcrypt" */"../helpers/bcrypt")
|
||||
|
|
@ -197,42 +230,67 @@ const FormElement = (props) => {
|
|||
.then((bcrypt) => bcrypt.bcrypt_password(value))
|
||||
.then((hash) => props.onChange(hash));
|
||||
};
|
||||
$input = ( <Input onChange={(e) => onBcryptChange(e.target.value)} {...id} name={struct.label} type="password" defaultValue={struct.value || ""} placeholder={ t(struct.placeholder) } /> );
|
||||
$input = (
|
||||
<Input onChange={(e) => onBcryptChange(e.target.value)} {...id} name={struct.label}
|
||||
type="password" defaultValue={struct.value || ""}
|
||||
placeholder={ t(struct.placeholder) } />
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "hidden":
|
||||
$input = ( <Input name={struct.label} type="hidden" defaultValue={struct.value} /> );
|
||||
$input = (
|
||||
<Input name={struct.label} type="hidden" defaultValue={struct.value} />
|
||||
);
|
||||
break;
|
||||
case "boolean":
|
||||
$input = ( <Input onChange={(e) => props.onChange(e.target.checked)} {...id} name={struct.label} type="checkbox" checked={struct.value === null ? !!struct.default : struct.value} /> );
|
||||
$input = (
|
||||
<Input onChange={(e) => props.onChange(e.target.checked)} {...id} name={struct.label}
|
||||
type="checkbox" checked={struct.value === null ? !!struct.default : struct.value} />
|
||||
);
|
||||
break;
|
||||
case "select":
|
||||
$input = ( <Select onChange={(e) => props.onChange(e.target.value)} {...id} name={struct.label} choices={struct.options} value={struct.value === null ? struct.default : struct.value} placeholder={ t(struct.placeholder) } />);
|
||||
$input = (
|
||||
<Select onChange={(e) => props.onChange(e.target.value)} {...id} name={struct.label}
|
||||
choices={struct.options} placeholder={ t(struct.placeholder) }
|
||||
value={struct.value === null ? struct.default : struct.value} />
|
||||
);
|
||||
break;
|
||||
case "enable":
|
||||
$input = ( <Enabler onChange={(e) => props.onChange(e.target.checked)} {...id} name={struct.label} target={props.target} defaultValue={struct.value === null ? struct.default : struct.value} /> );
|
||||
$input = (
|
||||
<Enabler onChange={(e) => props.onChange(e.target.checked)} {...id} name={struct.label}
|
||||
target={props.target}
|
||||
defaultValue={struct.value === null ? struct.default : struct.value} />
|
||||
);
|
||||
break;
|
||||
case "date":
|
||||
$input = ( <Input onChange={(e) => props.onChange(e.target.value)} {...id} name={struct.label} type="date" defaultValue={struct.value || ""} placeholder={ t(struct.placeholder) } /> );
|
||||
$input = (
|
||||
<Input onChange={(e) => props.onChange(e.target.value)} {...id} name={struct.label}
|
||||
type="date" defaultValue={struct.value || ""}
|
||||
placeholder={ t(struct.placeholder) } />
|
||||
);
|
||||
break;
|
||||
case "datetime":
|
||||
$input = ( <Input onChange={(e) => props.onChange(e.target.value)} {...id} name={struct.label} type="datetime-local" defaultValue={struct.value || ""} placeholder={ t(struct.placeholder) } /> );
|
||||
$input = (
|
||||
<Input onChange={(e) => props.onChange(e.target.value)} {...id} name={struct.label}
|
||||
type="datetime-local" defaultValue={struct.value || ""}
|
||||
placeholder={ t(struct.placeholder) } />
|
||||
);
|
||||
break;
|
||||
case "image":
|
||||
$input = ( <img {...id} src={struct.value} /> );
|
||||
break;
|
||||
case "file": {
|
||||
const getBase64 = function(file){
|
||||
const getBase64 = function(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new window.FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => resolve(reader.result);
|
||||
reader.onerror = error => reject(error);
|
||||
reader.onerror = (error) => reject(error);
|
||||
});
|
||||
};
|
||||
const onFileUpload = (e) => {
|
||||
if(e.target.files.length !== 1) return;
|
||||
if(e.target.files[0].size < 200000000){
|
||||
if (e.target.files.length !== 1) return;
|
||||
if (e.target.files[0].size < 200000000) {
|
||||
getBase64(e.target.files[0]).then((a) => {
|
||||
props.onChange(a);
|
||||
}).catch(() => {});
|
||||
|
|
@ -242,9 +300,15 @@ const FormElement = (props) => {
|
|||
};
|
||||
$input = (
|
||||
<div className="fileupload-image">
|
||||
<input onChange={(e) => onFileUpload(e)} type="file" {...id} name={struct.label} />
|
||||
{ struct.value.substring(0,10) === "data:image" ? <img src={struct.value} /> : null }
|
||||
{ struct.value.substring(0,20) === "data:application/pdf" ? <object data={struct.value} type="application/pdf" /> : null }
|
||||
<input onChange={(e) => onFileUpload(e)} type="file" {...id} name={struct.label} />
|
||||
{
|
||||
struct.value.substring(0, 10) === "data:image" ?
|
||||
<img src={struct.value} /> : null
|
||||
}
|
||||
{
|
||||
struct.value.substring(0, 20) === "data:application/pdf" ?
|
||||
<object data={struct.value} type="application/pdf" /> : null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
|
|
@ -258,5 +322,5 @@ const FormElement = (props) => {
|
|||
};
|
||||
|
||||
FormElement.propTypes = {
|
||||
render: PropTypes.func.isRequired
|
||||
render: PropTypes.func.isRequired,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -44,89 +44,89 @@ import img_copy from "../assets/img/copy.svg";
|
|||
export const img_placeholder = "/assets/icons/placeholder.png";
|
||||
|
||||
export const Icon = (props) => {
|
||||
if(props.name === null) return null;
|
||||
if (props.name === null) return null;
|
||||
let img;
|
||||
if(props.name === "directory"){
|
||||
if (props.name === "directory") {
|
||||
img = "/assets/icons/folder.svg";
|
||||
}else if(props.name === "file"){
|
||||
} else if (props.name === "file") {
|
||||
img = "/assets/icons/file.svg";
|
||||
}else if(props.name === "save"){
|
||||
} else if (props.name === "save") {
|
||||
img = img_save;
|
||||
}else if(props.name === "power"){
|
||||
} else if (props.name === "power") {
|
||||
img = img_power;
|
||||
}else if(props.name === "edit"){
|
||||
} else if (props.name === "edit") {
|
||||
img = "/assets/icons/edit.svg";
|
||||
}else if(props.name === "delete"){
|
||||
} else if (props.name === "delete") {
|
||||
img = "/assets/icons/delete.svg";
|
||||
}else if(props.name === "share"){
|
||||
} else if (props.name === "share") {
|
||||
img = "/assets/icons/share.svg";
|
||||
}else if(props.name === "bucket"){
|
||||
} else if (props.name === "bucket") {
|
||||
img = img_bucket;
|
||||
}else if(props.name === "download_white"){
|
||||
} else if (props.name === "download_white") {
|
||||
img = img_download_white;
|
||||
}else if(props.name === "upload_white"){
|
||||
} else if (props.name === "upload_white") {
|
||||
img = img_upload_white;
|
||||
}else if(props.name === "play"){
|
||||
} else if (props.name === "play") {
|
||||
img = img_play;
|
||||
}else if(props.name === "pause"){
|
||||
} else if (props.name === "pause") {
|
||||
img = img_pause;
|
||||
}else if(props.name === "error"){
|
||||
} else if (props.name === "error") {
|
||||
img = img_error;
|
||||
}else if(props.name === "loading_white"){
|
||||
} else if (props.name === "loading_white") {
|
||||
img = img_loading_white;
|
||||
}else if(props.name === "loading"){
|
||||
} else if (props.name === "loading") {
|
||||
img = img_loading;
|
||||
}else if(props.name === "calendar_white"){
|
||||
} else if (props.name === "calendar_white") {
|
||||
img = img_calendar_white;
|
||||
}else if(props.name === "schedule"){
|
||||
} else if (props.name === "schedule") {
|
||||
img = img_calendar;
|
||||
}else if(props.name === "deadline"){
|
||||
} else if (props.name === "deadline") {
|
||||
img = img_alarm;
|
||||
}else if(props.name === "todo_white"){
|
||||
} else if (props.name === "todo_white") {
|
||||
img = img_todo_white;
|
||||
}else if(props.name === "arrow_bottom"){
|
||||
} else if (props.name === "arrow_bottom") {
|
||||
img = img_arrow_bottom;
|
||||
}else if(props.name === "arrow_top"){
|
||||
} else if (props.name === "arrow_top") {
|
||||
img = img_arrow_top;
|
||||
}else if(props.name === "arrow_right"){
|
||||
} else if (props.name === "arrow_right") {
|
||||
img = img_arrow_right;
|
||||
}else if(props.name === "arrow_right_white"){
|
||||
} else if (props.name === "arrow_right_white") {
|
||||
img = img_arrow_right_white;
|
||||
}else if(props.name === "arrow_left_white"){
|
||||
} else if (props.name === "arrow_left_white") {
|
||||
img = img_arrow_left_white;
|
||||
}else if(props.name === "arrow_left"){
|
||||
} else if (props.name === "arrow_left") {
|
||||
img = img_arrow_left;
|
||||
}else if(props.name === "more"){
|
||||
} else if (props.name === "more") {
|
||||
img = img_more;
|
||||
}else if(props.name === "close"){
|
||||
} else if (props.name === "close") {
|
||||
img = img_close;
|
||||
}else if(props.name === "close_dark"){
|
||||
} else if (props.name === "close_dark") {
|
||||
img = img_close_dark;
|
||||
}else if(props.name === "arrow_up_double"){
|
||||
} else if (props.name === "arrow_up_double") {
|
||||
img = img_arrow_up_double;
|
||||
}else if(props.name === "arrow_down_double"){
|
||||
} else if (props.name === "arrow_down_double") {
|
||||
img = img_arrow_down_double;
|
||||
}else if(props.name === "arrow_down"){
|
||||
} else if (props.name === "arrow_down") {
|
||||
img = img_arrow_down;
|
||||
}else if(props.name === "search"){
|
||||
} else if (props.name === "search") {
|
||||
img = img_search;
|
||||
}else if(props.name === "search_dark"){
|
||||
} else if (props.name === "search_dark") {
|
||||
img = img_search_dark;
|
||||
}else if(props.name === "grid"){
|
||||
} else if (props.name === "grid") {
|
||||
img = img_grid;
|
||||
}else if(props.name === "list"){
|
||||
} else if (props.name === "list") {
|
||||
img = img_list;
|
||||
}else if(props.name === "sort"){
|
||||
} else if (props.name === "sort") {
|
||||
img = img_sort;
|
||||
}else if(props.name === "check"){
|
||||
} else if (props.name === "check") {
|
||||
img = img_check;
|
||||
}else if(props.name === "info"){
|
||||
} else if (props.name === "info") {
|
||||
img = img_info;
|
||||
}else if(props.name === "fullscreen"){
|
||||
} else if (props.name === "fullscreen") {
|
||||
img = img_fullscreen;
|
||||
}else if(props.name === "camera"){
|
||||
} else if (props.name === "camera") {
|
||||
img = img_camera;
|
||||
}else if(props.name === "location"){
|
||||
} else if (props.name === "location") {
|
||||
img = img_location;
|
||||
} else if (props.name === "stop") {
|
||||
img = img_stop;
|
||||
|
|
@ -134,16 +134,16 @@ export const Icon = (props) => {
|
|||
img = img_refresh;
|
||||
} else if (props.name === "copy") {
|
||||
img = img_copy;
|
||||
} else{
|
||||
throw(`unknown icon: "${props.name}"`);
|
||||
} else {
|
||||
throw (`unknown icon: "${props.name}"`);
|
||||
}
|
||||
|
||||
return (
|
||||
<img className="component_icon"
|
||||
onError={() => Log.send(`cannot load icon ${props.name}`)}
|
||||
style={props.style}
|
||||
onClick={props.onClick}
|
||||
src={img}
|
||||
alt={props.name}/>
|
||||
onError={() => Log.send(`cannot load icon ${props.name}`)}
|
||||
style={props.style}
|
||||
onClick={props.onClick}
|
||||
src={img}
|
||||
alt={props.name}/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,66 +3,67 @@ import PropTypes from "prop-types";
|
|||
import "./input.scss";
|
||||
|
||||
export class Input extends React.Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<input
|
||||
className="component_input"
|
||||
onChange={this.props.onChange}
|
||||
{...this.props}
|
||||
ref={(comp) => { this.ref = comp; }} />
|
||||
className="component_input"
|
||||
onChange={this.props.onChange}
|
||||
{...this.props}
|
||||
ref={(comp) => this.ref = comp } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Input.propTypes = {
|
||||
type: PropTypes.string,
|
||||
placeholder: PropTypes.string
|
||||
placeholder: PropTypes.string,
|
||||
};
|
||||
|
||||
|
||||
export const Select = (props) => {
|
||||
const choices = props.choices || [];
|
||||
const id = props.id ? {id: props.id} : {};
|
||||
const id = props.id ? { id: props.id } : {};
|
||||
return (
|
||||
<select className="component_select" {...id} name={props.name} onChange={props.onChange} defaultValue={props.value}>
|
||||
<option hidden>{props.placeholder}</option>
|
||||
{
|
||||
choices.map((choice, index) => {
|
||||
return (
|
||||
<option key={index} name={choice}>{choice}</option>
|
||||
);
|
||||
})
|
||||
}
|
||||
<select className="component_select" onChange={props.onChange} {...id}
|
||||
name={props.name} defaultValue={props.value}>
|
||||
<option hidden>{ props.placeholder }</option>
|
||||
{
|
||||
choices.map((choice, index) => {
|
||||
return (
|
||||
<option key={index} name={choice}>{choice}</option>
|
||||
);
|
||||
})
|
||||
}
|
||||
</select>
|
||||
);
|
||||
};
|
||||
|
||||
export class Enabler extends React.Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
componentDidMount() {
|
||||
requestAnimationFrame(() => {
|
||||
this.toggle(this.props.defaultValue || false);
|
||||
});
|
||||
}
|
||||
|
||||
onChange(e){
|
||||
onChange(e) {
|
||||
this.toggle(e.target.checked);
|
||||
this.props.onChange(e);
|
||||
}
|
||||
|
||||
toggle(value){
|
||||
toggle(value) {
|
||||
const target = this.props.target || [];
|
||||
target.map((t) => {
|
||||
let $el = document.getElementById(t);
|
||||
if(!$el) return;
|
||||
if(value === true){
|
||||
const $el = document.getElementById(t);
|
||||
if (!$el) return;
|
||||
if (value === true) {
|
||||
$el.parentElement.parentElement.parentElement.style.display = "block";
|
||||
$el.parentElement.parentElement.parentElement.style.opacity = "1";
|
||||
} else {
|
||||
|
|
@ -70,9 +71,9 @@ export class Enabler extends React.Component {
|
|||
$el.parentElement.parentElement.parentElement.style.opacity = "0";
|
||||
|
||||
// reset value
|
||||
if($el.value){
|
||||
if ($el.value) {
|
||||
$el.value = null;
|
||||
let event = new Event("input", { bubbles: true});
|
||||
const event = new Event("input", { bubbles: true });
|
||||
event.simulated = true;
|
||||
$el.dispatchEvent(event);
|
||||
}
|
||||
|
|
@ -80,9 +81,10 @@ export class Enabler extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
render(){
|
||||
render() {
|
||||
return (
|
||||
<Input type="checkbox" onChange={this.onChange.bind(this)} defaultChecked={this.props.defaultValue} />
|
||||
<Input type="checkbox" onChange={this.onChange.bind(this)}
|
||||
defaultChecked={this.props.defaultValue} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,16 +5,21 @@ import "./loader.scss";
|
|||
export const Loader = () => {
|
||||
return (
|
||||
<CSSTransition transitionName="loader" transitionAppearTimeout={700}>
|
||||
<div className="component_loader">
|
||||
<svg width="120px" height="120px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
||||
<rect x="0" y="0" width="100" height="100" fill="none"></rect>
|
||||
<circle cx="50" cy="50" r="40" stroke="rgba(100%,100%,100%,0.679)" fill="none" strokeWidth="10" strokeLinecap="round"></circle>
|
||||
<circle cx="50" cy="50" r="40" stroke="#6f6f6f" fill="none" strokeWidth="6" strokeLinecap="round">
|
||||
<animate attributeName="stroke-dashoffset" dur="2s" repeatCount="indefinite" from="0" to="502"></animate>
|
||||
<animate attributeName="stroke-dasharray" dur="2s" repeatCount="indefinite" values="150.6 100.4;1 250;150.6 100.4"></animate>
|
||||
</circle>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="component_loader">
|
||||
<svg width="120px" height="120px" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
||||
<rect x="0" y="0" width="100" height="100" fill="none"></rect>
|
||||
<circle cx="50" cy="50" r="40" stroke="rgba(100%,100%,100%,0.679)" fill="none"
|
||||
strokeWidth="10" strokeLinecap="round"></circle>
|
||||
<circle cx="50" cy="50" r="40" stroke="#6f6f6f" fill="none" strokeWidth="6"
|
||||
strokeLinecap="round">
|
||||
<animate attributeName="stroke-dashoffset" dur="2s" repeatCount="indefinite"
|
||||
from="0" to="502"></animate>
|
||||
<animate attributeName="stroke-dasharray" dur="2s" repeatCount="indefinite"
|
||||
values="150.6 100.4;1 250;150.6 100.4"></animate>
|
||||
</circle>
|
||||
</svg>
|
||||
</div>
|
||||
</CSSTransition>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,121 +4,165 @@ import { t } from "../locales/";
|
|||
import "./mapshot.scss";
|
||||
|
||||
export class MapShot extends React.Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
tile_size: 0,
|
||||
tile_loaded: 0,
|
||||
error: false
|
||||
error: false,
|
||||
};
|
||||
this.onRefresh = this.onRefresh.bind(this);
|
||||
}
|
||||
|
||||
onRefresh(){
|
||||
onRefresh() {
|
||||
requestAnimationFrame(() => {
|
||||
if(this.refs.$wrapper){
|
||||
if (this.refs.$wrapper) {
|
||||
this.setState({
|
||||
tile_size: this.calculateSize()
|
||||
tile_size: this.calculateSize(),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
calculateSize(){
|
||||
if(!this.refs.$wrapper) return 0;
|
||||
calculateSize() {
|
||||
if (!this.refs.$wrapper) return 0;
|
||||
return parseInt(this.refs.$wrapper.clientWidth / 3 * 100) / 100;
|
||||
}
|
||||
|
||||
|
||||
componentDidMount(){
|
||||
componentDidMount() {
|
||||
this.onRefresh();
|
||||
window.addEventListener("resize", this.onRefresh);
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener("resize", this.onRefresh);
|
||||
}
|
||||
|
||||
insert_marker(position){
|
||||
if(!(this.state.tile_size > 0)) return null;
|
||||
insert_marker(position) {
|
||||
if (!(this.state.tile_size > 0)) return null;
|
||||
return (
|
||||
<div className="marker" style={{
|
||||
left: this.state.tile_size * (1 + position[0]) - 15,
|
||||
top: this.state.tile_size * (1 + position[1]) - 30 }}>
|
||||
<Icon name="location"/>
|
||||
left: this.state.tile_size * (1 + position[0]) - 15,
|
||||
top: this.state.tile_size * (1 + position[1]) - 30,
|
||||
}}>
|
||||
<Icon name="location"/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onLoad(){
|
||||
this.setState({tile_loaded: this.state.tile_loaded + 1});
|
||||
onLoad() {
|
||||
this.setState({ tile_loaded: this.state.tile_loaded + 1 });
|
||||
}
|
||||
onError(){
|
||||
this.setState({error: true});
|
||||
onError() {
|
||||
this.setState({ error: true });
|
||||
}
|
||||
|
||||
render(){
|
||||
if(this.calculateSize() !== this.state.tile_size && this.calculateSize() !== 0){
|
||||
render() {
|
||||
if (this.calculateSize() !== this.state.tile_size && this.calculateSize() !== 0) {
|
||||
this.onRefresh();
|
||||
}
|
||||
const tile_server = this.props.tile || "https://maps.wikimedia.org/osm-intl/${z}/${x}/${y}.png";
|
||||
function map_url(lat, lng, zoom){
|
||||
const tile_server = this.props.tile ||
|
||||
"https://maps.wikimedia.org/osm-intl/${z}/${x}/${y}.png";
|
||||
function map_url(lat, lng, zoom) {
|
||||
// https://wiki.openstreetmap.org/wiki/Slippy_map_tilenamse
|
||||
const n = Math.pow(2, zoom);
|
||||
const tile_numbers = [
|
||||
(lng+180)/360*n,
|
||||
(1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2*n,
|
||||
zoom
|
||||
zoom,
|
||||
];
|
||||
return {
|
||||
tile: function(tile_server, x = 0, y = 0){
|
||||
tile: function(tile_server, x = 0, y = 0) {
|
||||
return tile_server
|
||||
.replace("${x}", Math.floor(tile_numbers[0])+x)
|
||||
.replace("${y}", Math.floor(tile_numbers[1])+y)
|
||||
.replace("${z}", Math.floor(zoom));
|
||||
},
|
||||
position: function(){
|
||||
position: function() {
|
||||
return [
|
||||
tile_numbers[0] - Math.floor(tile_numbers[0]),
|
||||
tile_numbers[1] - Math.floor(tile_numbers[1]),
|
||||
];
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
const mapper = map_url(this.props.lat, this.props.lng, 11);
|
||||
const center= (position, i) => {
|
||||
return parseInt(this.state.tile_size * (1 + position[i]) * 1000)/1000;
|
||||
};
|
||||
const link = "https://www.google.com/maps/search/?api=1&query=" +
|
||||
this.props.lat + "," + this.props.lng;
|
||||
return (
|
||||
<div ref="$wrapper" className={"component_mapshot"+(this.state.tile_loaded === 9 ? " loaded" : "")+(this.state.error === true ? " error": "")} style={{height: (this.state.tile_size*3)+"px"}}>
|
||||
<div className="wrapper">
|
||||
<div className="mapshot_placeholder error">
|
||||
<span><div>{ t("ERROR") }</div></span>
|
||||
<div ref="$wrapper"
|
||||
className={"component_mapshot" + (this.state.tile_loaded === 9 ? " loaded" : "") +
|
||||
(this.state.error === true ? " error": "")}
|
||||
style={{ height: (this.state.tile_size*3)+"px" }}
|
||||
>
|
||||
<div className="wrapper">
|
||||
<div className="mapshot_placeholder error">
|
||||
<span><div>{ t("ERROR") }</div></span>
|
||||
</div>
|
||||
<div className="mapshot_placeholder loading">
|
||||
<Loader/>
|
||||
</div>
|
||||
<a href={link}>
|
||||
{ this.insert_marker(mapper.position()) }
|
||||
<div className="bigpicture"
|
||||
style={{
|
||||
transformOrigin: center(mapper.position(), 0)+"px "+
|
||||
center(mapper.position(), 1)+"px",
|
||||
}}>
|
||||
<div className="line">
|
||||
<img onLoad={this.onLoad.bind(this)}
|
||||
onError={this.onError.bind(this)}
|
||||
src={mapper.tile(tile_server, -1, -1)}
|
||||
ref="$tile"
|
||||
style={{ height: this.state.tile_size+"px" }}
|
||||
className="btl" />
|
||||
<img onLoad={this.onLoad.bind(this)}
|
||||
onError={this.onError.bind(this)}
|
||||
src={mapper.tile(tile_server, 0, -1)}
|
||||
style={{ height: this.state.tile_size+"px" }} />
|
||||
<img onLoad={this.onLoad.bind(this)}
|
||||
onError={this.onError.bind(this)}
|
||||
src={mapper.tile(tile_server, 1, -1)}
|
||||
style={{ height: this.state.tile_size+"px" }}
|
||||
className="btr" />
|
||||
</div>
|
||||
<div className="line">
|
||||
<img onLoad={this.onLoad.bind(this)}
|
||||
onError={this.onError.bind(this)}
|
||||
src={mapper.tile(tile_server, -1, 0)}
|
||||
style={{ height: this.state.tile_size+"px" }} />
|
||||
<img onLoad={this.onLoad.bind(this)}
|
||||
onError={this.onError.bind(this)}
|
||||
src={mapper.tile(tile_server, 0, 0)}
|
||||
style={{ height: this.state.tile_size+"px" }} />
|
||||
<img onLoad={this.onLoad.bind(this)}
|
||||
onError={this.onError.bind(this)}
|
||||
src={mapper.tile(tile_server, 1, 0)}
|
||||
style={{ height: this.state.tile_size+"px" }} />
|
||||
</div>
|
||||
<div className="line">
|
||||
<img onLoad={this.onLoad.bind(this)}
|
||||
onError={this.onError.bind(this)}
|
||||
src={mapper.tile(tile_server, -1, 1)}
|
||||
style={{ height: this.state.tile_size+"px" }}
|
||||
className="bbl" />
|
||||
<img onLoad={this.onLoad.bind(this)}
|
||||
onError={this.onError.bind(this)}
|
||||
src={mapper.tile(tile_server, 0, 1)}
|
||||
style={{ height: this.state.tile_size+"px" }} />
|
||||
<img onLoad={this.onLoad.bind(this)}
|
||||
onError={this.onError.bind(this)}
|
||||
src={mapper.tile(tile_server, 1, 1)}
|
||||
style={{ height: this.state.tile_size+"px" }}
|
||||
className="bbr" />
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div className="mapshot_placeholder loading">
|
||||
<Loader/>
|
||||
</div>
|
||||
<a href={"https://www.google.com/maps/search/?api=1&query="+this.props.lat+","+this.props.lng}>
|
||||
{this.insert_marker(mapper.position())}
|
||||
<div className="bigpicture" style={{transformOrigin: center(mapper.position(), 0)+"px "+center(mapper.position(), 1)+"px"}}>
|
||||
<div className="line">
|
||||
<img onLoad={this.onLoad.bind(this)} onError={this.onError.bind(this)} src={mapper.tile(tile_server, -1, -1)} ref="$tile" style={{height: this.state.tile_size+"px"}} className="btl"/>
|
||||
<img onLoad={this.onLoad.bind(this)} onError={this.onError.bind(this)} src={mapper.tile(tile_server, 0, -1)} style={{height: this.state.tile_size+"px"}}/>
|
||||
<img onLoad={this.onLoad.bind(this)} onError={this.onError.bind(this)} src={mapper.tile(tile_server, 1, -1)} style={{height: this.state.tile_size+"px"}} className="btr"/>
|
||||
</div>
|
||||
<div className="line">
|
||||
<img onLoad={this.onLoad.bind(this)} onError={this.onError.bind(this)} src={mapper.tile(tile_server, -1, 0)} style={{height: this.state.tile_size+"px"}}/>
|
||||
<img onLoad={this.onLoad.bind(this)} onError={this.onError.bind(this)} src={mapper.tile(tile_server, 0, 0)} style={{height: this.state.tile_size+"px"}}/>
|
||||
<img onLoad={this.onLoad.bind(this)} onError={this.onError.bind(this)} src={mapper.tile(tile_server, 1, 0)} style={{height: this.state.tile_size+"px"}}/>
|
||||
</div>
|
||||
<div className="line">
|
||||
<img onLoad={this.onLoad.bind(this)} onError={this.onError.bind(this)} src={mapper.tile(tile_server, -1, 1)} style={{height: this.state.tile_size+"px"}} className="bbl"/>
|
||||
<img onLoad={this.onLoad.bind(this)} onError={this.onError.bind(this)} src={mapper.tile(tile_server, 0, 1)} style={{height: this.state.tile_size+"px"}}/>
|
||||
<img onLoad={this.onLoad.bind(this)} onError={this.onError.bind(this)} src={mapper.tile(tile_server, 1, 1)} style={{height: this.state.tile_size+"px"}} className="bbr"/>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,33 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import ReactCSSTransitionGroup from "react-addons-css-transition-group";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { NgIf } from "./";
|
||||
import { debounce, nop } from "../helpers/";
|
||||
import "./modal.scss";
|
||||
|
||||
export function Modal({
|
||||
isActive = false, children = null, className = null, onQuit = nop
|
||||
isActive = false, children = null, className = null, onQuit = nop,
|
||||
}) {
|
||||
const calculateMarginTop = () => {
|
||||
let size = 300;
|
||||
const $box = document.querySelector("#modal-box > div");
|
||||
if($box) size = $box.offsetHeight;
|
||||
if ($box) size = $box.offsetHeight;
|
||||
|
||||
size = Math.round((document.body.offsetHeight - size) / 2);
|
||||
if(size < 0) return 0;
|
||||
if(size > 250) return 250;
|
||||
if (size < 0) return 0;
|
||||
if (size > 250) return 250;
|
||||
return size;
|
||||
};
|
||||
const resizeHandler = debounce(() => {
|
||||
setMarginTop(calculateMarginTop())
|
||||
}, 100)
|
||||
setMarginTop(calculateMarginTop());
|
||||
}, 100);
|
||||
const keydownHandler = (e) => {
|
||||
if(e.keyCode === 27){
|
||||
if (e.keyCode === 27) {
|
||||
onQuit();
|
||||
}
|
||||
};
|
||||
const onClick = (e) => {
|
||||
if(e.target.getAttribute("id") === "modal-box"){
|
||||
if (e.target.getAttribute("id") === "modal-box") {
|
||||
onQuit();
|
||||
}
|
||||
};
|
||||
|
|
@ -40,14 +39,19 @@ export function Modal({
|
|||
return () => {
|
||||
window.removeEventListener("resize", resizeHandler);
|
||||
window.removeEventListener("keydown", keydownHandler);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<ReactCSSTransitionGroup transitionName="modal" transitionLeaveTimeout={300} transitionEnterTimeout={300} transitionAppear={true} transitionAppearTimeout={300}>
|
||||
<ReactCSSTransitionGroup transitionName="modal" transitionLeaveTimeout={300}
|
||||
transitionEnterTimeout={300} transitionAppear={true} transitionAppearTimeout={300}>
|
||||
<NgIf key={"modal-"+isActive} cond={isActive}>
|
||||
<div className={"component_modal"+(className? " " + className : "")} onClick={onClick} id="modal-box">
|
||||
<div style={{margin: marginTop+"px auto 0 auto", visibility: marginTop === -1 ? "hidden" : "visible"}}>
|
||||
<div className={"component_modal"+(className? " " + className : "")}
|
||||
onClick={onClick} id="modal-box">
|
||||
<div style={{
|
||||
margin: marginTop+"px auto 0 auto",
|
||||
visibility: marginTop === -1 ? "hidden" : "visible",
|
||||
}}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,22 +2,22 @@ import React from "react";
|
|||
import PropTypes from "prop-types";
|
||||
|
||||
export class NgIf extends React.Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let clean_prop = Object.assign({}, this.props);
|
||||
const clean_prop = Object.assign({}, this.props);
|
||||
delete clean_prop.cond;
|
||||
delete clean_prop.children;
|
||||
delete clean_prop.type;
|
||||
if(this.props.cond){
|
||||
if(this.props.type === "inline"){
|
||||
if (this.props.cond) {
|
||||
if (this.props.type === "inline") {
|
||||
return <span {...clean_prop}>{this.props.children}</span>;
|
||||
}else{
|
||||
} else {
|
||||
return <div {...clean_prop}>{this.props.children}</div>;
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -25,30 +25,30 @@ export class NgIf extends React.Component {
|
|||
|
||||
NgIf.propTypes = {
|
||||
cond: PropTypes.bool.isRequired,
|
||||
type: PropTypes.string
|
||||
type: PropTypes.string,
|
||||
};
|
||||
|
||||
|
||||
export class NgShow extends React.Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let clean_prop = Object.assign({}, this.props);
|
||||
const clean_prop = Object.assign({}, this.props);
|
||||
delete clean_prop.cond;
|
||||
delete clean_prop.children;
|
||||
delete clean_prop.type;
|
||||
if(this.props.cond){
|
||||
if(this.props.type === "inline"){
|
||||
return <span {...clean_prop}>{this.props.children}</span>;
|
||||
}else{
|
||||
return <div {...clean_prop}>{this.props.children}</div>;
|
||||
if (this.props.cond) {
|
||||
if (this.props.type === "inline") {
|
||||
return <span {...clean_prop}>{ this.props.children }</span>;
|
||||
} else {
|
||||
return <div {...clean_prop}>{ this.props.children }</div>;
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
return (
|
||||
<div style={{display: "none"}}>
|
||||
{this.props.children}
|
||||
<div style={{ display: "none" }}>
|
||||
{ this.props.children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -57,5 +57,5 @@ export class NgShow extends React.Component {
|
|||
|
||||
NgShow.propTypes = {
|
||||
cond: PropTypes.bool.isRequired,
|
||||
type: PropTypes.string
|
||||
type: PropTypes.string,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,128 +7,133 @@ import { t } from "../locales/";
|
|||
import "./notification.scss";
|
||||
|
||||
export class Notification extends React.Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
appear: false,
|
||||
message_text: null,
|
||||
message_type: null
|
||||
message_type: null,
|
||||
};
|
||||
this.runner = new TaskManager();
|
||||
this.notification_current = null;
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
componentDidMount() {
|
||||
this.runner.before_run((task) => {
|
||||
this.notification_current = task;
|
||||
});
|
||||
|
||||
notify.subscribe((message, type) => {
|
||||
this.runner.addTask(Task(
|
||||
this.openNotification.bind(this, {text: stringify(message), type: type}),
|
||||
this.openNotification.bind(this, { text: stringify(message), type: type }),
|
||||
this.closeNotification.bind(this),
|
||||
8000,
|
||||
500
|
||||
500,
|
||||
));
|
||||
});
|
||||
|
||||
function stringify(data){
|
||||
if(typeof data === "object" && data.message){
|
||||
function stringify(data) {
|
||||
if (typeof data === "object" && data.message) {
|
||||
return data.message;
|
||||
}else if(typeof data === "string"){
|
||||
} else if (typeof data === "string") {
|
||||
return data;
|
||||
}
|
||||
return JSON.stringify(data);
|
||||
}
|
||||
}
|
||||
|
||||
closeNotification(){
|
||||
closeNotification() {
|
||||
return new Promise((done) => {
|
||||
this.setState({
|
||||
appear: false
|
||||
appear: false,
|
||||
}, done);
|
||||
});
|
||||
}
|
||||
|
||||
openNotification(message){
|
||||
openNotification(message) {
|
||||
return new Promise((done) => {
|
||||
this.setState({
|
||||
appear: true,
|
||||
message_text: message.text,
|
||||
message_type: message.type
|
||||
message_type: message.type,
|
||||
}, done);
|
||||
});
|
||||
}
|
||||
|
||||
cancelAnimation(){
|
||||
cancelAnimation() {
|
||||
return this.notification_current.cancel();
|
||||
}
|
||||
|
||||
render(){
|
||||
render() {
|
||||
return (
|
||||
<ReactCSSTransitionGroup transitionName="notification" transitionLeave={true} transitionLeaveTimeout={200} transitionEnter={true} transitionEnterTimeout={100} transitionAppear={false} className="component_notification">
|
||||
<NgIf key={this.state.message_text+this.state.message_type+this.state.appear} cond={this.state.appear === true} className="no-select">
|
||||
<div className={"component_notification--container "+(this.state.message_type || "info")}>
|
||||
<div className="message">
|
||||
{ t(this.state.message_text || "") }
|
||||
</div>
|
||||
<div className="close" onClick={this.cancelAnimation.bind(this)}>
|
||||
<Icon name="close" />
|
||||
</div>
|
||||
</div>
|
||||
</NgIf>
|
||||
<ReactCSSTransitionGroup transitionName="notification" transitionLeave={true}
|
||||
transitionLeaveTimeout={200} transitionEnter={true} transitionEnterTimeout={100}
|
||||
transitionAppear={false} className="component_notification"
|
||||
>
|
||||
<NgIf key={this.state.message_text+this.state.message_type+this.state.appear}
|
||||
cond={this.state.appear === true} className="no-select">
|
||||
<div className={
|
||||
"component_notification--container "+(this.state.message_type || "info")
|
||||
}>
|
||||
<div className="message">
|
||||
{ t(this.state.message_text || "") }
|
||||
</div>
|
||||
<div className="close" onClick={this.cancelAnimation.bind(this)}>
|
||||
<Icon name="close" />
|
||||
</div>
|
||||
</div>
|
||||
</NgIf>
|
||||
</ReactCSSTransitionGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function TaskManager(){
|
||||
let tasks = [];
|
||||
function TaskManager() {
|
||||
const tasks = [];
|
||||
let is_running = false;
|
||||
let subscriber = null;
|
||||
let current_task = null;
|
||||
|
||||
const ret ={
|
||||
addTask: function(task){
|
||||
addTask: function(task) {
|
||||
current_task && current_task.cancel();
|
||||
tasks.push(task);
|
||||
if(tasks.length > 20){
|
||||
if (tasks.length > 20) {
|
||||
tasks.splice(0, tasks.length - 10);
|
||||
}
|
||||
if(is_running === false){
|
||||
if (is_running === false) {
|
||||
is_running = true;
|
||||
ret._run();
|
||||
}
|
||||
},
|
||||
before_run: function(fn){
|
||||
before_run: function(fn) {
|
||||
subscriber = fn;
|
||||
},
|
||||
_run: function(){
|
||||
_run: function() {
|
||||
current_task = tasks.shift();
|
||||
if(!current_task){
|
||||
if (!current_task) {
|
||||
is_running = false;
|
||||
return Promise.resolve();
|
||||
}else{
|
||||
} else {
|
||||
const mode = tasks.length > 0 ? "minimal" : "normal";
|
||||
subscriber(current_task, mode);
|
||||
return current_task.run(mode).then(ret._run);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
function Task(_runCallback, _finishCallback, wait_time_before_finish, minimum_running_time){
|
||||
function Task(_runCallback, _finishCallback, wait_time_before_finish, minimum_running_time) {
|
||||
let start_date = null;
|
||||
let done = null;
|
||||
let promise = new Promise((_done) => { done = _done; });
|
||||
const promise = new Promise((_done) => {
|
||||
done = _done;
|
||||
});
|
||||
let timeout = null;
|
||||
|
||||
const ret = {
|
||||
run: function(mode = "normal"){
|
||||
run: function(mode = "normal") {
|
||||
const wait = mode === "minimal" ? minimum_running_time : wait_time_before_finish;
|
||||
start_date = new Date();
|
||||
|
||||
|
|
@ -148,28 +153,28 @@ function Task(_runCallback, _finishCallback, wait_time_before_finish, minimum_ru
|
|||
});
|
||||
return promise;
|
||||
},
|
||||
cancel: function(){
|
||||
cancel: function() {
|
||||
window.clearTimeout(timeout);
|
||||
timeout = null;
|
||||
let elapsed_time = new Date() - start_date;
|
||||
const elapsed_time = new Date() - start_date;
|
||||
|
||||
if(elapsed_time < minimum_running_time){
|
||||
if (elapsed_time < minimum_running_time) {
|
||||
window.setTimeout(() => {
|
||||
ret._complete();
|
||||
}, minimum_running_time - elapsed_time);
|
||||
}else{
|
||||
} else {
|
||||
ret._complete();
|
||||
}
|
||||
return promise;
|
||||
},
|
||||
_complete: function(){
|
||||
if(done){
|
||||
_complete: function() {
|
||||
if (done) {
|
||||
_finishCallback();
|
||||
done();
|
||||
}
|
||||
done = null;
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,37 +4,37 @@ import { Modal } from "./";
|
|||
import "./popup.scss";
|
||||
|
||||
export class Popup extends React.Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
if(new.target === Popup){
|
||||
if (new.target === Popup) {
|
||||
throw new TypeError("Cannot construct Popup instances directly");
|
||||
}
|
||||
this.state = {
|
||||
appear: false
|
||||
appear: false,
|
||||
};
|
||||
}
|
||||
|
||||
onSubmit(e){
|
||||
onSubmit(e) {
|
||||
e && e.preventDefault && e.preventDefault();
|
||||
this.setState({appear: false});
|
||||
this.setState({ appear: false });
|
||||
}
|
||||
|
||||
onCancel(){
|
||||
this.setState({appear: false});
|
||||
onCancel() {
|
||||
this.setState({ appear: false });
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal isActive={this.state.appear} onQuit={this.onCancel.bind(this)}>
|
||||
<div className="component_popup">
|
||||
<div className="popup--content">
|
||||
{ this.modalContentBody() }
|
||||
<div className="component_popup">
|
||||
<div className="popup--content">
|
||||
{ this.modalContentBody() }
|
||||
</div>
|
||||
<div className="buttons">
|
||||
{ this.modalContentFooter() }
|
||||
</div>
|
||||
</div>
|
||||
<div className="buttons">
|
||||
{ this.modalContentFooter() }
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import { Popup } from "./popup";
|
|||
import { t } from "../locales/";
|
||||
|
||||
export class ModalPrompt extends Popup {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
componentDidMount() {
|
||||
prompt.subscribe((text, okCallback, cancelCallback, type) => {
|
||||
this.setState({
|
||||
appear: true,
|
||||
|
|
@ -18,37 +18,42 @@ export class ModalPrompt extends Popup {
|
|||
error: null,
|
||||
type: type || "text",
|
||||
text: text || "",
|
||||
fns: {ok: okCallback, cancel: cancelCallback}
|
||||
fns: { ok: okCallback, cancel: cancelCallback },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit(e){
|
||||
onSubmit(e) {
|
||||
e && e.preventDefault && e.preventDefault();
|
||||
this.state.fns.ok(this.state.value)
|
||||
.then(() => this.setState({appear: false}))
|
||||
.catch((message) => this.setState({error: message}));
|
||||
.then(() => this.setState({ appear: false }))
|
||||
.catch((message) => this.setState({ error: message }));
|
||||
}
|
||||
|
||||
modalContentBody(){
|
||||
modalContentBody() {
|
||||
return (
|
||||
<div>
|
||||
{this.state.text }
|
||||
<form onSubmit={this.onSubmit.bind(this)} style={{marginTop: "10px"}}>
|
||||
<Input autoFocus={true} value={this.state.value} type={this.state.type} autoComplete="new-password" onChange={(e) => this.setState({value: e.target.value})} />
|
||||
<div className="modal-error-message">{this.state.error} </div>
|
||||
</form>
|
||||
{ this.state.text }
|
||||
<form onSubmit={this.onSubmit.bind(this)} style={{ marginTop: "10px" }}>
|
||||
<Input autoFocus={true} value={this.state.value}
|
||||
type={this.state.type} autoComplete="new-password"
|
||||
onChange={(e) => this.setState({ value: e.target.value })} />
|
||||
<div className="modal-error-message">{this.state.error} </div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
modalContentFooter(){
|
||||
modalContentFooter() {
|
||||
return (
|
||||
<div>
|
||||
<Button type="button" onClick={this.onCancel.bind(this)}>{ t("CANCEL") }</Button>
|
||||
<Button type="submit" theme="emphasis" onClick={this.onSubmit.bind(this)}>{ t("OK") }</Button>
|
||||
<Button type="button" onClick={this.onCancel.bind(this)}>
|
||||
{ t("CANCEL") }
|
||||
</Button>
|
||||
<Button type="submit" theme="emphasis" onClick={this.onSubmit.bind(this)}>
|
||||
{ t("OK") }
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,35 @@
|
|||
import React, { useRef, useState, useLayoutEffect } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import "./textarea.scss";
|
||||
|
||||
export function Textarea({ ...props }) {
|
||||
const $el = useRef();
|
||||
const [className, setClassName] = useState(
|
||||
"component_textarea"
|
||||
+ (/Firefox/.test(navigator.userAgent) ? " firefox" : "")
|
||||
+ (props.value && props.value.length > 0 ? " hasText" : "")
|
||||
"component_textarea" +
|
||||
(/Firefox/.test(navigator.userAgent) ? " firefox" : "") +
|
||||
(props.value && props.value.length > 0 ? " hasText" : ""),
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if($el.current && $el.current.value.length > 0 && className.indexOf("hasText") === -1){
|
||||
setClassName(`${className} hasText`)
|
||||
if ($el.current && $el.current.value.length > 0 &&
|
||||
className.indexOf("hasText") === -1) {
|
||||
setClassName(`${className} hasText`);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const disabledEnter = (e) => {
|
||||
if(e.key === "Enter" && e.shiftKey === false){
|
||||
if (e.key === "Enter" && e.shiftKey === false) {
|
||||
e.preventDefault();
|
||||
const $form = getForm($el.current);
|
||||
if($form){
|
||||
if ($form) {
|
||||
$form.dispatchEvent(new Event("submit", { cancelable: true }));
|
||||
}
|
||||
}
|
||||
|
||||
function getForm($el){
|
||||
if(!$el.parentElement) return $el;
|
||||
if($el.parentElement.nodeName == "FORM"){
|
||||
function getForm($el) {
|
||||
console.log("GET FORM $el", $el);
|
||||
if (!$el.parentElement) return $el;
|
||||
if ($el.parentElement.nodeName == "FORM") {
|
||||
return $el.parentElement;
|
||||
}
|
||||
return getForm($el.parentElement);
|
||||
|
|
@ -36,7 +37,7 @@ export function Textarea({ ...props }) {
|
|||
};
|
||||
const inputProps = (p) => {
|
||||
return Object.keys(p).reduce((acc, key) => {
|
||||
if(key === "disabledEnter") return acc;
|
||||
if (key === "disabledEnter") return acc;
|
||||
acc[key] = p[key];
|
||||
return acc;
|
||||
}, {});
|
||||
|
|
@ -49,59 +50,5 @@ export function Textarea({ ...props }) {
|
|||
className={className}
|
||||
ref={$el}>
|
||||
</textarea>
|
||||
)
|
||||
);
|
||||
}
|
||||
export class Textarea2 extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let className = "component_textarea";
|
||||
if(/Firefox/.test(navigator.userAgent)){
|
||||
className += " firefox";
|
||||
}
|
||||
if((this.refs.el !== undefined && this.refs.el.value.length > 0) || (this.props.value !== undefined && this.props.value.length > 0)){
|
||||
className += " hasText";
|
||||
}
|
||||
|
||||
const disabledEnter = (e) => {
|
||||
if(e.key === "Enter" && e.shiftKey === false){
|
||||
e.preventDefault();
|
||||
const $form = getForm(this.refs.el);
|
||||
if($form){
|
||||
$form.dispatchEvent(new Event("submit", { cancelable: true }));
|
||||
}
|
||||
}
|
||||
|
||||
function getForm($el){
|
||||
if(!$el.parentElement) return $el;
|
||||
if($el.parentElement.nodeName == "FORM"){
|
||||
return $el.parentElement;
|
||||
}
|
||||
return getForm($el.parentElement);
|
||||
}
|
||||
};
|
||||
const inputProps = (p) => {
|
||||
return Object.keys(p).reduce((acc, key) => {
|
||||
if(key === "disabledEnter") return acc;
|
||||
acc[key] = p[key];
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
return (
|
||||
<textarea
|
||||
onKeyPress={disabledEnter}
|
||||
{...inputProps(this.props)}
|
||||
className={className}
|
||||
ref="el"
|
||||
></textarea>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Textarea.propTypes = {
|
||||
type: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
disabledEnter: PropTypes.bool
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@ import { Icon } from "./";
|
|||
import "./video.scss";
|
||||
|
||||
export class Video extends React.Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render(){
|
||||
render() {
|
||||
return (
|
||||
<div className="component_video">
|
||||
<div className="loader">
|
||||
<Icon name="loading"/>
|
||||
</div>
|
||||
<div className="loader">
|
||||
<Icon name="loading"/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,171 +1,181 @@
|
|||
export function http_get(url, type = 'json'){
|
||||
export function http_get(url, type = "json") {
|
||||
return new Promise((done, err) => {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", url, true);
|
||||
xhr.withCredentials = true;
|
||||
xhr.setRequestHeader('X-Requested-With', 'XmlHttpRequest');
|
||||
xhr.setRequestHeader("X-Requested-With", "XmlHttpRequest");
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if(xhr.status === 200){
|
||||
if(type === 'json'){
|
||||
try{
|
||||
let data = JSON.parse(xhr.responseText);
|
||||
if("status" in data === false || data.status === 'ok'){
|
||||
if (xhr.status === 200) {
|
||||
if (type === "json") {
|
||||
try {
|
||||
const data = JSON.parse(xhr.responseText);
|
||||
if ("status" in data === false || data.status === "ok") {
|
||||
done(data);
|
||||
}else{
|
||||
} else {
|
||||
err(data);
|
||||
}
|
||||
}catch(error){
|
||||
err({message: 'oups', trace: error});
|
||||
} catch (error) {
|
||||
err({ message: "oups", trace: error });
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
done(xhr.responseText);
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
handle_error_response(xhr, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send(null);
|
||||
xhr.onerror = function(){
|
||||
handle_error_response(xhr, err)
|
||||
}
|
||||
xhr.onerror = function() {
|
||||
handle_error_response(xhr, err);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function http_post(url, data, type = 'json', params){
|
||||
export function http_post(url, data, type = "json", params) {
|
||||
return new Promise((done, err) => {
|
||||
var xhr = new XMLHttpRequest();
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", url, true);
|
||||
xhr.withCredentials = true;
|
||||
xhr.setRequestHeader('X-Requested-With', 'XmlHttpRequest');
|
||||
if(type === 'json'){
|
||||
xhr.setRequestHeader("X-Requested-With", "XmlHttpRequest");
|
||||
if (type === "json") {
|
||||
data = JSON.stringify(data);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
}
|
||||
if (params && params.progress) {
|
||||
xhr.upload.addEventListener("progress", params.progress, false);
|
||||
}
|
||||
xhr.send(data);
|
||||
xhr.onload = function () {
|
||||
xhr.onload = function() {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if(xhr.status === 200){
|
||||
try{
|
||||
let data = JSON.parse(xhr.responseText);
|
||||
if(data.status === 'ok'){
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
const data = JSON.parse(xhr.responseText);
|
||||
if (data.status === "ok") {
|
||||
done(data);
|
||||
}else{
|
||||
} else {
|
||||
err(data);
|
||||
}
|
||||
}catch(error){
|
||||
err({message: 'oups', trace: error});
|
||||
} catch (error) {
|
||||
err({ message: "oups", trace: error });
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
handle_error_response(xhr, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
xhr.onerror = function(){
|
||||
handle_error_response(xhr, err)
|
||||
}
|
||||
};
|
||||
xhr.onerror = function() {
|
||||
handle_error_response(xhr, err);
|
||||
};
|
||||
if (params && params.abort) {
|
||||
params.abort(() => {
|
||||
xhr.abort();
|
||||
err({ message: 'aborted' });
|
||||
})
|
||||
err({ message: "aborted" });
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function http_delete(url){
|
||||
export function http_delete(url) {
|
||||
return new Promise((done, err) => {
|
||||
var xhr = new XMLHttpRequest();
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("DELETE", url, true);
|
||||
xhr.withCredentials = true;
|
||||
xhr.setRequestHeader('X-Requested-With', 'XmlHttpRequest');
|
||||
xhr.onload = function () {
|
||||
xhr.setRequestHeader("X-Requested-With", "XmlHttpRequest");
|
||||
xhr.onload = function() {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if(xhr.status === 200){
|
||||
try{
|
||||
let data = JSON.parse(xhr.responseText);
|
||||
if(data.status === 'ok'){
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
const data = JSON.parse(xhr.responseText);
|
||||
if (data.status === "ok") {
|
||||
done(data);
|
||||
}else{
|
||||
} else {
|
||||
err(data);
|
||||
}
|
||||
}catch(error){
|
||||
err({message: 'oups', trace: error});
|
||||
} catch (error) {
|
||||
err({ message: "oups", trace: error });
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
handle_error_response(xhr, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
xhr.onerror = function(){
|
||||
handle_error_response(xhr, err)
|
||||
}
|
||||
};
|
||||
xhr.onerror = function() {
|
||||
handle_error_response(xhr, err);
|
||||
};
|
||||
xhr.send(null);
|
||||
});
|
||||
}
|
||||
|
||||
export function http_options(url){
|
||||
export function http_options(url) {
|
||||
return new Promise((done, err) => {
|
||||
var xhr = new XMLHttpRequest();
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("OPTIONS", url, true);
|
||||
xhr.withCredentials = true;
|
||||
xhr.setRequestHeader('X-Requested-With', 'XmlHttpRequest');
|
||||
xhr.onload = function(){
|
||||
if(xhr.readyState === XMLHttpRequest.DONE){
|
||||
if(xhr.status !== 200){
|
||||
xhr.setRequestHeader("X-Requested-With", "XmlHttpRequest");
|
||||
xhr.onload = function() {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (xhr.status !== 200) {
|
||||
handle_error_response(xhr, err);
|
||||
return
|
||||
return;
|
||||
}
|
||||
done(xhr.getAllResponseHeaders()
|
||||
.split("\n")
|
||||
.reduce((acc, r) => {
|
||||
const a = r.split(": ");
|
||||
acc[a[0]] = a[1];
|
||||
return acc;
|
||||
}, {}));
|
||||
done(
|
||||
xhr.getAllResponseHeaders()
|
||||
.split("\n")
|
||||
.reduce((acc, r) => {
|
||||
const a = r.split(": ");
|
||||
acc[a[0]] = a[1];
|
||||
return acc;
|
||||
}, {}),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send(null);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function handle_error_response(xhr, err){
|
||||
const response = (function(content){
|
||||
function handle_error_response(xhr, err) {
|
||||
const response = (function(content) {
|
||||
let message = content;
|
||||
try{
|
||||
try {
|
||||
message = JSON.parse(content);
|
||||
}catch(err){}
|
||||
return message || {};
|
||||
} catch (err) {
|
||||
return { message: content };
|
||||
}
|
||||
return message || { message: "empty response" };
|
||||
})(xhr.responseText);
|
||||
|
||||
const message = response.message || null;
|
||||
|
||||
if(navigator.onLine === false){
|
||||
err({message: 'Connection Lost', code: "NO_INTERNET"});
|
||||
}else if(xhr.status === 0 && xhr.responseText === "") {
|
||||
err({message: "Service unavailable, if the problem persist, contact your administrator", code: "INTERNAL_SERVER_ERROR"});
|
||||
}else if(xhr.status === 500){
|
||||
err({message: message || "Oups something went wrong with our servers", code: "INTERNAL_SERVER_ERROR"});
|
||||
}else if(xhr.status === 401){
|
||||
err({message: message || "Authentication error", code: "Unauthorized"});
|
||||
}else if(xhr.status === 403){
|
||||
err({message: message || "You can\'t do that", code: "Forbidden"});
|
||||
}else if(xhr.status === 413){
|
||||
err({message: message || "Payload too large", code: "PAYLOAD_TOO_LARGE"});
|
||||
}else if(xhr.status === 502){
|
||||
err({message: message || "The destination is acting weird", code: "BAD_GATEWAY"});
|
||||
}else if(xhr.status === 409){
|
||||
if(response["error_summary"]){ // dropbox way to say doesn't exist
|
||||
err({message: "Doesn\'t exist", code: "UNKNOWN_PATH"})
|
||||
}else{
|
||||
err({message: message || "Oups you just ran into a conflict", code: "CONFLICT"});
|
||||
if (navigator.onLine === false) {
|
||||
err({ message: "Connection Lost", code: "NO_INTERNET" });
|
||||
} else if (xhr.status === 0 && xhr.responseText === "") {
|
||||
err({
|
||||
message: "Service unavailable, if the problem persist, contact your administrator",
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
});
|
||||
} else if (xhr.status === 500) {
|
||||
err({
|
||||
message: message || "Oups something went wrong with our servers",
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
});
|
||||
} else if (xhr.status === 401) {
|
||||
err({ message: message || "Authentication error", code: "Unauthorized" });
|
||||
} else if (xhr.status === 403) {
|
||||
err({ message: message || "You can\'t do that", code: "Forbidden" });
|
||||
} else if (xhr.status === 413) {
|
||||
err({ message: message || "Payload too large", code: "PAYLOAD_TOO_LARGE" });
|
||||
} else if (xhr.status === 502) {
|
||||
err({ message: message || "The destination is acting weird", code: "BAD_GATEWAY" });
|
||||
} else if (xhr.status === 409) {
|
||||
if (response["error_summary"]) { // dropbox way to say doesn't exist
|
||||
err({ message: "Doesn\'t exist", code: "UNKNOWN_PATH" });
|
||||
} else {
|
||||
err({ message: message || "Oups you just ran into a conflict", code: "CONFLICT" });
|
||||
}
|
||||
}else{
|
||||
err({message: message || 'Oups something went wrong'});
|
||||
} else {
|
||||
err({ message: message || "Oups something went wrong" });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
/* eslint-disable */
|
||||
|
||||
export function debounce(func, wait, immediate) {
|
||||
var timeout;
|
||||
return function() {
|
||||
var context = this, args = arguments;
|
||||
var later = function() {
|
||||
timeout = null;
|
||||
if (!immediate) func.apply(context, args);
|
||||
};
|
||||
var callNow = immediate && !timeout;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
if (callNow) func.apply(context, args);
|
||||
};
|
||||
};
|
||||
var timeout;
|
||||
return function() {
|
||||
var context = this, args = arguments;
|
||||
var later = function() {
|
||||
timeout = null;
|
||||
if (!immediate) func.apply(context, args);
|
||||
};
|
||||
var callNow = immediate && !timeout;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
if (callNow) func.apply(context, args);
|
||||
};
|
||||
}
|
||||
|
||||
export function throttle(func, wait, options) {
|
||||
var context, args, result;
|
||||
|
|
@ -43,4 +45,4 @@ export function throttle(func, wait, options) {
|
|||
}
|
||||
return result;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import bcrypt from 'bcryptjs';
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
export function bcrypt_password(password){
|
||||
export function bcrypt_password(password) {
|
||||
return new Promise((done, error) => {
|
||||
bcrypt.hash(password, 10, (err, hash) => {
|
||||
if(err) return error(err)
|
||||
if (err) return error(err);
|
||||
done(hash);
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,34 @@
|
|||
"use strict";
|
||||
|
||||
const DB_VERSION = 3,
|
||||
FILE_PATH = "file_path",
|
||||
FILE_CONTENT = "file_content";
|
||||
const DB_VERSION = 3;
|
||||
const FILE_PATH = "file_path";
|
||||
const FILE_CONTENT = "file_content";
|
||||
|
||||
function DataFromIndexedDB(){
|
||||
function DataFromIndexedDB() {
|
||||
this.db = null;
|
||||
this.FILE_PATH = FILE_PATH;
|
||||
this.FILE_CONTENT = FILE_CONTENT;
|
||||
return this._init();
|
||||
}
|
||||
function DataFromMemory(){
|
||||
function DataFromMemory() {
|
||||
this.data = {};
|
||||
this.FILE_PATH = FILE_PATH;
|
||||
this.FILE_CONTENT = FILE_CONTENT;
|
||||
return this._init();
|
||||
}
|
||||
|
||||
DataFromIndexedDB.prototype._init = function(){
|
||||
DataFromIndexedDB.prototype._init = function() {
|
||||
const request = indexedDB.open("filestash", DB_VERSION);
|
||||
request.onupgradeneeded = function(event){
|
||||
request.onupgradeneeded = function(event) {
|
||||
let store;
|
||||
let db = event.target.result;
|
||||
const db = event.target.result;
|
||||
|
||||
if(event.oldVersion == 1) {
|
||||
if (event.oldVersion == 1) {
|
||||
// we've change the schema on v2 adding an index, let's flush
|
||||
// to make sure everything will be fine
|
||||
db.deleteObjectStore(FILE_PATH);
|
||||
db.deleteObjectStore(FILE_CONTENT);
|
||||
}else if(event.oldVersion == 2){
|
||||
} else if (event.oldVersion == 2) {
|
||||
// we've change the primary key to be a (path,share)
|
||||
db.deleteObjectStore(FILE_PATH);
|
||||
db.deleteObjectStore(FILE_CONTENT);
|
||||
|
|
@ -37,7 +37,7 @@ DataFromIndexedDB.prototype._init = function(){
|
|||
store = db.createObjectStore(FILE_PATH, { keyPath: ["share", "path"] });
|
||||
store.createIndex("idx_path", ["share", "path"], { unique: true });
|
||||
|
||||
store = db.createObjectStore(FILE_CONTENT, {keyPath: ["share", "path"]});
|
||||
store = db.createObjectStore(FILE_CONTENT, { keyPath: ["share", "path"] });
|
||||
store.createIndex("idx_path", ["share", "path"], { unique: true });
|
||||
};
|
||||
|
||||
|
|
@ -48,14 +48,15 @@ DataFromIndexedDB.prototype._init = function(){
|
|||
request.onerror = (e) => err("INDEXEDDB_NOT_SUPPORTED");
|
||||
});
|
||||
};
|
||||
DataFromMemory.prototype._init = function(){
|
||||
|
||||
DataFromMemory.prototype._init = function() {
|
||||
};
|
||||
|
||||
/*
|
||||
* Fetch a record using its path, can be either a file path or content
|
||||
*/
|
||||
DataFromIndexedDB.prototype.get = function(type, key){
|
||||
if(type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
DataFromIndexedDB.prototype.get = function(type, key) {
|
||||
if (type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
|
||||
return this.db.then((db) => {
|
||||
const tx = db.transaction(type, "readonly");
|
||||
|
|
@ -63,22 +64,21 @@ DataFromIndexedDB.prototype.get = function(type, key){
|
|||
const query = store.get(key);
|
||||
return new Promise((done, error) => {
|
||||
query.onsuccess = (e) => {
|
||||
let data = query.result;
|
||||
done(query.result || null);
|
||||
};
|
||||
query.onerror = () => done()
|
||||
query.onerror = () => done();
|
||||
});
|
||||
});
|
||||
};
|
||||
DataFromMemory.prototype.get = function(type, key){
|
||||
if(type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
DataFromMemory.prototype.get = function(type, key) {
|
||||
if (type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
|
||||
const data = this.data[type] || null;
|
||||
if(data === null){
|
||||
if (data === null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
const value = data[key.join("_")] || null;
|
||||
if(value === null){
|
||||
if (value === null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return new Promise((done) => {
|
||||
|
|
@ -86,23 +86,23 @@ DataFromMemory.prototype.get = function(type, key){
|
|||
});
|
||||
};
|
||||
|
||||
DataFromIndexedDB.prototype.update = function(type, key, fn, exact = true){
|
||||
if(type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
DataFromIndexedDB.prototype.update = function(type, key, fn, exact = true) {
|
||||
if (type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
|
||||
return this.db.then((db) => {
|
||||
const tx = db.transaction(type, "readwrite");
|
||||
const store = tx.objectStore(type);
|
||||
const range = exact === true? IDBKeyRange.only(key) : IDBKeyRange.bound(
|
||||
[key[0], key[1]],
|
||||
[key[0], key[1]+'\uFFFF'],
|
||||
false, true
|
||||
[key[0], key[1]+"\uFFFF"],
|
||||
false, true,
|
||||
);
|
||||
const request = store.openCursor(range);
|
||||
let new_data = null;
|
||||
return new Promise((done, err) => {
|
||||
request.onsuccess = function(event) {
|
||||
const cursor = event.target.result;
|
||||
if(!cursor) return done(new_data);
|
||||
if (!cursor) return done(new_data);
|
||||
new_data = fn(cursor.value || null);
|
||||
cursor.delete([key[0], cursor.value.path]);
|
||||
store.put(new_data);
|
||||
|
|
@ -111,20 +111,21 @@ DataFromIndexedDB.prototype.update = function(type, key, fn, exact = true){
|
|||
});
|
||||
}).catch(() => Promise.resolve());
|
||||
};
|
||||
DataFromMemory.prototype.update = function(type, key, fn, exact = true){
|
||||
if(type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
|
||||
DataFromMemory.prototype.update = function(type, key, fn, exact = true) {
|
||||
if (type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
|
||||
const data = this.data[type];
|
||||
if(data === undefined){
|
||||
if (data === undefined) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const k = key.join("_");
|
||||
if(exact === true){
|
||||
if(this.data[type][k] !== undefined) this.data[type][k] = fn(data[k]);
|
||||
}else{
|
||||
for(let _k in data){
|
||||
if(_k.indexOf(k) === 0){
|
||||
if (exact === true) {
|
||||
if (this.data[type][k] !== undefined) this.data[type][k] = fn(data[k]);
|
||||
} else {
|
||||
for (const _k in data) {
|
||||
if (_k.indexOf(k) === 0) {
|
||||
this.data[type][_k] = fn(data[_k]);
|
||||
}
|
||||
}
|
||||
|
|
@ -132,8 +133,8 @@ DataFromMemory.prototype.update = function(type, key, fn, exact = true){
|
|||
return Promise.resolve();
|
||||
};
|
||||
|
||||
DataFromIndexedDB.prototype.upsert = function(type, key, fn){
|
||||
if(type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
DataFromIndexedDB.prototype.upsert = function(type, key, fn) {
|
||||
if (type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
|
||||
return this.db.then((db) => {
|
||||
const tx = db.transaction(type, "readwrite");
|
||||
|
|
@ -142,7 +143,7 @@ DataFromIndexedDB.prototype.upsert = function(type, key, fn){
|
|||
return new Promise((done, error) => {
|
||||
query.onsuccess = (e) => {
|
||||
const new_data = fn(query.result || null);
|
||||
if(!new_data) return done(query.result || null);
|
||||
if (!new_data) return done(query.result || null);
|
||||
|
||||
const request = store.put(new_data);
|
||||
request.onsuccess = () => done(new_data);
|
||||
|
|
@ -152,11 +153,11 @@ DataFromIndexedDB.prototype.upsert = function(type, key, fn){
|
|||
});
|
||||
});
|
||||
};
|
||||
DataFromMemory.prototype.upsert = function(type, key, fn){
|
||||
if(type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
DataFromMemory.prototype.upsert = function(type, key, fn) {
|
||||
if (type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
|
||||
const db = this.data[type] || null;
|
||||
if(db === null){
|
||||
if (db === null) {
|
||||
this.data[type] = {};
|
||||
}
|
||||
const k = key.join("_");
|
||||
|
|
@ -165,8 +166,8 @@ DataFromMemory.prototype.upsert = function(type, key, fn){
|
|||
return Promise.resolve(new_data);
|
||||
};
|
||||
|
||||
DataFromIndexedDB.prototype.add = function(type, key, data){
|
||||
if(type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
DataFromIndexedDB.prototype.add = function(type, key, data) {
|
||||
if (type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
|
||||
return this.db.then((db) => {
|
||||
return new Promise((done, error) => {
|
||||
|
|
@ -178,42 +179,42 @@ DataFromIndexedDB.prototype.add = function(type, key, data){
|
|||
});
|
||||
}).catch(() => Promise.resolve());
|
||||
};
|
||||
DataFromMemory.prototype.add = function(type, key, data){
|
||||
if(type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
DataFromMemory.prototype.add = function(type, key, data) {
|
||||
if (type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
|
||||
if(this.data[type] === undefined){
|
||||
if (this.data[type] === undefined) {
|
||||
this.data[type] = {};
|
||||
}
|
||||
this.data[type][key.join("_")] = data;
|
||||
return Promise.resolve(data);
|
||||
};
|
||||
|
||||
DataFromIndexedDB.prototype.remove = function(type, key, exact = true){
|
||||
if(type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
DataFromIndexedDB.prototype.remove = function(type, key, exact = true) {
|
||||
if (type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
|
||||
return this.db.then((db) => {
|
||||
const tx = db.transaction(type, "readwrite");
|
||||
const store = tx.objectStore(type);
|
||||
|
||||
if(exact === true){
|
||||
if (exact === true) {
|
||||
const req = store.delete(key);
|
||||
return new Promise((done, err) => {
|
||||
req.onsuccess = () => done();
|
||||
req.onerror = err;
|
||||
});
|
||||
}else{
|
||||
} else {
|
||||
const request = store.openCursor(IDBKeyRange.bound(
|
||||
[key[0], key[1]],
|
||||
[key[0], key[1]+'\uFFFF'],
|
||||
true, true
|
||||
[key[0], key[1]+"\uFFFF"],
|
||||
true, true,
|
||||
));
|
||||
return new Promise((done, err) => {
|
||||
request.onsuccess = function(event) {
|
||||
const cursor = event.target.result;
|
||||
if(cursor){
|
||||
if (cursor) {
|
||||
cursor.delete([key[0], cursor.value.path]);
|
||||
cursor.continue();
|
||||
}else{
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
|
@ -221,27 +222,27 @@ DataFromIndexedDB.prototype.remove = function(type, key, exact = true){
|
|||
}
|
||||
}).catch(() => Promise.resolve());
|
||||
};
|
||||
DataFromMemory.prototype.remove = function(type, key, exact = true){
|
||||
if(type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
DataFromMemory.prototype.remove = function(type, key, exact = true) {
|
||||
if (type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
|
||||
const data = this.data[type] || null;
|
||||
if(data === null){
|
||||
if (data === null) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const k = key.join("_");
|
||||
if(exact === true){
|
||||
if (exact === true) {
|
||||
delete data[k];
|
||||
}
|
||||
for(let _k in data){
|
||||
if(_k.indexOf(k) === 0){
|
||||
for (const _k in data) {
|
||||
if (_k.indexOf(k) === 0) {
|
||||
delete data[_k];
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
DataFromIndexedDB.prototype.fetchAll = function(fn, type = FILE_PATH, key){
|
||||
if(type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
DataFromIndexedDB.prototype.fetchAll = function(fn, type = FILE_PATH, key) {
|
||||
if (type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
|
||||
return this.db.then((db) => {
|
||||
const tx = db.transaction([type], "readonly");
|
||||
|
|
@ -249,17 +250,17 @@ DataFromIndexedDB.prototype.fetchAll = function(fn, type = FILE_PATH, key){
|
|||
const index = store.index("idx_path");
|
||||
const request = index.openCursor(IDBKeyRange.bound(
|
||||
[key[0], key[1]],
|
||||
[key[0], key[1]+("z".repeat(5000))]
|
||||
[key[0], key[1]+("z".repeat(5000))],
|
||||
));
|
||||
|
||||
return new Promise((done, error) => {
|
||||
request.onsuccess = function(event) {
|
||||
const cursor = event.target.result;
|
||||
if(!cursor){
|
||||
if (!cursor) {
|
||||
return done();
|
||||
}
|
||||
const ret = fn(cursor.value);
|
||||
if(ret === false){
|
||||
if (ret === false) {
|
||||
return done();
|
||||
}
|
||||
cursor.continue();
|
||||
|
|
@ -270,18 +271,18 @@ DataFromIndexedDB.prototype.fetchAll = function(fn, type = FILE_PATH, key){
|
|||
});
|
||||
}).catch(() => Promise.resolve());
|
||||
};
|
||||
DataFromMemory.prototype.fetchAll = function(fn, type = FILE_PATH, key){
|
||||
if(type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
DataFromMemory.prototype.fetchAll = function(fn, type = FILE_PATH, key) {
|
||||
if (type !== FILE_PATH && type !== FILE_CONTENT) return Promise.reject();
|
||||
|
||||
const data = this.data[type] || null;
|
||||
if(data === null){
|
||||
if (data === null) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const k = key.join("_");
|
||||
for(let _k in data){
|
||||
if(_k.indexOf(k) === 0){
|
||||
for (const _k in data) {
|
||||
if (_k.indexOf(k) === 0) {
|
||||
const ret = fn(data[_k]);
|
||||
if(ret === false){
|
||||
if (ret === false) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
|
@ -289,7 +290,7 @@ DataFromMemory.prototype.fetchAll = function(fn, type = FILE_PATH, key){
|
|||
return Promise.resolve();
|
||||
};
|
||||
|
||||
DataFromIndexedDB.prototype.destroy = function(){
|
||||
DataFromIndexedDB.prototype.destroy = function() {
|
||||
return new Promise((done, err) => {
|
||||
this.db.then((db) => {
|
||||
purgeAll(db, FILE_PATH);
|
||||
|
|
@ -297,21 +298,22 @@ DataFromIndexedDB.prototype.destroy = function(){
|
|||
});
|
||||
done();
|
||||
|
||||
function purgeAll(db, type){
|
||||
function purgeAll(db, type) {
|
||||
const tx = db.transaction(type, "readwrite");
|
||||
const store = tx.objectStore(type);
|
||||
store.clear();
|
||||
}
|
||||
});
|
||||
};
|
||||
DataFromMemory.prototype.destroy = function(){
|
||||
|
||||
DataFromMemory.prototype.destroy = function() {
|
||||
this.data = {};
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
export let cache = new DataFromMemory();
|
||||
if("indexedDB" in window && window.indexedDB !== null){
|
||||
var request = indexedDB.open("_indexedDB", 1);
|
||||
if ("indexedDB" in window && window.indexedDB !== null) {
|
||||
const request = indexedDB.open("_indexedDB", 1);
|
||||
request.onsuccess = (e) => {
|
||||
cache = new DataFromIndexedDB();
|
||||
indexedDB.deleteDatabase("_indexedDB");
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
export function leftPad(str, length, pad = "0"){
|
||||
if(typeof str !== 'string' || typeof pad !== 'string' || str.length >= length || !pad.length > 0) return str;
|
||||
export function leftPad(str, length, pad = "0") {
|
||||
if (typeof str !== "string" || typeof pad !== "string" || str.length >= length ||
|
||||
!pad.length > 0) {
|
||||
return str;
|
||||
}
|
||||
return leftPad(pad + str, length, pad);
|
||||
}
|
||||
|
||||
export function copyToClipboard (str){
|
||||
if(!str) return
|
||||
let $input = document.createElement("input");
|
||||
export function copyToClipboard(str) {
|
||||
if (!str) return;
|
||||
const $input = document.createElement("input");
|
||||
$input.setAttribute("type", "text");
|
||||
$input.setAttribute("style", "position: absolute; top:0;left:0;background:red")
|
||||
$input.setAttribute("style", "position: absolute; top:0;left:0;background:red");
|
||||
$input.setAttribute("display", "none");
|
||||
document.body.appendChild($input);
|
||||
$input.value = str;
|
||||
|
|
@ -16,11 +19,11 @@ export function copyToClipboard (str){
|
|||
$input.remove();
|
||||
}
|
||||
|
||||
export function format(str = ""){
|
||||
if(str.length === 0) return str;
|
||||
export function format(str = "") {
|
||||
if (str.length === 0) return str;
|
||||
return str.split("_")
|
||||
.map((word, index) => {
|
||||
if(index != 0) return word;
|
||||
if (index != 0) return word;
|
||||
return word[0].toUpperCase() + word.substring(1);
|
||||
})
|
||||
.join(" ");
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
import aesjs from "aes-js";
|
||||
import Aesjs from "aes-js";
|
||||
|
||||
export function encrypt(obj, key){
|
||||
const textBytes = aesjs.utils.utf8.toBytes(JSON.stringify(obj));
|
||||
const keyBytes = aesjs.padding.pkcs7.pad(aesjs.utils.utf8.toBytes(key));
|
||||
return aesjs.utils.hex.fromBytes(
|
||||
new aesjs.ModeOfOperation.ctr(keyBytes, new aesjs.Counter(5)).encrypt(textBytes)
|
||||
export function encrypt(obj, key) {
|
||||
const textBytes = Aesjs.utils.utf8.toBytes(JSON.stringify(obj));
|
||||
const keyBytes = Aesjs.padding.pkcs7.pad(Aesjs.utils.utf8.toBytes(key));
|
||||
return Aesjs.utils.hex.fromBytes(
|
||||
new Aesjs.ModeOfOperation.ctr(keyBytes, new Aesjs.Counter(5)).encrypt(textBytes),
|
||||
);
|
||||
}
|
||||
|
||||
export function decrypt(text, key){
|
||||
const textBytes = aesjs.utils.hex.toBytes(text);
|
||||
const keyBytes = aesjs.padding.pkcs7.pad(aesjs.utils.utf8.toBytes(key));
|
||||
return JSON.parse(aesjs.utils.utf8.fromBytes(
|
||||
new aesjs.ModeOfOperation.ctr(keyBytes, new aesjs.Counter(5)).decrypt(textBytes)
|
||||
export function decrypt(text, key) {
|
||||
const textBytes = Aesjs.utils.hex.toBytes(text);
|
||||
const keyBytes = Aesjs.padding.pkcs7.pad(Aesjs.utils.utf8.toBytes(key));
|
||||
return JSON.parse(Aesjs.utils.utf8.fromBytes(
|
||||
new Aesjs.ModeOfOperation.ctr(keyBytes, new Aesjs.Counter(5)).decrypt(textBytes),
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
function Event(){
|
||||
function Event() {
|
||||
this.fns = [];
|
||||
}
|
||||
Event.prototype.subscribe = function(name, fn){
|
||||
if(!name || typeof fn !== 'function') return;
|
||||
this.fns.push({key: name, fn: fn});
|
||||
}
|
||||
Event.prototype.unsubscribe = function(name){
|
||||
Event.prototype.subscribe = function(name, fn) {
|
||||
if (!name || typeof fn !== "function") return;
|
||||
this.fns.push({ key: name, fn: fn });
|
||||
};
|
||||
Event.prototype.unsubscribe = function(name) {
|
||||
this.fns = this.fns.filter((data) => {
|
||||
return data.key === name ? false : true;
|
||||
});
|
||||
}
|
||||
Event.prototype.emit = function(name, payload){
|
||||
};
|
||||
Event.prototype.emit = function(name, payload) {
|
||||
this.fns.map((data) => {
|
||||
if(data.key === name) data.fn(payload);
|
||||
if (data.key === name) data.fn(payload);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const event = new Event();
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
export const FormObjToJSON = function(o, fn, i = 0){
|
||||
if(i === 0) delete o["constants"];
|
||||
let obj = Object.assign({}, o);
|
||||
export const FormObjToJSON = function(o, fn, i = 0) {
|
||||
if (i === 0) delete o["constants"];
|
||||
const obj = Object.assign({}, o);
|
||||
Object.keys(obj).map((key) => {
|
||||
let t = obj[key];
|
||||
if("label" in t && "type" in t && "default" in t && "value" in t){
|
||||
if(typeof fn === "function"){
|
||||
const t = obj[key];
|
||||
if ("label" in t && "type" in t && "default" in t && "value" in t) {
|
||||
if (typeof fn === "function") {
|
||||
fn(obj, key);
|
||||
} else {
|
||||
obj[key] = obj[key].value;
|
||||
|
|
@ -13,44 +13,44 @@ export const FormObjToJSON = function(o, fn, i = 0){
|
|||
obj[key] = FormObjToJSON(obj[key], fn, i+1);
|
||||
}
|
||||
});
|
||||
return obj
|
||||
return obj;
|
||||
};
|
||||
|
||||
|
||||
export function createFormBackend(backend_available, backend_data){
|
||||
if(!backend_available) return {};
|
||||
else if(!backend_data) return {};
|
||||
else if(!backend_available[backend_data.type]) return {};
|
||||
export function createFormBackend(backend_available, backend_data) {
|
||||
if (!backend_available) return {};
|
||||
else if (!backend_data) return {};
|
||||
else if (!backend_available[backend_data.type]) return {};
|
||||
|
||||
let template = JSON.parse(JSON.stringify(backend_available[backend_data.type]));
|
||||
for(let key in backend_data){
|
||||
if(key in template){
|
||||
for (const key in backend_data) {
|
||||
if (key in template) {
|
||||
template[key].value = backend_data[key];
|
||||
template[key].enabled = true;
|
||||
} else {
|
||||
// create a form object if data isn't available in the template
|
||||
let obj = {};
|
||||
const obj = {};
|
||||
obj[key] = {
|
||||
label: key,
|
||||
type: "text",
|
||||
value: null,
|
||||
default: backend_data[key]
|
||||
default: backend_data[key],
|
||||
};
|
||||
template = Object.assign(obj, template);
|
||||
}
|
||||
|
||||
if(key === "label"){
|
||||
if (key === "label") {
|
||||
template[key].placeholder = "Name as shown on the login screen.";
|
||||
template[key].value = backend_data[key];
|
||||
template[key].enabled = true;
|
||||
} else if(key === "type"){
|
||||
} else if (key === "type") {
|
||||
template[key].enabled = true;
|
||||
} else if(key === "advanced"){
|
||||
} else if (key === "advanced") {
|
||||
template[key].enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
let obj = {};
|
||||
const obj = {};
|
||||
obj[backend_data.type] = template;
|
||||
return obj;
|
||||
}
|
||||
|
|
@ -59,35 +59,35 @@ export function createFormBackend(backend_available, backend_data){
|
|||
* return a new list of autocompletion candidates considering the current input
|
||||
*/
|
||||
export function autocomplete(values, list) {
|
||||
if(values.length === 0) return list;
|
||||
let candidates_input = [],
|
||||
candidates_output = [];
|
||||
if (values.length === 0) return list;
|
||||
let candidates_input = [];
|
||||
let candidates_output = [];
|
||||
|
||||
for(let i=0; i<list.length; i++){
|
||||
for (let i=0; i<list.length; i++) {
|
||||
const last_value = values[values.length - 1];
|
||||
|
||||
if(list[i].indexOf(last_value) === 0){
|
||||
let tmp = JSON.parse(JSON.stringify(values))
|
||||
if (list[i].indexOf(last_value) === 0) {
|
||||
const tmp = JSON.parse(JSON.stringify(values));
|
||||
tmp[values.length - 1] = list[i];
|
||||
if(list[i] === last_value){
|
||||
if (list[i] === last_value) {
|
||||
candidates_input = [tmp];
|
||||
} else {
|
||||
candidates_input.push(tmp)
|
||||
candidates_input.push(tmp);
|
||||
}
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
if(values.indexOf(list[i]) === -1){
|
||||
if (values.indexOf(list[i]) === -1) {
|
||||
candidates_output.push(list[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if(candidates_input.length === 0){
|
||||
candidates_input = [values]
|
||||
if (candidates_input.length === 0) {
|
||||
candidates_input = [values];
|
||||
}
|
||||
candidates_output = [""].concat(candidates_output);
|
||||
|
||||
if(candidates_input.length > 1) {
|
||||
if (candidates_input.length > 1) {
|
||||
return candidates_input.map((candidate) => {
|
||||
return candidate.join(", ");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,18 +1,24 @@
|
|||
export { URL_HOME, goToHome, URL_FILES, goToFiles, URL_VIEWER, goToViewer, URL_LOGIN, goToLogin, URL_LOGOUT, goToLogout, urlParams, URL_ADMIN, URL_SHARE } from './navigate';
|
||||
export { opener } from './mimetype';
|
||||
export { debounce, throttle } from './backpressure';
|
||||
export { event } from './events';
|
||||
export { cache } from './cache';
|
||||
export { pathBuilder, basename, dirname, absoluteToRelative, filetype, currentShare, findParams, appendShareToUrl } from './path';
|
||||
export { memory } from './memory';
|
||||
export { prepare } from './navigate';
|
||||
export { invalidate, http_get, http_post, http_delete, http_options } from './ajax';
|
||||
export { prompt, alert, confirm } from './popup';
|
||||
export { notify } from './notify';
|
||||
export { gid, randomString } from './random';
|
||||
export { leftPad, format, copyToClipboard } from './common';
|
||||
export { getMimeType } from './mimetype';
|
||||
export { settings_get, settings_put } from './settings';
|
||||
export { FormObjToJSON, createFormBackend, autocomplete } from './form';
|
||||
export { upload } from './upload';
|
||||
export {
|
||||
URL_HOME, goToHome, URL_FILES, goToFiles, URL_VIEWER, goToViewer, URL_LOGIN,
|
||||
goToLogin, URL_LOGOUT, goToLogout, urlParams, URL_ADMIN, URL_SHARE,
|
||||
} from "./navigate";
|
||||
export { opener } from "./mimetype";
|
||||
export { debounce, throttle } from "./backpressure";
|
||||
export { event } from "./events";
|
||||
export { cache } from "./cache";
|
||||
export {
|
||||
pathBuilder, basename, dirname, absoluteToRelative, filetype, currentShare,
|
||||
findParams, appendShareToUrl,
|
||||
} from "./path";
|
||||
export { memory } from "./memory";
|
||||
export { prepare } from "./navigate";
|
||||
export { invalidate, http_get, http_post, http_delete, http_options } from "./ajax";
|
||||
export { prompt, alert, confirm } from "./popup";
|
||||
export { notify } from "./notify";
|
||||
export { gid, randomString } from "./random";
|
||||
export { leftPad, format, copyToClipboard } from "./common";
|
||||
export { getMimeType } from "./mimetype";
|
||||
export { settings_get, settings_put } from "./settings";
|
||||
export { FormObjToJSON, createFormBackend, autocomplete } from "./form";
|
||||
export { upload } from "./upload";
|
||||
export function nop() {}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
function Memory(){
|
||||
let data = {};
|
||||
function Memory() {
|
||||
const data = {};
|
||||
|
||||
return {
|
||||
get: function(key){
|
||||
if(data[key] === undefined) return null;
|
||||
get: function(key) {
|
||||
if (data[key] === undefined) return null;
|
||||
return data[key];
|
||||
},
|
||||
set: function(key, value){
|
||||
set: function(key, value) {
|
||||
data[key] = value;
|
||||
},
|
||||
all: function(){
|
||||
all: function() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,38 +1,36 @@
|
|||
import Path from 'path';
|
||||
import Path from "path";
|
||||
|
||||
export function getMimeType(file){
|
||||
let ext = Path.extname(file).replace(/^\./, "").toLowerCase();
|
||||
let mime = CONFIG.mime[ext];
|
||||
if(mime){
|
||||
return mime;
|
||||
}else{
|
||||
export function getMimeType(file) {
|
||||
const ext = Path.extname(file).replace(/^\./, "").toLowerCase();
|
||||
if (!window.CONFIG.mime[ext]) {
|
||||
return "text/plain";
|
||||
}
|
||||
return window.CONFIG.mime[ext];
|
||||
}
|
||||
|
||||
export function opener(file){
|
||||
let mime = getMimeType(file);
|
||||
|
||||
let openerFromPlugin = window.overrides["xdg-open"](mime);
|
||||
if(openerFromPlugin !== null){
|
||||
export function opener(file) {
|
||||
const mime = getMimeType(file);
|
||||
const openerFromPlugin = window.overrides["xdg-open"](mime);
|
||||
if (openerFromPlugin !== null) {
|
||||
return openerFromPlugin;
|
||||
}else if(mime.split("/")[0] === "text"){
|
||||
} else if (mime.split("/")[0] === "text") {
|
||||
return ["editor", null];
|
||||
}else if(mime === "application/pdf"){
|
||||
} else if (mime === "application/pdf") {
|
||||
return ["pdf", null];
|
||||
}else if(mime.split("/")[0] === "image"){
|
||||
} else if (mime.split("/")[0] === "image") {
|
||||
return ["image", null];
|
||||
}else if(["application/javascript", "application/xml", "application/json", "application/x-perl"].indexOf(mime) !== -1){
|
||||
} else if (["application/javascript", "application/xml", "application/json",
|
||||
"application/x-perl"].indexOf(mime) !== -1) {
|
||||
return ["editor", null];
|
||||
}else if(["audio/wave", "audio/mp3", "audio/flac", "audio/ogg"].indexOf(mime) !== -1){
|
||||
} else if (["audio/wave", "audio/mp3", "audio/flac", "audio/ogg"].indexOf(mime) !== -1) {
|
||||
return ["audio", null];
|
||||
}else if(mime === "application/x-form"){
|
||||
} else if (mime === "application/x-form") {
|
||||
return ["form", null];
|
||||
}else if(mime.split("/")[0] === "video" || mime === "application/ogg"){
|
||||
} else if (mime.split("/")[0] === "video" || mime === "application/ogg") {
|
||||
return ["video", null];
|
||||
}else if(mime.split("/")[0] === "application"){
|
||||
} else if (mime.split("/")[0] === "application") {
|
||||
return ["download", null];
|
||||
}else{
|
||||
} else {
|
||||
return ["editor", null];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
export const URL_HOME = "/";
|
||||
export function goToHome(history){
|
||||
export function goToHome(history) {
|
||||
history.push(URL_HOME);
|
||||
return Promise.resolve("ok");
|
||||
}
|
||||
|
||||
export const URL_FILES = "/files";
|
||||
export function goToFiles(history, path, state){
|
||||
export function goToFiles(history, path, state) {
|
||||
history.push(URL_FILES+"?path="+encode_path(path), state);
|
||||
return Promise.resolve("ok");
|
||||
}
|
||||
|
||||
|
||||
export const URL_VIEWER = "/view";
|
||||
export function goToViewer(history, path, state){
|
||||
export function goToViewer(history, path, state) {
|
||||
history.push(URL_VIEWER+"?path="+encode_path(path), state);
|
||||
return Promise.resolve("ok");
|
||||
}
|
||||
|
||||
export const URL_LOGIN = "/login";
|
||||
export function goToLogin(history){
|
||||
export function goToLogin(history) {
|
||||
history.push(URL_EDIT);
|
||||
return Promise.resolve("ok");
|
||||
}
|
||||
|
||||
export const URL_LOGOUT = "/logout";
|
||||
export function goToLogout(history){
|
||||
export function goToLogout(history) {
|
||||
history.push(URL_LOGOUT);
|
||||
return Promise.resolve("ok");
|
||||
}
|
||||
|
|
@ -33,31 +33,31 @@ export const URL_ADMIN = "/admin";
|
|||
|
||||
export const URL_SHARE = "/s";
|
||||
|
||||
function encode_path(path){
|
||||
if(/%2F/.test(path) === false){
|
||||
return encodeURIComponent(path).replace(/%2F/g, "/"); // replace slash to make url more friendly
|
||||
}else{
|
||||
return encodeURIComponent(path) // in case you got a %2F folder somewhere ...
|
||||
function encode_path(path) {
|
||||
if (/%2F/.test(path) === false) { // replace slash to make url more friendly
|
||||
return encodeURIComponent(path).replace(/%2F/g, "/");
|
||||
} else { // in case you got a %2F folder somewhere ...
|
||||
return encodeURIComponent(path);
|
||||
}
|
||||
}
|
||||
|
||||
export function prepare(path){
|
||||
export function prepare(path) {
|
||||
return encodeURIComponent(decodeURIComponent(path.replace(/%/g, "%25")));
|
||||
}
|
||||
|
||||
export function urlParams() {
|
||||
let p = "";
|
||||
if(window.location.hash){
|
||||
if (window.location.hash) {
|
||||
p += window.location.hash.replace(/^\#/, "");
|
||||
}
|
||||
if(window.location.search){
|
||||
if(p !== "") p += "&";
|
||||
if (window.location.search) {
|
||||
if (p !== "") p += "&";
|
||||
p += window.location.search.replace(/^\?/, "");
|
||||
}
|
||||
return p.split("&").reduce((mem, chunk) => {
|
||||
const d = chunk.split("=");
|
||||
if(d.length !== 2) return mem;
|
||||
if (d.length !== 2) return mem;
|
||||
mem[decodeURIComponent(d[0])] = decodeURIComponent(d[1]);
|
||||
return mem;
|
||||
}, {})
|
||||
}, {});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
const Message = function (){
|
||||
const Message = function() {
|
||||
let fn = null;
|
||||
|
||||
return {
|
||||
send: function(text, type){
|
||||
if(['info', 'success', 'error'].indexOf(type) === -1){ type = 'info'; }
|
||||
if(!fn){ return window.setTimeout(() => this.send(text,type), 50); }
|
||||
send: function(text, type) {
|
||||
if (["info", "success", "error"].indexOf(type) === -1) {
|
||||
type = "info";
|
||||
}
|
||||
if (!fn) {
|
||||
return window.setTimeout(() => this.send(text, type), 50);
|
||||
}
|
||||
fn(text, type);
|
||||
return Promise.resolve();
|
||||
},
|
||||
subscribe: function(_fn){
|
||||
subscribe: function(_fn) {
|
||||
fn = _fn;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +1,39 @@
|
|||
import { gid, leftPad } from "./";
|
||||
|
||||
export function extractTodos(text){
|
||||
export function extractTodos(text) {
|
||||
const headlines = parse(text);
|
||||
let todos = [];
|
||||
for(let i=0; i < headlines.length; i++){
|
||||
let todo = formatTodo(headlines[i]);
|
||||
if(todo.status){
|
||||
const todos = [];
|
||||
for (let i=0; i < headlines.length; i++) {
|
||||
const todo = formatTodo(headlines[i]);
|
||||
if (todo.status) {
|
||||
todos.push(todo);
|
||||
}
|
||||
}
|
||||
return todos
|
||||
.sort((a,b) => {
|
||||
if(a.status === "NEXT" && b.status !== "NEXT") return -1;
|
||||
else if(b.status === "NEXT" && a.status !== "NEXT") return +1;
|
||||
else if(a.status === "TODO" && b.status !== "TODO") return -1;
|
||||
else if(b.status === "TODO" && a.status !== "TODO") return +1;
|
||||
else if(a.status === "DONE" && b.status !== "DONE" && b.todo_status === "done") return -1;
|
||||
else if(b.status === "DONE" && a.status !== "DONE" && a.todo_status === "done") return +1;
|
||||
else if(a.todo_status === "todo" && b.todo_status !== "todo") return -1;
|
||||
else if(a.todo_status === "done" && b.todo_status !== "done") return +1;
|
||||
else if(a.priority !== null && b.priority === null) return -1;
|
||||
else if(a.priority === null && b.priority !== null) return +1;
|
||||
else if(a.priority !== null && b.priority !== null && a.priority !== b.priority) return a.priority > b.priority? +1 : -1;
|
||||
else if(a.is_overdue === true && b.is_overdue === false) return -1;
|
||||
else if(a.is_overdue === false && b.is_overdue === true) return +1;
|
||||
else if(a.status === b.status) return a.id < b.id ? -1 : +1;
|
||||
.sort((a, b) => {
|
||||
if (a.status === "NEXT" && b.status !== "NEXT") return -1;
|
||||
else if (b.status === "NEXT" && a.status !== "NEXT") return +1;
|
||||
else if (a.status === "TODO" && b.status !== "TODO") return -1;
|
||||
else if (b.status === "TODO" && a.status !== "TODO") return +1;
|
||||
else if (a.status === "DONE" && b.status !== "DONE" &&
|
||||
b.todo_status === "done") return -1;
|
||||
else if (b.status === "DONE" && a.status !== "DONE" &&
|
||||
a.todo_status === "done") return +1;
|
||||
else if (a.todo_status === "todo" && b.todo_status !== "todo") return -1;
|
||||
else if (a.todo_status === "done" && b.todo_status !== "done") return +1;
|
||||
else if (a.priority !== null && b.priority === null) return -1;
|
||||
else if (a.priority === null && b.priority !== null) return +1;
|
||||
else if (a.priority !== null && b.priority !== null &&
|
||||
a.priority !== b.priority) return a.priority > b.priority? +1 : -1;
|
||||
else if (a.is_overdue === true && b.is_overdue === false) return -1;
|
||||
else if (a.is_overdue === false && b.is_overdue === true) return +1;
|
||||
else if (a.status === b.status) return a.id < b.id ? -1 : +1;
|
||||
});
|
||||
|
||||
function formatTodo(thing){
|
||||
const todo_status = ["TODO", "NEXT", "DOING", "WAITING", "PENDING"].indexOf(thing.header.todo_keyword) !== -1 ? 'todo' : 'done';
|
||||
function formatTodo(thing) {
|
||||
const todo_status = ["TODO", "NEXT", "DOING", "WAITING", "PENDING"].indexOf(
|
||||
thing.header.todo_keyword,
|
||||
) !== -1 ? "todo" : "done";
|
||||
return {
|
||||
key: thing.header.todo_keyword,
|
||||
id: thing.id,
|
||||
|
|
@ -41,31 +46,33 @@ export function extractTodos(text){
|
|||
scheduled: _find_scheduled(thing.timestamps),
|
||||
deadline: _find_deadline(thing.timestamps),
|
||||
tasks: thing.subtasks,
|
||||
tags: thing.header.tags
|
||||
tags: thing.header.tags,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function extractEvents(text){
|
||||
export function extractEvents(text) {
|
||||
const headlines = parse(text);
|
||||
let events = [];
|
||||
for(let i=0; i < headlines.length; i++){
|
||||
for (let i=0; i < headlines.length; i++) {
|
||||
events = events.concat(
|
||||
formatEvents(headlines[i])
|
||||
formatEvents(headlines[i]),
|
||||
);
|
||||
}
|
||||
return events.sort((a, b) => a.date - b.date);
|
||||
|
||||
function formatEvents(thing){
|
||||
let events = [];
|
||||
for(let i=0; i < thing.timestamps.length; i++){
|
||||
let timestamp = thing.timestamps[i];
|
||||
if(timestamp.active === false) continue;
|
||||
const todo_status = function(keyword){
|
||||
if(!keyword) return null;
|
||||
return ["TODO", "NEXT", "DOING", "WAITING", "PENDING"].indexOf(keyword) !== -1 ? 'todo' : 'done';
|
||||
function formatEvents(thing) {
|
||||
const events = [];
|
||||
for (let i=0; i < thing.timestamps.length; i++) {
|
||||
const timestamp = thing.timestamps[i];
|
||||
if (timestamp.active === false) continue;
|
||||
const todo_status = function(keyword) {
|
||||
if (!keyword) return null;
|
||||
return ["TODO", "NEXT", "DOING", "WAITING", "PENDING"].indexOf(
|
||||
keyword,
|
||||
) !== -1 ? "todo" : "done";
|
||||
}(thing.header.todo_keyword);
|
||||
let event = {
|
||||
const event = {
|
||||
id: thing.id,
|
||||
line: thing.header.line,
|
||||
title: thing.header.title,
|
||||
|
|
@ -76,9 +83,9 @@ export function extractEvents(text){
|
|||
is_overdue: _is_overdue(todo_status, thing.timestamps),
|
||||
priority: thing.header.priority,
|
||||
tasks: [],
|
||||
tags: thing.header.tags
|
||||
tags: thing.header.tags,
|
||||
};
|
||||
if(event.todo_status === 'done') continue;
|
||||
if (event.todo_status === "done") continue;
|
||||
|
||||
event.date = new Date(timestamp.timestamp);
|
||||
const today = new Date();
|
||||
|
|
@ -86,25 +93,25 @@ export function extractEvents(text){
|
|||
today.setMinutes(59);
|
||||
today.setSeconds(59);
|
||||
today.setMilliseconds(999);
|
||||
if(event.date < today){
|
||||
if (event.date < today) {
|
||||
event.date = today;
|
||||
}
|
||||
event.key = Intl.DateTimeFormat().format(event.date);
|
||||
event.date = event.date.toISOString();
|
||||
|
||||
if(timestamp.repeat){
|
||||
if(timestamp.repeat.interval === "m"){
|
||||
if (timestamp.repeat) {
|
||||
if (timestamp.repeat.interval === "m") {
|
||||
events.push(event);
|
||||
}else{
|
||||
if(timestamp.repeat.interval === "y"){
|
||||
} else {
|
||||
if (timestamp.repeat.interval === "y") {
|
||||
timestamp.repeat.n *= 365;
|
||||
}else if(timestamp.repeat.interval === "w"){
|
||||
} else if (timestamp.repeat.interval === "w") {
|
||||
timestamp.repeat.n *= 7;
|
||||
}
|
||||
const n_days = timestamp.repeat.n;
|
||||
let today = _normalise(new Date());
|
||||
for(let j=0;j<30;j++){
|
||||
if(((today - _normalise(new Date(timestamp.timestamp))) / 1000*60*60*24) % n_days === 0){
|
||||
const today = _normalise(new Date());
|
||||
for (let j=0; j<30; j++) {
|
||||
if (((today - _normalise(new Date(timestamp.timestamp))) / 1000*60*60*24) % n_days === 0) {/* eslint-disable-line max-len */
|
||||
event.date = today.getTime();
|
||||
event.key = Intl.DateTimeFormat().format(today);
|
||||
events.push(JSON.parse(JSON.stringify((event))));
|
||||
|
|
@ -112,7 +119,7 @@ export function extractEvents(text){
|
|||
today.setDate(today.getDate() + 1);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
events.push(event);
|
||||
}
|
||||
}
|
||||
|
|
@ -121,45 +128,49 @@ export function extractEvents(text){
|
|||
}
|
||||
|
||||
|
||||
export function parse(content){
|
||||
let todos = [], todo = reset(0), data, text, tags = [];
|
||||
export function parse(content) {
|
||||
const todos = [];
|
||||
let todo = reset(0);
|
||||
let data;
|
||||
let text;
|
||||
let tags = [];
|
||||
|
||||
const lines = content.split("\n");
|
||||
for(let i = 0; i<lines.length; i++){
|
||||
for (let i = 0; i<lines.length; i++) {
|
||||
text = lines[i];
|
||||
if(data = parse_header(text, i)){
|
||||
tags = tags.filter(e => e.level < data.level);
|
||||
if (data = parse_header(text, i)) {
|
||||
tags = tags.filter((e) => e.level < data.level);
|
||||
tags.push({ level: data.level, tags: data.tags });
|
||||
data.tags = tags.reduce((acc, el) => {
|
||||
return acc.concat(el.tags);
|
||||
}, []);
|
||||
|
||||
if(todo.header){
|
||||
if (todo.header) {
|
||||
todos.push(todo);
|
||||
todo = reset(i);
|
||||
}
|
||||
todo.header = data;
|
||||
}else if(data = parse_timestamp(text, i)){
|
||||
} else if (data = parse_timestamp(text, i)) {
|
||||
todo.timestamps = todo.timestamps.concat(data);
|
||||
}else if(data = parse_subtask(text, i)){
|
||||
} else if (data = parse_subtask(text, i)) {
|
||||
todo.subtasks.push(data);
|
||||
}
|
||||
if(i === lines.length - 1 && todo.header){
|
||||
if (i === lines.length - 1 && todo.header) {
|
||||
todos.push(todo);
|
||||
}
|
||||
}
|
||||
|
||||
return todos;
|
||||
|
||||
function reset(i){
|
||||
return {id: leftPad(i.toString(), 5) + gid(i), timestamps: [], subtasks: []};
|
||||
function reset(i) {
|
||||
return { id: leftPad(i.toString(), 5) + gid(i), timestamps: [], subtasks: [] };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function parse_header(text, line){
|
||||
const match = text.match(/^(\*+)\s(?:([A-Z]{4,})\s){0,1}(?:\[\#([A-C])\]\s){0,1}(.*?)(?:\s+\:((?:[a-z]+\:){1,})){0,1}$/);
|
||||
if(!match) return null;
|
||||
function parse_header(text, line) {
|
||||
const match = text.match(/^(\*+)\s(?:([A-Z]{4,})\s){0,1}(?:\[\#([A-C])\]\s){0,1}(.*?)(?:\s+\:((?:[a-z]+\:){1,})){0,1}$/); /* eslint-disable-line max-len */
|
||||
if (!match) return null;
|
||||
return {
|
||||
line: line,
|
||||
level: RegExp.$1.length,
|
||||
|
|
@ -170,30 +181,30 @@ function parse_header(text, line){
|
|||
.replace(/:/g, " ")
|
||||
.trim()
|
||||
.split(" ")
|
||||
.filter((e) => e)
|
||||
.filter((e) => e),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function parse_subtask(text, line){
|
||||
function parse_subtask(text, line) {
|
||||
const match = text.match(/(?:-|\d+[\.\)])\s\[([X\s-])\]\s(.*)/);
|
||||
if(!match) return null;
|
||||
if (!match) return null;
|
||||
return {
|
||||
line: line,
|
||||
status: function(state){
|
||||
if(state === "X") return "DONE";
|
||||
else if(state === " ") return "TODO";
|
||||
status: function(state) {
|
||||
if (state === "X") return "DONE";
|
||||
else if (state === " ") return "TODO";
|
||||
return null;
|
||||
}(match[1]),
|
||||
title: match[2] || "Empty task",
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function parse_timestamp(text, line, _memory){
|
||||
const reg = /(?:([A-Z]+)\:\s){0,1}([<\[])(\d{4}-\d{2}-\d{2})[^>](?:[A-Z][a-z]{1,2})(?:\s([0-9]{2}\:[0-9]{2})){0,1}(?:\-([0-9]{2}\:[0-9]{2})){0,1}(?:\s(\+{1,2}[0-9]+[dwmy])){0,1}[\>\]](?:--[<\[](\d{4}-\d{2}-\d{2})\s[A-Z][a-z]{1,2}\s(\d{2}:\d{2}){0,1}[>\]]){0,1}/;
|
||||
function parse_timestamp(text, line, _memory) {
|
||||
const reg = /(?:([A-Z]+)\:\s){0,1}([<\[])(\d{4}-\d{2}-\d{2})[^>](?:[A-Z][a-z]{1,2})(?:\s([0-9]{2}\:[0-9]{2})){0,1}(?:\-([0-9]{2}\:[0-9]{2})){0,1}(?:\s(\+{1,2}[0-9]+[dwmy])){0,1}[\>\]](?:--[<\[](\d{4}-\d{2}-\d{2})\s[A-Z][a-z]{1,2}\s(\d{2}:\d{2}){0,1}[>\]]){0,1}/; /* eslint-disable-line max-len */
|
||||
const match = text.match(reg);
|
||||
if(!match) return _memory || null;
|
||||
if (!match) return _memory || null;
|
||||
|
||||
// https://orgmode.org/manual/Timestamps.html
|
||||
const timestamp = {
|
||||
|
|
@ -201,53 +212,46 @@ function parse_timestamp(text, line, _memory){
|
|||
keyword: match[1],
|
||||
active: match[2] === "<" ? true : false,
|
||||
timestamp: new Date(match[3] + (match[4] ? " "+match[4] : "")).toISOString(),
|
||||
range: function(start_date, start_time = "", start_time_end, end_date = "", end_time = ""){
|
||||
if(start_time_end && !end_date){
|
||||
return new Date(start_date+" "+start_time_end) - new Date(start_date+" "+start_time);
|
||||
range: function(start_date, start_time = "", start_time_end, end_date = "", end_time = "") {
|
||||
if (start_time_end && !end_date) {
|
||||
return new Date(start_date+" "+start_time_end) -
|
||||
new Date(start_date+" "+start_time);
|
||||
}
|
||||
if(end_date){
|
||||
if (end_date) {
|
||||
return new Date(end_date+" "+end_time) - new Date(start_date+" "+start_time);
|
||||
}
|
||||
return null;
|
||||
}(match[3], match[4], match[5], match[7], match[8]),
|
||||
repeat: function(keyword){
|
||||
if(!keyword) return null;
|
||||
repeat: function(keyword) {
|
||||
if (!keyword) return null;
|
||||
return {
|
||||
n: parseInt(keyword.replace(/^.*([0-9]+).*$/, "$1")),
|
||||
interval: keyword.replace(/^.*([dwmy])$/, "$1")
|
||||
interval: keyword.replace(/^.*([dwmy])$/, "$1"),
|
||||
};
|
||||
}(match[6])
|
||||
}(match[6]),
|
||||
};
|
||||
if(!_memory) _memory = [];
|
||||
if (!_memory) _memory = [];
|
||||
_memory.push(timestamp);
|
||||
return parse_timestamp(text.replace(reg, ""), line, _memory);
|
||||
}
|
||||
|
||||
function _find_deadline(timestamps){
|
||||
function _find_deadline(timestamps) {
|
||||
return timestamps.filter((e) => e.keyword === "DEADLINE")[0] || null;
|
||||
}
|
||||
function _find_scheduled(timestamps){
|
||||
function _find_scheduled(timestamps) {
|
||||
return timestamps.filter((e) => e.keyword === "SCHEDULED")[0] || null;
|
||||
}
|
||||
|
||||
function _is_overdue(status, timestamp){
|
||||
if(status !== "todo") return false;
|
||||
function _is_overdue(status, timestamp) {
|
||||
if (status !== "todo") return false;
|
||||
return timestamp.filter((timeObj) => {
|
||||
if(_normalise(new Date()) < _normalise(new Date(timeObj.timestamp))) return false;
|
||||
if(timeObj.keyword === "DEADLINE" || timeObj.keyword === "SCHEDULED") return true;
|
||||
if (_normalise(new Date()) < _normalise(new Date(timeObj.timestamp))) return false;
|
||||
if (timeObj.keyword === "DEADLINE" || timeObj.keyword === "SCHEDULED") return true;
|
||||
return false;
|
||||
}).length > 0 ? true : false;
|
||||
}
|
||||
|
||||
function _date_label(date){
|
||||
date.setHours(0);
|
||||
date.setMinutes(0);
|
||||
date.setSeconds(0);
|
||||
date.setMilliseconds(0);
|
||||
return window.Intl.DateTimeFormat().format(date);
|
||||
}
|
||||
|
||||
function _normalise(date){
|
||||
function _normalise(date) {
|
||||
date.setHours(0);
|
||||
date.setMinutes(0);
|
||||
date.setSeconds(0);
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
import Path from 'path';
|
||||
import Path from "path";
|
||||
|
||||
export function pathBuilder(path, filename, type = 'file'){
|
||||
let tmp = Path.resolve(path, filename)
|
||||
if(type === 'file'){
|
||||
export function pathBuilder(path, filename, type = "file") {
|
||||
const tmp = Path.resolve(path, filename);
|
||||
if (type === "file") {
|
||||
return tmp;
|
||||
}else{
|
||||
return tmp + '/';
|
||||
} else {
|
||||
return tmp + "/";
|
||||
}
|
||||
}
|
||||
|
||||
export function basename(path){
|
||||
export function basename(path) {
|
||||
return Path.basename(path);
|
||||
}
|
||||
|
||||
export function dirname(path){
|
||||
export function dirname(path) {
|
||||
const dir = Path.dirname(path);
|
||||
if(dir === '/') return dir;
|
||||
if (dir === "/") return dir;
|
||||
return dir + "/";
|
||||
}
|
||||
|
||||
|
|
@ -23,35 +23,35 @@ export function filetype(path) {
|
|||
return path.slice(-1) === "/" ? "directory" : "file";
|
||||
}
|
||||
|
||||
export function absoluteToRelative(from, to){
|
||||
export function absoluteToRelative(from, to) {
|
||||
// remove any trace of file that would be interpreted by the path lib as a folder
|
||||
from = from.replace(/\/[^\/]+$/, "/");
|
||||
let r = Path.relative(from, to);
|
||||
if(r.substring(0,3) !== "../"){
|
||||
r = "./"+r
|
||||
if (r.substring(0, 3) !== "../") {
|
||||
r = "./" + r;
|
||||
}
|
||||
if(/\/$/.test(to) === true && r !== "./"){
|
||||
r += "/"
|
||||
if (/\/$/.test(to) === true && r !== "./") {
|
||||
r += "/";
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
export function currentShare(){
|
||||
export function currentShare() {
|
||||
return findParams("share");
|
||||
}
|
||||
|
||||
export function findParams(p){
|
||||
return new window.URL(location.href).searchParams.get(p) || ""
|
||||
export function findParams(p) {
|
||||
return new window.URL(location.href).searchParams.get(p) || "";
|
||||
}
|
||||
|
||||
export function appendShareToUrl(link) {
|
||||
let url = new window.URL(location.href);
|
||||
let share = url.searchParams.get("share");
|
||||
const share = url.searchParams.get("share");
|
||||
|
||||
if(share){
|
||||
url = new window.URL(location.origin + link)
|
||||
url.searchParams.set("share", share)
|
||||
return url.pathname + url.search
|
||||
if (share) {
|
||||
url = new window.URL(location.origin + link);
|
||||
url.searchParams.set("share", share);
|
||||
return url.pathname + url.search;
|
||||
}
|
||||
return link;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,46 +1,56 @@
|
|||
const Alert = function (){
|
||||
const Alert = function() {
|
||||
let fn = null;
|
||||
|
||||
return {
|
||||
now: function(Component, okCallback){
|
||||
if(!fn){ return window.setTimeout(() => this.now(Component, okCallback), 50); }
|
||||
now: function(Component, okCallback) {
|
||||
if (!fn) {
|
||||
return window.setTimeout(() => this.now(Component, okCallback), 50);
|
||||
}
|
||||
fn(Component, okCallback);
|
||||
},
|
||||
subscribe: function(_fn){
|
||||
subscribe: function(_fn) {
|
||||
fn = _fn;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
export const alert = new Alert();
|
||||
|
||||
|
||||
const Prompt = function (){
|
||||
const Prompt = function() {
|
||||
let fn = null;
|
||||
|
||||
return {
|
||||
now: function(text, okCallback, cancelCallback, type){
|
||||
if(!fn){ return window.setTimeout(() => this.now(text, okCallback, cancelCallback, type), 50); }
|
||||
now: function(text, okCallback, cancelCallback, type) {
|
||||
if (!fn) {
|
||||
return window.setTimeout(() => {
|
||||
this.now(text, okCallback, cancelCallback, type);
|
||||
}, 50);
|
||||
}
|
||||
fn(text, okCallback, cancelCallback, type);
|
||||
},
|
||||
subscribe: function(_fn){
|
||||
subscribe: function(_fn) {
|
||||
fn = _fn;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
export const prompt = new Prompt();
|
||||
|
||||
|
||||
const Confirm = function (){
|
||||
const Confirm = function() {
|
||||
let fn = null;
|
||||
|
||||
return {
|
||||
now: function(comp, okCallback, cancelCallback){
|
||||
if(!fn){ return window.setTimeout(() => this.now(comp, okCallback, cancelCallback), 50); }
|
||||
now: function(comp, okCallback, cancelCallback) {
|
||||
if (!fn) {
|
||||
return window.setTimeout(() => {
|
||||
this.now(comp, okCallback, cancelCallback);
|
||||
}, 50);
|
||||
}
|
||||
fn(comp, okCallback, cancelCallback);
|
||||
},
|
||||
subscribe: function(_fn){
|
||||
subscribe: function(_fn) {
|
||||
fn = _fn;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
export const confirm = new Confirm();
|
||||
|
|
|
|||
|
|
@ -1,20 +1,22 @@
|
|||
export function gid(prefix){
|
||||
let id = prefix !== undefined ? prefix : '';
|
||||
export function gid(prefix) {
|
||||
let id = prefix !== undefined ? prefix : "";
|
||||
id += new Date().getTime().toString(32);
|
||||
id += parseInt(Math.random()*Math.pow(10,16)).toString(32);
|
||||
id += parseInt(Math.random()*Math.pow(10, 16)).toString(32);
|
||||
return id;
|
||||
}
|
||||
|
||||
const alphabet = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p",
|
||||
"q","r","s","t","u","v","x","y","z","A","B","C","D","E","F","G",
|
||||
"H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W",
|
||||
"X","Y","Z","0","1","2","3","4","5","6","7","8","9"];
|
||||
const alphabet = [
|
||||
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p",
|
||||
"q", "r", "s", "t", "u", "v", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G",
|
||||
"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
|
||||
"X", "Y", "Z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
||||
];
|
||||
const alphabet_size = alphabet.length;
|
||||
|
||||
export function randomString(size = 16){
|
||||
export function randomString(size = 16) {
|
||||
let str = "";
|
||||
for(let i=0; i<size; i++){
|
||||
str += alphabet[Math.floor(Math.random()*alphabet_size)]
|
||||
for (let i=0; i<size; i++) {
|
||||
str += alphabet[Math.floor(Math.random()*alphabet_size)];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
let settings = JSON.parse(window.localStorage.getItem("settings")) || {};
|
||||
const settings = JSON.parse(window.localStorage.getItem("settings")) || {};
|
||||
|
||||
export function settings_get(key){
|
||||
if(settings[key] === undefined){
|
||||
export function settings_get(key) {
|
||||
if (settings[key] === undefined) {
|
||||
return null;
|
||||
}
|
||||
return settings[key];
|
||||
}
|
||||
|
||||
export function settings_put(key, value){
|
||||
export function settings_put(key, value) {
|
||||
settings[key] = value;
|
||||
save(settings);
|
||||
}
|
||||
|
||||
function save(d){
|
||||
function save(d) {
|
||||
setInterval(() => {
|
||||
window.localStorage.setItem("settings", JSON.stringify(d));
|
||||
}, 500);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
const Upload = function () {
|
||||
const Upload = function() {
|
||||
let fn = null;
|
||||
|
||||
return {
|
||||
add: function (path, files) {
|
||||
if (!fn) { return window.setTimeout(() => this.add(path, files), 50); }
|
||||
add: function(path, files) {
|
||||
if (!fn) {
|
||||
return window.setTimeout(() => this.add(path, files), 50);
|
||||
}
|
||||
fn(path, files);
|
||||
return Promise.resolve();
|
||||
},
|
||||
subscribe: function (_fn) {
|
||||
subscribe: function(_fn) {
|
||||
fn = _fn;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,46 +1,49 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Router from './router';
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import Router from "./router";
|
||||
|
||||
import { Config, Log } from "./model/";
|
||||
import { http_get } from "./helpers/ajax";
|
||||
import load from "little-loader";
|
||||
|
||||
import './assets/css/reset.scss';
|
||||
import "./assets/css/reset.scss";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
const className = 'ontouchstart' in window ? 'touch-yes' : 'touch-no';
|
||||
const className = "ontouchstart" in window ? "touch-yes" : "touch-no";
|
||||
document.body.classList.add(className);
|
||||
|
||||
const $loader = document.querySelector("#n-lder");
|
||||
|
||||
function render(){
|
||||
ReactDOM.render(<Router/>, document.querySelector("div[role='main']"));
|
||||
function render() {
|
||||
ReactDOM.render(
|
||||
<React.StrictMode><Router/></React.StrictMode>,
|
||||
document.querySelector("div[role='main']"),
|
||||
);
|
||||
return Promise.resolve();
|
||||
};
|
||||
function waitFor(n){
|
||||
}
|
||||
function waitFor(n) {
|
||||
return new Promise((done) => {
|
||||
window.setTimeout(() => {
|
||||
window.requestAnimationFrame(() => done());
|
||||
}, n);
|
||||
});
|
||||
}
|
||||
function removeLoaderWithAnimation(){
|
||||
if(!$loader) return Promise.resolve();
|
||||
function removeLoaderWithAnimation() {
|
||||
if (!$loader) return Promise.resolve();
|
||||
$loader.classList.add("done");
|
||||
return new Promise((done) => {
|
||||
window.setTimeout(() => requestAnimationFrame(done), 500);
|
||||
});
|
||||
}
|
||||
function removeLoader(){
|
||||
if($loader) $loader.remove();
|
||||
function removeLoader() {
|
||||
if ($loader) $loader.remove();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
Promise.all([Config.refresh(), setup_xdg_open(), translation()]).then(() => {
|
||||
const timeSinceBoot = new Date() - window.initTime;
|
||||
if(window.CONFIG.name) document.title = window.CONFIG.name;
|
||||
if(timeSinceBoot >= 1500){
|
||||
if (window.CONFIG.name) document.title = window.CONFIG.name;
|
||||
if (timeSinceBoot >= 1500) {
|
||||
const timeoutToAvoidFlickering = timeSinceBoot > 2500 ? 0 : 500;
|
||||
return waitFor(timeoutToAvoidFlickering)
|
||||
.then(removeLoaderWithAnimation)
|
||||
|
|
@ -49,20 +52,20 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
return removeLoader().then(render);
|
||||
}).catch((e) => {
|
||||
const msg = navigator.onLine === false ? "OFFLINE" : "CAN'T LOAD FILESTASH";
|
||||
Log.report(msg, location.href);
|
||||
Log.report(msg + " - " + (e && e.message), location.href);
|
||||
return removeLoaderWithAnimation().then(() => {
|
||||
$error(msg);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
window.onerror = function (msg, url, lineNo, colNo, error) {
|
||||
window.onerror = function(msg, url, lineNo, colNo, error) {
|
||||
Log.report(msg, url, lineNo, colNo, error);
|
||||
$error(msg);
|
||||
}
|
||||
};
|
||||
|
||||
function $error(msg){
|
||||
let $code = document.createElement("code");
|
||||
function $error(msg) {
|
||||
const $code = document.createElement("code");
|
||||
$code.style.textAlign = "center";
|
||||
$code.style.display = "block";
|
||||
$code.style.margin = "50px 0";
|
||||
|
|
@ -74,12 +77,14 @@ function $error(msg){
|
|||
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", function() {
|
||||
if(navigator.userAgent.indexOf("Mozilla/") !== -1 && navigator.userAgent.indexOf("Firefox/") !== -1 && navigator.userAgent.indexOf("Gecko/") !== -1){
|
||||
if (navigator.userAgent.indexOf("Mozilla/") !== -1 &&
|
||||
navigator.userAgent.indexOf("Firefox/") !== -1 &&
|
||||
navigator.userAgent.indexOf("Gecko/") !== -1) {
|
||||
// Firefox was acting weird with service worker so we disabled it
|
||||
// see: https://github.com/mickael-kerjean/filestash/issues/255
|
||||
return
|
||||
return;
|
||||
}
|
||||
navigator.serviceWorker.register("/sw_cache.js").catch(function(err){
|
||||
navigator.serviceWorker.register("/sw_cache.js").catch(function(err) {
|
||||
console.error("ServiceWorker registration failed:", err);
|
||||
});
|
||||
});
|
||||
|
|
@ -87,25 +92,25 @@ if ("serviceWorker" in navigator) {
|
|||
|
||||
// server generated frontend overrides
|
||||
window.overrides = {};
|
||||
function setup_xdg_open(){
|
||||
function setup_xdg_open() {
|
||||
return new Promise((done, err) => {
|
||||
load("/overrides/xdg-open.js", function(error) {
|
||||
if(error) return err(error);
|
||||
done()
|
||||
if (error) return err(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function translation(){
|
||||
function translation() {
|
||||
const userLanguage = navigator.language.split("-")[0];
|
||||
const selectedLanguage = [
|
||||
"az", "be", "bg", "ca", "cs", "da", "de", "el", "es", "et",
|
||||
"eu", "fi", "fr", "gl", "hr", "hu", "id", "is", "it", "ja",
|
||||
"ka", "ko", "lt", "lv", "mn", "nb", "nl", "pl", "pt", "ro",
|
||||
"ru", "sk", "sl", "sr", "sv", "th", "tr", "uk", "vi", "zh"
|
||||
"ru", "sk", "sl", "sr", "sv", "th", "tr", "uk", "vi", "zh",
|
||||
].indexOf(userLanguage) === -1 ? "en" : userLanguage;
|
||||
|
||||
if(selectedLanguage === "en"){
|
||||
if (selectedLanguage === "en") {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return http_get("/assets/locales/"+selectedLanguage+".json").then((d) => {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
export function t(str = "", replacementString, requestedKey){
|
||||
const calculatedKey = str.toUpperCase().replace(/ /g, "_").replace(/[^a-zA-Z0-9\-\_\*\{\}\?]/g, "").replace(/\_+$/, "");
|
||||
const value = requestedKey === undefined ? window.LNG && window.LNG[calculatedKey] : window.LNG && window.LNG[requestedKey];
|
||||
export function t(str = "", replacementString, requestedKey) {
|
||||
const calculatedKey = str.toUpperCase()
|
||||
.replace(/ /g, "_")
|
||||
.replace(/[^a-zA-Z0-9\-\_\*\{\}\?]/g, "")
|
||||
.replace(/\_+$/, "");
|
||||
const value = requestedKey === undefined ?
|
||||
window.LNG && window.LNG[calculatedKey] :
|
||||
window.LNG && window.LNG[requestedKey];
|
||||
return reformat(
|
||||
value || str || "",
|
||||
str
|
||||
str,
|
||||
).replace("{{VALUE}}", replacementString);
|
||||
}
|
||||
|
||||
function reformat(translated, initial){
|
||||
if(initial[0] && initial[0].toLowerCase() === initial[0]){
|
||||
function reformat(translated, initial) {
|
||||
if (initial[0] && initial[0].toLowerCase() === initial[0]) {
|
||||
return translated || "";
|
||||
}
|
||||
return (translated[0] && translated[0].toUpperCase() + translated.substring(1)) || "";
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { http_post, http_get } from '../helpers';
|
||||
import { http_post, http_get } from "../helpers";
|
||||
|
||||
export const Admin = {
|
||||
login: function(password = ""){
|
||||
return http_post("/admin/api/session", {password: password});
|
||||
login: function(password = "") {
|
||||
return http_post("/admin/api/session", { password: password });
|
||||
},
|
||||
isAdmin: function(){
|
||||
isAdmin: function() {
|
||||
return http_get("/admin/api/session").then((res) => res.result);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,39 +1,40 @@
|
|||
import { http_get, http_post, http_delete, debounce } from '../helpers/';
|
||||
import { http_get, http_post, debounce } from "../helpers/";
|
||||
|
||||
class ConfigModel {
|
||||
constructor(){}
|
||||
constructor() {
|
||||
}
|
||||
|
||||
all(){
|
||||
all() {
|
||||
return http_get("/admin/api/config").then((d) => d.result);
|
||||
}
|
||||
|
||||
save(config, debounced = true, fn_ok, fn_err){
|
||||
let url = "/admin/api/config";
|
||||
save(config, debounced = true, fn_ok, fn_err) {
|
||||
const url = "/admin/api/config";
|
||||
|
||||
if(debounced){
|
||||
if(!this.debounced_post){
|
||||
if (debounced) {
|
||||
if (!this.debounced_post) {
|
||||
this.debounced_post = debounce((url, config) => {
|
||||
return http_post(url, config).then(this.refresh).then((a) => {
|
||||
if(typeof fn_ok === "function") return fn_ok();
|
||||
return Promise.resolve(a)
|
||||
if (typeof fn_ok === "function") return fn_ok();
|
||||
return Promise.resolve(a);
|
||||
}).catch((err) => {
|
||||
if(typeof fn_err === "function") return fn_err();
|
||||
return Promise.reject(err)
|
||||
if (typeof fn_err === "function") return fn_err();
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
return this.debounced_post(url, config)
|
||||
return this.debounced_post(url, config);
|
||||
}
|
||||
return http_post(url, config).then(this.refresh).then((a) => {
|
||||
if(typeof fn_ok === "function") return fn_ok();
|
||||
return Promise.resolve(a)
|
||||
if (typeof fn_ok === "function") return fn_ok();
|
||||
return Promise.resolve(a);
|
||||
}).catch((err) => {
|
||||
if(typeof fn_err === "function") return fn_err();
|
||||
return Promise.reject(err)
|
||||
if (typeof fn_err === "function") return fn_err();
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
refresh(){
|
||||
refresh() {
|
||||
return http_get("/api/config").then((config) => {
|
||||
window.CONFIG = config.result;
|
||||
});
|
||||
|
|
@ -41,9 +42,10 @@ class ConfigModel {
|
|||
}
|
||||
|
||||
class BackendModel {
|
||||
constructor(){}
|
||||
constructor() {
|
||||
}
|
||||
|
||||
all(){
|
||||
all() {
|
||||
return http_get("/api/backend").then((r) => r.result);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
"use strict";
|
||||
|
||||
import { http_get, http_post, http_options, prepare, basename, dirname, pathBuilder } from '../helpers/';
|
||||
import { filetype, currentShare, appendShareToUrl } from '../helpers/';
|
||||
import {
|
||||
http_get, http_post, http_options, prepare, basename, dirname, pathBuilder,
|
||||
currentShare, appendShareToUrl,
|
||||
} from "../helpers/";
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { cache } from '../helpers/';
|
||||
import { Observable } from "rxjs/Observable";
|
||||
import { cache } from "../helpers/";
|
||||
|
||||
class FileSystem{
|
||||
constructor(){
|
||||
class FileSystem {
|
||||
constructor() {
|
||||
this.obs = null;
|
||||
this.current_path = null;
|
||||
}
|
||||
|
||||
ls(path, show_hidden = false){
|
||||
ls(path, show_hidden = false) {
|
||||
this.current_path = path;
|
||||
this.obs && this.obs.complete();
|
||||
return Observable.create((obs) => {
|
||||
|
|
@ -20,19 +22,20 @@ class FileSystem{
|
|||
let keep_pulling_from_http = false;
|
||||
this._ls_from_cache(path, true).then((cache) => {
|
||||
const fetch_from_http = (_path) => {
|
||||
return this._ls_from_http(_path, show_hidden).then(() => new Promise((done, err) => {
|
||||
window.setTimeout(() => done(), 2000);
|
||||
})).then(() => {
|
||||
if(keep_pulling_from_http === false) return Promise.resolve();
|
||||
return fetch_from_http(_path);
|
||||
}).catch((err) => {
|
||||
if(cache === null){
|
||||
this.obs && this.obs.error({message: "Unknown Path"});
|
||||
}
|
||||
});
|
||||
return this._ls_from_http(_path, show_hidden)
|
||||
.then(() => new Promise((done, err) => {
|
||||
window.setTimeout(() => done(), 2000);
|
||||
})).then(() => {
|
||||
if (keep_pulling_from_http === false) return Promise.resolve();
|
||||
return fetch_from_http(_path);
|
||||
}).catch((err) => {
|
||||
if (cache === null) {
|
||||
this.obs && this.obs.error({ message: "Unknown Path" });
|
||||
}
|
||||
});
|
||||
};
|
||||
fetch_from_http(path);
|
||||
}).catch((err) => this.obs.error({message: err && err.message}));
|
||||
}).catch((err) => this.obs.error({ message: err && err.message }));
|
||||
|
||||
return () => {
|
||||
keep_pulling_from_http = false;
|
||||
|
|
@ -40,33 +43,33 @@ class FileSystem{
|
|||
});
|
||||
}
|
||||
|
||||
_ls_from_http(path, show_hidden){
|
||||
const url = appendShareToUrl("/api/files/ls?path="+prepare(path));
|
||||
_ls_from_http(path, show_hidden) {
|
||||
const url = appendShareToUrl("/api/files/ls?path=" + prepare(path));
|
||||
return http_get(url).then((response) => {
|
||||
response = fileMiddleware(response, path, show_hidden);
|
||||
|
||||
return cache.upsert(cache.FILE_PATH, [currentShare(), path], (_files) => {
|
||||
let store = Object.assign({
|
||||
const store = Object.assign({
|
||||
share: currentShare(),
|
||||
status: "ok",
|
||||
path: path,
|
||||
results: null,
|
||||
access_count: 0,
|
||||
metadata: null
|
||||
metadata: null,
|
||||
}, _files);
|
||||
store.metadata = response.metadata;
|
||||
store.results = response.results;
|
||||
|
||||
if(_files && _files.results){
|
||||
if (_files && _files.results) {
|
||||
store.access_count = _files.access_count;
|
||||
// find out which entry we want to keep from the cache
|
||||
let _files_virtual_to_keep = _files.results.filter((file) => {
|
||||
return file.icon === 'loading';
|
||||
const _files_virtual_to_keep = _files.results.filter((file) => {
|
||||
return file.icon === "loading";
|
||||
});
|
||||
// update file results when something is going on
|
||||
for(let i=0; i<_files_virtual_to_keep.length; i++){
|
||||
for(let j=0; j<store.results.length; j++){
|
||||
if(store.results[j].name === _files_virtual_to_keep[i].name){
|
||||
for (let i=0; i<_files_virtual_to_keep.length; i++) {
|
||||
for (let j=0; j<store.results.length; j++) {
|
||||
if (store.results[j].name === _files_virtual_to_keep[i].name) {
|
||||
store.results[j] = Object.assign({}, _files_virtual_to_keep[i]);
|
||||
_files_virtual_to_keep.splice(i, 1);
|
||||
i -= 1;
|
||||
|
|
@ -81,41 +84,41 @@ class FileSystem{
|
|||
store.last_access = new Date();
|
||||
return store;
|
||||
}).catch(() => Promise.resolve(response)).then((data) => {
|
||||
if(this.current_path === path){
|
||||
if (this.current_path === path) {
|
||||
this.obs && this.obs.next(data);
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
}).catch((_err) => {
|
||||
if(_err.code === "Unauthorized"){
|
||||
location = "/login?next="+location.pathname;
|
||||
if (_err.code === "Unauthorized") {
|
||||
location = "/login?next=" + location.pathname;
|
||||
}
|
||||
this.obs.next(_err);
|
||||
return Promise.reject(_err);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
_ls_from_cache(path, _record_access = false){
|
||||
_ls_from_cache(path, _record_access = false) {
|
||||
return cache.get(cache.FILE_PATH, [currentShare(), path]).then((response) => {
|
||||
if(!response || !response.results) return null;
|
||||
if(this.current_path === path){
|
||||
if (!response || !response.results) return null;
|
||||
if (this.current_path === path) {
|
||||
this.obs && this.obs.next({
|
||||
status: 'ok',
|
||||
status: "ok",
|
||||
results: response.results,
|
||||
metadata: response.metadata
|
||||
metadata: response.metadata,
|
||||
});
|
||||
}
|
||||
return response;
|
||||
}).then((e) => {
|
||||
requestAnimationFrame(() => {
|
||||
if(_record_access === true){
|
||||
if (_record_access === true) {
|
||||
cache.upsert(cache.FILE_PATH, [currentShare(), path], (response) => {
|
||||
if(!response || !response.results) return null;
|
||||
if(this.current_path === path){
|
||||
if (!response || !response.results) return null;
|
||||
if (this.current_path === path) {
|
||||
this.obs && this.obs.next({
|
||||
status: 'ok',
|
||||
status: "ok",
|
||||
results: response.results,
|
||||
metadata: response.metadata
|
||||
metadata: response.metadata,
|
||||
});
|
||||
}
|
||||
response.last_access = new Date();
|
||||
|
|
@ -128,40 +131,43 @@ class FileSystem{
|
|||
});
|
||||
}
|
||||
|
||||
rm(path){
|
||||
const url = appendShareToUrl('/api/files/rm?path='+prepare(path));
|
||||
return this._replace(path, 'loading')
|
||||
.then((res) => this.current_path === dirname(path) ? this._ls_from_cache(dirname(path)) : Promise.resolve(res))
|
||||
rm(path) {
|
||||
const url = appendShareToUrl("/api/files/rm?path=" + prepare(path));
|
||||
return this._replace(path, "loading")
|
||||
.then((res) => this.current_path === dirname(path) ?
|
||||
this._ls_from_cache(dirname(path)) : Promise.resolve(res))
|
||||
.then(() => http_get(url))
|
||||
.then((res) => {
|
||||
return cache.remove(cache.FILE_CONTENT, [currentShare(), path])
|
||||
.then(cache.remove(cache.FILE_CONTENT, [currentShare(), path], false))
|
||||
.then(cache.remove(cache.FILE_PATH, [currentShare(), dirname(path)], false))
|
||||
.then(this._remove(path, 'loading'))
|
||||
.then((res) => this.current_path === dirname(path) ? this._ls_from_cache(dirname(path)) : Promise.resolve(res))
|
||||
.then(this._remove(path, "loading"))
|
||||
.then((res) => this.current_path === dirname(path) ?
|
||||
this._ls_from_cache(dirname(path)) : Promise.resolve(res));
|
||||
})
|
||||
.catch((err) => {
|
||||
return this._replace(path, 'error', 'loading')
|
||||
.then((res) => this.current_path === dirname(path) ? this._ls_from_cache(dirname(path)) : Promise.resolve(res))
|
||||
return this._replace(path, "error", "loading")
|
||||
.then((res) => this.current_path === dirname(path) ?
|
||||
this._ls_from_cache(dirname(path)) : Promise.resolve(res))
|
||||
.then(() => Promise.reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
cat(path){
|
||||
const url = appendShareToUrl('/api/files/cat?path='+prepare(path));
|
||||
return http_get(url, 'raw')
|
||||
cat(path) {
|
||||
const url = appendShareToUrl("/api/files/cat?path=" + prepare(path));
|
||||
return http_get(url, "raw")
|
||||
.then((res) => {
|
||||
if(this.is_binary(res) === true){
|
||||
return Promise.reject({code: 'BINARY_FILE'});
|
||||
if (this.is_binary(res) === true) {
|
||||
return Promise.reject({ code: "BINARY_FILE" });
|
||||
}
|
||||
return cache.upsert(cache.FILE_CONTENT, [currentShare(), path], (response) => {
|
||||
let file = response? response : {
|
||||
const file = response ? response : {
|
||||
share: currentShare(),
|
||||
path: path,
|
||||
last_update: null,
|
||||
last_access: null,
|
||||
access_count: -1,
|
||||
result: null
|
||||
result: null,
|
||||
};
|
||||
file.result = res;
|
||||
file.access_count += 1;
|
||||
|
|
@ -171,63 +177,66 @@ class FileSystem{
|
|||
});
|
||||
}
|
||||
|
||||
zip(paths){
|
||||
const url = appendShareToUrl('/api/files/zip?'+paths.map((p)=> "path=" +prepare(p)).join("&"))
|
||||
zip(paths) {
|
||||
const url = appendShareToUrl(
|
||||
"/api/files/zip?" + paths.map((p) => "path=" + prepare(p)).join("&"),
|
||||
);
|
||||
window.open(url);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
options(path){
|
||||
const url = appendShareToUrl('/api/files/cat?path='+prepare(path));
|
||||
options(path) {
|
||||
const url = appendShareToUrl("/api/files/cat?path=" + prepare(path));
|
||||
return http_options(url);
|
||||
}
|
||||
|
||||
url(path){
|
||||
const url = appendShareToUrl('/api/files/cat?path='+prepare(path));
|
||||
url(path) {
|
||||
const url = appendShareToUrl("/api/files/cat?path=" + prepare(path));
|
||||
return Promise.resolve(url);
|
||||
}
|
||||
|
||||
save(path, file){
|
||||
const url = appendShareToUrl('/api/files/cat?path='+prepare(path));
|
||||
return this._replace(path, 'loading')
|
||||
.then(() => http_post(url, file, 'blob'))
|
||||
save(path, file) {
|
||||
const url = appendShareToUrl("/api/files/cat?path=" + prepare(path));
|
||||
return this._replace(path, "loading")
|
||||
.then(() => http_post(url, file, "blob"))
|
||||
.then(() => {
|
||||
return this._saveFileToCache(path, file)
|
||||
.then(() => this._replace(path, null, 'loading'))
|
||||
.then(() => this._replace(path, null, "loading"))
|
||||
.then(() => this._refresh(path));
|
||||
})
|
||||
.catch((err) => {
|
||||
return this._replace(path, 'error', 'loading')
|
||||
return this._replace(path, "error", "loading")
|
||||
.then(() => this._refresh(path))
|
||||
.then(() => Promise.reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
mkdir(path, step){
|
||||
const url = appendShareToUrl('/api/files/mkdir?path='+prepare(path)),
|
||||
origin_path = pathBuilder(this.current_path, basename(path), 'directoy'),
|
||||
destination_path = path;
|
||||
mkdir(path, step) {
|
||||
const url = appendShareToUrl("/api/files/mkdir?path=" + prepare(path));
|
||||
const origin_path = pathBuilder(this.current_path, basename(path), "directoy");
|
||||
const destination_path = path;
|
||||
|
||||
const action_prepare = (part_of_a_batch_operation = false) => {
|
||||
if(part_of_a_batch_operation === true){
|
||||
return this._add(destination_path, 'loading')
|
||||
if (part_of_a_batch_operation === true) {
|
||||
return this._add(destination_path, "loading")
|
||||
.then(() => this._refresh(destination_path));
|
||||
}
|
||||
|
||||
return this._add(destination_path, 'loading')
|
||||
.then(() => origin_path !== destination_path ? this._add(origin_path, 'loading') : Promise.resolve())
|
||||
return this._add(destination_path, "loading")
|
||||
.then(() => origin_path !== destination_path ?
|
||||
this._add(origin_path, "loading") : Promise.resolve())
|
||||
.then(() => this._refresh(origin_path, destination_path));
|
||||
};
|
||||
|
||||
const action_execute = (part_of_a_batch_operation = false) => {
|
||||
if(part_of_a_batch_operation === true){
|
||||
if (part_of_a_batch_operation === true) {
|
||||
return http_get(url)
|
||||
.then(() => {
|
||||
return this._replace(destination_path, null, 'loading')
|
||||
return this._replace(destination_path, null, "loading")
|
||||
.then(() => this._refresh(destination_path));
|
||||
})
|
||||
.catch((err) => {
|
||||
this._replace(destination_path, 'error', 'loading')
|
||||
this._replace(destination_path, "error", "loading")
|
||||
.then(() => this._refresh(origin_path, destination_path));
|
||||
return Promise.reject(err);
|
||||
});
|
||||
|
|
@ -235,60 +244,63 @@ class FileSystem{
|
|||
|
||||
return http_get(url)
|
||||
.then(() => {
|
||||
return this._replace(destination_path, null, 'loading')
|
||||
.then(() => origin_path !== destination_path ? this._remove(origin_path, 'loading') : Promise.resolve())
|
||||
return this._replace(destination_path, null, "loading")
|
||||
.then(() => origin_path !== destination_path ?
|
||||
this._remove(origin_path, "loading") : Promise.resolve())
|
||||
.then(() => cache.add(cache.FILE_PATH, [currentShare(), destination_path], {
|
||||
path: destination_path,
|
||||
share: currentShare(),
|
||||
results: [],
|
||||
access_count: 0,
|
||||
last_access: null,
|
||||
last_update: new Date()
|
||||
last_update: new Date(),
|
||||
}))
|
||||
.then(() => this._refresh(origin_path, destination_path));
|
||||
})
|
||||
.catch((err) => {
|
||||
this._replace(origin_path, 'error', 'loading')
|
||||
.then(() => origin_path !== destination_path ? this._remove(destination_path, 'loading') : Promise.resolve())
|
||||
this._replace(origin_path, "error", "loading")
|
||||
.then(() => origin_path !== destination_path ?
|
||||
this._remove(destination_path, "loading") : Promise.resolve())
|
||||
.then(() => this._refresh(origin_path, destination_path));
|
||||
return Promise.reject(err);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
if(step === 'prepare_only'){
|
||||
if (step === "prepare_only") {
|
||||
return action_prepare(true);
|
||||
}else if(step === 'execute_only'){
|
||||
} else if (step === "execute_only") {
|
||||
return action_execute(true);
|
||||
}else{
|
||||
} else {
|
||||
return action_prepare().then(action_execute);
|
||||
}
|
||||
}
|
||||
|
||||
touch(path, file, step, params){
|
||||
const origin_path = pathBuilder(this.current_path, basename(path), 'file'),
|
||||
destination_path = path;
|
||||
touch(path, file, step, params) {
|
||||
const origin_path = pathBuilder(this.current_path, basename(path), "file");
|
||||
const destination_path = path;
|
||||
|
||||
const action_prepare = (part_of_a_batch_operation = false) => {
|
||||
if(part_of_a_batch_operation === true){
|
||||
return this._add(destination_path, 'loading')
|
||||
if (part_of_a_batch_operation === true) {
|
||||
return this._add(destination_path, "loading")
|
||||
.then(() => this._refresh(destination_path));
|
||||
}else{
|
||||
return this._add(destination_path, 'loading')
|
||||
.then(() => origin_path !== destination_path ? this._add(origin_path, 'loading') : Promise.resolve())
|
||||
} else {
|
||||
return this._add(destination_path, "loading")
|
||||
.then(() => origin_path !== destination_path ?
|
||||
this._add(origin_path, "loading") : Promise.resolve())
|
||||
.then(() => this._refresh(origin_path, destination_path));
|
||||
}
|
||||
};
|
||||
const action_execute = (part_of_a_batch_operation = false) => {
|
||||
if(part_of_a_batch_operation === true){
|
||||
if (part_of_a_batch_operation === true) {
|
||||
return query()
|
||||
.then(() => {
|
||||
return this._replace(destination_path, null, 'loading')
|
||||
return this._replace(destination_path, null, "loading")
|
||||
.then(() => this._refresh(destination_path));
|
||||
})
|
||||
.catch((err) => {
|
||||
this._replace(destination_path, null, 'error')
|
||||
.then(() => this._replace(destination_path, null, 'loading'))
|
||||
this._replace(destination_path, null, "error")
|
||||
.then(() => this._replace(destination_path, null, "loading"))
|
||||
.then(() => this._refresh(destination_path));
|
||||
return Promise.reject(err);
|
||||
});
|
||||
|
|
@ -296,49 +308,51 @@ class FileSystem{
|
|||
return query()
|
||||
.then(() => {
|
||||
return this._saveFileToCache(path, file)
|
||||
.then(() => this._replace(destination_path, null, 'loading'))
|
||||
.then(() => origin_path !== destination_path ? this._remove(origin_path, 'loading') : Promise.resolve())
|
||||
.then(() => this._replace(destination_path, null, "loading"))
|
||||
.then(() => origin_path !== destination_path ?
|
||||
this._remove(origin_path, "loading") : Promise.resolve())
|
||||
.then(() => this._refresh(origin_path, destination_path));
|
||||
})
|
||||
.catch((err) => {
|
||||
this._replace(origin_path, 'error', 'loading')
|
||||
.then(() => origin_path !== destination_path ? this._remove(destination_path, 'loading') : Promise.resolve())
|
||||
this._replace(origin_path, "error", "loading")
|
||||
.then(() => origin_path !== destination_path ?
|
||||
this._remove(destination_path, "loading") : Promise.resolve())
|
||||
.then(() => this._refresh(origin_path, destination_path));
|
||||
return Promise.reject(err);
|
||||
});
|
||||
|
||||
function query(){
|
||||
if(file){
|
||||
const url = appendShareToUrl('/api/files/cat?path='+prepare(path));
|
||||
return http_post(url, file, 'blob', params);
|
||||
}else{
|
||||
const url = appendShareToUrl('/api/files/touch?path='+prepare(path));
|
||||
function query() {
|
||||
if (file) {
|
||||
const url = appendShareToUrl("/api/files/cat?path=" + prepare(path));
|
||||
return http_post(url, file, "blob", params);
|
||||
} else {
|
||||
const url = appendShareToUrl("/api/files/touch?path=" + prepare(path));
|
||||
return http_get(url);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if(step === 'prepare_only'){
|
||||
if (step === "prepare_only") {
|
||||
return action_prepare(true);
|
||||
}else if(step === 'execute_only'){
|
||||
} else if (step === "execute_only") {
|
||||
return action_execute(true);
|
||||
}else{
|
||||
} else {
|
||||
return action_prepare().then(action_execute);
|
||||
}
|
||||
}
|
||||
|
||||
mv(from, to){
|
||||
const url = appendShareToUrl('/api/files/mv?from='+prepare(from)+"&to="+prepare(to)),
|
||||
origin_path = from,
|
||||
destination_path = to;
|
||||
mv(from, to) {
|
||||
const url = appendShareToUrl("/api/files/mv?from=" + prepare(from) + "&to=" + prepare(to));
|
||||
const origin_path = from;
|
||||
const destination_path = to;
|
||||
|
||||
return this._replace(origin_path, 'loading')
|
||||
.then(this._add(destination_path, 'loading'))
|
||||
return this._replace(origin_path, "loading")
|
||||
.then(this._add(destination_path, "loading"))
|
||||
.then(() => this._refresh(origin_path, destination_path))
|
||||
.then(() => http_get(url))
|
||||
.then((res) => {
|
||||
return this._remove(origin_path, 'loading')
|
||||
.then(() => this._replace(destination_path, null, 'loading'))
|
||||
return this._remove(origin_path, "loading")
|
||||
.then(() => this._replace(destination_path, null, "loading"))
|
||||
.then(() => this._refresh(origin_path, destination_path))
|
||||
.then(() => {
|
||||
cache.update(cache.FILE_PATH, [currentShare(), origin_path], (data) => {
|
||||
|
|
@ -353,56 +367,62 @@ class FileSystem{
|
|||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
this._replace(origin_path, 'error', 'loading')
|
||||
.then(() => this._remove(destination_path, 'loading'))
|
||||
.then(() => this._refresh(origin_path, destination_path))
|
||||
this._replace(origin_path, "error", "loading")
|
||||
.then(() => this._remove(destination_path, "loading"))
|
||||
.then(() => this._refresh(origin_path, destination_path));
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
search(keyword, path = "/", show_hidden){
|
||||
const url = appendShareToUrl("/api/files/search?path="+prepare(path)+"&q="+encodeURIComponent(keyword))
|
||||
search(keyword, path = "/", show_hidden) {
|
||||
const url = appendShareToUrl(
|
||||
"/api/files/search?path=" + prepare(path) +
|
||||
"&q="+encodeURIComponent(keyword),
|
||||
);
|
||||
return http_get(url).then((response) => {
|
||||
response = fileMiddleware(response, path, show_hidden);
|
||||
return response.results;
|
||||
});
|
||||
}
|
||||
|
||||
frequents(){
|
||||
let data = [];
|
||||
frequents() {
|
||||
const data = [];
|
||||
return cache.fetchAll((value) => {
|
||||
if(value.access_count >= 1 && value.path !== "/"){
|
||||
if (value.access_count >= 1 && value.path !== "/") {
|
||||
data.push(value);
|
||||
}
|
||||
}, cache.FILE_PATH, [currentShare(), "/"]).then(() => {
|
||||
return Promise.resolve(
|
||||
data
|
||||
.sort((a,b) => a.access_count > b.access_count? -1 : 1)
|
||||
.sort((a, b) => a.access_count > b.access_count? -1 : 1)
|
||||
.map((a) => a.path)
|
||||
.slice(0,6)
|
||||
.slice(0, 6),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
_saveFileToCache(path, file){
|
||||
if(!file) return update_cache("");
|
||||
_saveFileToCache(path, file) {
|
||||
if (!file) return update_cache("");
|
||||
return new Promise((done, err) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(file);
|
||||
reader.onload = () => this.is_binary(reader.result) === false? update_cache(reader.result).then(done) : done();
|
||||
reader.onload = () => this.is_binary(reader.result) === false ?
|
||||
update_cache(reader.result).then(done) : done();
|
||||
reader.onerror = (_err) => err(_err);
|
||||
});
|
||||
|
||||
function update_cache(result){
|
||||
function update_cache(result) {
|
||||
return cache.upsert(cache.FILE_CONTENT, [currentShare(), path], (response) => {
|
||||
if(!response) response = {
|
||||
share: currentShare(),
|
||||
path: path,
|
||||
last_access: null,
|
||||
last_update: null,
|
||||
result: null,
|
||||
access_count: 0
|
||||
};
|
||||
if (!response) {
|
||||
response = {
|
||||
share: currentShare(),
|
||||
path: path,
|
||||
last_access: null,
|
||||
last_update: null,
|
||||
result: null,
|
||||
access_count: 0,
|
||||
};
|
||||
}
|
||||
response.last_update = new Date();
|
||||
response.result = result;
|
||||
return response;
|
||||
|
|
@ -410,51 +430,55 @@ class FileSystem{
|
|||
}
|
||||
}
|
||||
|
||||
_refresh(origin_path, destination_path){
|
||||
if(this.current_path === dirname(origin_path) ||
|
||||
this.current_path === dirname(destination_path)){
|
||||
_refresh(origin_path, destination_path) {
|
||||
if (this.current_path === dirname(origin_path) ||
|
||||
this.current_path === dirname(destination_path)) {
|
||||
return this._ls_from_cache(this.current_path);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
_replace(path, icon, icon_previous){
|
||||
return cache.update(cache.FILE_PATH, [currentShare(), dirname(path)], function(res){
|
||||
_replace(path, icon, icon_previous) {
|
||||
return cache.update(cache.FILE_PATH, [currentShare(), dirname(path)], function(res) {
|
||||
res.results = res.results.map((file) => {
|
||||
if(file.name === basename(path) && file.icon == icon_previous){
|
||||
if(!icon){ delete file.icon; }
|
||||
if(icon){ file.icon = icon; }
|
||||
if (file.name === basename(path) && file.icon == icon_previous) {
|
||||
if (!icon) {
|
||||
delete file.icon;
|
||||
}
|
||||
if (icon) {
|
||||
file.icon = icon;
|
||||
}
|
||||
}
|
||||
return file;
|
||||
});
|
||||
return res;
|
||||
});
|
||||
}
|
||||
_add(path, icon){
|
||||
_add(path, icon) {
|
||||
return cache.upsert(cache.FILE_PATH, [currentShare(), dirname(path)], (res) => {
|
||||
if(!res || !res.results){
|
||||
if (!res || !res.results) {
|
||||
res = {
|
||||
path: path,
|
||||
share: currentShare(),
|
||||
results: [],
|
||||
access_count: 0,
|
||||
last_access: null,
|
||||
last_update: new Date()
|
||||
last_update: new Date(),
|
||||
};
|
||||
}
|
||||
let file = mutateFile({
|
||||
const file = mutateFile({
|
||||
path: path,
|
||||
name: basename(path),
|
||||
type: /\/$/.test(path) ? 'directory' : 'file'
|
||||
type: /\/$/.test(path) ? "directory" : "file",
|
||||
}, path);
|
||||
if(icon) file.icon = icon;
|
||||
if (icon) file.icon = icon;
|
||||
res.results.push(file);
|
||||
return res;
|
||||
});
|
||||
}
|
||||
_remove(path, previous_icon){
|
||||
return cache.update(cache.FILE_PATH, [currentShare(), dirname(path)], function(res){
|
||||
if(!res) return null;
|
||||
_remove(path, previous_icon) {
|
||||
return cache.update(cache.FILE_PATH, [currentShare(), dirname(path)], function(res) {
|
||||
if (!res) return null;
|
||||
res.results = res.results.filter((file) => {
|
||||
return file.name === basename(path) && file.icon == previous_icon ? false : true;
|
||||
});
|
||||
|
|
@ -463,7 +487,7 @@ class FileSystem{
|
|||
}
|
||||
|
||||
|
||||
is_binary(str){
|
||||
is_binary(str) {
|
||||
// Reference: https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character
|
||||
return /\ufffd/.test(str);
|
||||
}
|
||||
|
|
@ -475,9 +499,9 @@ const createLink = (type, path) => {
|
|||
};
|
||||
|
||||
const fileMiddleware = (response, path, show_hidden) => {
|
||||
for(let i=0; i<response.results.length; i++){
|
||||
let f = mutateFile(response.results[i], path);
|
||||
if(show_hidden === false && f.path.indexOf("/.") !== -1){
|
||||
for (let i=0; i<response.results.length; i++) {
|
||||
const f = mutateFile(response.results[i], path);
|
||||
if (show_hidden === false && f.path.indexOf("/.") !== -1) {
|
||||
response.results.splice(i, 1);
|
||||
i -= 1;
|
||||
}
|
||||
|
|
@ -486,7 +510,7 @@ const fileMiddleware = (response, path, show_hidden) => {
|
|||
};
|
||||
|
||||
const mutateFile = (file, path) => {
|
||||
if(file.path === undefined) {
|
||||
if (file.path === undefined) {
|
||||
file.path = pathBuilder(path, file.name, file.type);
|
||||
}
|
||||
file.link = createLink(file.type, file.path);
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@ export { Session } from "./session";
|
|||
export { Share } from "./share";
|
||||
export { Config, Backend } from "./config";
|
||||
export { Log } from "./log";
|
||||
export { Admin } from "./admin"
|
||||
export { Admin } from "./admin";
|
||||
|
|
|
|||
|
|
@ -1,35 +1,36 @@
|
|||
import { http_get, http_post } from '../helpers/';
|
||||
import { http_get, http_post } from "../helpers/";
|
||||
|
||||
class LogManager{
|
||||
constructor(){}
|
||||
|
||||
get(maxSize = -1){
|
||||
let url = this.url();
|
||||
if(maxSize > 0){
|
||||
url += "?maxSize="+maxSize
|
||||
}
|
||||
return http_get(url, 'text');
|
||||
class LogManager {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
url(){
|
||||
get(maxSize = -1) {
|
||||
let url = this.url();
|
||||
if (maxSize > 0) {
|
||||
url += "?maxSize=" + maxSize;
|
||||
}
|
||||
return http_get(url, "text");
|
||||
}
|
||||
|
||||
url() {
|
||||
return "/admin/api/logs";
|
||||
}
|
||||
|
||||
send(msg) {
|
||||
let url = "/report?";
|
||||
url += "message="+encodeURIComponent(msg)
|
||||
url += "message=" + encodeURIComponent(msg);
|
||||
return http_post(url).catch();
|
||||
}
|
||||
|
||||
report(msg, link, lineNo, columnNo, error){
|
||||
if(navigator.onLine === false) return Promise.resolve();
|
||||
report(msg, link, lineNo, columnNo, error) {
|
||||
if (navigator.onLine === false) return Promise.resolve();
|
||||
let url = "/report?";
|
||||
url += "url="+encodeURIComponent(location.href)+"&";
|
||||
url += "msg="+encodeURIComponent(msg)+"&";
|
||||
url += "from="+encodeURIComponent(link)+"&";
|
||||
url += "from.lineNo="+lineNo+"&";
|
||||
url += "from.columnNo="+columnNo;
|
||||
if(error) url += "error="+encodeURIComponent(error.message)+"&";
|
||||
if (error) url += "error="+encodeURIComponent(error.message)+"&";
|
||||
return http_post(url).catch();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,27 @@
|
|||
import { http_get, http_post, http_delete } from '../helpers/';
|
||||
import { http_get, http_post, http_delete } from "../helpers/";
|
||||
|
||||
class SessionManager{
|
||||
currentUser(){
|
||||
let url = '/api/session'
|
||||
class SessionManager {
|
||||
currentUser() {
|
||||
const url = "/api/session";
|
||||
return http_get(url)
|
||||
.then(data => data.result);
|
||||
.then((data) => data.result);
|
||||
}
|
||||
|
||||
oauth2(url){
|
||||
oauth2(url) {
|
||||
return http_get(url)
|
||||
.then(data => data.result);
|
||||
.then((data) => data.result);
|
||||
}
|
||||
|
||||
authenticate(params){
|
||||
let url = '/api/session';
|
||||
authenticate(params) {
|
||||
const url = "/api/session";
|
||||
return http_post(url, params)
|
||||
.then(data => data.result);
|
||||
.then((data) => data.result);
|
||||
}
|
||||
|
||||
logout(){
|
||||
let url = '/api/session';
|
||||
logout() {
|
||||
const url = "/api/session";
|
||||
return http_delete(url)
|
||||
.then(data => data.result);
|
||||
.then((data) => data.result);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,45 +1,46 @@
|
|||
import { http_get, http_post, http_delete, appendShareToUrl } from '../helpers/';
|
||||
import { http_get, http_post, http_delete, appendShareToUrl } from "../helpers/";
|
||||
|
||||
class ShareModel {
|
||||
constructor(){}
|
||||
constructor() {
|
||||
}
|
||||
|
||||
all(path = "/"){
|
||||
all(path = "/") {
|
||||
const url = `/api/share?path=${path}`;
|
||||
return http_get(url).then((res) => res.results.map((el) => {
|
||||
if(el.can_read === true && el.can_write === false && el.can_upload === false){
|
||||
if (el.can_read === true && el.can_write === false && el.can_upload === false) {
|
||||
el.role = "viewer";
|
||||
}else if(el.can_read === false && el.can_write === false && el.can_upload === true){
|
||||
} else if (el.can_read === false && el.can_write === false && el.can_upload === true) {
|
||||
el.role = "uploader";
|
||||
}else if(el.can_read === true && el.can_write === true && el.can_upload === true){
|
||||
} else if (el.can_read === true && el.can_write === true && el.can_upload === true) {
|
||||
el.role = "editor";
|
||||
}else{
|
||||
} else {
|
||||
el.role = "n/a";
|
||||
}
|
||||
return el;
|
||||
}));
|
||||
}
|
||||
|
||||
get(id){
|
||||
get(id) {
|
||||
const url = `/api/share/${id}`;
|
||||
return http_get(url).then((res) => res.result);
|
||||
}
|
||||
|
||||
upsert(obj){
|
||||
const url = appendShareToUrl(`/api/share/${obj.id}`)
|
||||
upsert(obj) {
|
||||
const url = appendShareToUrl(`/api/share/${obj.id}`);
|
||||
const data = Object.assign({}, obj);
|
||||
delete data.role;
|
||||
return http_post(url, data);
|
||||
}
|
||||
|
||||
remove(id){
|
||||
remove(id) {
|
||||
const url = appendShareToUrl(`/api/share/${id}`);
|
||||
return http_delete(url);
|
||||
}
|
||||
|
||||
proof(id, data){
|
||||
proof(id, data) {
|
||||
const url = `/api/share/${id}/proof`;
|
||||
return http_post(url, data).then((res) => res.result);
|
||||
}
|
||||
}
|
||||
|
||||
export const Share = new ShareModel()
|
||||
export const Share = new ShareModel();
|
||||
|
|
|
|||
|
|
@ -1,42 +1,44 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import Path from "path";
|
||||
import { Route, Switch, Link, NavLink, useRouteMatch } from "react-router-dom";
|
||||
import { Route, Switch, NavLink, useRouteMatch } from "react-router-dom";
|
||||
|
||||
import "./error.scss";
|
||||
import "./adminpage.scss";
|
||||
import { Icon, LoadingPage, CSSTransition } from "../components/";
|
||||
import { Config, Admin } from "../model";
|
||||
import { Admin } from "../model";
|
||||
import { notify } from "../helpers/";
|
||||
import { HomePage, BackendPage, SettingsPage, LogPage, SetupPage, LoginPage } from "./adminpage/";
|
||||
import {
|
||||
HomePage, BackendPage, SettingsPage, LogPage, SetupPage, LoginPage,
|
||||
} from "./adminpage/";
|
||||
import { t } from "../locales/";
|
||||
|
||||
function AdminOnly(WrappedComponent){
|
||||
function AdminOnly(WrappedComponent) {
|
||||
let initIsAdmin = null;
|
||||
return function(props) {
|
||||
const [isAdmin, setIsAdmin] = useState(initIsAdmin);
|
||||
|
||||
const refresh = () => {
|
||||
Admin.isAdmin().then((t) => {
|
||||
initIsAdmin = t
|
||||
setIsAdmin(t)
|
||||
initIsAdmin = t;
|
||||
setIsAdmin(t);
|
||||
}).catch((err) => {
|
||||
notify.send("Error: " + (err && err.message) , "error");
|
||||
notify.send("Error: " + (err && err.message), "error");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refresh()
|
||||
refresh();
|
||||
const timeout = window.setInterval(refresh, 5 * 1000);
|
||||
return () => clearInterval(timeout);
|
||||
}, []);
|
||||
|
||||
if(isAdmin === true) {
|
||||
if (isAdmin === true) {
|
||||
return ( <WrappedComponent {...props} /> );
|
||||
} else if(isAdmin === false) {
|
||||
} else if (isAdmin === false) {
|
||||
return ( <LoginPage reload={refresh} /> );
|
||||
}
|
||||
|
||||
return ( <LoadingPage /> );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default AdminOnly((props) => {
|
||||
|
|
@ -46,11 +48,20 @@ export default AdminOnly((props) => {
|
|||
<div className="component_page_admin">
|
||||
<SideMenu url={match.url} isLoading={isSaving}/>
|
||||
<div className="page_container scroll-y">
|
||||
<CSSTransition key={location.pathname} transitionName="adminpage" transitionAppearTimeout={30000}>
|
||||
<CSSTransition key={location.pathname} transitionName="adminpage"
|
||||
transitionAppearTimeout={30000}>
|
||||
<Switch>
|
||||
<Route path={match.url + "/backend"} render={()=><BackendPage isSaving={setIsSaving}/>} />
|
||||
<Route path={match.url + "/settings"} render={()=><SettingsPage isSaving={setIsSaving}/>} />
|
||||
<Route path={match.url + "/logs"} render={() =><LogPage isSaving={setIsSaving}/>} />
|
||||
<Route
|
||||
path={match.url + "/backend"}
|
||||
render={()=> <BackendPage isSaving={setIsSaving}/>}
|
||||
/>
|
||||
<Route
|
||||
path={match.url + "/settings"}
|
||||
render={()=> <SettingsPage isSaving={setIsSaving}/>}
|
||||
/>
|
||||
<Route
|
||||
path={match.url + "/logs"}
|
||||
render={() =><LogPage isSaving={setIsSaving}/>} />
|
||||
<Route path={match.url + "/setup"} component={SetupPage} />
|
||||
<Route path={match.url} component={HomePage} />
|
||||
</Switch>
|
||||
|
|
@ -63,15 +74,18 @@ export default AdminOnly((props) => {
|
|||
function SideMenu(props) {
|
||||
return (
|
||||
<div className="component_menu_sidebar no-select">
|
||||
{ props.isLoading ?
|
||||
<div className="header">
|
||||
<Icon name="arrow_left" style={{"opacity": 0}}/>
|
||||
<Icon name="loading" />
|
||||
</div> :
|
||||
<NavLink to="/" className="header">
|
||||
<Icon name="arrow_left" />
|
||||
<img src="/assets/logo/android-chrome-512x512.png" />
|
||||
</NavLink>
|
||||
{
|
||||
props.isLoading ? (
|
||||
<div className="header">
|
||||
<Icon name="arrow_left" style={{ "opacity": 0 }}/>
|
||||
<Icon name="loading" />
|
||||
</div>
|
||||
) : (
|
||||
<NavLink to="/" className="header">
|
||||
<Icon name="arrow_left" />
|
||||
<img src="/assets/logo/android-chrome-512x512.png" />
|
||||
</NavLink>
|
||||
)
|
||||
}
|
||||
<h2>{ t("Admin console") }</h2>
|
||||
<ul>
|
||||
|
|
|
|||
|
|
@ -1,82 +1,85 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Files } from '../model/';
|
||||
import { notify, upload } from '../helpers/';
|
||||
import Path from 'path';
|
||||
import { Files } from "../model/";
|
||||
import { notify, upload } from "../helpers/";
|
||||
import Path from "path";
|
||||
import { Observable } from "rxjs/Observable";
|
||||
import { t } from '../locales/';
|
||||
import { t } from "../locales/";
|
||||
|
||||
export const sort = function(files, type){
|
||||
if(type === 'name'){
|
||||
export const sort = function(files, type) {
|
||||
if (type === "name") {
|
||||
return sortByName(files);
|
||||
}else if(type === 'date'){
|
||||
} else if (type === "date") {
|
||||
return sortByDate(files);
|
||||
}else{
|
||||
} else {
|
||||
return sortByType(files);
|
||||
}
|
||||
function _moveLoadingDownward(fileA, fileB){
|
||||
if(fileA.icon === 'loading' && fileB.icon !== 'loading'){return +1;}
|
||||
else if(fileA.icon !== 'loading' && fileB.icon === 'loading'){return -1;}
|
||||
return 0;
|
||||
};
|
||||
function _moveFolderUpward(fileA, fileB){
|
||||
if(['directory', 'link'].indexOf(fileA.type) === -1 && ['directory', 'link'].indexOf(fileB.type) !== -1){
|
||||
function _moveLoadingDownward(fileA, fileB) {
|
||||
if (fileA.icon === "loading" && fileB.icon !== "loading") {
|
||||
return +1;
|
||||
}else if(['directory', 'link'].indexOf(fileA.type) !== -1 && ['directory', 'link'].indexOf(fileB.type) === -1){
|
||||
} else if (fileA.icon !== "loading" && fileB.icon === "loading") {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
function _moveHiddenFilesDownward(fileA, fileB){
|
||||
if(fileA.name[0] === "." && fileB.name[0] !== ".") return +1;
|
||||
else if(fileA.name[0] !== "." && fileB.name[0] === ".") return -1;
|
||||
return 0
|
||||
function _moveFolderUpward(fileA, fileB) {
|
||||
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;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
function sortByType(files){
|
||||
function _moveHiddenFilesDownward(fileA, fileB) {
|
||||
if (fileA.name[0] === "." && fileB.name[0] !== ".") return +1;
|
||||
else if (fileA.name[0] !== "." && fileB.name[0] === ".") return -1;
|
||||
return 0;
|
||||
};
|
||||
function sortByType(files) {
|
||||
return files.sort((fileA, fileB) => {
|
||||
let tmp = _moveLoadingDownward(fileA, fileB);
|
||||
if(tmp !== 0) return tmp;
|
||||
if (tmp !== 0) return tmp;
|
||||
|
||||
tmp = _moveFolderUpward(fileA, fileB);
|
||||
if(tmp !== 0) return tmp;
|
||||
if (tmp !== 0) return tmp;
|
||||
|
||||
tmp = _moveHiddenFilesDownward(fileA, fileB);
|
||||
if(tmp !== 0) return tmp;
|
||||
if (tmp !== 0) return tmp;
|
||||
|
||||
const aExt = Path.extname(fileA.name.toLowerCase()),
|
||||
bExt = Path.extname(fileB.name.toLowerCase());
|
||||
const aExt = Path.extname(fileA.name.toLowerCase());
|
||||
const bExt = Path.extname(fileB.name.toLowerCase());
|
||||
|
||||
if(fileA.name.toLowerCase() === fileB.name.toLowerCase()){
|
||||
if (fileA.name.toLowerCase() === fileB.name.toLowerCase()) {
|
||||
return fileA.name > fileB.name ? +1 : -1;
|
||||
}else{
|
||||
if(aExt !== bExt) return aExt > bExt ? +1 : -1;
|
||||
} else {
|
||||
if (aExt !== bExt) return aExt > bExt ? +1 : -1;
|
||||
else return fileA.name.toLowerCase() > fileB.name.toLowerCase() ? +1 : -1;
|
||||
}
|
||||
});
|
||||
}
|
||||
function sortByName(files){
|
||||
function sortByName(files) {
|
||||
return files.sort((fileA, fileB) => {
|
||||
let tmp = _moveLoadingDownward(fileA, fileB);
|
||||
if(tmp !== 0) return tmp;
|
||||
if (tmp !== 0) return tmp;
|
||||
|
||||
tmp = _moveFolderUpward(fileA, fileB);
|
||||
if(tmp !== 0) return tmp;
|
||||
if (tmp !== 0) return tmp;
|
||||
|
||||
tmp = _moveHiddenFilesDownward(fileA, fileB);
|
||||
if(tmp !== 0) return tmp;
|
||||
if (tmp !== 0) return tmp;
|
||||
|
||||
if(fileA.name.toLowerCase() === fileB.name.toLowerCase()){
|
||||
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){
|
||||
function sortByDate(files) {
|
||||
return files.sort((fileA, fileB) => {
|
||||
let tmp = _moveLoadingDownward(fileA, fileB);
|
||||
if(tmp !== 0) return tmp;
|
||||
const tmp = _moveLoadingDownward(fileA, fileB);
|
||||
if (tmp !== 0) return tmp;
|
||||
|
||||
if(fileB.time === fileA.time){
|
||||
if (fileB.time === fileA.time) {
|
||||
return fileA.name > fileB.name ? +1 : -1;
|
||||
}
|
||||
return fileB.time - fileA.time;
|
||||
|
|
@ -84,54 +87,69 @@ export const sort = function(files, type){
|
|||
}
|
||||
};
|
||||
|
||||
export const onCreate = function(path, type, file){
|
||||
if(type === 'file'){
|
||||
export const onCreate = function(path, type, file) {
|
||||
if (type === "file") {
|
||||
return Files.touch(path, file)
|
||||
.then(() => {
|
||||
notify.send(t('A file named "{{VALUE}}" was created', Path.basename(path)), 'success');
|
||||
notify.send(
|
||||
t("A file named '{{VALUE}}' was created", Path.basename(path)),
|
||||
"success",
|
||||
);
|
||||
return Promise.resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
notify.send(err, 'error');
|
||||
notify.send(err, "error");
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}else if(type === 'directory'){
|
||||
} else if (type === "directory") {
|
||||
return Files.mkdir(path)
|
||||
.then(() => notify.send(t('A folder named "{{VALUE}}" was created"', Path.basename(path)), 'success'))
|
||||
.catch((err) => notify.send(err, 'error'));
|
||||
}else{
|
||||
return Promise.reject({message: t('internal error: can\'t create a {{VALUE}}', type.toString()), code: 'UNKNOWN_TYPE'});
|
||||
.then(() => notify.send(
|
||||
t("A folder named '{{VALUE}}' was created", Path.basename(path)),
|
||||
"success",
|
||||
))
|
||||
.catch((err) => notify.send(err, "error"));
|
||||
} else {
|
||||
return Promise.reject({
|
||||
message: t("internal error: can't create a {{VALUE}}", type.toString()),
|
||||
code: "UNKNOWN_TYPE",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const onRename = function(from, to, type){
|
||||
export const onRename = function(from, to, type) {
|
||||
return Files.mv(from, to, type)
|
||||
.then(() => notify.send(t('The file "{{VALUE}}" was renamed', Path.basename(from)), 'success'))
|
||||
.catch((err) => notify.send(err, 'error'));
|
||||
.then(() => notify.send(
|
||||
t("The file '{{VALUE}}' was renamed", Path.basename(from)),
|
||||
"success",
|
||||
))
|
||||
.catch((err) => notify.send(err, "error"));
|
||||
};
|
||||
|
||||
export const onDelete = function(path, type){
|
||||
export const onDelete = function(path, type) {
|
||||
return Files.rm(path, type)
|
||||
.then(() => notify.send(t('The file {{VALUE}} was deleted"', Path.basename(path)), 'success'))
|
||||
.catch((err) => notify.send(err, 'error'));
|
||||
.then(() => notify.send(
|
||||
t("The file {{VALUE}} was deleted", Path.basename(path)),
|
||||
"success",
|
||||
))
|
||||
.catch((err) => notify.send(err, "error"));
|
||||
};
|
||||
|
||||
export const onMultiDelete = function(arrOfPath){
|
||||
export const onMultiDelete = function(arrOfPath) {
|
||||
return Promise.all(arrOfPath.map((p) => Files.rm(p)))
|
||||
.then(() => notify.send(t('All done!'), 'success'))
|
||||
.catch((err) => notify.send(err, 'error'));
|
||||
}
|
||||
.then(() => notify.send(t("All done!"), "success"))
|
||||
.catch((err) => notify.send(err, "error"));
|
||||
};
|
||||
|
||||
export const onMultiDownload = function(arr){
|
||||
export const onMultiDownload = function(arr) {
|
||||
return Files.zip(arr)
|
||||
.catch((err) => notify.send(err, 'error'));
|
||||
}
|
||||
.catch((err) => notify.send(err, "error"));
|
||||
};
|
||||
|
||||
export const onMultiRename = function(arrOfPath){
|
||||
export const onMultiRename = function(arrOfPath) {
|
||||
return Promise.all(arrOfPath.map((p) => Files.mv(p[0], p[1])))
|
||||
.then(() => notify.send(t('All done!'), 'success'))
|
||||
.catch((err) => notify.send(err, 'error'));
|
||||
}
|
||||
.then(() => notify.send(t("All done!"), "success"))
|
||||
.catch((err) => notify.send(err, "error"));
|
||||
};
|
||||
|
||||
/*
|
||||
* The upload method has a few strategies:
|
||||
|
|
@ -139,29 +157,31 @@ export const onMultiRename = function(arrOfPath){
|
|||
* 2. user is coming from drag and drop + browser DOES NOT provides support to read entire folders
|
||||
* 3. user is coming from a upload form button as he doesn't have drag and drop with files
|
||||
*/
|
||||
export const onUpload = function(path, e){
|
||||
export const onUpload = function(path, e) {
|
||||
let extractFiles = null;
|
||||
if(e.dataTransfer === undefined){ // case 3
|
||||
if (e.dataTransfer === undefined) { // case 3
|
||||
extractFiles = extract_upload_crappy_hack_but_official_way(e.target);
|
||||
} else {
|
||||
if(e.dataTransfer.types && e.dataTransfer.types.length >= 0){
|
||||
if(e.dataTransfer.types[0] === "text/uri-list"){
|
||||
return
|
||||
if (e.dataTransfer.types && e.dataTransfer.types.length >= 0) {
|
||||
if (e.dataTransfer.types[0] === "text/uri-list") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
extractFiles = extract_upload_directory_the_way_that_works_but_non_official(e.dataTransfer.items || [], []) // case 1
|
||||
extractFiles = extract_upload_directory_the_way_that_works_but_non_official(
|
||||
e.dataTransfer.items || [], [],
|
||||
) // case 1
|
||||
.then((files) => {
|
||||
if(files.length === 0){ // case 2
|
||||
if (files.length === 0) { // case 2
|
||||
return extract_upload_crappy_hack_but_official_way(e.dataTransfer);
|
||||
}
|
||||
return Promise.resolve(files);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
extractFiles.then((files) => upload.add(path, files));
|
||||
|
||||
// adapted from: https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
|
||||
function _rand_id(){
|
||||
function _rand_id() {
|
||||
function s4() {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
|
|
@ -170,100 +190,102 @@ export const onUpload = function(path, e){
|
|||
return s4() + s4() + s4() + s4();
|
||||
}
|
||||
|
||||
function extract_upload_directory_the_way_that_works_but_non_official(items, files = []){
|
||||
function extract_upload_directory_the_way_that_works_but_non_official(items, files = []) {
|
||||
const traverseDirectory = (item, _files, parent_id) => {
|
||||
let file = {
|
||||
path: item.fullPath
|
||||
const file = {
|
||||
path: item.fullPath,
|
||||
};
|
||||
if(item.isFile){
|
||||
if (item.isFile) {
|
||||
return new Promise((done, err) => {
|
||||
file.type = "file";
|
||||
item.file((_file, _err) => {
|
||||
if(!_err){
|
||||
if (!_err) {
|
||||
file.file = _file;
|
||||
if(parent_id) file._prior = parent_id;
|
||||
if (parent_id) file._prior = parent_id;
|
||||
_files.push(file);
|
||||
}
|
||||
done(_files);
|
||||
});
|
||||
});
|
||||
}else if(item.isDirectory){
|
||||
} else if (item.isDirectory) {
|
||||
file.type = "directory";
|
||||
file.path += "/";
|
||||
file._id = _rand_id();
|
||||
if(parent_id) file._prior = parent_id;
|
||||
if (parent_id) file._prior = parent_id;
|
||||
_files.push(file);
|
||||
|
||||
let reader = item.createReader();
|
||||
const filereader = function(r){
|
||||
const reader = item.createReader();
|
||||
const filereader = function(r) {
|
||||
return new Promise((done) => {
|
||||
r.readEntries(function(entries){
|
||||
r.readEntries(function(entries) {
|
||||
Promise.all(entries.map((entry) => {
|
||||
return traverseDirectory(entry, _files, file._id);
|
||||
})).then((e) => {
|
||||
if(entries.length > 0){
|
||||
if (entries.length > 0) {
|
||||
return filereader(r).then(done);
|
||||
}
|
||||
return done(e);
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
return filereader(reader).then(() => {
|
||||
return Promise.resolve(_files)
|
||||
return Promise.resolve(_files);
|
||||
});
|
||||
}else{
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
return Promise.all(
|
||||
Array.prototype.slice.call(items).map((item) => {
|
||||
if(typeof item.webkitGetAsEntry === 'function'){
|
||||
if (typeof item.webkitGetAsEntry === "function") {
|
||||
return traverseDirectory(item.webkitGetAsEntry(), files.slice(0));
|
||||
}
|
||||
}).filter((e) => e)
|
||||
}).filter((e) => e),
|
||||
).then((res) => Promise.resolve([].concat.apply([], res)));
|
||||
}
|
||||
|
||||
function extract_upload_crappy_hack_but_official_way(data){
|
||||
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');
|
||||
function detectType(_f) {
|
||||
// the 4096 is an heuristic I've observed and taken from:
|
||||
// https://stackoverflow.com/questions/25016442
|
||||
// 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();
|
||||
const reader = new window.FileReader();
|
||||
reader.onload = function() {
|
||||
done('file');
|
||||
done("file");
|
||||
};
|
||||
reader.onerror = function() {
|
||||
done('directory');
|
||||
done("directory");
|
||||
};
|
||||
reader.readAsText(_f);
|
||||
});
|
||||
}
|
||||
|
||||
function transform(_type){
|
||||
let file = {
|
||||
function transform(_type) {
|
||||
const file = {
|
||||
type: _type,
|
||||
path: _file.name
|
||||
path: _file.name,
|
||||
};
|
||||
if(file.type === 'file'){
|
||||
if (file.type === "file") {
|
||||
file.file = _file;
|
||||
}else{
|
||||
} else {
|
||||
file.path += "/";
|
||||
}
|
||||
return Promise.resolve(file);
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Redirect } from "react-router";
|
||||
|
||||
import { Session } from "../model/";
|
||||
|
|
@ -9,15 +9,15 @@ export function HomePage() {
|
|||
|
||||
useEffect(() => {
|
||||
Session.currentUser().then((res) => {
|
||||
if(res && res.is_authenticated === true){
|
||||
if (res && res.is_authenticated === true) {
|
||||
setRedirection(res.home ? "/files" + res.home : "/files");
|
||||
}else{
|
||||
} else {
|
||||
setRedirection("/login");
|
||||
}
|
||||
}).catch((err) => setRedirection("/login"));
|
||||
}, []);
|
||||
|
||||
if(!redirection) {
|
||||
if (!redirection) {
|
||||
return ( <div> <Loader /> </div> );
|
||||
}
|
||||
return ( <Redirect to={redirection} /> );
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
export { HomePage } from './homepage';
|
||||
export { SharePage } from './sharepage';
|
||||
export { ConnectPage } from './connectpage';
|
||||
export { LogoutPage } from './logout';
|
||||
export { NotFoundPage } from './notfoundpage';
|
||||
export { FilesPage } from './filespage';
|
||||
export { ViewerPage } from './viewerpage';
|
||||
export { HomePage } from "./homepage";
|
||||
export { SharePage } from "./sharepage";
|
||||
export { ConnectPage } from "./connectpage";
|
||||
export { LogoutPage } from "./logout";
|
||||
export { NotFoundPage } from "./notfoundpage";
|
||||
export { FilesPage } from "./filespage";
|
||||
export { ViewerPage } from "./viewerpage";
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { Button } from "../components/";
|
||||
import "./error.scss";
|
||||
|
||||
export function NotFoundPage({ history }) {
|
||||
const [t, setTimer] = useState(10);
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = window.setTimeout(() => {
|
||||
if (t == 0) {
|
||||
history.push("/");
|
||||
return
|
||||
return;
|
||||
}
|
||||
setTimer(t - 1);
|
||||
}, 1000);
|
||||
|
|
@ -21,11 +19,10 @@ export function NotFoundPage({ history }) {
|
|||
};
|
||||
}, [t]);
|
||||
|
||||
|
||||
return (
|
||||
<div className="component_page_notfound error-page">
|
||||
<h1>Oops!</h1>
|
||||
<h2>We can"t seem to find the page you"re looking for.</h2>
|
||||
<h2>We can't seem to find the page you're looking for.</h2>
|
||||
<p>
|
||||
You will be redirected to the <Link to="/">homepage</Link> in {t} seconds
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,17 @@
|
|||
import React, { Suspense } from "react";
|
||||
import { BrowserRouter, Route, Switch } from "react-router-dom";
|
||||
import { NotFoundPage, ConnectPage, HomePage, SharePage, LogoutPage, FilesPage, ViewerPage } from "./pages/";
|
||||
import { URL_HOME, URL_FILES, URL_VIEWER, URL_LOGIN, URL_LOGOUT, URL_ADMIN, URL_SHARE } from "./helpers/";
|
||||
import { ModalPrompt, ModalAlert, ModalConfirm, Notification, UploadQueue, LoadingPage } from "./components/";
|
||||
import {
|
||||
NotFoundPage, ConnectPage, HomePage, SharePage, LogoutPage,
|
||||
FilesPage, ViewerPage,
|
||||
} from "./pages/";
|
||||
import {
|
||||
URL_HOME, URL_FILES, URL_VIEWER, URL_LOGIN, URL_LOGOUT,
|
||||
URL_ADMIN, URL_SHARE,
|
||||
} from "./helpers/";
|
||||
import {
|
||||
ModalPrompt, ModalAlert, ModalConfirm, Notification, UploadQueue,
|
||||
LoadingPage,
|
||||
} from "./components/";
|
||||
|
||||
|
||||
const LazyAdminPage = React.lazy(() => import(/* webpackChunkName: "admin" */"./pages/adminpage"));
|
||||
|
|
@ -12,25 +21,23 @@ const AdminPage = () => (
|
|||
</Suspense>
|
||||
);
|
||||
|
||||
export default class AppRouter extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div style={{height: "100%"}}>
|
||||
<BrowserRouter>
|
||||
export default function AppRouter() {
|
||||
return (
|
||||
<div style={{ height: "100%" }}>
|
||||
<BrowserRouter>
|
||||
<Switch>
|
||||
<Route exact path={URL_HOME} component={HomePage} />
|
||||
<Route path={`${URL_SHARE}/:id*`} component={SharePage} />
|
||||
<Route path={URL_LOGIN} component={ConnectPage} />
|
||||
<Route path={`${URL_FILES}/:path*`} component={FilesPage} />
|
||||
<Route path={`${URL_VIEWER}/:path*`} component={ViewerPage} />
|
||||
<Route path={URL_LOGOUT} component={LogoutPage} />
|
||||
<Route path={URL_ADMIN} component={AdminPage} />
|
||||
<Route component={NotFoundPage} />
|
||||
<Route exact path={URL_HOME} component={HomePage} />
|
||||
<Route path={`${URL_SHARE}/:id*`} component={SharePage} />
|
||||
<Route path={URL_LOGIN} component={ConnectPage} />
|
||||
<Route path={`${URL_FILES}/:path*`} component={FilesPage} />
|
||||
<Route path={`${URL_VIEWER}/:path*`} component={ViewerPage} />
|
||||
<Route path={URL_LOGOUT} component={LogoutPage} />
|
||||
<Route path={URL_ADMIN} component={AdminPage} />
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</BrowserRouter>
|
||||
<ModalPrompt /> <ModalAlert /> <ModalConfirm />
|
||||
<Notification /> <UploadQueue/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</BrowserRouter>
|
||||
<ModalPrompt /> <ModalAlert /> <ModalConfirm />
|
||||
<Notification /> <UploadQueue/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ const CACHE_NAME = "v0.3";
|
|||
* Control everything going through the wire, applying different
|
||||
* strategy for caching, fetching resources
|
||||
*/
|
||||
self.addEventListener("fetch", function(event){
|
||||
if(is_a_ressource(event.request)){
|
||||
self.addEventListener("fetch", function(event) {
|
||||
if (is_a_ressource(event.request)) {
|
||||
return event.respondWith(cacheFirstStrategy(event));
|
||||
}else if(is_an_api_call(event.request)){
|
||||
} else if (is_an_api_call(event.request)) {
|
||||
return event;
|
||||
}else if(is_an_index(event.request)){
|
||||
} else if (is_an_index(event.request)) {
|
||||
return event.respondWith(cacheFirstStrategy(event));
|
||||
}else{
|
||||
} else {
|
||||
return event;
|
||||
}
|
||||
});
|
||||
|
|
@ -20,11 +20,11 @@ self.addEventListener("fetch", function(event){
|
|||
* When a new service worker is coming in, we need to do a bit of
|
||||
* cleanup to get rid of the rotten cache
|
||||
*/
|
||||
self.addEventListener("activate", function(event){
|
||||
self.addEventListener("activate", function(event) {
|
||||
vacuum(event);
|
||||
});
|
||||
|
||||
self.addEventListener("error", function(err){
|
||||
self.addEventListener("error", function(err) {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
|
|
@ -32,55 +32,56 @@ self.addEventListener("error", function(err){
|
|||
* When a newly installed service worker is coming in, we want to use it
|
||||
* straight away (make it active). By default it would be in a "waiting state"
|
||||
*/
|
||||
self.addEventListener("install", function(){
|
||||
self.addEventListener("install", function() {
|
||||
caches.open(CACHE_NAME).then(function(cache) {
|
||||
return cache.addAll([
|
||||
"/",
|
||||
"/api/config"
|
||||
"/api/config",
|
||||
]);
|
||||
});
|
||||
|
||||
if (self.skipWaiting) { self.skipWaiting(); }
|
||||
if (self.skipWaiting) {
|
||||
self.skipWaiting();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
function is_a_ressource(request){
|
||||
function is_a_ressource(request) {
|
||||
const p = _pathname(request);
|
||||
if(["assets", "manifest.json", "favicon.ico"].indexOf(p[0]) !== -1){
|
||||
if (["assets", "manifest.json", "favicon.ico"].indexOf(p[0]) !== -1) {
|
||||
return true;
|
||||
} else if(p[0] === "api" && (p[1] === "config")){
|
||||
} else if (p[0] === "api" && (p[1] === "config")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function is_an_api_call(request){
|
||||
function is_an_api_call(request) {
|
||||
return _pathname(request)[0] === "api" ? true : false;
|
||||
}
|
||||
function is_an_index(request){
|
||||
return ["files", "view", "login", "logout", ""].indexOf(_pathname(request)[0]) >= 0? true : false;
|
||||
function is_an_index(request) {
|
||||
return ["files", "view", "login", "logout", ""]
|
||||
.indexOf(_pathname(request)[0]) >= 0? true : false;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////
|
||||
// //////////////////////////////////////
|
||||
// HELPERS
|
||||
////////////////////////////////////////
|
||||
// //////////////////////////////////////
|
||||
|
||||
function vacuum(event){
|
||||
function vacuum(event) {
|
||||
return event.waitUntil(
|
||||
caches.keys().then(function(cachesName){
|
||||
return Promise.all(cachesName.map(function(cacheName){
|
||||
if(cacheName !== CACHE_NAME){
|
||||
caches.keys().then(function(cachesName) {
|
||||
return Promise.all(cachesName.map(function(cacheName) {
|
||||
if (cacheName !== CACHE_NAME) {
|
||||
return caches.delete(cacheName);
|
||||
}
|
||||
}));
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function _pathname(request){
|
||||
//eslint-disable-next-line no-useless-escape
|
||||
function _pathname(request) {
|
||||
return request.url.replace(/^http[s]?:\/\/[^\/]*\//, "").split("/");
|
||||
}
|
||||
|
||||
|
|
@ -89,34 +90,36 @@ function _pathname(request){
|
|||
* 1. use whatever is in the cache
|
||||
* 2. perform the network call to update the cache
|
||||
*/
|
||||
function cacheFirstStrategy(event){
|
||||
return caches.open(CACHE_NAME).then(function(cache){
|
||||
return cache.match(event.request).then(function(response){
|
||||
if(!response){
|
||||
function cacheFirstStrategy(event) {
|
||||
return caches.open(CACHE_NAME).then(function(cache) {
|
||||
return cache.match(event.request).then(function(response) {
|
||||
if (!response) {
|
||||
return fetchAndCache(event);
|
||||
}
|
||||
|
||||
fetchAndCache(event).catch(nil);
|
||||
return response;
|
||||
});
|
||||
});
|
||||
|
||||
function fetchAndCache(event){
|
||||
function fetchAndCache(event) {
|
||||
// A request is a stream and can only be consumed once. Since we are consuming this
|
||||
// once by cache and once by the browser for fetch, we need to clone the response as
|
||||
// seen on https://developers.google.com/web/fundamentals/getting-started/primers/service-workers
|
||||
// seen on:
|
||||
// https://developers.google.com/web/fundamentals/getting-started/primers/service-workers
|
||||
return fetch(event.request)
|
||||
.then(function(response){
|
||||
if(!response || response.status !== 200){ return response; }
|
||||
.then(function(response) {
|
||||
if (!response || response.status !== 200) {
|
||||
return response;
|
||||
}
|
||||
|
||||
// A response is a stream and can only because we want the browser to consume the
|
||||
// response as well as the cache consuming the response, we need to clone it
|
||||
const responseClone = response.clone();
|
||||
caches.open(CACHE_NAME).then(function(cache){
|
||||
caches.open(CACHE_NAME).then(function(cache) {
|
||||
cache.put(event.request, responseClone);
|
||||
});
|
||||
return response;
|
||||
});
|
||||
}
|
||||
function nil(){}
|
||||
function nil() {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
"wavesurfer.js": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.16.5",
|
||||
"babel-core": "^6.13.2",
|
||||
"babel-loader": "^6.2.10",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
|
|
@ -50,8 +51,9 @@
|
|||
"compression-webpack-plugin": "^1.1.11",
|
||||
"copy-webpack-plugin": "^4.5.2",
|
||||
"css-loader": "^0.28.10",
|
||||
"eslint": "^7.16.0",
|
||||
"eslint-plugin-react": "^7.21.5",
|
||||
"eslint": "^8.5.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-react": "^7.27.1",
|
||||
"html-loader": "^0.4.5",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"node-sass": "^4.10.0",
|
||||
|
|
|
|||
|
|
@ -1,69 +1,69 @@
|
|||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
const webpack = require("webpack");
|
||||
const path = require("path");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
// const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
|
||||
const UglifyJSPlugin = require("uglifyjs-webpack-plugin");
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const CompressionPlugin = require('compression-webpack-plugin');
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const CompressionPlugin = require("compression-webpack-plugin");
|
||||
|
||||
let config = {
|
||||
const config = {
|
||||
entry: {
|
||||
app: path.join(__dirname, 'client', 'index.js')
|
||||
app: path.join(__dirname, "client", "index.js"),
|
||||
},
|
||||
output: {
|
||||
path: path.join(__dirname, 'dist', 'data', 'public'),
|
||||
publicPath: '/',
|
||||
filename: 'assets/js/[name]_[chunkhash].js',
|
||||
chunkFilename: "assets/js/chunk_[name]_[id]_[chunkhash].js"
|
||||
path: path.join(__dirname, "dist", "data", "public"),
|
||||
publicPath: "/",
|
||||
filename: "assets/js/[name]_[chunkhash].js",
|
||||
chunkFilename: "assets/js/chunk_[name]_[id]_[chunkhash].js",
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: path.join(__dirname, 'client'),
|
||||
use: ['babel-loader'],
|
||||
exclude: /node_modules/
|
||||
test: path.join(__dirname, "client"),
|
||||
use: ["babel-loader"],
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
loader: 'html-loader'
|
||||
loader: "html-loader",
|
||||
},
|
||||
{
|
||||
test: /\.woff2$/,
|
||||
loader: 'woff-loader'
|
||||
loader: "woff-loader",
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
loaders: ['style-loader', 'css-loader', 'sass-loader']
|
||||
loaders: ["style-loader", "css-loader", "sass-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
loaders: ['style-loader', 'css-loader']
|
||||
loaders: ["style-loader", "css-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.(pdf|jpg|png|gif|svg|ico|woff|woff2|eot|ttf)$/,
|
||||
loader: "url-loader"
|
||||
loader: "url-loader",
|
||||
},
|
||||
{
|
||||
test: /[a-z]+\.worker\.js$/,
|
||||
loader: "worker-loader",
|
||||
options: { name: 'assets/js/[name]_[hash].js' }
|
||||
}
|
||||
]
|
||||
options: { name: "assets/js/[name]_[hash].js" },
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
|
||||
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
|
||||
}),
|
||||
new webpack.optimize.OccurrenceOrderPlugin(),
|
||||
new HtmlWebpackPlugin({
|
||||
template: path.join(__dirname, 'client', 'index.html'),
|
||||
template: path.join(__dirname, "client", "index.html"),
|
||||
inject: true,
|
||||
minify: {
|
||||
collapseWhitespace: true,
|
||||
removeComments: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
}
|
||||
},
|
||||
}),
|
||||
new CopyWebpackPlugin([
|
||||
{ from: "locales/*.json", to: "assets/" },
|
||||
|
|
@ -72,16 +72,16 @@ let config = {
|
|||
{ from: "assets/logo/*" },
|
||||
{ from: "assets/icons/*" },
|
||||
{ from: "assets/fonts/*" },
|
||||
], { context: path.join(__dirname, 'client') }),
|
||||
], { context: path.join(__dirname, "client") }),
|
||||
new CopyWebpackPlugin([
|
||||
{ from: "node_modules/pdfjs-dist/", to: "assets/vendor/pdfjs/2.6.347/"}
|
||||
{ from: "node_modules/pdfjs-dist/", to: "assets/vendor/pdfjs/2.6.347/" },
|
||||
]),
|
||||
//new BundleAnalyzerPlugin()
|
||||
]
|
||||
// new BundleAnalyzerPlugin()
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
config.plugins.push(new UglifyJSPlugin({
|
||||
sourceMap: false,
|
||||
extractComments: true,
|
||||
|
|
@ -91,17 +91,17 @@ if (process.env.NODE_ENV === 'production') {
|
|||
algorithm: "gzip",
|
||||
test: /\.js$|\.json$|\.html$|\.svg|\.ico$/,
|
||||
threshold: 0,
|
||||
minRatio: 0.8
|
||||
minRatio: 0.8,
|
||||
}));
|
||||
config.plugins.push(new CompressionPlugin({
|
||||
asset: "[path].br[query]",
|
||||
algorithm: "brotliCompress",
|
||||
test: /\.js$|\.json$|\.html$|\.svg|\.ico$/,
|
||||
threshold: 0,
|
||||
minRatio: 0.8
|
||||
minRatio: 0.8,
|
||||
}));
|
||||
} else {
|
||||
config.devtool = '#inline-source-map';
|
||||
config.devtool = "#inline-source-map";
|
||||
}
|
||||
|
||||
module.exports = config;
|
||||
|
|
|
|||
Loading…
Reference in a new issue