+
{this.props.children}
);
diff --git a/client/components/decorator.js b/client/components/decorator.js
index 99f47dc7..d90dec3e 100644
--- a/client/components/decorator.js
+++ b/client/components/decorator.js
@@ -3,7 +3,7 @@ import { Link } from 'react-router-dom';
import { Session } from '../model/';
import { Container, Loader, Icon } from '../components/';
-import { memory } from '../helpers/';
+import { memory, currentShare } from '../helpers/';
import '../pages/error.scss';
@@ -19,7 +19,7 @@ export function LoggedInOnly(WrappedComponent){
}
componentDidMount(){
- if(this.state.is_logged_in === false){
+ if(this.state.is_logged_in === false && currentShare() === null){
Session.currentUser().then((res) => {
if(res.is_authenticated === false){
this.props.error({message: "Authentication Required"});
@@ -38,7 +38,7 @@ export function LoggedInOnly(WrappedComponent){
}
render(){
- if(this.state.is_logged_in === true){
+ if(this.state.is_logged_in === true || currentShare() !== null){
return
;
}
return null;
@@ -65,7 +65,7 @@ export function ErrorPage(WrappedComponent){
const message = this.state.error.message || "There is nothing in here";
return (
-
+
home
diff --git a/client/helpers/cache.js b/client/helpers/cache.js
index f4fc36a8..bb363d1d 100644
--- a/client/helpers/cache.js
+++ b/client/helpers/cache.js
@@ -7,7 +7,7 @@ function Data(){
this._init();
}
-const DB_VERSION = 2;
+const DB_VERSION = 3;
Data.prototype._init = function(){
const request = indexedDB.open('nuage', DB_VERSION);
@@ -27,30 +27,34 @@ Data.prototype._setup = function(e){
let store;
let db = e.target.result;
- if(e.oldVersion == 1){
+ if(e.oldVersion == 1) {
// we've change the schema on v2 adding an index, let's flush
// to make sure everything will be fine
db.deleteObjectStore(this.FILE_PATH);
db.deleteObjectStore(this.FILE_CONTENT);
+ }else if(e.oldVersion == 2){
+ // we've change the primary key to be a (path,share)
+ db.deleteObjectStore(this.FILE_PATH);
+ db.deleteObjectStore(this.FILE_CONTENT);
}
- store = db.createObjectStore(this.FILE_PATH, {keyPath: "path"});
- store.createIndex("idx_path", "path", { unique: true });
+ store = db.createObjectStore(this.FILE_PATH, {keyPath: ["share", "path"]});
+ store.createIndex("idx_path", ["share", "path"], { unique: true });
- store = db.createObjectStore(this.FILE_CONTENT, {keyPath: "path"});
- store.createIndex("idx_path", "path", { unique: true });
+ store = db.createObjectStore(this.FILE_CONTENT, {keyPath: ["share", "path"]});
+ store.createIndex("idx_path", ["share", "path"], { unique: true });
}
/*
* Fetch a record using its path, can be whether a file path or content
*/
-Data.prototype.get = function(type, path){
+Data.prototype.get = function(type, key){
if(type !== this.FILE_PATH && type !== this.FILE_CONTENT) return Promise.reject({});
return this.db.then((db) => {
const tx = db.transaction(type, "readonly");
const store = tx.objectStore(type);
- const query = store.get(path);
+ const query = store.get(key);
return new Promise((done, error) => {
query.onsuccess = (e) => {
let data = query.result;
@@ -61,13 +65,13 @@ Data.prototype.get = function(type, path){
}).catch(() => Promise.resolve(null));
}
-Data.prototype.update = function(type, path, fn, exact = true){
+Data.prototype.update = function(type, key, fn, exact = true){
return this.db.then((db) => {
const tx = db.transaction(type, "readwrite");
const store = tx.objectStore(type);
- const range = exact === true? IDBKeyRange.only(path) : IDBKeyRange.bound(
- path,
- path+'\uFFFF',
+ const range = exact === true? IDBKeyRange.only(key) : IDBKeyRange.bound(
+ [key[0], key[1]],
+ [key[0], key[1]+'\uFFFF'],
false, true
);
const request = store.openCursor(range);
@@ -77,7 +81,7 @@ Data.prototype.update = function(type, path, fn, exact = true){
const cursor = event.target.result;
if(!cursor) return done(new_data);
new_data = fn(cursor.value || null);
- cursor.delete(cursor.value.path);
+ cursor.delete([key[0], cursor.value.path]);
store.put(new_data);
cursor.continue();
};
@@ -86,11 +90,11 @@ Data.prototype.update = function(type, path, fn, exact = true){
}
-Data.prototype.upsert = function(type, path, fn){
+Data.prototype.upsert = function(type, key, fn){
return this.db.then((db) => {
const tx = db.transaction(type, "readwrite");
const store = tx.objectStore(type);
- const query = store.get(path);
+ const query = store.get(key);
return new Promise((done, error) => {
query.onsuccess = (e) => {
const new_data = fn(query.result || null);
@@ -105,7 +109,7 @@ Data.prototype.upsert = function(type, path, fn){
}).catch(() => Promise.resolve(null));
}
-Data.prototype.add = function(type, path, data){
+Data.prototype.add = function(type, key, data){
if(type !== this.FILE_PATH && type !== this.FILE_CONTENT) return Promise.reject({});
return this.db.then((db) => {
@@ -119,28 +123,28 @@ Data.prototype.add = function(type, path, data){
}).catch(() => Promise.resolve(null));
}
-Data.prototype.remove = function(type, path, exact = true){
+Data.prototype.remove = function(type, key, exact = true){
return this.db.then((db) => {
const tx = db.transaction(type, "readwrite");
const store = tx.objectStore(type);
if(exact === true){
- const req = store.delete(path);
+ const req = store.delete(key);
return new Promise((done, err) => {
req.onsuccess = () => done();
req.onerror = err;
});
}else{
const request = store.openCursor(IDBKeyRange.bound(
- path,
- path+'\uFFFF',
+ [key[0], key[1]],
+ [key[0], key[1]+'\uFFFF'],
true, true
));
return new Promise((done, err) => {
request.onsuccess = function(event) {
const cursor = event.target.result;
if(cursor){
- cursor.delete(cursor.value.path);
+ cursor.delete([key[0], cursor.value.path]);
cursor.continue();
}else{
done();
@@ -151,12 +155,15 @@ Data.prototype.remove = function(type, path, exact = true){
}).catch(() => Promise.resolve(null));
}
-Data.prototype.fetchAll = function(fn, type = this.FILE_PATH, key = "/"){
+Data.prototype.fetchAll = function(fn, type = this.FILE_PATH, key){
return this.db.then((db) => {
const tx = db.transaction([type], "readonly");
const store = tx.objectStore(type);
const index = store.index("idx_path");
- const request = index.openCursor(IDBKeyRange.bound(key, key+("z".repeat(5000))));
+ const request = index.openCursor(IDBKeyRange.bound(
+ [key[0], key[1]],
+ [key[0], key[1]+("z".repeat(5000))]
+ ));
return new Promise((done, error) => {
request.onsuccess = function(event) {
diff --git a/client/helpers/common.js b/client/helpers/common.js
index ab39f90a..b5735bd8 100644
--- a/client/helpers/common.js
+++ b/client/helpers/common.js
@@ -2,3 +2,16 @@ 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");
+ $input.setAttribute("type", "text");
+ $input.setAttribute("style", "position: absolute; top:0;left:0;background:red")
+ $input.setAttribute("display", "none");
+ document.body.appendChild($input);
+ $input.value = str;
+ $input.select();
+ document.execCommand("copy");
+ $input.remove();
+}
diff --git a/client/helpers/index.js b/client/helpers/index.js
index 08b3cd84..1a04c97c 100644
--- a/client/helpers/index.js
+++ b/client/helpers/index.js
@@ -4,13 +4,13 @@ export { debounce, throttle } from './backpressure';
export { encrypt, decrypt } from './crypto';
export { event } from './events';
export { cache } from './cache';
-export { pathBuilder, basename, dirname, absoluteToRelative, filetype } from './path';
+export { pathBuilder, basename, dirname, absoluteToRelative, filetype, currentShare, appendShareToUrl } from './path';
export { memory } from './memory';
export { prepare } from './navigate';
export { invalidate, http_get, http_post, http_delete } from './ajax';
export { prompt, alert, confirm } from './popup';
export { notify } from './notify';
export { gid, randomString } from './random';
-export { leftPad } from './common';
+export { leftPad, copyToClipboard } from './common';
export { getMimeType } from './mimetype';
export { settings_get, settings_put } from './settings';
diff --git a/client/helpers/path.js b/client/helpers/path.js
index 974669bf..10b7760e 100644
--- a/client/helpers/path.js
+++ b/client/helpers/path.js
@@ -35,3 +35,19 @@ export function absoluteToRelative(from, to){
}
return r;
}
+
+export function currentShare(){
+ return new window.URL(location.href).searchParams.get("share") || ""
+}
+
+export function appendShareToUrl(link) {
+ let url = new window.URL(location.href);
+ let share = url.searchParams.get("share");
+
+ if(share){
+ url = new window.URL(location.origin + link)
+ url.searchParams.set("share", share)
+ return url.pathname + url.search
+ }
+ return link;
+}
diff --git a/client/model/files.js b/client/model/files.js
index ec6ad0ff..b59d8807 100644
--- a/client/model/files.js
+++ b/client/model/files.js
@@ -1,7 +1,7 @@
"use strict";
import { http_get, http_post, prepare, basename, dirname, pathBuilder } from '../helpers/';
-import { filetype } from '../helpers/';
+import { filetype, currentShare, appendShareToUrl } from '../helpers/';
import { Observable } from 'rxjs/Observable';
import { cache } from '../helpers/';
@@ -46,16 +46,17 @@ class FileSystem{
}
_ls_from_http(path){
- const url = '/api/files/ls?path='+prepare(path);
+ let url = appendShareToUrl('/api/files/ls?path='+prepare(path));
+
return http_get(url).then((response) => {
- return cache.upsert(cache.FILE_PATH, path, (_files) => {
+ return cache.upsert(cache.FILE_PATH, [currentShare(), path], (_files) => {
let store = Object.assign({
+ share: currentShare(),
path: path,
results: null,
access_count: 0,
metadata: null
}, _files);
- store.access_count += 1;
store.results = response.results || [];
store.results = store.results.map((f) => {
f.path = pathBuilder(path, f.name, f.type);
@@ -64,6 +65,8 @@ class FileSystem{
store.metadata = response.metadata;
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';
@@ -101,44 +104,47 @@ class FileSystem{
}
_ls_from_cache(path, _record_access = false){
- if(_record_access === false){
- return cache.get(cache.FILE_PATH, path).then((response) => {
- if(!response || !response.results) return null;
- if(this.current_path === path){
- this.obs && this.obs.next({
- status: 'ok',
- results: response.results,
- metadata: response.metadata
+ return cache.get(cache.FILE_PATH, [currentShare(), path]).then((response) => {
+ if(!response || !response.results) return null;
+ if(this.current_path === path){
+ this.obs && this.obs.next({
+ status: 'ok',
+ results: response.results,
+ metadata: response.metadata
+ });
+ }
+ return response;
+ }).then((e) => {
+ requestAnimationFrame(() => {
+ if(_record_access === true){
+ cache.upsert(cache.FILE_PATH, [currentShare(), path], (response) => {
+ if(!response || !response.results) return null;
+ if(this.current_path === path){
+ this.obs && this.obs.next({
+ status: 'ok',
+ results: response.results,
+ metadata: response.metadata
+ });
+ }
+ response.last_access = new Date();
+ response.access_count += 1;
+ return response;
});
}
- return response;
});
- }else{
- return cache.upsert(cache.FILE_PATH, path, (response) => {
- if(!response || !response.results) return null;
- if(this.current_path === path){
- this.obs && this.obs.next({
- status: 'ok',
- results: response.results,
- metadata: response.metadata
- });
- }
- response.last_access = new Date();
- response.access_count += 1;
- return response;
- });
- }
+ return Promise.resolve(e);
+ });
}
rm(path){
- const url = '/api/files/rm?path='+prepare(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, path)
- .then(cache.remove(cache.FILE_CONTENT, path, false))
- .then(cache.remove(cache.FILE_PATH, dirname(path), false))
+ 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))
})
@@ -150,14 +156,15 @@ class FileSystem{
}
cat(path){
- const url = '/api/files/cat?path='+prepare(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'});
}
- return cache.upsert(cache.FILE_CONTENT, path, (response) => {
+ return cache.upsert(cache.FILE_CONTENT, [currentShare(), path], (response) => {
let file = response? response : {
+ share: currentShare(),
path: path,
last_update: null,
last_access: null,
@@ -173,7 +180,7 @@ class FileSystem{
.catch((err) => {
if(err.code === 'BINARY_FILE') return Promise.reject(err);
- return cache.update(cache.FILE_CONTENT, path, (response) => {
+ return cache.update(cache.FILE_CONTENT, [currentShare(), path], (response) => {
response.last_access = new Date();
response.access_count += 1;
return response;
@@ -184,12 +191,12 @@ class FileSystem{
});
}
url(path){
- const url = '/api/files/cat?path='+prepare(path);
+ const url = appendShareToUrl('/api/files/cat?path='+prepare(path));
return Promise.resolve(url);
}
save(path, file){
- const url = '/api/files/cat?path='+prepare(path);
+ const url = appendShareToUrl('/api/files/cat?path='+prepare(path));
let formData = new window.FormData();
formData.append('file', file, "test");
return this._replace(path, 'loading')
@@ -207,7 +214,7 @@ class FileSystem{
}
mkdir(path, step){
- const url = '/api/files/mkdir?path='+prepare(path),
+ const url = appendShareToUrl('/api/files/mkdir?path='+prepare(path)),
origin_path = pathBuilder(this.current_path, basename(path), 'directoy'),
destination_path = path;
@@ -240,8 +247,9 @@ class FileSystem{
.then(() => {
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, destination_path, {
+ .then(() => cache.add(cache.FILE_PATH, [currentShare(), destination_path], {
path: destination_path,
+ share: currentShare(),
results: [],
access_count: 0,
last_access: null,
@@ -310,12 +318,12 @@ class FileSystem{
function query(){
if(file){
- const url = '/api/files/cat?path='+prepare(path);
+ const url = appendShareToUrl('/api/files/cat?path='+prepare(path));
let formData = new window.FormData();
formData.append('file', file);
return http_post(url, formData, 'multipart');
}else{
- const url = '/api/files/touch?path='+prepare(path);
+ const url = appendShareToUrl('/api/files/touch?path='+prepare(path));
return http_get(url);
}
}
@@ -331,7 +339,7 @@ class FileSystem{
}
mv(from, to){
- const url = '/api/files/mv?from='+prepare(from)+"&to="+prepare(to),
+ const url = appendShareToUrl('/api/files/mv?from='+prepare(from)+"&to="+prepare(to)),
origin_path = from,
destination_path = to;
@@ -344,11 +352,11 @@ class FileSystem{
.then(() => this._replace(destination_path, null, 'loading'))
.then(() => this._refresh(origin_path, destination_path))
.then(() => {
- cache.update(cache.FILE_PATH, origin_path, (data) => {
+ cache.update(cache.FILE_PATH, [currentShare(), origin_path], (data) => {
data.path = data.path.replace(origin_path, destination_path);
return data;
}, false);
- cache.update(cache.FILE_CONTENT, origin_path, (data) => {
+ cache.update(cache.FILE_CONTENT, [currentShare(), origin_path], (data) => {
data.path = data.path.replace(origin_path, destination_path);
return data;
}, false);
@@ -369,7 +377,7 @@ class FileSystem{
if(value.access_count >= 1 && value.path !== "/"){
data.push(value);
}
- }).then(() => {
+ }, cache.FILE_PATH, [currentShare(), "/"]).then(() => {
return Promise.resolve(
data
.sort((a,b) => a.access_count > b.access_count? -1 : 1)
@@ -389,8 +397,9 @@ class FileSystem{
});
function update_cache(result){
- return cache.upsert(cache.FILE_CONTENT, path, (response) => {
+ return cache.upsert(cache.FILE_CONTENT, [currentShare(), path], (response) => {
if(!response) response = {
+ share: currentShare(),
path: path,
last_access: null,
last_update: null,
@@ -413,7 +422,7 @@ class FileSystem{
}
_replace(path, icon, icon_previous){
- return cache.update(cache.FILE_PATH, dirname(path), function(res){
+ 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; }
@@ -425,7 +434,7 @@ class FileSystem{
});
}
_add(path, icon){
- return cache.upsert(cache.FILE_PATH, dirname(path), function(res){
+ return cache.upsert(cache.FILE_PATH, [currentShare(), dirname(path)], function(res){
if(!res || !res.results){
res = {
path: dirname(path),
@@ -445,7 +454,7 @@ class FileSystem{
});
}
_remove(path, previous_icon){
- return cache.update(cache.FILE_PATH, dirname(path), function(res){
+ 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;
@@ -461,5 +470,4 @@ class FileSystem{
}
}
-
export const Files = new FileSystem();
diff --git a/client/model/share.js b/client/model/share.js
index 68108a55..6a3c727c 100644
--- a/client/model/share.js
+++ b/client/model/share.js
@@ -1,4 +1,4 @@
-import { http_get, http_post, http_delete } from '../helpers/';
+import { http_get, http_post, http_delete, appendShareToUrl } from '../helpers/';
class ShareModel {
constructor(){}
@@ -25,19 +25,20 @@ class ShareModel {
}
upsert(obj){
- const url = `/api/share/${obj.id}`
+ const url = appendShareToUrl(`/api/share/${obj.id}`)
const data = Object.assign({}, obj);
delete data.role;
return http_post(url, data);
}
remove(id){
- const url = `/api/share/${id}`;
+ const url = appendShareToUrl(`/api/share/${id}`);
return http_delete(url);
}
proof(id, data){
- // TODO
+ const url = `/api/share/${id}/proof`;
+ return http_post(url, data).then((res) => res.result);
}
}
diff --git a/client/pages/error.scss b/client/pages/error.scss
index 18a7b582..26a4bbb7 100644
--- a/client/pages/error.scss
+++ b/client/pages/error.scss
@@ -5,15 +5,17 @@
flex-direction: column;
h1{margin: 5px 0; font-size: 3.1em;}
- h2{margin: 10px 0; font-weight: normal; opacity: 0.9;}
+ h2{margin: 10px 0; font-weight: normal; opacity: 0.9; font-weight: 100;}
p{font-style: italic;}
a{border-bottom: 1px dashed;}
}
.backnav {
+ font-weight: 100;
.component_icon {
- height: 25px;
- margin-right: -2px;
+ height: 23px;
+ margin-right: -3px;
+ vertical-align: middle;
}
line-height: 25px;
}
diff --git a/client/pages/filespage.helper.js b/client/pages/filespage.helper.js
index 96e35946..0e90c7de 100644
--- a/client/pages/filespage.helper.js
+++ b/client/pages/filespage.helper.js
@@ -1,7 +1,7 @@
import React from 'react';
import { Files } from '../model/';
-import { notify, alert } from '../helpers/';
+import { notify, alert, currentShare } from '../helpers/';
import Path from 'path';
import Worker from "../worker/search.worker.js";
import { Observable } from "rxjs/Observable";
@@ -371,11 +371,12 @@ export const onUpload = function(path, e){
-const worker = new Worker();9
+const worker = new Worker();
export const onSearch = (keyword, path = "/") => {
worker.postMessage({
action: "search::find",
path: path,
+ share: currentShare(),
keyword: keyword
});
return new Observable((obs) => {
diff --git a/client/pages/filespage.js b/client/pages/filespage.js
index 8178595d..f2763cf0 100644
--- a/client/pages/filespage.js
+++ b/client/pages/filespage.js
@@ -244,6 +244,9 @@ export class FilesPage extends React.Component {
+
+ THIS IS A MENUBAR
+
);
diff --git a/client/pages/filespage.scss b/client/pages/filespage.scss
index 1c755186..c9f58d90 100644
--- a/client/pages/filespage.scss
+++ b/client/pages/filespage.scss
@@ -25,6 +25,13 @@
}
}
}
+
+ .sidebar{
+ width: 250px;
+ transition: width 0.3s ease;
+ background: var(--light);
+ &.close{width: 0;}
+ }
}
.scroll-y{
diff --git a/client/pages/filespage/frequently_access.js b/client/pages/filespage/frequently_access.js
index 1f60fbb1..07935c4a 100644
--- a/client/pages/filespage/frequently_access.js
+++ b/client/pages/filespage/frequently_access.js
@@ -17,11 +17,12 @@ export class FrequentlyAccess extends React.Component {
return (