Skip to content

Commit

Permalink
Merge pull request #489 from Peergos/feat/folder-picker
Browse files Browse the repository at this point in the history
add a folder picker
  • Loading branch information
ianopolous committed Apr 21, 2023
2 parents 72f531c + 1bd5ac5 commit 4102fa2
Show file tree
Hide file tree
Showing 5 changed files with 309 additions and 4 deletions.
7 changes: 7 additions & 0 deletions assets/apps/sandbox/sw-sandbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ let dataRequest = apiRequest + "/data/";
let formRequest = apiRequest + "/form/";
let chatRequest = apiRequest + "/chat/";
let saveRequest = apiRequest + "/save/";
let foldersRequest = apiRequest + "/folders/";
let printRequest = apiRequest + "/print/";
let installAppRequest = apiRequest + "/install-app/";

Expand Down Expand Up @@ -377,6 +378,12 @@ function appFetch(event) {
}
restFilePath = restFilePath.substring(saveRequest.length);
api = saveRequest;
} else if (filePath.startsWith(foldersRequest)) {
if (method != 'GET') {
return new Response('Unknown folders action!', {status: 400})
}
restFilePath = restFilePath.substring(foldersRequest.length);
api = foldersRequest;
} else if (filePath.startsWith(printRequest)) {
if (!(method == 'POST')) {
return new Response('Unknown print action!', {status: 400})
Expand Down
196 changes: 196 additions & 0 deletions src/components/picker/FolderPicker.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
<template>
<transition name="modal">
<div class="modal-mask" @click="close">
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<div @click.stop class="folder-picker-container">
<span @click="close" tabindex="0" v-on:keyup.enter="close" aria-label="close" class="close">&times;</span>
<div class="modal-header">
<h2>Folder Picker</h2>
</div>
<div class="modal-body">
<spinner v-if="showSpinner"></spinner>
<div class="folder-picker-view" class="scroll-style">
<ul>
<TreeItem class="item" :model="treeData" :selectFolder_func="selectFolder"></TreeItem>
</ul>
</div>
<h4>Selected:</h4>
<div v-if="selectedFoldersList.length == 0">
No folders selected...
</div>
<div v-if="selectedFoldersList.length != 0">
<div class="selected-folders-view" class="scroll-style">
<ul>
<li v-for="selectedFolder in selectedFoldersList">
{{ selectedFolder }}
</li>
</ul>
</div>
</div>
<div class="flex-line-item">
<div>
<button class="btn btn-success" style = "width:80%" @click="foldersSelected()">Done</button>
</div>
</div>
</div>
</div>
</div>
</transition>
</template>

<script>
const TreeItem = require("TreeItem.vue");
module.exports = {
components: {
TreeItem
},
data: function() {
return {
showSpinner: true,
spinnerMessage: 'Loading folders...',
treeData: {},
selectedFoldersList: [],
walkCounter: 0,
}
},
props: ['baseFolder', 'selectedFolder_func'],
mixins:[],
computed: {
...Vuex.mapState([
'context',
]),
},
created: function() {
this.loadFolders();
},
methods: {
loadFolders: function() {
var that = this;
let path = this.baseFolder + "/";
this.walkCounter = 0;
let baseOfFolderTree = {};
this.context.getByPath(path).thenApply(function(dir){
that.walk(dir.get(), path, baseOfFolderTree, () => that.ready(baseOfFolderTree));
}).exceptionally(function(throwable) {
that.showSpinner = false;
this.spinnerMessage = 'Unable to load folders...';
throwable.printStackTrace();
});
},
ready: function(baseOfFolderTree) {
this.treeData = baseOfFolderTree;
this.showSpinner = false;
this.spinnerMessage = '';
},
walk: function(file, path, currentTreeData, cb) {
let fileProperties = file.getFileProperties();
if (fileProperties.isHidden)
return;
currentTreeData.path = path.substring(0, path.length -1);
currentTreeData.children = [];
let that = this;
if (fileProperties.isDirectory) {
that.walkCounter++;
if (that.walkCounter == 1) {
that.showSpinner = true;
}
file.getChildren(that.context.crypto.hasher, that.context.network).thenApply(function(children) {
let arr = children.toArray();
let size = arr.length;
if (size == 0) {
that.walkCounter--;
if (that.walkCounter == 0) {
cb();
}
}
arr.forEach(function(child, index){
let childProps = child.getFileProperties();
let newPath = childProps.isDirectory ? path + child.getFileProperties().name + '/' : path;
if (childProps.isDirectory && !childProps.isHidden) {
let node = {};
currentTreeData.children.push(node);
that.walk(child, newPath, node, cb);
}
if (index == size - 1) {
that.walkCounter--;
if (that.walkCounter == 0) {
cb();
}
}
});
});
}
},
close: function () {
this.selectedFolder_func([]);
},
selectFolder: function (folderName, add) {
if (add) {
this.selectedFoldersList.push(folderName);
} else {
let index = this.selectedFoldersList.findIndex(v => v === folderName);
if (index > -1) {
this.selectedFoldersList.splice(index, 1);
}
}
},
foldersSelected: function() {
let selectedFolders = this.selectedFoldersList;
// remove duplicates (one folder includes another)
let dedupList = [];
for(var i = 0; i < selectedFolders.length; i++) {
let folder = selectedFolders[i] + '/';
var isDuplicated = false;
for(var j = 0; j < selectedFolders.length; j++) {
if (i != j) {
let anotherFolder = selectedFolders[j];
if (anotherFolder.startsWith(folder)) {
isDuplicated = true;
break;
}
}
}
if (! isDuplicated) {
dedupList.push(selectedFolders[i]);
}
}
this.selectedFolder_func(dedupList);
}
}
}
</script>

<style>
.folder-picker-container {
height: 100%;
width: 600px;
overflow-y: auto;
position: fixed;
left: 50%;
transform: translate(-50%, 0);
padding: 20px 30px;
background-color: var(--bg);
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0,0,0,.33);
transition: all .3s ease;
}
.folder-picker-view {
font-size: 1.3em;
}
.selected-folders-view {
font-size: 1.3em;
}
.item {
cursor: pointer;
line-height: 1.5;
}
.bold {
font-weight: bold;
}
.scroll-style {
max-height: 250px;
overflow-y: scroll;
}
</style>
61 changes: 61 additions & 0 deletions src/components/picker/TreeItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<template>
<ul style="list-style-type: none">
<label class="checkbox__group">
<input type="checkbox" v-bind:value="model.path" @click="addChild">
<span class="checkmark"></span>
<div :class="{ bold: isFolder }" @click="toggle">
{{ displayFolderName(model.path) }}
<span v-if="isFolder">[{{ isOpen ? '-' : '+' }}]</span>
</div>
</label>
<li v-show="isOpen" v-if="isFolder" style="list-style-type: none">
<TreeItem
class="item"
v-for="model in model.children"
:model="model" :selectFolder_func="selectFolder_func" >
</TreeItem>
</li>
</ul>
</template>

<script>
module.exports = {
name: 'TreeItem', // necessary for self-reference
props: {
model: Object,
selectFolder_func: Function
},
data() {
return {
isOpen: false,
}
},
computed: {
isFolder() {
return this.model.children && this.model.children.length
}
},
methods: {
displayFolderName(folderName) {
if (folderName == null) {
return "";
} else {
let index = folderName.lastIndexOf('/');
return folderName.substring(index + 1);
}
},
toggle(e) {
e.preventDefault()
if (this.isFolder) {
this.isOpen = !this.isOpen
}
},
addChild(selectedFolder) {
this.selectFolder_func(selectedFolder.currentTarget.value, selectedFolder.currentTarget.checked);
}
}
}
</script>

<style>
</style>
45 changes: 43 additions & 2 deletions src/components/sandbox/AppSandbox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
:friendNames="friendnames"
:updateChat="updateChat">
</AddToChat>
<FolderPicker
v-if="showFolderPicker"
:baseFolder="folderPickerBaseFolder" :selectedFolder_func="selectedFoldersFromPicker">
</FolderPicker>
<AppPrompt
v-if="showPrompt"
@hide-prompt="closePrompt()"
Expand Down Expand Up @@ -118,6 +122,10 @@ module.exports = {
appInstallPropsFile: null,
appInstallFolder: '',
currentAppName: null,
showFolderPicker: false,
folderPickerBaseFolder: "",
selectedFolders: [],
selectedFolderStems: []
}
},
computed: {
Expand Down Expand Up @@ -554,6 +562,8 @@ module.exports = {
} else {
that.buildResponse(headerFunc(), null, that.ACTION_FAILED);
}
} else if (api =='/peergos-api/v0/folders/') {
that.handleFolderPickerRequest(headerFunc, path, apiMethod, data, hasFormData, params);
} else {
var bytes = convertToByteArray(new Int8Array(data));
if (apiMethod == 'GET') {
Expand All @@ -569,7 +579,9 @@ module.exports = {
let prefix = !this.browserMode
&& !(path == this.appPath || (this.isAppPathAFolder && path.startsWith(this.appPath)))
&& !(this.appPath.length > 0 && !this.isAppPathAFolder && path.startsWith(that.getPath))
&& !path.startsWith(that.apiRequest + '/data') ? '/assets' : '';
&& !path.startsWith(that.apiRequest + '/data')
&& !(this.isSelectedFolder(path))
? '/assets' : '';
if (this.browserMode) {
that.handleBrowserRequest(headerFunc, path, params, isFromRedirect, isNavigate);
} else {
Expand Down Expand Up @@ -692,6 +704,24 @@ module.exports = {
closePrompt() {
this.showPrompt = false;
},
handleFolderPickerRequest: function(headerFunc, path, apiMethod, data, hasFormData, params) {
let that = this;
if (apiMethod == 'GET') {
this.folderPickerBaseFolder = "/" + this.context.username;
this.selectedFoldersFromPicker = function (chosenFolders) {
that.selectedFolders = chosenFolders;
that.selectedFolderStems = chosenFolders.map(n => n + '/');
that.showFolderPicker = false;
let encoder = new TextEncoder();
let data = encoder.encode(JSON.stringify(chosenFolders));
that.buildResponse(headerFunc(), data, that.UPDATE_SUCCESS);
}.bind(this);
this.showFolderPicker = true;
} else {
that.showError("App attempted unexpected action: " + apiMethod);
that.buildResponse(headerFunc(), null, that.ACTION_FAILED);
}
},
validateRange: function(from, to) {
if (from == null || to == null) {
return false;
Expand Down Expand Up @@ -1369,14 +1399,25 @@ module.exports = {
context.stream(seekHi, seekLo, seekLength);
}
},
isSelectedFolder(folderPath) {
if (this.permissionsMap.get(this.PERMISSION_READ_CHOSEN_FOLDER) != null) {
if (this.selectedFolders.includes(folderPath)) {
return true;
} else {
return this.selectedFolderStems.filter(e => e.startsWith(folderPath)).length > 0;
}
} else {
return false;
}
},
expandFilePath(filePath, isFromRedirect) {
if (this.browserMode) {
if (filePath.startsWith(this.currentPath) || isFromRedirect) {
return filePath;
} else {
return this.currentPath.substring(0, this.currentPath.length -1) + filePath;
}
} else if (this.appPath.length > 0 && filePath.startsWith(this.getPath)) {
} else if ( (this.appPath.length > 0 && filePath.startsWith(this.getPath)) || this.isSelectedFolder(filePath)) {
return filePath;
} else if (this.currentProps != null) { //running in-place
let filePathWithoutSlash = filePath.startsWith('/') ? filePath.substring(1) : filePath;
Expand Down
4 changes: 2 additions & 2 deletions src/peergos.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var CodeEditor = require('./components/code-editor');
var Downloader = require('./components/downloader');
var Error = require('./components/error');
var Fingerprint = require('./components/fingerprint');
var FolderPicker = require('./components/picker/FolderPicker.vue');
var Hex = require('./components/viewers/hex.vue');
var Identity = require('./components/identity-proof-viewer.vue');
var Message = require('./components/message');
Expand Down Expand Up @@ -50,8 +51,7 @@ Vue.component('App', Vue.extend(App));
Vue.component('AppButton', Vue.extend(AppButton));
Vue.component('AppIcon', Vue.extend(AppIcon));
Vue.component('AppModal', Vue.extend(AppModal));


Vue.component('FolderPicker', Vue.extend(FolderPicker));

Vue.directive('focus', {
inserted: function (el) {
Expand Down

0 comments on commit 4102fa2

Please sign in to comment.