diff --git a/package.json b/package.json
index 68f8bf6..bfbb6fb 100644
--- a/package.json
+++ b/package.json
@@ -39,7 +39,6 @@
"angular": "^1.5.0",
"angular-animate": "^1.5.0",
"angular-aria": "^1.5.0",
- "angular-breadcrumb": "^0.4.1",
"angular-material": "^1.0.5",
"angular-messages": "^1.5.0",
"angular-translate": "^2.9.2",
@@ -49,6 +48,7 @@
"javascript-natural-sort": "^0.7.1",
"lodash": "^4.5.1",
"material-design-icons": "^2.2.0",
+ "ng-file-upload": "^12.0.4",
"satellizer": "^0.14.0"
},
"devDependencies": {
diff --git a/src/components/bucket/bucket.controller.js b/src/components/bucket/bucket.controller.js
index 766be3c..e30e595 100644
--- a/src/components/bucket/bucket.controller.js
+++ b/src/components/bucket/bucket.controller.js
@@ -1,8 +1,8 @@
export default class BucketController {
/** @ngInject */
- constructor($scope, $bucket) {
+ constructor($scope, $bucket, $state, $breadcrumb) {
Object.assign(this, {
- $scope, $bucket,
+ $scope, $bucket, $state,
});
this.$scope.$watch(
@@ -10,10 +10,19 @@ export default class BucketController {
newVal => Object.assign(this, newVal)
, true);
+ $breadcrumb.initPaths();
this.$bucket.getBuckets();
}
createBucket($event) {
this.$bucket.createDialog($event);
}
+
+ clickBucket(path) {
+ this.$state.go('file', { path });
+ }
+
+ selectBucket(name) {
+ this.$bucket.selectBucket(name);
+ }
}
diff --git a/src/components/bucket/bucket.html b/src/components/bucket/bucket.html
index fd50d39..9f142bb 100644
--- a/src/components/bucket/bucket.html
+++ b/src/components/bucket/bucket.html
@@ -6,24 +6,30 @@
+
+
+
+
+
+
+
+
+
refresh
-
+
\ No newline at end of file
diff --git a/src/components/bucket/bucket.js b/src/components/bucket/bucket.js
index 1c31f17..1721a52 100644
--- a/src/components/bucket/bucket.js
+++ b/src/components/bucket/bucket.js
@@ -14,9 +14,6 @@ const route = $stateProvider => {
controllerAs: 'bucket',
template: BucketTemplate,
onEnter: $nav => $nav.setTypeToBucket(),
- ncyBreadcrumb: {
- label: 'All Buckets ( {{ bucket.data.length }} )',
- },
});
};
diff --git a/src/components/bucket/bucket.service.js b/src/components/bucket/bucket.service.js
index 6fee2d9..b1cc6d1 100644
--- a/src/components/bucket/bucket.service.js
+++ b/src/components/bucket/bucket.service.js
@@ -1,13 +1,15 @@
import { element } from 'angular';
-import natural from 'javascript-natural-sort';
+import { sortByName } from '../../utils/sort';
import BucketCreateController from './create/create.controller';
import BucketCreateTemplate from './create/create.html';
+import BucketDeleteController from './delete/delete.controller';
+import BucketDeleteTemplate from './delete/delete.html';
export default class BucketService {
/** @ngInject */
- constructor($fetch, $toast, $mdDialog) {
+ constructor($fetch, $toast, $mdDialog, $breadcrumb) {
Object.assign(this, {
- $fetch, $toast, $mdDialog,
+ $fetch, $toast, $mdDialog, $breadcrumb,
});
this.initState();
@@ -30,6 +32,9 @@ export default class BucketService {
checked: false,
duplicated: false,
},
+ delete: {
+ name: null,
+ }
};
}
@@ -63,6 +68,17 @@ export default class BucketService {
});
}
+ deleteDialog($event) {
+ this.$mdDialog.show({
+ controller: BucketDeleteController,
+ controllerAs: 'delete',
+ template: BucketDeleteTemplate,
+ parent: element(document.body),
+ targetEvent: $event,
+ clickOutsideToClose: true,
+ });
+ }
+
/**
* Close the dialog.
*
@@ -73,18 +89,31 @@ export default class BucketService {
this.resetCheckBucketState();
}
- /**
- * Natural sort for the specified object key.
- *
- * @param {Object} a
- * @param {Object} b
- * @return {Integer}
- */
- sortByName(a, b) {
- const x = a.Name;
- const y = b.Name;
+ selectBucket(name) {
+ const { data } = this.state.lists;
+ const index = data.findIndex(bucket => bucket.Name === name);
+ this.state.lists.data = data.map((bucket, id) => ({
+ ...bucket,
+ checked: (id === index) ? ! bucket.checked : false,
+ }));
+
+ this.state.delete.name = this.state.lists.data[index].checked ? data[index].Name : null;
+ }
- return natural(x, y);
+ deleteBucket() {
+ const { name } = this.state.delete;
+ this.$fetch.delete(`/v1/bucket/delete/${name}`)
+ .then(() => {
+ this.state.delete.name = null;
+ this.$toast.show(`Bucket ${name} has been deleted!`);
+ this.getBuckets();
+ })
+ .catch(err => {
+ this.$toast.show(`Bucket ${name} delete failed, please try again!`);
+ })
+ .finally(() => {
+ this.closeDialog();
+ });
}
/**
@@ -99,13 +128,18 @@ export default class BucketService {
this.$fetch.post('/v1/bucket/list')
.then(({ data }) => {
this.state.lists.error = false;
- this.state.lists.data = data.Buckets.sort(this.sortByName);
+ const buckets = data.Buckets.map(bucket => ({
+ ...bucket,
+ checked: false,
+ }));
+ this.state.lists.data = buckets.sort(sortByName);
})
.catch(() => {
this.state.lists.error = true;
})
.finally(() => {
this.state.lists.requesting = false;
+ this.$breadcrumb.updateBucketPath(this.state.lists.data.length);
});
}
@@ -140,7 +174,7 @@ export default class BucketService {
createBucket(bucket) {
this.$fetch.post('/v1/bucket/create', { bucket })
.then(({ data }) => {
- this.state.lists.data = data.Buckets.sort(this.sortByName);
+ this.state.lists.data = data.Buckets.sort(sortByName);
this.$toast.show(`Bucket ${bucket} has created!`);
})
.catch(() => {
diff --git a/src/components/bucket/bucket.spec.js b/src/components/bucket/bucket.spec.js
new file mode 100644
index 0000000..1b041b3
--- /dev/null
+++ b/src/components/bucket/bucket.spec.js
@@ -0,0 +1,483 @@
+import bucketCtrl from './bucket.controller';
+import createCtrl from './create/create.controller';
+import bucketServ from './bucket.service';
+import createTem from './create/create.html';
+import app from './../../index.js';
+
+describe('bucket testing', function() {
+ let $rootScope;
+ let makeService;
+ let makeDeferred;
+ let makeController;
+ let makeCreateController;
+ let $httpBackend;
+ let $auth;
+ let $compile;
+ let $toast;
+ let $mdDialog;
+ let $fetch;
+ let form;
+ let $breadcrumb;
+ let $bucket;
+ let $state;
+
+ beforeEach(angular.mock.module('app'));
+
+ beforeEach(inject(($q, _$bucket_ ,_$compile_, _$rootScope_, _$auth_, _$toast_, _$mdDialog_, _$breadcrumb_, _$state_, _$fetch_, _$httpBackend_) => {
+ $rootScope = _$rootScope_;
+
+ $bucket = _$bucket_;
+
+ $compile = _$compile_;
+
+ $fetch = _$fetch_;
+
+ $toast = _$toast_;
+
+ $state = _$state_;
+
+ $mdDialog = _$mdDialog_;
+
+ $auth = _$auth_;
+
+ $breadcrumb = _$breadcrumb_;
+
+ $compile(createTem)($rootScope);
+
+ form = $rootScope.create.form;
+
+ form.bucket.$options.debounce = 0;
+
+ $auth.isAuthenticated = () => true;
+
+ $httpBackend = _$httpBackend_;
+
+ makeService = () => {
+ return new bucketServ($fetch, $toast, $mdDialog, $breadcrumb);
+ };
+
+ makeDeferred = () => {
+ return $q.defer();
+ }
+
+ makeController = (service = $bucket) => {
+ return new bucketCtrl($rootScope, service, $state, $breadcrumb);
+ };
+
+ makeCreateController = (service) => {
+ return new createCtrl(service, $rootScope);
+ }
+ }));
+ describe('when init service', function() {
+ it('should let state.lists.requesting to be false', function() {
+ const service = makeService();
+ $rootScope.$digest();
+ expect(service.state.lists.requesting).to.eq(false);
+ });
+ it('should let state.lists.error to be false', function() {
+ const service = makeService();
+ $rootScope.$digest();
+ expect(service.state.lists.error).to.eq(false);
+ });
+ it('should declare delete.name', () => {
+ const service = makeService();
+ $rootScope.$digest();
+ expect(service.state.delete.name).to.be.null;
+ });
+ });
+ describe('when resetCheckBucketState in service', function() {
+ it('should let checking, checked and duplicated be false', function() {
+ const service = makeService();
+ service.getBuckets = () => {};
+ service.resetCheckBucketState();
+ $rootScope.$digest();
+ expect(service.state.create.checking).to.eq(false);
+ expect(service.state.create.checked).to.eq(false);
+ expect(service.state.create.duplicated).to.eq(false);
+ });
+ });
+ describe('when createDialog', function() {
+ it('should invoke $mdDialog.show', function() {
+ const service = makeService();
+ const dialog = sinon.spy($mdDialog, 'show');
+ service.createDialog();
+ $rootScope.$digest();
+ expect(dialog.called).to.eq(true);
+ });
+ });
+ // describe('when deleteDialog in service', () => {
+ // it('should invoke $mdDialog.show', () => {
+ // const service = makeService();
+ // const mockDialog = sinon.spy($mdDialog, 'show');
+ // service.deleteDialog();
+ // $rootScope.$digest();
+ // expect(mockDialog.called).to.eq(true);
+ // });
+ // });
+ describe('when close Dialog', function() {
+ it('should invoke $mdDialog.cancel', function() {
+ const service = makeService();
+ const dialog = sinon.spy($mdDialog, 'cancel')
+ service.closeDialog();
+ $rootScope.$digest();
+ expect(dialog.called).to.eq(true);
+ });
+ });
+ describe('when selectBucket in service', () => {
+ let service;
+ beforeEach(() => {
+ service = makeService();
+ service.state.lists.data = [
+ {id:'a', Name:'aName'}, { id:'B', Name:'bName'},
+ {id:'c', Name:'cName'}
+ ];
+ service.selectBucket('cName')
+ });
+ it('should checked which bucket name called', () => {
+ expect(service.state.lists.data[2].checked).to.eq(true);
+ expect(service.state.lists.data[1].checked).to.eq(false);
+ expect(service.state.lists.data[0].checked).to.eq(false);
+ });
+ });
+ describe('when deleteBucket in service and success', () => {
+ let service;
+ let deferred;
+ let mockFetch;
+ let mockToast;
+ let mockGetBucket;
+ let mockClose;
+ let message;
+ beforeEach(() => {
+ service = makeService();
+ service.state.lists.data = [
+ {id:'a', Name:'aName'}, { id:'B', Name:'bName'},
+ {id:'c', Name:'cName'}
+ ];
+ service.state.delete.name = 'aName';
+ service.getBuckets = () => {};
+ deferred = makeDeferred();
+ mockFetch = sinon.mock($fetch);
+ mockFetch.expects('delete').returns(deferred.promise);
+ deferred.resolve();
+ mockToast = sinon.spy($toast, 'show');
+ mockGetBucket = sinon.spy(service, 'getBuckets');
+ mockClose = sinon.spy(service, 'closeDialog');
+ message = 'Bucket aName has been deleted!';
+ service.deleteBucket();
+ $rootScope.$digest();
+ });
+ it('should let state.delete.name to be null', () => {
+ expect(service.state.delete.name).to.be.null;
+ });
+ it('should invoke $toast.show and call by message', () => {
+ expect(mockToast).to.have.been.calledWith(message);
+ });
+ it('should invoke getBuckets', () => {
+ expect(mockGetBucket.called).to.eq(true);
+ });
+ it('should invoke closeDialog', () => {
+ expect(mockClose.called).to.eq(true);
+ });
+ });
+ describe('when deleteBucket in service and fail', () => {
+ let service;
+ let mockToast;
+ let mockFetch;
+ let deferred;
+ let mockClose;
+ let message;
+ beforeEach(() => {
+ service = makeService();
+ service.state.lists.data = [
+ {id:'a', Name:'aName'}, { id:'B', Name:'bName'},
+ {id:'c', Name:'cName'}
+ ];
+ service.state.delete.name = 'aName';
+ deferred = makeDeferred();
+ mockFetch = sinon.mock($fetch);
+ mockToast = sinon.spy($toast, 'show');
+ mockClose = sinon.spy(service, 'closeDialog');
+ mockFetch.expects('delete').returns(deferred.promise);
+ deferred.reject();
+ service.deleteBucket();
+ message = 'Bucket aName delete failed, please try again!';
+ $rootScope.$digest();
+ });
+ it('should invoke toast.show and call by fail message', () => {
+ expect(mockToast).to.have.been.calledWith(message);
+ });
+ it('should invoke updateBucketPath', () => {
+ expect(mockClose.called).to.eq(true);
+ });
+ });
+ describe('when getBuckets in service and success', () => {
+ let service;
+ let deferred;
+ let mockUpdate;
+ let mockFetch;
+ let res;
+ beforeEach(() => {
+ res = {Buckets:
+ [{ Name: 'b' }, { Name: 'c' }, { Name: 'a'},
+ { Name: 'a1'}]
+ };
+ deferred = makeDeferred();
+ service = makeService();
+ mockFetch = sinon.mock($fetch);
+ mockUpdate = sinon.spy($breadcrumb, 'updateBucketPath');
+ mockFetch.expects('post').returns(deferred.promise);
+ deferred.resolve({data: res});
+ service.getBuckets();
+ $rootScope.$digest();
+ });
+ it('should invoke updateBucketPath and call by length', () => {
+ expect(mockUpdate).to.have.been.calledWith(res.Buckets.length);
+ });
+ it('should get sorted data', () => {
+ expect(service.state.lists.data[0].Name).to.eq('a');
+ expect(service.state.lists.data[1].Name).to.eq('a1');
+ expect(service.state.lists.data[2].Name).to.eq('b');
+ expect(service.state.lists.data[3].Name).to.eq('c');
+ });
+ it('should let requesting to be false', () => {
+ expect(service.state.lists.requesting).to.eq(false);
+ });
+ });
+ describe('when getBuckets in service and fail', () => {
+ let service;
+ let deferred;
+ let mockFetch;
+ let mockUpdate;
+ beforeEach(() => {
+ service = makeService();
+ deferred = makeDeferred()
+ mockFetch = sinon.mock($fetch);
+ mockFetch.expects('post').returns(deferred.promise);
+ mockUpdate = sinon.spy($breadcrumb, 'updateBucketPath')
+ deferred.reject();
+ service.getBuckets();
+ $rootScope.$digest();
+ });
+ it('should invoke updateBucketPath and call by length', () => {
+ expect(mockUpdate).to.have.been.calledWith(0);
+ });
+ it('should let requesting to be false', () => {
+ expect(service.state.lists.requesting).to.eq(false);
+ });
+ it('should let error to be true', () => {
+ expect(service.state.lists.error).to.eq(true);
+ });
+ });
+ describe('when checkBucket in service and success', function() {
+ let service;
+ let deferred;
+ let mockFetch;
+ beforeEach(() => {
+ service = makeService();
+ deferred =makeDeferred();
+ mockFetch = sinon.mock($fetch)
+ mockFetch.expects('post').returns(deferred.promise);
+ deferred.resolve();
+ service.checkBucket('BucketName');
+ $rootScope.$digest();
+ });
+ it('should let duplicated to be false', function() {
+ expect(service.state.create.duplicated).to.eq(false);
+ });
+ it('should let checking to be false', function() {
+ expect(service.state.create.checking).to.eq(false);
+ });
+ it('should let checked to be true', function() {
+ expect(service.state.create.checked).to.eq(true);
+ });
+ });
+ describe('when checkBucket in service and fail', function() {
+ let service;
+ let deferred;
+ let mockFetch;
+ beforeEach(() => {
+ service = makeService();
+ deferred =makeDeferred();
+ mockFetch = sinon.mock($fetch)
+ mockFetch.expects('post').returns(deferred.promise);
+ deferred.reject();
+ service.checkBucket('BucketName');
+ $rootScope.$digest();
+ });
+ it('should let duplicated to be true', function() {
+ expect(service.state.create.duplicated).to.eq(true);
+ });
+ it('should let checking to be false', function() {
+ expect(service.state.create.checking).to.eq(false);
+ });
+ it('should let checked to be true', function() {
+ expect(service.state.create.checked).to.eq(true);
+ });
+ });
+ describe('when createBucket in service and success', function() {
+ let service;
+ let deferred;
+ let mockFetch;
+ let mockToast;
+ let res;
+ let message;
+ let mockClose;
+ beforeEach(() => {
+ res = {Buckets:
+ [{ Name: 'b' }, { Name: 'c' }, { Name: 'a'},
+ { Name: 'a1'}, {Name: 'BucketName'}]
+ };
+ message = 'Bucket BucketName has created!';
+ service = makeService();
+ mockClose = sinon.spy(service, 'closeDialog');
+ mockToast = sinon.spy($toast, 'show');
+ deferred =makeDeferred();
+ mockFetch = sinon.mock($fetch)
+ mockFetch.expects('post').returns(deferred.promise);
+ deferred.resolve({data: res});
+ service.createBucket('BucketName');
+ $rootScope.$digest();
+ });
+ it('should invoke toast.show and called with success message', function() {
+ expect(mockToast).to.have.been.calledWith(message);
+ });
+ it('should get sorted bucket', function() {
+ expect(service.state.lists.data[0].Name).to.eq('BucketName');
+ expect(service.state.lists.data[1].Name).to.eq('a');
+ expect(service.state.lists.data[2].Name).to.eq('a1');
+ expect(service.state.lists.data[3].Name).to.eq('b');
+ expect(service.state.lists.data[4].Name).to.eq('c');
+ });
+ it('should invoke closeDialog', function() {
+ expect(mockClose.called).to.eq(true);
+ });
+ });
+ describe('when createBucket reject', function() {
+ let service;
+ let deferred;
+ let mockFetch;
+ let mockToast;
+ let res;
+ let message;
+ let mockClose;
+ beforeEach(() => {
+ res = {Buckets:
+ [{ Name: 'b' }, { Name: 'c' }, { Name: 'a'},
+ { Name: 'a1'}, {Name: 'BucketName'}]
+ };
+ message = 'Bucket create failure, please try again!';
+ service = makeService();
+ mockClose = sinon.spy(service, 'closeDialog');
+ mockToast = sinon.spy($toast, 'show');
+ deferred =makeDeferred();
+ mockFetch = sinon.mock($fetch)
+ mockFetch.expects('post').returns(deferred.promise);
+ deferred.reject();
+ service.createBucket('BucketName');
+ $rootScope.$digest();
+ });
+ it('should invoke $toast.show and call with fail message', function() {
+ expect(mockToast).to.have.been.calledWith(message);
+ });
+ it('should invoke closeDialog', function() {
+ expect(mockClose.called).to.eq(true);
+ });
+ });
+ describe('when init controller', function() {
+ let service;
+ let controller;
+ let mockGet;
+ let mockBread;
+ beforeEach(() => {
+ service = makeService();
+ service.getBuckets = () => {};
+ mockGet = sinon.spy(service, 'getBuckets');
+ mockBread = sinon.spy($breadcrumb, 'initPaths')
+ controller = makeController(service);
+ });
+ it('should invoke getBuckets in service', function() {
+ expect(mockGet.called).to.eq(true);
+ });
+ it('should initPaths in breadcrumb service', function() {
+ expect(mockBread.called).to.eq(true);
+ });
+ });
+ describe('when createBucket in bucketCtrl', function() {
+ it('should invoke service.createDialog', function() {
+ const service = makeService();
+ service.getBuckets = () => {};
+ const controller = makeController(service);
+ const dialog = sinon.spy(service, 'createDialog');
+ controller.createBucket();
+ $rootScope.$digest();
+ expect(dialog.called).to.eq(true);
+ });
+ });
+ describe('when clickBucket in bucketCtrl', () => {
+ let controller;
+ let mockState;
+ let path;
+ beforeEach(() => {
+ path = { path: 'String' };
+ controller = makeController();
+ mockState = sinon.spy($state, 'go');
+ controller.clickBucket(path);
+ });
+ it('should invoke state.go and call with file and path', () => {
+ expect(mockState).to.have.been.calledWith('file', {path:path});
+ });
+ });
+ describe('when selectBucket in bucket controller', () => {
+ let controller;
+ let mockSelect;
+ beforeEach(() => {
+ $bucket.selectBucket = () => {};
+ controller = makeController();
+ mockSelect = sinon.spy($bucket, 'selectBucket');
+ controller.selectBucket('name')
+ });
+ it('should invoke selectBucket in bucket service and called with name', () => {
+ expect(mockSelect).to.have.been.calledWith('name');
+ });
+ });
+ describe('when fill valid bucket name', function() {
+ it('should be valid', function() {
+ form.bucket.$setViewValue('BucketName');
+ $rootScope.$digest();
+ expect(form.bucket.$viewValue).to.eq('BucketName');
+ expect(form.bucket.$valid).to.eq(true);
+ expect(form.bucket.$invalid).to.eq(false);
+ });
+ });
+ describe('when fill a non-valid email', function() {
+ it('should be invalid', function() {
+ form.bucket.$setViewValue('');
+ $rootScope.$digest();
+ expect(form.bucket.$viewValue).to.eq('');
+ expect(form.bucket.$valid).to.eq(false);
+ expect(form.bucket.$invalid).to.eq(true);
+ });
+ });
+ describe('when create() in bucketCtrl', function() {
+ it('should invoke service.createBucket and call with bucket', function() {
+ const service = makeService();
+ service.createBucket = () => {};
+ const controller = makeCreateController(service);
+ controller.bucket = 'BucketName';
+ const cBucket = sinon.spy(service, 'createBucket');
+ controller.create();
+ $rootScope.$digest();
+ expect(cBucket).to.have.been.calledWith('BucketName');
+ });
+ });
+ describe('when cancel() in bucketCtrl', function() {
+ it('should invoke service.closeDialog', function() {
+ const service = makeService();
+ const controller = makeCreateController(service);
+ const close = sinon.spy(service, 'closeDialog');
+ controller.cancel();
+ $rootScope.$digest();
+ expect(close.called).to.eq(true);
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/components/bucket/delete/delete.controller.js b/src/components/bucket/delete/delete.controller.js
new file mode 100644
index 0000000..7ae2c43
--- /dev/null
+++ b/src/components/bucket/delete/delete.controller.js
@@ -0,0 +1,22 @@
+export default class BucketDeleteController {
+ /** @ngInject */
+ constructor($scope, $bucket) {
+ Object.assign(this, {
+ $bucket,
+ });
+
+ $scope.$watch(() => $bucket.state.delete.name, newVal => this.deleteName = newVal);
+ }
+
+ check() {
+ this.checkStatus = this.inputName !== this.deleteName;
+ }
+
+ deleteBucket() {
+ this.$bucket.deleteBucket();
+ }
+
+ cancel() {
+ this.$bucket.closeDialog();
+ }
+}
\ No newline at end of file
diff --git a/src/components/bucket/delete/delete.html b/src/components/bucket/delete/delete.html
new file mode 100644
index 0000000..41a9d77
--- /dev/null
+++ b/src/components/bucket/delete/delete.html
@@ -0,0 +1,72 @@
+
+
+
\ No newline at end of file
diff --git a/src/components/bucket/delete/delete.spec.js b/src/components/bucket/delete/delete.spec.js
new file mode 100644
index 0000000..922d2f3
--- /dev/null
+++ b/src/components/bucket/delete/delete.spec.js
@@ -0,0 +1,56 @@
+import deleteCtrl from './delete.controller';
+import app from '../../../index.js';
+
+describe('bucket testing', function() {
+ let $rootScope;
+ let makeController;
+ let $bucket;
+
+ beforeEach(angular.mock.module('app'));
+
+ beforeEach(inject((_$bucket_ , _$rootScope_,) => {
+ $rootScope = _$rootScope_;
+
+ $bucket = _$bucket_;
+
+ makeController = () => {
+ return new deleteCtrl($rootScope, $bucket);
+ };
+ }));
+ describe('when check()', () => {
+ let controller;
+ let bool;
+ it('should declare checkStatus', () => {
+ controller = makeController();
+ controller.inputName = 'Abc';
+ controller.deleteName = 'abc';
+ $rootScope.$digest();
+ controller.check();
+ expect(controller.checkStatus).to.eq(true);
+ });
+ });
+ describe('when deleteBucket()', () => {
+ let controller;
+ let mockDelete;
+ beforeEach(() => {
+ controller = makeController();
+ mockDelete = sinon.spy($bucket, 'deleteBucket');
+ controller.deleteBucket();
+ });
+ it('should invoke deleteBucket in bucket service', () => {
+ expect(mockDelete.called).to.eq(true);
+ });
+ });
+ describe('when cancel()', () => {
+ let controller;
+ let mockClose;
+ beforeEach(() => {
+ controller = makeController();
+ mockClose = sinon.spy($bucket, 'closeDialog');
+ controller.cancel();
+ });
+ it('should invoke deleteBucket in bucket service', () => {
+ expect(mockClose.called).to.eq(true);
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/components/file/file.controller.js b/src/components/file/file.controller.js
new file mode 100644
index 0000000..ac164d4
--- /dev/null
+++ b/src/components/file/file.controller.js
@@ -0,0 +1,34 @@
+export default class FileController {
+ /** @ngInject */
+ constructor($scope, $stateParams, $file, $bucket, $breadcrumb, $upload) {
+ Object.assign(this, {
+ $file, $upload, $bucket, $breadcrumb,
+ });
+
+ $scope.$watch(
+ () => $file.state.lists,
+ newVal => Object.assign(this, newVal)
+ , true);
+
+ const paths = $stateParams.path.split('/');
+ const [bucket, ...folders] = paths;
+
+ this.$file.setPaths(bucket, folders);
+ this.$breadcrumb.updateFilePath(paths);
+
+ this.$bucket.getBuckets();
+ this.$file.getFiles();
+ }
+
+ createFolder($event) {
+ this.$file.createFolder($event);
+ }
+
+ upload($event) {
+ this.$upload.createDialog($event);
+ }
+
+ refresh() {
+ this.$file.getFiles();
+ }
+}
diff --git a/src/components/file/file.css b/src/components/file/file.css
new file mode 100644
index 0000000..551d8f5
--- /dev/null
+++ b/src/components/file/file.css
@@ -0,0 +1,24 @@
+
+/**
+ * @author Jamie jamie.h@inwinstack.com
+ */
+
+.checkbox-icon-width {
+ width: 80px;
+}
+
+.storage-class-width {
+ width: 140px;
+}
+
+.size-width {
+ width: 84px;
+}
+
+.time-width {
+ width: 270px;
+}
+
+.time-title-width {
+ width: 286px;
+}
\ No newline at end of file
diff --git a/src/components/file/file.html b/src/components/file/file.html
new file mode 100644
index 0000000..86eb9cd
--- /dev/null
+++ b/src/components/file/file.html
@@ -0,0 +1,99 @@
+
+
+
+
+
+ |
+ Name |
+ Storage Class |
+ Size |
+ Last Modified |
+
+
+
+
+
+
+ insert_drive_file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
This bucket is empty
+
You can do the following actions
+
+
+
+ file_upload
+ Upload File
+
+
+ or
+
+
+ create_new_folder
+ Create Folder
+
+
+
+
+
+
Oops, your connection seems off...
+
Don't worry. You can refresh to try again.
+
+
+ refresh
+
+
+
diff --git a/src/components/file/file.js b/src/components/file/file.js
new file mode 100644
index 0000000..469bb64
--- /dev/null
+++ b/src/components/file/file.js
@@ -0,0 +1,29 @@
+import { module } from 'angular';
+import router from 'angular-ui-router';
+
+import FileController from './file.controller';
+import FileService from './file.service';
+import FileTemplate from './file.html';
+import UploadService from './upload/upload.servce';
+import './file.css';
+
+/** @ngInject */
+const route = $stateProvider => {
+ $stateProvider.state('file', {
+ url: '/bucket/*path',
+ parent: 'root',
+ controller: FileController,
+ controllerAs: 'file',
+ template: FileTemplate,
+ onEnter: $nav => $nav.setTypeToFile(),
+ });
+};
+
+const File = module('file', [
+ router,
+])
+.service('$file', FileService)
+.service('$upload', UploadService)
+.config(route);
+
+export default File.name;
diff --git a/src/components/file/file.service.js b/src/components/file/file.service.js
new file mode 100644
index 0000000..54c721e
--- /dev/null
+++ b/src/components/file/file.service.js
@@ -0,0 +1,49 @@
+export default class FileService {
+ /** @ngInject */
+ constructor($mdDialog, $fetch, $bucket) {
+ Object.assign(this, {
+ $mdDialog, $fetch, $bucket,
+ });
+
+ this.initState();
+ }
+
+ initState() {
+ this.state = {
+ paths: {
+ bucket: '',
+ folders: [],
+ },
+ lists: {
+ data: [],
+ requesting: false,
+ error: false,
+ },
+ };
+ }
+
+ setPaths(bucket, folders) {
+ this.paths = { bucket, folders };
+ }
+
+ getFiles() {
+ const { bucket, folders } = this.paths;
+ const endpoint = `/v1/file/list/${bucket}?prefix=${folders.join('/')}`;
+
+ this.state.lists.requesting = true;
+ this.state.lists.data = [];
+
+ this.$fetch
+ .get(endpoint)
+ .then(({ data }) => {
+ this.state.lists.error = false;
+ this.state.lists.data = data.files || [];
+ })
+ .catch(() => {
+ this.state.lists.error = true;
+ })
+ .finally(() => {
+ this.state.lists.requesting = false;
+ });
+ }
+}
diff --git a/src/components/file/upload/upload.controller.js b/src/components/file/upload/upload.controller.js
new file mode 100644
index 0000000..a9f246a
--- /dev/null
+++ b/src/components/file/upload/upload.controller.js
@@ -0,0 +1,29 @@
+export default class FileUploadController {
+ /** @ngInject */
+ constructor($file, $upload, $scope) {
+ Object.assign(this, {
+ $file, $upload, $scope,
+ });
+
+ $scope.$watch(
+ () => $upload.state,
+ newVal => Object.assign(this, newVal)
+ , true);
+ }
+
+ upload() {
+ this.$upload.upload();
+ }
+
+ select(files) {
+ this.$upload.select(files);
+ }
+
+ delete(name) {
+ this.$upload.delete(name);
+ }
+
+ cancel() {
+ this.$upload.closeDialog();
+ }
+}
diff --git a/src/components/file/upload/upload.html b/src/components/file/upload/upload.html
new file mode 100644
index 0000000..5b60208
--- /dev/null
+++ b/src/components/file/upload/upload.html
@@ -0,0 +1,85 @@
+
+
+
diff --git a/src/components/file/upload/upload.servce.js b/src/components/file/upload/upload.servce.js
new file mode 100644
index 0000000..f4e6ace
--- /dev/null
+++ b/src/components/file/upload/upload.servce.js
@@ -0,0 +1,92 @@
+import { element } from 'angular';
+import totalSize from '../../../utils/totalSize';
+import FileUploadController from './upload.controller';
+import FileUploadTemplate from './upload.html';
+
+export default class FileUploadService {
+ /** @ngInject */
+ constructor(Config, Upload, $mdDialog, $file, $transfer) {
+ Object.assign(this, {
+ Config, Upload, $mdDialog, $file, $transfer,
+ });
+
+ this.initState();
+ }
+
+ initState() {
+ this.state = {
+ files: [],
+ size: 0,
+ };
+ }
+
+ select(selectedFiles) {
+ const additionalFiles = selectedFiles.filter(selectedFile =>
+ this.state.files.every(({ detail }) => detail.name !== selectedFile.name)
+ ).map(detail => ({
+ id: Symbol('unique id'), detail,
+ }));
+
+ const files = [...this.state.files, ...additionalFiles];
+ const size = totalSize(files);
+
+ this.state = { files, size };
+ }
+
+ delete(id) {
+ const files = this.state.files.filter(file => file.id !== id);
+ const size = totalSize(files);
+
+ this.state = { files, size };
+ }
+
+ upload() {
+ const { bucket, folders } = this.$file.paths;
+ const prefix = folders.length ? '' : `${folders.join('/')}/`;
+ const url = `${this.Config.API_URL}/v1/file/create`;
+
+ this.state.uploading = true;
+ this.$transfer.put(this.state.files.map(({
+ id, detail,
+ }) => ({
+ id,
+ bucket,
+ name: detail.name,
+ type: 'UPLOAD',
+ status: 'UPLOADING',
+ upload: this.uploadFile(id, {
+ bucket, prefix, file: detail,
+ }, url),
+ })));
+
+ this.closeDialog();
+ }
+
+ uploadFile(id, data, url) {
+ const upload = this.Upload.upload({ url, data });
+
+ upload.then(
+ res => this.$transfer.handleSuccess(id, res),
+ err => this.$transfer.handleFailure(id, err),
+ evt => this.$transfer.handleEvent(id, evt)
+ );
+
+ return upload;
+ }
+
+ createDialog($event) {
+ this.$mdDialog.show({
+ controller: FileUploadController,
+ controllerAs: 'upload',
+ template: FileUploadTemplate,
+ parent: element(document.body),
+ targetEvent: $event,
+ clickOutsideToClose: true,
+ });
+ }
+
+ closeDialog() {
+ this.$mdDialog.cancel();
+ this.initState();
+ }
+}
diff --git a/src/components/index.js b/src/components/index.js
index 8c1c994..d209813 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -3,12 +3,14 @@ import Layout from './layout/layout';
import NotFound from './not-found/not-found';
import Auth from './auth/auth';
import Bucket from './bucket/bucket';
+import File from './file/file';
const Components = module('app.components', [
Layout,
NotFound,
Auth,
Bucket,
+ File,
]);
export default Components.name;
diff --git a/src/components/layout/action-navbar/action-navbar.controller.js b/src/components/layout/action-navbar/action-navbar.controller.js
index 34da8c9..56fe7c1 100644
--- a/src/components/layout/action-navbar/action-navbar.controller.js
+++ b/src/components/layout/action-navbar/action-navbar.controller.js
@@ -1,14 +1,24 @@
export default class ActionNavbarController {
/** @ngInject */
- constructor($scope, $bucket, $nav) {
+ constructor($scope, $bucket, $nav, $file, $upload, $layout) {
Object.assign(this, {
- $scope, $bucket,
+ $scope, $bucket, $file, $upload, $layout,
});
this.$scope.$watch(
() => $nav.type,
newVal => (this.type = newVal)
);
+
+ this.$scope.$watch(
+ () => $layout.state,
+ newVal => Object.assign(this, newVal)
+ );
+
+ this.$scope.$watch(
+ () => $bucket.state.delete.name,
+ newVal => this.disableDeleteButton = ! newVal && ! this.isFile()
+ );
}
/**
@@ -28,24 +38,24 @@ export default class ActionNavbarController {
//
}
- upload() {
- //
- }
-
delete() {
- //
+ if (this.isFile()) {
+ // handle delete file
+ } else {
+ this.$bucket.deleteDialog();
+ }
}
- none() {
- //
+ closeSidePanels() {
+ this.$layout.closeSidePanels();
}
- properties() {
- //
+ openProperties() {
+ this.$layout.openProperties();
}
- transfers() {
- //
+ openTransfers() {
+ this.$layout.openTransfers();
}
/**
@@ -56,12 +66,16 @@ export default class ActionNavbarController {
*/
create($event) {
if (this.isFile()) {
- // create file dialog
+ this.$upload.createDialog($event);
} else {
this.$bucket.createDialog($event);
}
}
+ createFolder($event) {
+ // handle the create folder event
+ }
+
/**
* Refresh the list by `this.type`
*
@@ -69,7 +83,7 @@ export default class ActionNavbarController {
*/
refresh() {
if (this.isFile()) {
- // get the files
+ this.$file.getFiles();
} else {
this.$bucket.getBuckets();
}
diff --git a/src/components/layout/action-navbar/action-navbar.html b/src/components/layout/action-navbar/action-navbar.html
index 8cbd1cb..72204e0 100644
--- a/src/components/layout/action-navbar/action-navbar.html
+++ b/src/components/layout/action-navbar/action-navbar.html
@@ -2,22 +2,21 @@