Merge pull request #368 from vitaminac/master
add folder structure to select files in select-file option
This commit is contained in:
commit
10b5ac2f3a
|
@ -34,11 +34,15 @@
|
|||
width: 80px;
|
||||
}
|
||||
|
||||
.selectFiles > div > .control-label {
|
||||
.selectFiles div .control-label {
|
||||
font-weight: normal;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.selectFiles > div > .controls {
|
||||
.selectFiles div .controls {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.selectFiles div.recursivedir {
|
||||
width: 100%;
|
||||
}
|
46
index.html
46
index.html
|
@ -41,6 +41,7 @@
|
|||
|
||||
<script src="js/directives/chunkbar.js"></script>
|
||||
<script src="js/directives/dgraph.js"></script>
|
||||
<script src="js/directives/fileselect.js"></script>
|
||||
<script src="js/directives/fselect.js"></script>
|
||||
<script src="js/directives/textarea.js"></script>
|
||||
|
||||
|
@ -1030,6 +1031,39 @@
|
|||
</script>
|
||||
<!-- }}} -->
|
||||
|
||||
<!-- {{{ select files checkbox modal -->
|
||||
<script type="text/ng-template" id="selectFilesCheckBox.html">
|
||||
<div ng-repeat="(folderName,folder) in files.dirs">
|
||||
<!--recursive folder-->
|
||||
|
||||
<div class="controls">
|
||||
<!--click to toggle show the subfolders and files-->
|
||||
<div class="checkbox" data-ng-click="folder.show=!folder.show">
|
||||
<!-- The value of indeterminate="" can be bound to any angular expression -->
|
||||
<input type="checkbox" data-ng-model="folder.selected" data-ng-click="$event.stopPropagation()" indeterminate/>{{folderName}}
|
||||
<span ng-show="!folder.show" class="control-label">{{ 'click the text to expand the folder' | translate }}</span>
|
||||
<span ng-show="folder.show" class="control-label">{{ 'click the text to collapse the folder' | translate }}</span>
|
||||
</div>
|
||||
<div ng-show="folder.show" class="form-group selectFiles recursivedir" ng-include="'selectFilesCheckBox.html'" ng-init="files=folder">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-repeat="file in files.files">
|
||||
<!--files-->
|
||||
<label class="control-label">{{ 'Select to download' | translate }}</label>
|
||||
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-ng-model="file.selected" indeterminate="false"/>{{file.relpath}}
|
||||
</label>
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
</div>
|
||||
</script>
|
||||
<!-- }}} -->
|
||||
|
||||
<!-- {{{ select file modal -->
|
||||
<script type="text/ng-template" id="selectFiles.html">
|
||||
<div class="modal-header">
|
||||
|
@ -1039,17 +1073,7 @@
|
|||
|
||||
<form class="form-horizontal modal-body">
|
||||
<fieldset>
|
||||
<div class="form-group selectFiles">
|
||||
<div ng-repeat="file in selectFiles.files">
|
||||
<label class="control-label">{{ 'Select to download' | translate }}</label>
|
||||
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" ng-model="file.selected"/>{{file.relpath}}
|
||||
</label>
|
||||
</div>
|
||||
<br /><br />
|
||||
</div>
|
||||
<div class="form-group selectFiles" ng-include="'selectFilesCheckBox.html'" ng-init="files=selectFiles.groupedFiles">
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
|
|
@ -123,6 +123,37 @@ angular
|
|||
var self = this;
|
||||
|
||||
this.files = _.cloneDeep(files);
|
||||
var groupFiles = function (files) {
|
||||
// sort files alphabetically
|
||||
files.sort(function (a, b) {
|
||||
if (a.relpath < b.relpath) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
function OrganizedFolder () {
|
||||
this.dirs = {};
|
||||
this.files = [];
|
||||
this.show = false;
|
||||
this.selected = true;
|
||||
}
|
||||
var folder = new OrganizedFolder(), tmp;
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
tmp = folder;
|
||||
var str = files[i].relpath;
|
||||
var arr = str.split("/");
|
||||
for (var j = 0; j < arr.length - 1; j++) {
|
||||
if (!tmp.dirs[arr[j]]) {
|
||||
tmp.dirs[arr[j]] = new OrganizedFolder();
|
||||
}
|
||||
tmp = tmp.dirs[arr[j]];
|
||||
}
|
||||
tmp.files.push(files[i]);
|
||||
}
|
||||
return folder;
|
||||
};
|
||||
this.groupedFiles = groupFiles(this.files);
|
||||
this.inst = $modal.open({
|
||||
templateUrl: "selectFiles.html",
|
||||
scope: scope,
|
||||
|
|
126
js/directives/fileselect.js
Normal file
126
js/directives/fileselect.js
Normal file
|
@ -0,0 +1,126 @@
|
|||
// watches changes in the file upload control (input[file]) and
|
||||
// puts the files selected in an attribute
|
||||
var app = angular.module("webui.directives.fileselect", ["webui.services.deps"]);
|
||||
app.directive("indeterminate", [
|
||||
"$parse", function syncInput (parse) {
|
||||
return {
|
||||
require : "ngModel",
|
||||
// Restrict the directive so it can only be used as an attribute
|
||||
restrict : "A",
|
||||
|
||||
link : function link (scope, elem, attr, ngModelCtrl) {
|
||||
// Whenever the bound value of the attribute changes we update
|
||||
// use upward emit notification for change to prevent the performance penalty bring by $scope.$watch
|
||||
var getter = parse(attr["ngModel"]);
|
||||
// var setter = getter.assign;
|
||||
var children = []; // cache children input
|
||||
var cacheSelectedSubInputNumber = 0;
|
||||
var cacheNoSelectedSubInputNumber = 0;
|
||||
var get = function () {
|
||||
return getter(scope);
|
||||
};
|
||||
// the internal 'indeterminate' flag on the attached dom element
|
||||
var setIndeterminateState = function (newValue) {
|
||||
elem.prop("indeterminate", newValue);
|
||||
};
|
||||
var setModelValueWithSideEffect = function (newVal) { // will cause to emit corresponding events
|
||||
ngModelCtrl.$setViewValue(newVal);
|
||||
ngModelCtrl.$render();
|
||||
};
|
||||
var passIfIsLeafChild = function (callback) { // ensure to execute callback only when this input has one or more subinputs
|
||||
return function () {
|
||||
if (children.length > 0) {
|
||||
callback.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
};
|
||||
var passIfNotIsLeafChild = function (callback) { // ensure to execute callback only when this input hasn't any subinput
|
||||
return function () {
|
||||
if (children.length === 0) {
|
||||
callback.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
};
|
||||
var passThroughThisScope = function (callback) { // pass through the event from the scope where they sent
|
||||
return function (event) {
|
||||
if (event.targetScope !== event.currentScope) {
|
||||
return callback.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
};
|
||||
var passIfIsIndeterminate = function (callback) { // pass through the event when this input is indeterminate
|
||||
return function () {
|
||||
if (!elem.prop("indeterminate")) {
|
||||
return callback.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
};
|
||||
/* var catchEventOnlyOnce = function (callback) { // only fire once, and stop event's propagation
|
||||
return function (event) {
|
||||
callback.apply(this, arguments);
|
||||
return event.stopPropagation();
|
||||
};
|
||||
}; */
|
||||
if (attr["indeterminate"] && parse(attr["indeterminate"]).constant) {
|
||||
setIndeterminateState(scope.$eval(attr["indeterminate"])); // set to default value (set in template)
|
||||
}
|
||||
if (attr["indeterminate"] && parse(attr["indeterminate"]).constant && !scope.$eval(attr["indeterminate"])) {
|
||||
// when this input wont have subinput, they will only receive parent change and emit child change event
|
||||
ngModelCtrl.$viewChangeListeners.push(passIfNotIsLeafChild(function () {
|
||||
scope.$emit("childSelectedChange", get()); // notifies parents to change their state
|
||||
}));
|
||||
scope.$on("ParentSelectedChange", passThroughThisScope(passIfNotIsLeafChild(
|
||||
function (event, newVal) {
|
||||
setModelValueWithSideEffect(newVal); // set value to parent's value; this will cause listener to emit childChange event; this won't be a infinite loop
|
||||
})));
|
||||
// init first time and only once
|
||||
scope.$emit("i'm child input", get); // traverses upwards toward the root scope notifying the listeners for keep reference to this input's value
|
||||
scope.$emit("childSelectedChange", get()); // force emitted, and force the parent change their state base on children at first time
|
||||
} else {
|
||||
// establish parent-child's relation
|
||||
// listen for the child emitted token
|
||||
scope.$on("i'm child input", passThroughThisScope( // can't apply pass passIfIsLeafChild, at beginning all has not child input
|
||||
function (event, child) {
|
||||
children.push(child);
|
||||
})
|
||||
);
|
||||
var updateBaseOnChildrenState = function (event, newChildValue) {
|
||||
if ((cacheSelectedSubInputNumber + cacheNoSelectedSubInputNumber) !== children.length) {
|
||||
// cache childern state
|
||||
cacheSelectedSubInputNumber = 0;
|
||||
cacheNoSelectedSubInputNumber = 0;
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
if (children[i]()) {
|
||||
cacheSelectedSubInputNumber += 1;
|
||||
} else {
|
||||
cacheNoSelectedSubInputNumber += 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no need for recalculated children state
|
||||
// just make a few change to cache value
|
||||
if (newChildValue) {
|
||||
cacheSelectedSubInputNumber++;
|
||||
cacheNoSelectedSubInputNumber--;
|
||||
} else {
|
||||
cacheSelectedSubInputNumber--;
|
||||
cacheNoSelectedSubInputNumber++;
|
||||
}
|
||||
}
|
||||
var allSelected = (cacheNoSelectedSubInputNumber === 0); // if doesn't has any no selected input
|
||||
var anySeleted = (cacheSelectedSubInputNumber > 0);
|
||||
setIndeterminateState(allSelected !== anySeleted); // if at least one is selected, but not all then set input property indeterminate to true
|
||||
setModelValueWithSideEffect(allSelected); // change binding model value and trigger onchange event
|
||||
};
|
||||
// is not leaf input, Only receive child change and parent change event
|
||||
ngModelCtrl.$viewChangeListeners.push(passIfIsLeafChild(passIfIsIndeterminate(function () {
|
||||
// emit when input property indeterminate is false, prevent recursively emitting event from parent to children, children to parent
|
||||
scope.$broadcast("ParentSelectedChange", get());
|
||||
})));
|
||||
// reset input state base on children inputs
|
||||
scope.$on("childSelectedChange", passThroughThisScope(passIfIsLeafChild(updateBaseOnChildrenState)));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
|
@ -4,7 +4,7 @@ var webui = angular.module('webui', [
|
|||
'webui.services.modals', 'webui.services.alerts',
|
||||
'webui.services.settings', 'webui.services.settings.filters',
|
||||
'webui.filters.bytes','webui.filters.url',
|
||||
'webui.directives.chunkbar', 'webui.directives.dgraph', 'webui.directives.fselect',
|
||||
'webui.directives.chunkbar', 'webui.directives.dgraph', 'webui.directives.fselect', "webui.directives.fileselect",
|
||||
'webui.ctrls.download', 'webui.ctrls.nav', 'webui.ctrls.modal', 'webui.ctrls.alert',
|
||||
'webui.ctrls.props',
|
||||
// external deps
|
||||
|
|
Loading…
Reference in New Issue
Block a user