diff --git a/lab-remil/.babelrc b/lab-remil/.babelrc new file mode 100644 index 00000000..a0765e18 --- /dev/null +++ b/lab-remil/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015"] +} diff --git a/lab-remil/.eslintignore b/lab-remil/.eslintignore new file mode 100644 index 00000000..3c3629e6 --- /dev/null +++ b/lab-remil/.eslintignore @@ -0,0 +1 @@ +node_modules diff --git a/lab-remil/.eslintrc b/lab-remil/.eslintrc new file mode 100644 index 00000000..cac15fda --- /dev/null +++ b/lab-remil/.eslintrc @@ -0,0 +1,25 @@ +{ + "rules": { + "no-console": "off", + "indent": [ "error", 2 ], + "quotes": [ "error", "single" ], + "semi": ["error", "always"], + "linebreak-style": [ "error", "unix" ], + "comma-dangle": ["error", "always-multiline"], + }, + "env": { + "es6": true, + "node": true, + "mocha": true, + "jasmine": true, + }, + "ecmaFeatures": { + "modules": true, + "experimentalObjectRestSpread": true, + "impliedStrict": true, + }, + "extends": "eslint:recommended", + "parserOptions": { + "sourceType": "module", + }, +} diff --git a/lab-remil/.gitignore b/lab-remil/.gitignore new file mode 100644 index 00000000..1426a62b --- /dev/null +++ b/lab-remil/.gitignore @@ -0,0 +1,122 @@ +# Created by https://www.gitignore.io/api/node,vim,osx,macos,linux + +### My Custom Jams ### +*.env +.appnote +build + +*node_modules + +### Node ### +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + + + +### Vim ### +# swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-v][a-z] +[._]sw[a-p] +# session +Session.vim +# temporary +.netrwhist +*~ +# auto-generated tag files +tags + + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon +# Thumbnails +._* +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +### macOS ### +# Icon must end with two \r +# Thumbnails +# Files that might appear in the root of a volume +# Directories potentially created on remote AFP share + + +### Linux ### + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# End of https://www.gitignore.io/api/node,vim,osx,macos,linux diff --git a/lab-remil/app/component/gallery/create-gallery/_create-gallery.scss b/lab-remil/app/component/gallery/create-gallery/_create-gallery.scss new file mode 100644 index 00000000..e69de29b diff --git a/lab-remil/app/component/gallery/create-gallery/create-gallery.html b/lab-remil/app/component/gallery/create-gallery/create-gallery.html new file mode 100644 index 00000000..7e90818f --- /dev/null +++ b/lab-remil/app/component/gallery/create-gallery/create-gallery.html @@ -0,0 +1,19 @@ + +
diff --git a/lab-remil/app/component/gallery/create-gallery/create-gallery.js b/lab-remil/app/component/gallery/create-gallery/create-gallery.js new file mode 100644 index 00000000..5130b098 --- /dev/null +++ b/lab-remil/app/component/gallery/create-gallery/create-gallery.js @@ -0,0 +1,21 @@ +'use strict'; + +module.exports = { + template: require('./create-gallery.html'), + controller: ['$log', 'galleryService', CreateGalleryController], + controllerAs: 'createGalleryCtrl', +}; + +function CreateGalleryController($log, galleryService) { + $log.debug('CreateGalleryController'); + + this.gallery = {}; + + this.createGallery = function() { + galleryService.createGallery(this.gallery) + .then( () => { + this.gallery.name = null; + this.gallery.desc = null; + }); + }; +} diff --git a/lab-remil/app/component/gallery/edit-gallery/_edit-gallery.scss b/lab-remil/app/component/gallery/edit-gallery/_edit-gallery.scss new file mode 100644 index 00000000..03290076 --- /dev/null +++ b/lab-remil/app/component/gallery/edit-gallery/_edit-gallery.scss @@ -0,0 +1,26 @@ +@import '../../../scss/lib/theme/vars'; +@import '../gallery-item/gallery-item'; + +.edit-gallery { + @extend .gallery-item; + + fieldset { + + input { + background: $color-primary * 2.5; + border: 1px solid $color-primary; + } + + &:first-child { + input { + width: 88%; + } + } + &:last-child { + input { + width: 79%; + } + } + } + +} diff --git a/lab-remil/app/component/gallery/edit-gallery/edit-gallery.html b/lab-remil/app/component/gallery/edit-gallery/edit-gallery.html new file mode 100644 index 00000000..bec87b3a --- /dev/null +++ b/lab-remil/app/component/gallery/edit-gallery/edit-gallery.html @@ -0,0 +1,38 @@ +
+ +
+ + diff --git a/lab-remil/app/component/gallery/edit-gallery/edit-gallery.js b/lab-remil/app/component/gallery/edit-gallery/edit-gallery.js new file mode 100644 index 00000000..5c037cc7 --- /dev/null +++ b/lab-remil/app/component/gallery/edit-gallery/edit-gallery.js @@ -0,0 +1,21 @@ +'use strict'; + +require('./_edit-gallery.scss'); + +module.exports = { + template: require('./edit-gallery.html'), + controller: ['$log', 'galleryService', EditGalleryController], + controllerAs: 'editGalleryCtrl', + bindings: { + gallery: '<', + }, +}; + +function EditGalleryController($log, galleryService) { + $log.debug('EditGalleryController'); + + this.updateGallery = function() { + $log.debug('editGalleryCtrl.updateGallery'); + galleryService.updateGallery(this.gallery._id, this.gallery); + }; +} diff --git a/lab-remil/app/component/gallery/gallery-item/_gallery-item.scss b/lab-remil/app/component/gallery/gallery-item/_gallery-item.scss new file mode 100644 index 00000000..78fd1d31 --- /dev/null +++ b/lab-remil/app/component/gallery/gallery-item/_gallery-item.scss @@ -0,0 +1,29 @@ +@import '../../../scss/lib/theme/vars'; + +.gallery-item { + display: flex; + flex-direction: column; + margin-bottom: $gutter-std; + + .current-item { + border-radius: $border-radius; + background: $color-primary * 2.3; + } + + .gallery-property { + padding: $input-padding; + + &:first-child { + padding-bottom: 0; + } + } + + .item-label { + font-weight: 900; + } + + .button-container { + align-self: flex-end; + margin-top: $gutter-sm; + } +} diff --git a/lab-remil/app/component/gallery/gallery-item/gallery-item.html b/lab-remil/app/component/gallery/gallery-item/gallery-item.html new file mode 100644 index 00000000..e970e5d7 --- /dev/null +++ b/lab-remil/app/component/gallery/gallery-item/gallery-item.html @@ -0,0 +1,22 @@ + diff --git a/lab-remil/app/component/gallery/gallery-item/gallery-item.js b/lab-remil/app/component/gallery/gallery-item/gallery-item.js new file mode 100644 index 00000000..b6c1d988 --- /dev/null +++ b/lab-remil/app/component/gallery/gallery-item/gallery-item.js @@ -0,0 +1,28 @@ +'use strict'; + +require('./_gallery-item.scss'); + +module.exports = { + template: require('./gallery-item.html'), + controller: ['$log', 'galleryService', GalleryItemController], + controllerAs: 'galleryItemCtrl', + bindings: { + gallery: '<', + deleteDone: '&', + }, +}; + +function GalleryItemController($log, galleryService) { + $log.debug('GalleryItemController'); + + this.showEditGallery = false; + + this.deleteGallery = function() { + console.log(this); + galleryService.deleteGallery(this.gallery._id) + .then(() => { + this.deleteDone(this.gallery); + }); + + }; +} diff --git a/lab-remil/app/component/gallery/thumbnail-container/_thumbnail-container.scss b/lab-remil/app/component/gallery/thumbnail-container/_thumbnail-container.scss new file mode 100644 index 00000000..8a72d479 --- /dev/null +++ b/lab-remil/app/component/gallery/thumbnail-container/_thumbnail-container.scss @@ -0,0 +1,10 @@ +@import '../../../scss/lib/theme/vars'; + +.thumbnail-container { + display: flex; + flex-direction: column; + + & > * { + margin-bottom: $gutter-std; + } +} diff --git a/lab-remil/app/component/gallery/thumbnail-container/thumbnail-container.html b/lab-remil/app/component/gallery/thumbnail-container/thumbnail-container.html new file mode 100644 index 00000000..104b5c83 --- /dev/null +++ b/lab-remil/app/component/gallery/thumbnail-container/thumbnail-container.html @@ -0,0 +1,8 @@ +
+

{{ thumbnailContainerCtrl.gallery.name }}

+ + +
+ +
+
diff --git a/lab-remil/app/component/gallery/thumbnail-container/thumbnail-container.js b/lab-remil/app/component/gallery/thumbnail-container/thumbnail-container.js new file mode 100644 index 00000000..ef86e7b3 --- /dev/null +++ b/lab-remil/app/component/gallery/thumbnail-container/thumbnail-container.js @@ -0,0 +1,31 @@ +'use strict'; + +require('./_thumbnail-container.scss'); + +module.exports = { + template: require('./thumbnail-container.html'), + // controller: ['$log', '$uibModal', ThumbnailContainerController], + controllerAs: 'thumbnailContainerCtrl', + bindings: { + gallery: '<', + }, +}; +// +// function ThumbnailContainerController($log, $uibModal) { +// this.open = function () { +// var modalInstance = $uibModal.open({ +// animation: this.animationsEnabled, +// ariaLabelledBy: 'modal-title', +// ariaDescribedBy: 'modal-body', +// templateUrl: 'myModalContent.html', +// controller: 'ModalInstanceCtrl', +// controllerAs: 'ModalInstanceCtrl', +// appendTo: parentElem, +// resolve: { +// items: function () { +// return $ctrl.items; +// } +// } +// }); +// }; +// } diff --git a/lab-remil/app/component/gallery/thumbnail/_thumbnail.scss b/lab-remil/app/component/gallery/thumbnail/_thumbnail.scss new file mode 100644 index 00000000..ead8a1bd --- /dev/null +++ b/lab-remil/app/component/gallery/thumbnail/_thumbnail.scss @@ -0,0 +1,17 @@ +@import '../../../scss/lib/theme/vars'; + +.thumbnail { + display: flex; + flex-direction: column; + + img{ + width: 100%; + margin-bottom: $gutter-sm; + } + + button { + align-self: flex-end; + margin-left: 0; + margin-right: 0; + } +} diff --git a/lab-remil/app/component/gallery/thumbnail/thumbnail.html b/lab-remil/app/component/gallery/thumbnail/thumbnail.html new file mode 100644 index 00000000..ab5a4145 --- /dev/null +++ b/lab-remil/app/component/gallery/thumbnail/thumbnail.html @@ -0,0 +1,5 @@ +
+ {{ thumbnailCtrl.pic.desc }} + + +
diff --git a/lab-remil/app/component/gallery/thumbnail/thumbnail.js b/lab-remil/app/component/gallery/thumbnail/thumbnail.js new file mode 100644 index 00000000..b3ea234b --- /dev/null +++ b/lab-remil/app/component/gallery/thumbnail/thumbnail.js @@ -0,0 +1,32 @@ +'use strict'; + +require('./_thumbnail.scss'); + +module.exports = { + template: require('./thumbnail.html'), + controller: ['$log', 'picService', '$uibModal',ThumbnailController], + controllerAs: 'thumbnailCtrl', + bindings: { + pic: '<', + gallery: '<', + }, +}; + +function ThumbnailController($log, picService, $uibModal) { + $log.debug('ThumbnailController'); + + this.deletePic = function() { + $log.debug('thumbnailCtrl.deletePic'); + + picService.deletePic(this.gallery,this.pic._id); + }; + + this.open = function() { + $uibModal.open({ + component: 'thumbnail-modal', + resolve: { + modalPic: this.pic, + }, + }); + }; +} diff --git a/lab-remil/app/component/gallery/upload-pic/_upload-pic.scss b/lab-remil/app/component/gallery/upload-pic/_upload-pic.scss new file mode 100644 index 00000000..7d937d96 --- /dev/null +++ b/lab-remil/app/component/gallery/upload-pic/_upload-pic.scss @@ -0,0 +1,29 @@ +@import '../../../scss/lib/theme/vars'; + +.upload-pic { + + h3 { + margin-bottom: $gutter-sm; + } + input { + background: $color-primary * 2.3; + padding-top: $input-padding/2; + padding-bottom: $input-padding/2; + color: $white; + } + + .upload-controls { + display: flex; + justify-content: flex-end; + + p { + padding: $input-padding; + } + + button { + margin-left: 0; + margin-right: 0; + } + + } +} diff --git a/lab-remil/app/component/gallery/upload-pic/upload-pic.html b/lab-remil/app/component/gallery/upload-pic/upload-pic.html new file mode 100644 index 00000000..06e2886a --- /dev/null +++ b/lab-remil/app/component/gallery/upload-pic/upload-pic.html @@ -0,0 +1,23 @@ +
+
+ +

upload new swag

+ +
+ + +
+ +
+

+ select dat swag to upload +

+ + +
+
+
diff --git a/lab-remil/app/component/gallery/upload-pic/upload-pic.js b/lab-remil/app/component/gallery/upload-pic/upload-pic.js new file mode 100644 index 00000000..e16e821e --- /dev/null +++ b/lab-remil/app/component/gallery/upload-pic/upload-pic.js @@ -0,0 +1,27 @@ +'use strict'; + +require('./_upload-pic.scss'); + +module.exports = { + template: require('./upload-pic.html'), + controller: ['$log', 'picService', UploadPicController], + controllerAs: 'uploadPicCtrl', + bindings: { + gallery: '<', + }, +}; + +function UploadPicController($log, picService) { + $log.debug('UploadPicController'); + + this.pic = {}; + + this.uploadPic = function() { + picService.uploadGalleryPic(this.gallery, this.pic) + .then( () => { + this.pic.name = null; + this.pic.desc = null; + this.pic.file = null; + }); + }; +} diff --git a/lab-remil/app/component/landing/login/_login.scss b/lab-remil/app/component/landing/login/_login.scss new file mode 100644 index 00000000..a9b9a1b0 --- /dev/null +++ b/lab-remil/app/component/landing/login/_login.scss @@ -0,0 +1,6 @@ +@import '../../../scss/lib/theme/vars'; +@import '../signup/signup'; + +.login-form { + @extend .signup-form; +} diff --git a/lab-remil/app/component/landing/login/login.html b/lab-remil/app/component/landing/login/login.html new file mode 100644 index 00000000..cd0d771b --- /dev/null +++ b/lab-remil/app/component/landing/login/login.html @@ -0,0 +1,35 @@ +
+

sign in

+
+
+ +
+ +
+ +
+ + +
+
diff --git a/lab-remil/app/component/landing/login/login.js b/lab-remil/app/component/landing/login/login.js new file mode 100644 index 00000000..207888c6 --- /dev/null +++ b/lab-remil/app/component/landing/login/login.js @@ -0,0 +1,23 @@ +'use strict'; + +require('./_login.scss'); + +module.exports = { + template: require('./login.html'), + controller: ['$log', '$location', 'authService', LoginController], + controllerAs: 'loginCtrl', +}; + +function LoginController($log, $location, authService) { + $log.debug('LoginController'); + + authService.getToken() + .then( () => $location.url('/home')); + + this.login = function() { + $log.debug('loginCtrl.login()'); + + authService.login(this.user) + .then( () => $location.url('/home')); + }; +} diff --git a/lab-remil/app/component/landing/signup/_signup.scss b/lab-remil/app/component/landing/signup/_signup.scss new file mode 100644 index 00000000..73131b39 --- /dev/null +++ b/lab-remil/app/component/landing/signup/_signup.scss @@ -0,0 +1,13 @@ +@import '../../../scss/lib/theme/vars'; + +.signup-form { + margin-top: $gutter-std; + + h2 { + margin: $gutter-sm 0; + } + + button { + float: right; + } +} diff --git a/lab-remil/app/component/landing/signup/signup.html b/lab-remil/app/component/landing/signup/signup.html new file mode 100644 index 00000000..65dc9187 --- /dev/null +++ b/lab-remil/app/component/landing/signup/signup.html @@ -0,0 +1,26 @@ +
+

sign up

+
+ + + + + + + + +
+
diff --git a/lab-remil/app/component/landing/signup/signup.js b/lab-remil/app/component/landing/signup/signup.js new file mode 100644 index 00000000..3eb1c471 --- /dev/null +++ b/lab-remil/app/component/landing/signup/signup.js @@ -0,0 +1,23 @@ +'use strict'; + +require('./_signup.scss'); + +module.exports = { + template: require('./signup.html'), + controller: ['$log', '$location', 'authService', SignupController], + controllerAs: 'signupCtrl', +}; + +function SignupController($log, $location, authService) { + $log.debug('SignupController'); + + authService.getToken() + .then( () => $location.url('/home')); + + this.signup = function(user) { + $log.debug('signupCtrl.signup()'); + + authService.signup(user) + .then( () => $location.url('/home')); + }; +} diff --git a/lab-remil/app/component/navbar/_navbar.scss b/lab-remil/app/component/navbar/_navbar.scss new file mode 100644 index 00000000..af1f4a6d --- /dev/null +++ b/lab-remil/app/component/navbar/_navbar.scss @@ -0,0 +1,34 @@ +@import '../../scss/lib/theme/vars'; + +$header-font-size: 3vh; + +.navbar { + width: 100% - 2 * $gutter-std; + margin: 0 auto; + display: flex; + + img { + height: $height-header * .6; + float: left; + margin-top: $height-header * .2; + margin-right: $height-header * .2; + } + + h1 { + font-size: $header-font-size; + margin-top: ($height-header - $header-font-size) / 2; + } + + div { + margin-left: auto; + } + + button { + width: 10vh; + height: $height-header * .4; + margin-top: $height-header * .3; + background: $color-secondary; + color: $color-primary; + padding: 0; + } +} diff --git a/lab-remil/app/component/navbar/navbar.html b/lab-remil/app/component/navbar/navbar.html new file mode 100644 index 00000000..8fac5dbb --- /dev/null +++ b/lab-remil/app/component/navbar/navbar.html @@ -0,0 +1,7 @@ + diff --git a/lab-remil/app/component/navbar/navbar.js b/lab-remil/app/component/navbar/navbar.js new file mode 100644 index 00000000..d67e1061 --- /dev/null +++ b/lab-remil/app/component/navbar/navbar.js @@ -0,0 +1,43 @@ +'use strict'; + +require('./_navbar.scss'); + +module.exports = { + template: require('./navbar.html'), + controller: ['$log', '$location', '$rootScope', 'authService', NavbarController], + controllerAs: 'navbarCtrl', +}; + +function NavbarController($log, $location, $rootScope, authService) { + $log.debug('NavbarController'); + + this.checkPath = function() { + let path = $location.path(); + if (path === '/join') { + this.hideButtons = true; + } + + if (path !== '/join') { + this.hideButtons = false; + authService.getToken() + .catch( () => { + $location.url('/join#login'); + }); + } + }; + + this.checkPath(); + + $rootScope.$on('$locationChangeSuccess', () => { + this.checkPath(); + }); + + this.logout = function() { + $log.log('navbarCtrl.logout'); + this.hideButtons = true; + authService.logout() + .then( () => { + $location.url('/'); + }); + }; +} diff --git a/lab-remil/app/component/thumbnail-modal/_thumbnail-modal.scss b/lab-remil/app/component/thumbnail-modal/_thumbnail-modal.scss new file mode 100644 index 00000000..5f41a02d --- /dev/null +++ b/lab-remil/app/component/thumbnail-modal/_thumbnail-modal.scss @@ -0,0 +1,11 @@ +@import '../../scss/lib/theme/vars'; + +.thumbnail-modal { + display: flex; + flex-direction: column; + text-align:center; + img { + width: 50%; + margin: 0 auto; + } +} diff --git a/lab-remil/app/component/thumbnail-modal/thumbnail-modal.html b/lab-remil/app/component/thumbnail-modal/thumbnail-modal.html new file mode 100644 index 00000000..46fe96d8 --- /dev/null +++ b/lab-remil/app/component/thumbnail-modal/thumbnail-modal.html @@ -0,0 +1,5 @@ +
+
Pic Name: {{ thumbnailModalCtrl.resolve.modalPic.name }}
+
Pic Descip: {{ thumbnailModalCtrl.resolve.modalPic.desc }}
+ +
diff --git a/lab-remil/app/component/thumbnail-modal/thumbnail-modal.js b/lab-remil/app/component/thumbnail-modal/thumbnail-modal.js new file mode 100644 index 00000000..9046f921 --- /dev/null +++ b/lab-remil/app/component/thumbnail-modal/thumbnail-modal.js @@ -0,0 +1,18 @@ +'use strict'; + +require('./_thumbnail-modal.scss'); + +module.exports = { + template: require('./thumbnail-modal.html'), + controller: ['$log', ThumbnailModalController], + controllerAs: 'thumbnailModalCtrl', + bindings: { + modalInstance: '<', + resolve: '<', + }, +}; + +function ThumbnailModalController($log) { + $log.debug('ThumbnailModalController'); + +} diff --git a/lab-remil/app/config/log-config.js b/lab-remil/app/config/log-config.js new file mode 100644 index 00000000..01068985 --- /dev/null +++ b/lab-remil/app/config/log-config.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = ['$logProvider', logConfig]; + +function logConfig($logProvider) { + $logProvider.debugEnabled(__DEBUG__); +} diff --git a/lab-remil/app/config/router-config.js b/lab-remil/app/config/router-config.js new file mode 100644 index 00000000..427c7f96 --- /dev/null +++ b/lab-remil/app/config/router-config.js @@ -0,0 +1,31 @@ +'use strict'; + +module.exports = ['$stateProvider', '$urlRouterProvider', routerConfig]; + +function routerConfig($stateProvider, $urlRouterProvider) { + $urlRouterProvider.when('', '/join#signup'); + $urlRouterProvider.when('/', '/join#signup'); + $urlRouterProvider.when('/signup', '/join#signup'); + $urlRouterProvider.when('/login', '/join#login'); + + let states = [ + { + name: 'home', + url: '/home', + template: require('../view/home/home.html'), + controller: 'HomeController', + controllerAs: 'homeCtrl', + }, + { + name: 'landing', + url: '/join', + template: require('../view/landing/landing.html'), + controller: 'LandingController', + controllerAs: 'landingCtrl', + }, + ]; + + states.forEach( state => { + $stateProvider.state(state); + }); +} diff --git a/lab-remil/app/directive/social-icons/_social-icons.scss b/lab-remil/app/directive/social-icons/_social-icons.scss new file mode 100644 index 00000000..22c460ae --- /dev/null +++ b/lab-remil/app/directive/social-icons/_social-icons.scss @@ -0,0 +1,36 @@ +.social-icons-css { + display: flex; +} + +.icon-list { + display: flex; +} + +.social-icon { + background-image: url(./social-icons.png); + background-repeat: no-repeat; +} + +.social-icon-github { + width: 30px; + height: 30px; + background-position: -5px -5px; +} + +.social-icon-linkedin { + width: 30px; + height: 30px; + background-position: -45px -5px; +} + +.social-icon-tumblr { + width: 30px; + height: 30px; + background-position: -5px -45px; +} + +.social-icon-twitter { + width: 30px; + height: 30px; + background-position: -45px -45px; +} diff --git a/lab-remil/app/directive/social-icons/social-icons.html b/lab-remil/app/directive/social-icons/social-icons.html new file mode 100644 index 00000000..a7ab57df --- /dev/null +++ b/lab-remil/app/directive/social-icons/social-icons.html @@ -0,0 +1,7 @@ +
+

{{ socialIconsCtrl.socialIconsTitle }}

+ + +
diff --git a/lab-remil/app/directive/social-icons/social-icons.js b/lab-remil/app/directive/social-icons/social-icons.js new file mode 100644 index 00000000..43702bef --- /dev/null +++ b/lab-remil/app/directive/social-icons/social-icons.js @@ -0,0 +1,20 @@ +'use strict'; + +require('./_social-icons.scss'); + +module.exports = function() { + return { + restrict: 'EAC', + template: require('./social-icons.html'), + controller: ['$log', SocialIconsController], + bindToController: true, + controllerAs: 'socialIconsCtrl', + scope: { + socialIconsTitle: '@', + }, + }; +}; + +function SocialIconsController() { + this.icons = ['github', 'linkedin', 'tumblr', 'twitter']; +} diff --git a/lab-remil/app/directive/social-icons/social-icons.png b/lab-remil/app/directive/social-icons/social-icons.png new file mode 100644 index 00000000..4f78a1e5 Binary files /dev/null and b/lab-remil/app/directive/social-icons/social-icons.png differ diff --git a/lab-remil/app/entry.js b/lab-remil/app/entry.js new file mode 100644 index 00000000..e13db164 --- /dev/null +++ b/lab-remil/app/entry.js @@ -0,0 +1,57 @@ +'use strict'; + +require('./scss/main.scss'); + +const + path = require('path'), + angular = require('angular'), + camelcase = require('camelcase'), + pascalcase = require('pascalcase'), + uiRouter = require('angular-ui-router'), + ngTouch = require('angular-touch'), + ngAnimate = require('angular-animate'), + ngFileUpload = require('ng-file-upload'), + uiBootstrap = require('angular-ui-bootstrap'); + +const ayogram = angular.module('ayogram', [ngTouch, ngAnimate, uiRouter, ngFileUpload, uiBootstrap]); + + +let context = require.context('./config/', true, /\.js$/); +context.keys().forEach( key => { + ayogram.config(context(key)); +}); + +context = require.context('./view/', true, /\.js$/); +context.keys().forEach( key => { + let name = pascalcase(path.basename(key, '.js')); + let module = context(key); + ayogram.controller(name, module); +}); + +context = require.context('./service/', true, /\.js$/); +context.keys().forEach( key => { + let name = camelcase(path.basename(key, '.js')); + let module = context(key); + ayogram.service(name, module); +}); + +context = require.context('./component/', true, /\.js$/); +context.keys().forEach( key => { + let name = camelcase(path.basename(key, '.js')); + let module = context(key); + ayogram.component(name, module); +}); + +context = require.context('./filter/', true, /\.js$/); +context.keys().forEach( key => { + let name = camelcase(path.basename(key, '.js')); + let module = context(key); + ayogram.filter(name, module); +}); + +context = require.context('./directive/', true, /\.js$/); +context.keys().forEach( key => { + let name = camelcase(path.basename(key, '.js')); + let module = context(key); + ayogram.directive(name, module); +}); diff --git a/lab-remil/app/filter/gallery-search.js b/lab-remil/app/filter/gallery-search.js new file mode 100644 index 00000000..1572b3d7 --- /dev/null +++ b/lab-remil/app/filter/gallery-search.js @@ -0,0 +1,17 @@ +'use strict'; + +module.exports = function() { + return function(galleries, searchTerm) { + let fuzzyRegex = generateFuzzyRegex(searchTerm); + + return galleries.filter(gallery => { + return fuzzyRegex.test(gallery.name.toUpperCase()); + }); + }; +}; + +function generateFuzzyRegex(input) { + if (!input) return /.*/; + let fuzzyString = '.*' + input.toUpperCase() + '.*'; + return new RegExp(fuzzyString); +} diff --git a/lab-remil/app/filter/swag-case.js b/lab-remil/app/filter/swag-case.js new file mode 100644 index 00000000..ddf9364c --- /dev/null +++ b/lab-remil/app/filter/swag-case.js @@ -0,0 +1,19 @@ +'use strict'; + +const vowels = { + a: 'a', + e: 'e', + i: 'i', + o: 'o', + u: 'u', +}; + +module.exports = function() { + return function(string) { + return string.split('').reduce((str, char) => { + if (vowels[char.toLowerCase()]) char = char.toLowerCase(); + if (!vowels[char.toLowerCase()]) char = char.toUpperCase(); + return str.concat(char); + }, ''); + }; +}; diff --git a/lab-remil/app/index.html b/lab-remil/app/index.html new file mode 100644 index 00000000..7beb9486 --- /dev/null +++ b/lab-remil/app/index.html @@ -0,0 +1,19 @@ + + + + + + ayoGRAM + +
+ +
+ +
+ +
+ + + diff --git a/lab-remil/app/scss/lib/base/_base.scss b/lab-remil/app/scss/lib/base/_base.scss new file mode 100644 index 00000000..8a674733 --- /dev/null +++ b/lab-remil/app/scss/lib/base/_base.scss @@ -0,0 +1,73 @@ +@import '../theme/vars'; + +body { + font-family: helvetica; + font-size: 2.75vw; + background: $color-secondary; + color: $color-primary; +} + +a { + text-decoration: none; +} + +strong { + font-weight:900; +} + +h2 { + font-size: 2.2vh; + margin: $gutter-std/2 0; +} + +.input-std { + width: 100%; + margin: 0 0 $gutter-std 0; + padding: $input-padding; + border-radius: $border-radius; +} + +.input-sm { + @extend .input-std; + margin: 0; + // padding: 0; + padding: $input-padding/2 $input-padding; +} + +.btn-std { + // padding: $input-padding $input-padding * 6; + padding: $input-padding 0; + display: inline-block; + width: 35vw; + background: $color-primary; + color: $white; + border-radius: $border-radius; + margin: 0 auto; + text-align: center; +} +.btn-sm { + @extend .btn-std; + width: 20vw; + padding: $input-padding/2 0; +} +.btn-sm-secondary { + @extend .btn-sm; + background: $color-secondary * .8; + color: $color-primary; +} + +.form { + margin-top: $gutter-std; + + h2 { + margin: $gutter-sm 0; + } + + button { + float: right; + } +} + +.underline { + text-decoration: underline; +} diff --git a/lab-remil/app/scss/lib/base/_reset.scss b/lab-remil/app/scss/lib/base/_reset.scss new file mode 100644 index 00000000..ed11813c --- /dev/null +++ b/lab-remil/app/scss/lib/base/_reset.scss @@ -0,0 +1,48 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/lab-remil/app/scss/lib/bootstrap/_custom-bootstrap-vars.scss b/lab-remil/app/scss/lib/bootstrap/_custom-bootstrap-vars.scss new file mode 100644 index 00000000..bf577e0f --- /dev/null +++ b/lab-remil/app/scss/lib/bootstrap/_custom-bootstrap-vars.scss @@ -0,0 +1,688 @@ +$app-primary: #a7f7fa; +$app-secondary: #7fa7af; +$app-disabled: #abeaeb; +$app-active: #fa7fa7; +$app-success: #a7ff7a; +$app-error: #ff7a7a; +$app-warn: #ffa77a; +$app-info: #7aa7ff; +$app-white: #fff; +$app-black: #000; +$bootstrap-sass-asset-helper: true !default; +// +// Variables +// -------------------------------------------------- +//== Colors +// +//## Gray and brand colors for use across Bootstrap. +$gray-base : #000 !default; +$gray-darker : lighten($gray-base, 13.5%) !default; // #222 +$gray-dark : lighten($gray-base, 20%) !default; // #333 +$gray : lighten($gray-base, 33.5%) !default; // #555 +$gray-light : lighten($gray-base, 46.7%) !default; // #777 +$gray-lighter : lighten($gray-base, 93.5%) !default; // #eee +$brand-primary : darken(#428bca, 6.5%) !default; // #337ab7 +$brand-success : #5cb85c !default; +$brand-info : #5bc0de !default; +$brand-warning : #f0ad4e !default; +$brand-danger : #d9534f !default; +//== Scaffolding +// +//## Settings for some of the most global styles. +//** Background color for ``. +$body-bg : #fff !default; +//** Global text color on ``. +$text-color : $gray-dark !default; +//** Global textual link color. +$link-color : $brand-primary !default; +//** Link hover color set via `darken()` function. +$link-hover-color : darken($link-color, 15%) !default; +//** Link hover decoration. +$link-hover-decoration: underline !default; +//== Typography +// +//## Font, line-height, and color for body text, headings, and more. +$font-family-sans-serif : "Helvetica Neue", +Helvetica, +Arial, +sans-serif !default; +$font-family-serif : Georgia, +"Times New Roman", +Times, +serif !default; +//** Default monospace fonts for ``, ``, and `
`.
+$font-family-monospace : Menlo,
+Monaco,
+Consolas,
+"Courier New",
+monospace !default;
+$font-family-base : $font-family-sans-serif !default;
+$font-size-base : 14px !default;
+$font-size-large : ceil(($font-size-base * 1.25)) !default; // ~18px
+$font-size-small : ceil(($font-size-base * 0.85)) !default; // ~12px
+$font-size-h1 : floor(($font-size-base * 2.6)) !default; // ~36px
+$font-size-h2 : floor(($font-size-base * 2.15)) !default; // ~30px
+$font-size-h3 : ceil(($font-size-base * 1.7)) !default; // ~24px
+$font-size-h4 : ceil(($font-size-base * 1.25)) !default; // ~18px
+$font-size-h5 : $font-size-base !default;
+$font-size-h6 : ceil(($font-size-base * 0.85)) !default; // ~12px
+//** Unit-less `line-height` for use in components like buttons.
+$line-height-base : 1.428571429 !default; // 20/14
+//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
+$line-height-computed : floor(($font-size-base * $line-height-base)) !default; // ~20px
+//** By default, this inherits from the ``.
+$headings-font-family : inherit !default;
+$headings-font-weight : 500 !default;
+$headings-line-height : 1.1 !default;
+$headings-color : inherit !default;
+//== Iconography
+//
+//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
+//** Load fonts from this directory.
+// [converter] If $bootstrap-sass-asset-helper if used, provide path relative to the assets load path.
+// [converter] This is because some asset helpers, such as Sprockets, do not work with file-relative paths.
+$icon-font-path: if($bootstrap-sass-asset-helper, "bootstrap/", "../fonts/bootstrap/") !default;
+//** File name for all font files.
+$icon-font-name : "glyphicons-halflings-regular" !default;
+//** Element ID within SVG icon file.
+$icon-font-svg-id : "glyphicons_halflingsregular" !default;
+//== Components
+//
+//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
+$padding-base-vertical : 6px !default;
+$padding-base-horizontal : 12px !default;
+$padding-large-vertical : 10px !default;
+$padding-large-horizontal : 16px !default;
+$padding-small-vertical : 5px !default;
+$padding-small-horizontal : 10px !default;
+$padding-xs-vertical : 1px !default;
+$padding-xs-horizontal : 5px !default;
+$line-height-large : 1.3333333 !default; // extra decimals for Win 8.1 Chrome
+$line-height-small : 1.5 !default;
+$border-radius-base : 4px !default;
+$border-radius-large : 6px !default;
+$border-radius-small : 3px !default;
+//** Global color for active items (e.g., navs or dropdowns).
+$component-active-color : #fff !default;
+//** Global background color for active items (e.g., navs or dropdowns).
+$component-active-bg : $brand-primary !default;
+//** Width of the `border` for generating carets that indicate dropdowns.
+$caret-width-base : 4px !default;
+//** Carets increase slightly in size for larger components.
+$caret-width-large : 5px !default;
+//== Tables
+//
+//## Customizes the `.table` component with basic values, each used across all table variations.
+//** Padding for ``s and ``s.
+$table-cell-padding : 8px !default;
+//** Padding for cells in `.table-condensed`.
+$table-condensed-cell-padding : 5px !default;
+//** Default background color used for all tables.
+$table-bg : transparent !default;
+//** Background color used for `.table-striped`.
+$table-bg-accent : #f9f9f9 !default;
+//** Background color used for `.table-hover`.
+$table-bg-hover : #f5f5f5 !default;
+$table-bg-active : $table-bg-hover !default;
+//** Border color for table and cell borders.
+$table-border-color : #ddd !default;
+//== Buttons
+//
+//## For each of Bootstrap's buttons, define text, background and border color.
+$btn-font-weight : normal !default;
+$btn-default-color : #333 !default;
+$btn-default-bg : #fff !default;
+$btn-default-border : #ccc !default;
+$btn-primary-color : #fff !default;
+$btn-primary-bg : $brand-primary !default;
+$btn-primary-border : darken($btn-primary-bg, 5%) !default;
+$btn-success-color : #fff !default;
+$btn-success-bg : $brand-success !default;
+$btn-success-border : darken($btn-success-bg, 5%) !default;
+$btn-info-color : #fff !default;
+$btn-info-bg : $brand-info !default;
+$btn-info-border : darken($btn-info-bg, 5%) !default;
+$btn-warning-color : #fff !default;
+$btn-warning-bg : $brand-warning !default;
+$btn-warning-border : darken($btn-warning-bg, 5%) !default;
+$btn-danger-color : #fff !default;
+$btn-danger-bg : $brand-danger !default;
+$btn-danger-border : darken($btn-danger-bg, 5%) !default;
+$btn-link-disabled-color : $gray-light !default;
+// Allows for customizing button radius independently from global border radius
+$btn-border-radius-base : $border-radius-base !default;
+$btn-border-radius-large : $border-radius-large !default;
+$btn-border-radius-small : $border-radius-small !default;
+//== Forms
+//
+//##
+//** `` background color
+$input-bg : #fff !default;
+//** `` background color
+$input-bg-disabled : $gray-lighter !default;
+//** Text color for ``s
+$input-color : $gray !default;
+//** `` border color
+$input-border : #ccc !default;
+// TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4
+//** Default `.form-control` border radius
+// This has no effect on ``s in CSS.
+$input-border-radius : $border-radius-base !default;
+//** Large `.form-control` border radius
+$input-border-radius-large : $border-radius-large !default;
+//** Small `.form-control` border radius
+$input-border-radius-small : $border-radius-small !default;
+//** Border color for inputs on focus
+$input-border-focus : #66afe9 !default;
+//** Placeholder text color
+$input-color-placeholder : #999 !default;
+//** Default `.form-control` height
+$input-height-base : ($line-height-computed + ($padding-base-vertical * 2) + 2) !default;
+//** Large `.form-control` height
+$input-height-large : (ceil($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 2) !default;
+//** Small `.form-control` height
+$input-height-small : (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 2) !default;
+//** `.form-group` margin
+$form-group-margin-bottom : 15px !default;
+$legend-color : $gray-dark !default;
+$legend-border-color : #e5e5e5 !default;
+//** Background color for textual input addons
+$input-group-addon-bg : $gray-lighter !default;
+//** Border color for textual input addons
+$input-group-addon-border-color: $input-border !default;
+//** Disabled cursor for form controls and buttons.
+$cursor-disabled : not-allowed !default;
+//== Dropdowns
+//
+//## Dropdown menu container and contents.
+//** Background for the dropdown menu.
+$dropdown-bg : #fff !default;
+//** Dropdown menu `border-color`.
+$dropdown-border : rgba(0, 0, 0, .15) !default;
+//** Dropdown menu `border-color` **for IE8**.
+$dropdown-fallback-border : #ccc !default;
+//** Divider color for between dropdown items.
+$dropdown-divider-bg : #e5e5e5 !default;
+//** Dropdown link text color.
+$dropdown-link-color : $gray-dark !default;
+//** Hover color for dropdown links.
+$dropdown-link-hover-color : darken($gray-dark, 5%) !default;
+//** Hover background for dropdown links.
+$dropdown-link-hover-bg : #f5f5f5 !default;
+//** Active dropdown menu item text color.
+$dropdown-link-active-color : $component-active-color !default;
+//** Active dropdown menu item background color.
+$dropdown-link-active-bg : $component-active-bg !default;
+//** Disabled dropdown menu item background color.
+$dropdown-link-disabled-color : $gray-light !default;
+//** Text color for headers within dropdown menus.
+$dropdown-header-color : $gray-light !default;
+//** Deprecated `$dropdown-caret-color` as of v3.1.0
+$dropdown-caret-color : #000 !default;
+//-- Z-index master list
+//
+// Warning: Avoid customizing these values. They're used for a bird's eye view
+// of components dependent on the z-axis and are designed to all work together.
+//
+// Note: These variables are not generated into the Customizer.
+$zindex-navbar : 1000 !default;
+$zindex-dropdown : 1000 !default;
+$zindex-popover : 1060 !default;
+$zindex-tooltip : 1070 !default;
+$zindex-navbar-fixed : 1030 !default;
+$zindex-modal-background : 1040 !default;
+$zindex-modal : 1050 !default;
+//== Media queries breakpoints
+//
+//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
+// Extra small screen / phone
+//** Deprecated `$screen-xs` as of v3.0.1
+$screen-xs : 480px !default;
+//** Deprecated `$screen-xs-min` as of v3.2.0
+$screen-xs-min : $screen-xs !default;
+//** Deprecated `$screen-phone` as of v3.0.1
+$screen-phone : $screen-xs-min !default;
+// Small screen / tablet
+//** Deprecated `$screen-sm` as of v3.0.1
+$screen-sm : 768px !default;
+$screen-sm-min : $screen-sm !default;
+//** Deprecated `$screen-tablet` as of v3.0.1
+$screen-tablet : $screen-sm-min !default;
+// Medium screen / desktop
+//** Deprecated `$screen-md` as of v3.0.1
+$screen-md : 992px !default;
+$screen-md-min : $screen-md !default;
+//** Deprecated `$screen-desktop` as of v3.0.1
+$screen-desktop : $screen-md-min !default;
+// Large screen / wide desktop
+//** Deprecated `$screen-lg` as of v3.0.1
+$screen-lg : 1200px !default;
+$screen-lg-min : $screen-lg !default;
+//** Deprecated `$screen-lg-desktop` as of v3.0.1
+$screen-lg-desktop : $screen-lg-min !default;
+// So media queries don't overlap when required, provide a maximum
+$screen-xs-max : ($screen-sm-min - 1) !default;
+$screen-sm-max : ($screen-md-min - 1) !default;
+$screen-md-max : ($screen-lg-min - 1) !default;
+//== Grid system
+//
+//## Define your custom responsive grid.
+//** Number of columns in the grid.
+$grid-columns : 12 !default;
+//** Padding between columns. Gets divided in half for the left and right.
+$grid-gutter-width : 30px !default;
+// Navbar collapse
+//** Point at which the navbar becomes uncollapsed.
+$grid-float-breakpoint : $screen-sm-min !default;
+//** Point at which the navbar begins collapsing.
+$grid-float-breakpoint-max: ($grid-float-breakpoint - 1) !default;
+//== Container sizes
+//
+//## Define the maximum width of `.container` for different screen sizes.
+// Small screen / tablet
+$container-tablet : (720px + $grid-gutter-width) !default;
+//** For `$screen-sm-min` and up.
+$container-sm : $container-tablet !default;
+// Medium screen / desktop
+$container-desktop : (940px + $grid-gutter-width) !default;
+//** For `$screen-md-min` and up.
+$container-md : $container-desktop !default;
+// Large screen / wide desktop
+$container-large-desktop : (1140px + $grid-gutter-width) !default;
+//** For `$screen-lg-min` and up.
+$container-lg : $container-large-desktop !default;
+//== Navbar
+//
+//##
+// Basics of a navbar
+$navbar-height : 50px !default;
+$navbar-margin-bottom : $line-height-computed !default;
+$navbar-border-radius : $border-radius-base !default;
+$navbar-padding-horizontal : floor(($grid-gutter-width / 2)) !default;
+$navbar-padding-vertical : (($navbar-height - $line-height-computed) / 2) !default;
+$navbar-collapse-max-height : 340px !default;
+$navbar-default-color : #777 !default;
+$navbar-default-bg : #f8f8f8 !default;
+$navbar-default-border : darken($navbar-default-bg, 6.5%) !default;
+// Navbar links
+$navbar-default-link-color : #777 !default;
+$navbar-default-link-hover-color : #333 !default;
+$navbar-default-link-hover-bg : transparent !default;
+$navbar-default-link-active-color : #555 !default;
+$navbar-default-link-active-bg : darken($navbar-default-bg, 6.5%) !default;
+$navbar-default-link-disabled-color : #ccc !default;
+$navbar-default-link-disabled-bg : transparent !default;
+// Navbar brand label
+$navbar-default-brand-color : $navbar-default-link-color !default;
+$navbar-default-brand-hover-color : darken($navbar-default-brand-color, 10%) !default;
+$navbar-default-brand-hover-bg : transparent !default;
+// Navbar toggle
+$navbar-default-toggle-hover-bg : #ddd !default;
+$navbar-default-toggle-icon-bar-bg : #888 !default;
+$navbar-default-toggle-border-color : #ddd !default;
+//=== Inverted navbar
+// Reset inverted navbar basics
+$navbar-inverse-color : lighten($gray-light, 15%) !default;
+$navbar-inverse-bg : #222 !default;
+$navbar-inverse-border : darken($navbar-inverse-bg, 10%) !default;
+// Inverted navbar links
+$navbar-inverse-link-color : lighten($gray-light, 15%) !default;
+$navbar-inverse-link-hover-color : #fff !default;
+$navbar-inverse-link-hover-bg : transparent !default;
+$navbar-inverse-link-active-color : $navbar-inverse-link-hover-color !default;
+$navbar-inverse-link-active-bg : darken($navbar-inverse-bg, 10%) !default;
+$navbar-inverse-link-disabled-color : #444 !default;
+$navbar-inverse-link-disabled-bg : transparent !default;
+// Inverted navbar brand label
+$navbar-inverse-brand-color : $navbar-inverse-link-color !default;
+$navbar-inverse-brand-hover-color : #fff !default;
+$navbar-inverse-brand-hover-bg : transparent !default;
+// Inverted navbar toggle
+$navbar-inverse-toggle-hover-bg : #333 !default;
+$navbar-inverse-toggle-icon-bar-bg : #fff !default;
+$navbar-inverse-toggle-border-color : #333 !default;
+//== Navs
+//
+//##
+//=== Shared nav styles
+$nav-link-padding : 10px 15px !default;
+$nav-link-hover-bg : $gray-lighter !default;
+$nav-disabled-link-color : $gray-light !default;
+$nav-disabled-link-hover-color : $gray-light !default;
+//== Tabs
+$nav-tabs-border-color : #ddd !default;
+$nav-tabs-link-hover-border-color : $gray-lighter !default;
+$nav-tabs-active-link-hover-bg : $body-bg !default;
+$nav-tabs-active-link-hover-color : $gray !default;
+$nav-tabs-active-link-hover-border-color : #ddd !default;
+$nav-tabs-justified-link-border-color : #ddd !default;
+$nav-tabs-justified-active-link-border-color : $body-bg !default;
+//== Pills
+$nav-pills-border-radius : $border-radius-base !default;
+$nav-pills-active-link-hover-bg : $component-active-bg !default;
+$nav-pills-active-link-hover-color : $component-active-color !default;
+//== Pagination
+//
+//##
+$pagination-color : $link-color !default;
+$pagination-bg : #fff !default;
+$pagination-border : #ddd !default;
+$pagination-hover-color : $link-hover-color !default;
+$pagination-hover-bg : $gray-lighter !default;
+$pagination-hover-border : #ddd !default;
+$pagination-active-color : #fff !default;
+$pagination-active-bg : $brand-primary !default;
+$pagination-active-border : $brand-primary !default;
+$pagination-disabled-color : $gray-light !default;
+$pagination-disabled-bg : #fff !default;
+$pagination-disabled-border : #ddd !default;
+//== Pager
+//
+//##
+$pager-bg : $pagination-bg !default;
+$pager-border : $pagination-border !default;
+$pager-border-radius : 15px !default;
+$pager-hover-bg : $pagination-hover-bg !default;
+$pager-active-bg : $pagination-active-bg !default;
+$pager-active-color : $pagination-active-color !default;
+$pager-disabled-color : $pagination-disabled-color !default;
+//== Jumbotron
+//
+//##
+$jumbotron-padding : 30px !default;
+$jumbotron-color : inherit !default;
+$jumbotron-bg : $gray-lighter !default;
+$jumbotron-heading-color : inherit !default;
+$jumbotron-font-size : ceil(($font-size-base * 1.5)) !default;
+$jumbotron-heading-font-size : ceil(($font-size-base * 4.5)) !default;
+//== Form states and alerts
+//
+//## Define colors for form feedback states and, by default, alerts.
+$state-success-text : #3c763d !default;
+$state-success-bg : #dff0d8 !default;
+$state-success-border : darken(adjust-hue($state-success-bg, -10), 5%) !default;
+$state-info-text : #31708f !default;
+$state-info-bg : #d9edf7 !default;
+$state-info-border : darken(adjust-hue($state-info-bg, -10), 7%) !default;
+$state-warning-text : #8a6d3b !default;
+$state-warning-bg : #fcf8e3 !default;
+$state-warning-border : darken(adjust-hue($state-warning-bg, -10), 5%) !default;
+$state-danger-text : #a94442 !default;
+$state-danger-bg : #f2dede !default;
+$state-danger-border : darken(adjust-hue($state-danger-bg, -10), 5%) !default;
+//== Tooltips
+//
+//##
+//** Tooltip max width
+$tooltip-max-width : 200px !default;
+//** Tooltip text color
+$tooltip-color : #fff !default;
+//** Tooltip background color
+$tooltip-bg : #000 !default;
+$tooltip-opacity : .9 !default;
+//** Tooltip arrow width
+$tooltip-arrow-width : 5px !default;
+//** Tooltip arrow color
+$tooltip-arrow-color : $tooltip-bg !default;
+//== Popovers
+//
+//##
+//** Popover body background color
+$popover-bg : #fff !default;
+//** Popover maximum width
+$popover-max-width : 276px !default;
+//** Popover border color
+$popover-border-color : rgba(0, 0, 0, .2) !default;
+//** Popover fallback border color
+$popover-fallback-border-color : #ccc !default;
+//** Popover title background color
+$popover-title-bg : darken($popover-bg, 3%) !default;
+//** Popover arrow width
+$popover-arrow-width : 10px !default;
+//** Popover arrow color
+$popover-arrow-color : $popover-bg !default;
+//** Popover outer arrow width
+$popover-arrow-outer-width : ($popover-arrow-width + 1) !default;
+//** Popover outer arrow color
+$popover-arrow-outer-color : fade_in($popover-border-color, 0.05) !default;
+//** Popover outer arrow fallback color
+$popover-arrow-outer-fallback-color : darken($popover-fallback-border-color, 20%) !default;
+//== Labels
+//
+//##
+//** Default label background color
+$label-default-bg : $gray-light !default;
+//** Primary label background color
+$label-primary-bg : $brand-primary !default;
+//** Success label background color
+$label-success-bg : $brand-success !default;
+//** Info label background color
+$label-info-bg : $brand-info !default;
+//** Warning label background color
+$label-warning-bg : $brand-warning !default;
+//** Danger label background color
+$label-danger-bg : $brand-danger !default;
+//** Default label text color
+$label-color : #fff !default;
+//** Default text color of a linked label
+$label-link-hover-color : #fff !default;
+//== Modals
+//
+//##
+//** Padding applied to the modal body
+$modal-inner-padding : 15px !default;
+//** Padding applied to the modal title
+$modal-title-padding : 15px !default;
+//** Modal title line-height
+$modal-title-line-height : $line-height-base !default;
+//** Background color of modal content area
+$modal-content-bg : #fff !default;
+//** Modal content border color
+$modal-content-border-color : rgba(0, 0, 0, .2) !default;
+//** Modal content border color **for IE8**
+$modal-content-fallback-border-color : #999 !default;
+//** Modal backdrop background color
+$modal-backdrop-bg : #000 !default;
+//** Modal backdrop opacity
+$modal-backdrop-opacity : .5 !default;
+//** Modal header border color
+$modal-header-border-color : #e5e5e5 !default;
+//** Modal footer border color
+$modal-footer-border-color : $modal-header-border-color !default;
+$modal-lg : 900px !default;
+$modal-md : 600px !default;
+$modal-sm : 300px !default;
+//== Alerts
+//
+//## Define alert colors, border radius, and padding.
+$alert-padding : 15px !default;
+$alert-border-radius : $border-radius-base !default;
+$alert-link-font-weight : bold !default;
+$alert-success-bg : $state-success-bg !default;
+$alert-success-text : $state-success-text !default;
+$alert-success-border : $state-success-border !default;
+$alert-info-bg : $state-info-bg !default;
+$alert-info-text : $state-info-text !default;
+$alert-info-border : $state-info-border !default;
+$alert-warning-bg : $state-warning-bg !default;
+$alert-warning-text : $state-warning-text !default;
+$alert-warning-border : $state-warning-border !default;
+$alert-danger-bg : $state-danger-bg !default;
+$alert-danger-text : $state-danger-text !default;
+$alert-danger-border : $state-danger-border !default;
+//== Progress bars
+//
+//##
+//** Background color of the whole progress component
+$progress-bg : #f5f5f5 !default;
+//** Progress bar text color
+$progress-bar-color : #fff !default;
+//** Variable for setting rounded corners on progress bar.
+$progress-border-radius : $border-radius-base !default;
+//** Default progress bar color
+$progress-bar-bg : $brand-primary !default;
+//** Success progress bar color
+$progress-bar-success-bg : $brand-success !default;
+//** Warning progress bar color
+$progress-bar-warning-bg : $brand-warning !default;
+//** Danger progress bar color
+$progress-bar-danger-bg : $brand-danger !default;
+//** Info progress bar color
+$progress-bar-info-bg : $brand-info !default;
+//== List group
+//
+//##
+//** Background color on `.list-group-item`
+$list-group-bg : #fff !default;
+//** `.list-group-item` border color
+$list-group-border : #ddd !default;
+//** List group border radius
+$list-group-border-radius : $border-radius-base !default;
+//** Background color of single list items on hover
+$list-group-hover-bg : #f5f5f5 !default;
+//** Text color of active list items
+$list-group-active-color : $component-active-color !default;
+//** Background color of active list items
+$list-group-active-bg : $component-active-bg !default;
+//** Border color of active list elements
+$list-group-active-border : $list-group-active-bg !default;
+//** Text color for content within active list items
+$list-group-active-text-color : lighten($list-group-active-bg, 40%) !default;
+//** Text color of disabled list items
+$list-group-disabled-color : $gray-light !default;
+//** Background color of disabled list items
+$list-group-disabled-bg : $gray-lighter !default;
+//** Text color for content within disabled list items
+$list-group-disabled-text-color: $list-group-disabled-color !default;
+$list-group-link-color : #555 !default;
+$list-group-link-hover-color : $list-group-link-color !default;
+$list-group-link-heading-color: #333 !default;
+//== Panels
+//
+//##
+$panel-bg : #fff !default;
+$panel-body-padding : 15px !default;
+$panel-heading-padding : 10px 15px !default;
+$panel-footer-padding : $panel-heading-padding !default;
+$panel-border-radius : $border-radius-base !default;
+//** Border color for elements within panels
+$panel-inner-border : #ddd !default;
+$panel-footer-bg : #f5f5f5 !default;
+$panel-default-text : $gray-dark !default;
+$panel-default-border : #ddd !default;
+$panel-default-heading-bg : #f5f5f5 !default;
+$panel-primary-text : #fff !default;
+$panel-primary-border : $brand-primary !default;
+$panel-primary-heading-bg : $brand-primary !default;
+$panel-success-text : $state-success-text !default;
+$panel-success-border : $state-success-border !default;
+$panel-success-heading-bg : $state-success-bg !default;
+$panel-info-text : $state-info-text !default;
+$panel-info-border : $state-info-border !default;
+$panel-info-heading-bg : $state-info-bg !default;
+$panel-warning-text : $state-warning-text !default;
+$panel-warning-border : $state-warning-border !default;
+$panel-warning-heading-bg : $state-warning-bg !default;
+$panel-danger-text : $state-danger-text !default;
+$panel-danger-border : $state-danger-border !default;
+$panel-danger-heading-bg : $state-danger-bg !default;
+//== Thumbnails
+//
+//##
+//** Padding around the thumbnail image
+$thumbnail-padding : 4px !default;
+//** Thumbnail background color
+$thumbnail-bg : $body-bg !default;
+//** Thumbnail border color
+$thumbnail-border : #ddd !default;
+//** Thumbnail border radius
+$thumbnail-border-radius : $border-radius-base !default;
+//** Custom text color for thumbnail captions
+$thumbnail-caption-color : $text-color !default;
+//** Padding around the thumbnail caption
+$thumbnail-caption-padding : 9px !default;
+//== Wells
+//
+//##
+$well-bg : #f5f5f5 !default;
+$well-border : darken($well-bg, 7%) !default;
+//== Badges
+//
+//##
+$badge-color : #fff !default;
+//** Linked badge text color on hover
+$badge-link-hover-color : #fff !default;
+$badge-bg : $gray-light !default;
+//** Badge text color in active nav link
+$badge-active-color : $link-color !default;
+//** Badge background color in active nav link
+$badge-active-bg : #fff !default;
+$badge-font-weight : bold !default;
+$badge-line-height : 1 !default;
+$badge-border-radius : 10px !default;
+//== Breadcrumbs
+//
+//##
+$breadcrumb-padding-vertical : 8px !default;
+$breadcrumb-padding-horizontal: 15px !default;
+//** Breadcrumb background color
+$breadcrumb-bg : #f5f5f5 !default;
+//** Breadcrumb text color
+$breadcrumb-color : #ccc !default;
+//** Text color of current page in the breadcrumb
+$breadcrumb-active-color : $gray-light !default;
+//** Textual separator for between breadcrumb elements
+$breadcrumb-separator : "/" !default;
+//== Carousel
+//
+//##
+$carousel-text-shadow : 0 1px 2px rgba(0, 0, 0, .6) !default;
+$carousel-control-color : #fff !default;
+$carousel-control-width : 15% !default;
+$carousel-control-opacity : .5 !default;
+$carousel-control-font-size : 20px !default;
+$carousel-indicator-active-bg : #fff !default;
+$carousel-indicator-border-color : #fff !default;
+$carousel-caption-color : #fff !default;
+//== Close
+//
+//##
+$close-font-weight : bold !default;
+$close-color : #000 !default;
+$close-text-shadow : 0 1px 0 #fff !default;
+//== Code
+//
+//##
+$code-color : #c7254e !default;
+$code-bg : #f9f2f4 !default;
+$kbd-color : #fff !default;
+$kbd-bg : #333 !default;
+$pre-bg : #f5f5f5 !default;
+$pre-color : $gray-dark !default;
+$pre-border-color : #ccc !default;
+$pre-scrollable-max-height : 340px !default;
+//== Type
+//
+//##
+//** Horizontal offset for forms and lists.
+$component-offset-horizontal: 180px !default;
+//** Text muted color
+$text-muted : $gray-light !default;
+//** Abbreviations and acronyms border color
+$abbr-border-color : $gray-light !default;
+//** Headings small color
+$headings-small-color : $gray-light !default;
+//** Blockquote small color
+$blockquote-small-color : $gray-light !default;
+//** Blockquote font size
+$blockquote-font-size : ($font-size-base * 1.25) !default;
+//** Blockquote border color
+$blockquote-border-color : $gray-lighter !default;
+//** Page header border color
+$page-header-border-color : $gray-lighter !default;
+//** Width of horizontal description list titles
+$dl-horizontal-offset : $component-offset-horizontal !default;
+//** Point at which .dl-horizontal becomes horizontal
+$dl-horizontal-breakpoint : $grid-float-breakpoint !default;
+//** Horizontal line color.
+$hr-border : $gray-lighter !default;
diff --git a/lab-remil/app/scss/lib/bootstrap/_custom-bootstrap.scss b/lab-remil/app/scss/lib/bootstrap/_custom-bootstrap.scss
new file mode 100644
index 00000000..d4af9c48
--- /dev/null
+++ b/lab-remil/app/scss/lib/bootstrap/_custom-bootstrap.scss
@@ -0,0 +1,50 @@
+@import "custom-bootstrap-vars";
+
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/mixins";
+
+// Reset and dependencies
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/normalize";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/print";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/glyphicons";
+
+// Core CSS
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/scaffolding";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/type";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/code";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/grid";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/tables";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/forms";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/buttons";
+
+// Components
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/component-animations";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/dropdowns";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/button-groups";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/input-groups";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/navs";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/navbar";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/breadcrumbs";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/pagination";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/pager";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/labels";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/badges";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/jumbotron";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/thumbnails";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/alerts";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/progress-bars";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/media";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/list-group";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/panels";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/responsive-embed";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/wells";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/close";
+
+// Components w/ JavaScript
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/modals";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/tooltip";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/popovers";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/carousel";
+
+// Utility classes
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/utilities";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/responsive-utilities";
diff --git a/lab-remil/app/scss/lib/layout/_footer.scss b/lab-remil/app/scss/lib/layout/_footer.scss
new file mode 100644
index 00000000..00d9644d
--- /dev/null
+++ b/lab-remil/app/scss/lib/layout/_footer.scss
@@ -0,0 +1,6 @@
+@import '../theme/vars';
+
+footer {
+  height: $height-header * .8;
+  background: $color-primary * 2.3;
+}
diff --git a/lab-remil/app/scss/lib/layout/_header.scss b/lab-remil/app/scss/lib/layout/_header.scss
new file mode 100644
index 00000000..28d9e363
--- /dev/null
+++ b/lab-remil/app/scss/lib/layout/_header.scss
@@ -0,0 +1,9 @@
+@import '../theme/vars';
+
+$header-font-size: 3vh;
+
+header {
+  height: $height-header;
+  background: $color-primary;
+  color: $white;
+}
diff --git a/lab-remil/app/scss/lib/layout/_layout.scss b/lab-remil/app/scss/lib/layout/_layout.scss
new file mode 100644
index 00000000..dcb7dca7
--- /dev/null
+++ b/lab-remil/app/scss/lib/layout/_layout.scss
@@ -0,0 +1,14 @@
+@import '../theme/vars';
+
+body * {
+  box-sizing: border-box;
+}
+
+.clearfix {
+  clear: both;
+}
+
+.inner {
+  width: 100% - 2 * $gutter-std;
+  margin: 0 auto;
+}
diff --git a/lab-remil/app/scss/lib/layout/_main.scss b/lab-remil/app/scss/lib/layout/_main.scss
new file mode 100644
index 00000000..df54e055
--- /dev/null
+++ b/lab-remil/app/scss/lib/layout/_main.scss
@@ -0,0 +1,5 @@
+@import '../theme/vars';
+
+main {
+  min-height: 100vh - ( $height-header * 1.8 );
+}
diff --git a/lab-remil/app/scss/lib/theme/_vars.scss b/lab-remil/app/scss/lib/theme/_vars.scss
new file mode 100644
index 00000000..c4e634bb
--- /dev/null
+++ b/lab-remil/app/scss/lib/theme/_vars.scss
@@ -0,0 +1,14 @@
+//:::: COLORS ::::
+$color-primary: #444;
+$color-secondary: #ccc;
+$black: #000;
+$white: #fff;
+
+//:::: LAYOUT ::::
+$gutter-std: 5%;
+$gutter-sm: $gutter-std / 2;
+$height-header: 8vh;
+
+//:::: ELEMENTS ::::
+$border-radius: 5px;
+$input-padding: 1.5vh;
diff --git a/lab-remil/app/scss/main.scss b/lab-remil/app/scss/main.scss
new file mode 100644
index 00000000..34d8024e
--- /dev/null
+++ b/lab-remil/app/scss/main.scss
@@ -0,0 +1,12 @@
+//:::: BASE ::::
+@import './lib/base/reset',
+        './lib/base/base',
+        './lib/bootstrap/custom-bootstrap';
+
+//:::: LAYOUT ::::
+@import './lib/layout/layout',
+        './lib/layout/header',
+        './lib/layout/main',
+        './lib/layout/footer';
+
+//:::: BOOTSTRAP :::::
diff --git a/lab-remil/app/service/auth-service.js b/lab-remil/app/service/auth-service.js
new file mode 100644
index 00000000..75fa970c
--- /dev/null
+++ b/lab-remil/app/service/auth-service.js
@@ -0,0 +1,88 @@
+'use strict';
+
+module.exports = ['$q', '$log', '$http', '$window', authService];
+
+function authService($q, $log, $http, $window) {
+  $log.debug('authService');
+
+  let service = {};
+  let token = null;
+
+  function setToken(_token) {
+    $log.debug('authService.setToken()');
+
+    if (! _token) {
+      return $q.reject(new Error('no token'));
+    }
+
+    $window.localStorage.setItem('token', _token);
+    token = _token;
+    return $q.resolve(token);
+  }
+
+  service.getToken = function() {
+    $log.debug('authService.getToken()');
+    if (token) {
+      return $q.resolve(token);
+    }
+
+    token = $window.localStorage.getItem('token');
+    if (token) return $q.resolve(token);
+    return $q.reject(new Error('token not found'));
+  };
+
+  service.signup = function(user) {
+    $log.debug('authService.signup()');
+
+    let url = `${__API_URL__}/api/signup`;
+    let config = {
+      headers: {
+        'Content-Type': 'application/json',
+        'Accept': 'application/json',
+      },
+    };
+
+    return $http.post(url, user, config)
+    .then( res => {
+      $log.log('signup success', res.data);
+      return setToken(res.data);
+    })
+    .catch( err => {
+      $log.error('signup failure:', err.message);
+      return $q.reject(err);
+    });
+  };
+
+  service.logout = function() {
+    $log.debug('authService.logout()');
+
+    $window.localStorage.removeItem('token');
+    token = null;
+    return $q.resolve();
+  };
+
+  service.login = function(user) {
+    $log.debug('authService.login()');
+
+    let url = `${__API_URL__}/api/login`;
+    let base64 = $window.btoa(`${user.username}:${user.password}`);
+    let config = {
+      headers: {
+        Accept: 'application/json',
+        Authorization: `Basic ${base64}`,
+      },
+    };
+
+    return $http.get(url, config)
+    .then( res => {
+      $log.log('login: success', res.data);
+      return setToken(res.data);
+    })
+    .catch( err => {
+      $log.error(err.message);
+      return $q.reject(err);
+    });
+  };
+
+  return service;
+}
diff --git a/lab-remil/app/service/gallery-service.js b/lab-remil/app/service/gallery-service.js
new file mode 100644
index 00000000..91fa372a
--- /dev/null
+++ b/lab-remil/app/service/gallery-service.js
@@ -0,0 +1,150 @@
+'use strict';
+
+module.exports = ['$q', '$log', '$http', 'authService', galleryService];
+
+function galleryService($q, $log, $http, authService) {
+  $log.debug('galleryService');
+
+  let service = {};
+  service.galleries = [];
+
+  service.createGallery = function(gallery) {
+    $log.debug('galleryService.createGallery');
+
+    return authService.getToken()
+    .then( token => {
+      let url = `${__API_URL__}/api/gallery`;
+      let config = {
+        headers: {
+          Accept: 'application/json',
+          'Content-Type': 'application/json',
+          Authorization: `Bearer ${token}`,
+        },
+      };
+
+      return $http.post(url, gallery, config);
+    })
+    .then( res => {
+      $log.log('gallery created');
+      let gallery = res.data;
+      service.galleries.unshift(gallery);
+      return gallery;
+    })
+    .catch( err => {
+      $log.error(err.message);
+      return $q.reject(err);
+    });
+  };
+
+  service.deleteGalleries = function(galleryID) {
+    $log.debug('galleryService.deleteGalleries');
+
+    return authService.getToken()
+    .then( token => {
+      let url = `${__API_URL__}/api/gallery/${galleryID}`;
+      let config = {
+        headers: {
+          Accept: 'application/json',
+          Authorization: `Bearer ${token}`,
+        },
+      };
+
+      // TODO: create $http.delete request
+    });
+  };
+
+  service.fetchGalleries = function() {
+    $log.debug('galleryService.fetchGalleries');
+
+    return authService.getToken()
+    .then( token => {
+      let url = `${__API_URL__}/api/gallery`;
+      let config = {
+        headers: {
+          Accept: 'application/json',
+          Authorization: `Bearer ${token}`,
+        },
+      };
+
+      return $http.get(url, config);
+    })
+    .then( res => {
+      $log.log('galleries retrieved');
+      service.galleries = res.data;
+      return service.galleries;
+    })
+    .catch( err => {
+      $log.error(err.message);
+      return $q.reject(err);
+    });
+  };
+
+  service.updateGallery = function(galleryID, galleryData) {
+    $log.debug('galleryService.updateGallery');
+
+    return authService.getToken()
+    .then( token => {
+      let url = `${__API_URL__}/api/gallery/${galleryID}`;
+      let config = {
+        headers: {
+          Accept: 'application/json',
+          Authorization: `Bearer ${token}`,
+          'Content-Type': 'application/json',
+        },
+      };
+
+      return $http.put(url, galleryData, config);
+    })
+    .then( res => {
+      for (let i=0; i < service.galleries.length; i++) {
+        let current = service.galleries[i];
+        if (current._id === galleryID) {
+          service.galleries[i] = res.data;
+          break;
+        }
+      }
+
+      return res.data;
+    })
+    .catch( err => {
+      $log.error(err.message);
+      return $q.reject(err);
+    });
+  };
+
+  service.deleteGallery = function(galleryID) {
+    $log.debug('galleryService.deleteGallery');
+
+    return authService.getToken()
+    .then( token => {
+      let url = `${__API_URL__}/api/gallery/${galleryID}`;
+      let config = {
+        headers: {
+          Authorization: `Bearer ${token}`,
+          Accept:'application/json',
+        },
+      };
+
+      return $http.delete(url, config);
+    })
+    .then( res => {
+      for (let i=0; i < service.galleries.length; i++) {
+        let current = service.galleries[i];
+        if (current._id === galleryID) {
+          service.galleries.splice(i, 1);
+          return res.data;
+        }
+      }
+    })
+    .catch( err => {
+      $log.error(err.message);
+      return $q.reject(err);
+    });
+  };
+
+  service.getGalleryState = function() {
+    return service.galleries;
+  };
+
+  return service;
+}
diff --git a/lab-remil/app/service/pic-service.js b/lab-remil/app/service/pic-service.js
new file mode 100644
index 00000000..1edd4b94
--- /dev/null
+++ b/lab-remil/app/service/pic-service.js
@@ -0,0 +1,73 @@
+'use strict';
+
+module.exports = ['$q', '$log', '$http', 'Upload', 'authService', picService];
+
+function picService($q, $log, $http, Upload, authService) {
+  $log.debug('picService');
+
+  let service = {};
+
+  service.uploadGalleryPic = function(galleryData, picData) {
+    $log.debug('service.uploadGalleryPic');
+
+    return authService.getToken()
+    .then( token => {
+      let url = `${__API_URL__}/api/gallery/${galleryData._id}/pic`;
+      let headers = {
+        Authorization: `Bearer ${token}`,
+        Accept: 'application/json',
+      };
+
+      return Upload.upload({
+        url,
+        headers,
+        method: 'POST',
+        data: {
+          name: picData.name,
+          desc: picData.desc,
+          file: picData.file,
+        },
+      });
+    })
+    .then( res => {
+      galleryData.pics.unshift(res.data);
+      return res.data;
+    })
+    .catch( err => {
+      $log.error(err.message);
+      return $q.reject(err);
+    });
+  };
+
+  service.deletePic = function(galleryData, picID) {
+    $log.debug('service.deletePic');
+
+    // /api/gallery/:galleryID/pic/:picID
+    return authService.getToken()
+    .then( token => {
+      let url = `${__API_URL__}/api/gallery/${galleryData._id}/pic/${picID}`;
+      let config = {
+        headers: {
+          Authorization: `Bearer ${token}`,
+        },
+      };
+
+      return $http.delete(url, config);
+    })
+    .then( () => {
+      for (let i=0; i < galleryData.pics.length; i++) {
+        if (galleryData.pics[i]._id === picID) {
+          galleryData.pics.splice(i, 1);
+          break;
+        }
+      }
+    })
+    .catch( err => {
+      $log.error(err.message);
+      return $q.reject(err);
+    });
+
+  };
+
+  return service;
+}
diff --git a/lab-remil/app/view/home/_home.scss b/lab-remil/app/view/home/_home.scss
new file mode 100644
index 00000000..bd6650dc
--- /dev/null
+++ b/lab-remil/app/view/home/_home.scss
@@ -0,0 +1,5 @@
+@import '../../scss/lib/theme/vars';
+
+.home {
+  margin-top: $gutter-std;
+}
diff --git a/lab-remil/app/view/home/home-controller.js b/lab-remil/app/view/home/home-controller.js
new file mode 100644
index 00000000..842c4dd0
--- /dev/null
+++ b/lab-remil/app/view/home/home-controller.js
@@ -0,0 +1,32 @@
+'use strict';
+
+require('./_home.scss');
+
+module.exports = ['$log', '$rootScope', 'galleryService', HomeController];
+
+function HomeController($log, $rootScope, galleryService) {
+  $log.debug('HomeController');
+
+  this.galleries = [];
+
+  this.fetchGalleries = function() {
+    galleryService.fetchGalleries()
+    .then( galleries => {
+      this.galleries = galleries;
+      this.currentGallery = galleries[0];
+    });
+  };
+
+  this.galleryDeleteDone = function(gallery) {
+    console.log('delete done gallery', gallery);
+    if (this.currentGallery._id === gallery._id) {
+      this.currentGallery = null;
+    }
+  };
+
+  this.fetchGalleries();
+
+  $rootScope.$on('$locationChangeSuccess', () => {
+    this.fetchGalleries();
+  });
+}
diff --git a/lab-remil/app/view/home/home.html b/lab-remil/app/view/home/home.html
new file mode 100644
index 00000000..1d01675c
--- /dev/null
+++ b/lab-remil/app/view/home/home.html
@@ -0,0 +1,22 @@
+
+
+ +

Create New Swaggerry

+ + + +

Swaggeries

+ +
    + +
+ + + +
+
diff --git a/lab-remil/app/view/landing/_landing.scss b/lab-remil/app/view/landing/_landing.scss new file mode 100644 index 00000000..f58eab16 --- /dev/null +++ b/lab-remil/app/view/landing/_landing.scss @@ -0,0 +1,25 @@ +@import '../../scss/lib/theme/vars'; + +.landing { + + span { + margin-top: $gutter-sm; + display: block; + float: right; + } + p { + display: inline-block; + } + + a { + font-weight: bolder; + color: $color-primary; + } +} + +.join-container { + .join-inner { + width: 100% - 2 * $gutter-std; + margin: 0 auto; + } +} diff --git a/lab-remil/app/view/landing/landing-controller.js b/lab-remil/app/view/landing/landing-controller.js new file mode 100644 index 00000000..ad4e3b7e --- /dev/null +++ b/lab-remil/app/view/landing/landing-controller.js @@ -0,0 +1,10 @@ +'use strict'; + +require('./_landing.scss'); + +module.exports = ['$log', '$location', '$rootScope', 'authService', LandingController]; + +function LandingController($log, $location, authService) { + let url = $location.url(); + this.showSignup = url === '/join#signup' || url === '/join'; +} diff --git a/lab-remil/app/view/landing/landing.html b/lab-remil/app/view/landing/landing.html new file mode 100644 index 00000000..834434b0 --- /dev/null +++ b/lab-remil/app/view/landing/landing.html @@ -0,0 +1,32 @@ +
+
+
+ +
+ +

already a member?

+ + sign in here. + +
+
+
+ + +
+
+ +
+ +

want to sign up?

+ + sign up here. + +
+
+
+
diff --git a/lab-remil/assets/cf-logo.png b/lab-remil/assets/cf-logo.png new file mode 100644 index 00000000..38a23b2d Binary files /dev/null and b/lab-remil/assets/cf-logo.png differ diff --git a/lab-remil/karma.conf.js b/lab-remil/karma.conf.js new file mode 100644 index 00000000..1582766f --- /dev/null +++ b/lab-remil/karma.conf.js @@ -0,0 +1,56 @@ +const webpack = require('./webpack.config.js'); +delete webpack.entry; + +const my = function(config) { + config.set({ + webpack, + basePath: '', + frameworks: ['jasmine'], + files: [ + 'test/**/*-test.js', + 'node_modules/angular-mocks/angular-mocks.js', + ], + exclude: [ + ], + preprocessors: { + 'test/**/*-test.js': ['webpack'], + }, + reporters: ['mocha'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['PhantomJS'], + singleRun: false, + concurrency: Infinity, + }); +}; + +const cf = function(config) { + config.set({ + webpack, + basePath: '', + frameworks: ['jasmine'], + files: [ + 'app/entry.js', + 'test/**/*-test.js', + 'node_modules/angular-mocks/angular-mocks.js', + ], + exclude: [ + ], + preprocessors: { + 'test/**/*-test.js': ['webpack'], + 'app/entry.js': ['webpack'], + }, + reporters: ['mocha'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false, + concurrency: Infinity, + }); +}; + +module.exports = cf; diff --git a/lab-remil/oldkarma.conf.js b/lab-remil/oldkarma.conf.js new file mode 100644 index 00000000..067f1cbd --- /dev/null +++ b/lab-remil/oldkarma.conf.js @@ -0,0 +1,26 @@ +const webpackConfig = require('./webpack.config.js'); + +module.exports = function(config) { + config.set({ + webpack: webpackConfig, + basePath: '', + frameworks: ['jasmine'], + files: [ + 'test/**/*-test.js', + 'node_modules/angular-mocks/angular-mocks.js', + ], + exclude: [ + ], + preprocessors: { + 'test/**/*-test.js': ['webpack'], + }, + reporters: ['mocha'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['PhantomJS'], + singleRun: false, + concurrency: Infinity, + }); +}; diff --git a/lab-remil/package.json b/lab-remil/package.json new file mode 100644 index 00000000..8f04fad2 --- /dev/null +++ b/lab-remil/package.json @@ -0,0 +1,58 @@ +{ + "name": "lab-remil", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "build": "./node_modules/webpack/bin/webpack.js", + "watch": "./node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot", + "lint": "./node_modules/eslint/bin/eslint.js .", + "test-watch": "./node_modules/karma/bin/karma start --run-watch", + "test": "./node_modules/karma/bin/karma start --single-run" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "angular": "^1.6.3", + "angular-animate": "^1.6.3", + "angular-material": "^1.1.3", + "angular-route": "^1.6.3", + "angular-touch": "^1.6.3", + "angular-ui-bootstrap": "^2.5.0", + "angular-ui-router": "^0.4.2", + "babel-core": "^6.24.0", + "babel-loader": "^6.4.1", + "babel-preset-es2015": "^6.24.0", + "bootstrap-sass": "^3.3.7", + "camelcase": "^4.0.0", + "clean-webpack-plugin": "^0.1.16", + "css-loader": "^0.27.3", + "dotenv": "^4.0.0", + "extract-text-webpack-plugin": "^2.1.0", + "file-loader": "^0.10.1", + "html-loader": "^0.4.5", + "html-webpack-plugin": "^2.28.0", + "ng-file-upload": "^12.2.13", + "node-sass": "^4.5.1", + "pascalcase": "^0.1.1", + "resolve-url-loader": "^2.0.2", + "sass-loader": "^6.0.3", + "style-loader": "^0.16.1", + "ui-router": "^1.0.0-alpha.3", + "url-loader": "^0.5.8", + "webpack": "^2.3.2" + }, + "devDependencies": { + "angular-mocks": "^1.6.3", + "eslint": "^3.18.0", + "jasmine-core": "^2.5.2", + "karma": "^1.5.0", + "karma-chrome-launcher": "^2.0.0", + "karma-jasmine": "^1.1.0", + "karma-mocha-reporter": "^2.2.3", + "karma-phantomjs-launcher": "^1.0.4", + "karma-webpack": "^2.0.3", + "webpack-dev-server": "^2.4.2" + } +} diff --git a/lab-remil/test/auth-service-test.js b/lab-remil/test/auth-service-test.js new file mode 100644 index 00000000..e2225085 --- /dev/null +++ b/lab-remil/test/auth-service-test.js @@ -0,0 +1,31 @@ +'use strict'; + +describe('Auth Service', function() { + + beforeEach(() => { + angular.mock.module('ayogram'); + angular.mock.inject(($rootScope, authService, $window, $httpBackend) => { + this.$window = $window; + this.$rootScope = $rootScope; + this.authService = authService; + this.$httpBackend = $httpBackend; + }); + }); + + describe('authService.getToken()', () => { + it('should return a token', () => { + this.authService.token = null; + this.$window.localStorage.setItem('token', 'test token'); + + this.authService.getToken() + .then( token => { + expect(token).toEqual('test token'); + }) + .catch( err => { + expect(err).toEqual(null); + }); + + this.$rootScope.$apply(); + }); + }); +}); diff --git a/lab-remil/test/edit-gallery-component-test.js b/lab-remil/test/edit-gallery-component-test.js new file mode 100644 index 00000000..30ca0716 --- /dev/null +++ b/lab-remil/test/edit-gallery-component-test.js @@ -0,0 +1,62 @@ +'use strict'; + +describe('Edit Gallery Component', function(){ + + beforeEach(() => { + angular.mock.module('ayogram'); + angular.mock.inject(($rootScope, $componentController, $httpBackend, authService) => { + this.$rootScope = $rootScope; + this.$componentController = $componentController; + this.$httpBackend = $httpBackend; + this.authService = authService; + }); + }); + + it('should have right component bindings', () => { + let mockBindings = { + gallery: { + name: 'swag gallery', + desc: 'too swaggy description', + }, + }; + + let editGalleryCtrl = this.$componentController('editGallery', null, mockBindings); + expect(editGalleryCtrl.gallery.name).toEqual(mockBindings.gallery.name); + expect(editGalleryCtrl.gallery.desc).toEqual(mockBindings.gallery.desc); + + this.$rootScope.$apply(); + }); + + describe('editGalleryCtrl.updateGallery()', () => { + it('should make a valid PUT request', () => { + let url = `${__API_URL__}/api/gallery/12345`; + let headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + Authorization: 'Bearer test token', + }; + + this.$httpBackend.expectPUT(url, { + _id: '12345', + name: 'updated name', + desc: 'updated description', + }, headers).respond(200); + + let mockBindings = { + gallery: { + _id: '12345', + name: 'updated name', + desc: 'updated description', + }, + }; + + let editGalleryCtrl = this.$componentController('editGallery', null, mockBindings); + editGalleryCtrl.gallery.name = 'updated name'; + editGalleryCtrl.gallery.desc = 'updated description'; + editGalleryCtrl.updateGallery(); + + this.$httpBackend.flush(); + this.$rootScope.$apply(); + }); + }); +}); diff --git a/lab-remil/test/ex-test.js b/lab-remil/test/ex-test.js new file mode 100644 index 00000000..a41ce3fa --- /dev/null +++ b/lab-remil/test/ex-test.js @@ -0,0 +1,7 @@ +'use strict'; + +describe('Example Test', function() { + it('should pass this test wit swag', () => { + expect(!!'swag').toEqual(true); + }); +}); diff --git a/lab-remil/test/gallery-item-component-test.js b/lab-remil/test/gallery-item-component-test.js new file mode 100644 index 00000000..c1038b41 --- /dev/null +++ b/lab-remil/test/gallery-item-component-test.js @@ -0,0 +1,62 @@ +'use strict'; + +describe('Gallery Item Component', function(){ + beforeEach(() => { + angular.mock.module('ayogram'); + angular.mock.inject(($rootScope, $componentController, $httpBackend, authService) => { + this.$rootScope = $rootScope; + this.$componentController = $componentController; + this.$httpBackend = $httpBackend; + this.authService = authService; + }); + }); + + describe('galleryItemCtrl.deleteDone()', () => { + it('should call deleteDone', () => { + let mockBindings = { + gallery: { + _id: '12345', + name: 'test name', + desc: 'test description', + pics: [], + }, + deleteDone: function(data){ + expect(data.galleryData._id).toEqual('12345'); + }, + }; + + let galleryItemCtrl = this.$componentController('galleryItem', null, mockBindings); + galleryItemCtrl.deleteDone({galleryData: galleryItemCtrl.gallery}); + + this.$rootScope.$apply(); + }); + }); + + it('should call deleteDone with gallery after galleryDelete', () => { + let url = `${__API_URL__}/api/gallery/12345`; + let headers = { + Authorization: 'Bearer test token', + Accept: 'application/json', + }; + + let mockBindings = { + gallery: { + _id: '12345', + name: 'test name', + desc: 'test description', + pics: [], + }, + deleteDone: function(data){ + expect(data._id).toEqual(mockBindings.gallery._id); + }, + }; + + this.$httpBackend.expectDELETE(url, headers).respond(204); + + let galleryItemCtrl = this.$componentController('galleryItem', null, mockBindings); + galleryItemCtrl.deleteGallery(mockBindings.gallery); + + this.$httpBackend.flush(); + this.$rootScope.$apply(); + }); +}); diff --git a/lab-remil/test/gallery-service-test.js b/lab-remil/test/gallery-service-test.js new file mode 100644 index 00000000..211c5cd0 --- /dev/null +++ b/lab-remil/test/gallery-service-test.js @@ -0,0 +1,72 @@ +'use strict'; + +describe('Gallery Service', function() { + + beforeEach(() => { + angular.mock.module('ayogram'); + angular.mock.inject(($rootScope, authService, galleryService, $window, $httpBackend) => { + this.$window = $window; + this.$rootScope = $rootScope; + this.authService = authService; + this.galleryService = galleryService; + this.$httpBackend = $httpBackend; + }); + }); + + describe('galleryService.createGallery()', () => { + it('should create a new swag gallery', () => { + let galleryData = { + name: 'swaggy gallery', + desc: 'swaggy description', + }; + + let headers = { + 'Content-Type': 'application/json', + Accept: 'application/json', + Authorization: 'Bearer test token', + }; + + this.$httpBackend.expectPOST(`${__API_URL__}/api/gallery`, galleryData, headers) + .respond(200, { + _id: '12345', + username: 'ayoplaya', + name: galleryData.name, + desc: galleryData.desc, + pics: [], + }); + + this.galleryService.createGallery(galleryData); + this.$httpBackend.flush(); + this.$rootScope.$apply(); + }); + }); + + describe('galleryService.deleteGallery()', () => { + it('should create delete a gallery', () => { + let galleryData = { + _id: '12345', + name: 'swaggy gallery', + desc: 'swaggy description', + }; + + let headers = { + Authorization: 'Bearer test token', + Accept:'application/json', + }; + + let resHeaders = { + Authorization:'Bearer test token', + Accept:'application/json', + }; + + console.log('delete headers', headers); + + this.$httpBackend.expectDELETE(`${__API_URL__}/api/gallery/${galleryData._id}`, headers) + .respond(204, resHeaders); + + this.galleryService.deleteGallery(galleryData._id); + this.$httpBackend.flush(); + this.$rootScope.$apply(); + }); + }); +}); diff --git a/lab-remil/webpack.config.js b/lab-remil/webpack.config.js new file mode 100644 index 00000000..6af18fce --- /dev/null +++ b/lab-remil/webpack.config.js @@ -0,0 +1,55 @@ +'use strict'; + +const dotenv = require('dotenv'); +const webpack = require('webpack'); +const HTMLPlugin = require('html-webpack-plugin'); +const ExtractTextPlugin = require('extract-text-webpack-plugin'); + +const production = process.env.NODE_ENV === 'production'; + +dotenv.load(); + +module.exports = { + devtool: 'eval', + entry: `${__dirname}/app/entry.js`, + output: { + filename: 'bundle.js', + path: `${__dirname}/build`, + }, + plugins: [ + new HTMLPlugin({ template: `${__dirname}/app/index.html` }), + new ExtractTextPlugin('bundle.css'), + new webpack.DefinePlugin({ + __API_URL__: JSON.stringify(process.env.API_URL), + __DEBUG__: JSON.stringify(!production), + }), + ], + module: { + rules: [ + { //:::: babel :::: + test: /\.js$/, + exclude: /node_modules/, + use: 'babel-loader', + }, + {//:::: html :::: + test: /\.html$/, + use: 'html-loader', + }, + { //:::: sass :::: + test: /\.scss$/, + use: ExtractTextPlugin.extract({ + fallback: 'style-loader', + use: ['css-loader', 'sass-loader'], + }), + }, + { //:::: fonts :::: + test: /\.(eot|woff|tff|svg).*/, + use: 'url-loader?limit=10000&name=fonts/[hash].[ext]', + }, + { + test: /\.png$/, + use: 'url-loader', + }, + ], + }, +};