Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add folder structure to select files in select-file option #368

Merged
merged 15 commits into from
Aug 4, 2017
Merged
8 changes: 6 additions & 2 deletions css/modals.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 35 additions & 11 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -1030,6 +1031,39 @@ <h4>{{ 'Add Downloads By Metalinks' | translate }}</h4>
</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">
Expand All @@ -1039,17 +1073,7 @@ <h4>{{ 'Choose files to start download for' | translate }}</h4>

<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>
Expand Down
31 changes: 31 additions & 0 deletions js/ctrls/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
126 changes: 126 additions & 0 deletions js/directives/fileselect.js
Original file line number Diff line number Diff line change
@@ -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)));
}
}
};
}
]);
2 changes: 1 addition & 1 deletion js/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down