`, 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 some browsers, due to the limited stylability of ``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/app/scss/lib/bootstrap/_custom-bootstrap.scss b/app/scss/lib/bootstrap/_custom-bootstrap.scss
new file mode 100644
index 00000000..b32e1cdb
--- /dev/null
+++ b/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/app/scss/lib/layout/_footer.scss b/app/scss/lib/layout/_footer.scss
new file mode 100644
index 00000000..88468ba3
--- /dev/null
+++ b/app/scss/lib/layout/_footer.scss
@@ -0,0 +1,5 @@
+footer {
+ height: 10vw;
+ background: $app-secondary;
+ margin-top: $gutter-std * 2;
+}
diff --git a/app/scss/lib/layout/_header.scss b/app/scss/lib/layout/_header.scss
new file mode 100644
index 00000000..ad0e5164
--- /dev/null
+++ b/app/scss/lib/layout/_header.scss
@@ -0,0 +1,4 @@
+header {
+ background: $app-primary;
+ height: 8vw;
+}
diff --git a/app/scss/lib/layout/_layout.scss b/app/scss/lib/layout/_layout.scss
new file mode 100644
index 00000000..cb94f019
--- /dev/null
+++ b/app/scss/lib/layout/_layout.scss
@@ -0,0 +1,5 @@
+main {
+ width: 90%;
+ margin: 5%;
+ min-height: 600px;
+}
diff --git a/app/scss/lib/theme/_vars.scss b/app/scss/lib/theme/_vars.scss
new file mode 100644
index 00000000..9fbded1f
--- /dev/null
+++ b/app/scss/lib/theme/_vars.scss
@@ -0,0 +1,21 @@
+// Global variables //
+
+// Pallette //
+$app-primary: #444;
+$app-color: $app-primary / 2;
+$app-secondary: $app-primary * 2;
+$btn-primary: $app-primary / 2;
+$white: #fff;
+$black: #000;
+$disabled: #e3e3e3;
+
+// spacing //
+$gutter-std: 5%;
+$gutter-sm: $gutter-std / 2;
+
+// font //
+$font-std: helvetica;
+$font-secondary: monospace;
+
+// other //
+$btn-radius: 5px;
diff --git a/app/scss/main.scss b/app/scss/main.scss
new file mode 100644
index 00000000..69da6562
--- /dev/null
+++ b/app/scss/main.scss
@@ -0,0 +1,24 @@
+// ::: Reset :::: //
+@import "./lib/base/reset";
+
+// bring in bootstrap componentes
+@import "./lib/bootstrap/custom-bootstrap";
+// ::::: Base and Theme::::::::://
+@import "lib/theme/vars";
+@import "./lib/base/base";
+// ::::: Layout :::::: //
+@import "./lib/layout/header";
+@import "./lib/layout/layout";
+@import "./lib/layout/footer";
+
+@import "../view/landing/landing";
+
+// ::::: Components :::::://
+@import "../component/landing/login/login";
+@import "../component/landing/signup/signup";
+@import "../component/gallery/create-gallery/create-gallery";
+@import "../component/gallery/edit-gallery/edit-gallery";
+@import "../component/gallery/gallery-item/gallery-item";
+@import "../component/gallery/thumbnail/thumbnail";
+@import "../component/gallery/thumbnail-container/thumbnail-container";
+@import "../component/gallery/upload-pic/upload-pic";
diff --git a/app/service/auth-service.js b/app/service/auth-service.js
new file mode 100644
index 00000000..41fe4c15
--- /dev/null
+++ b/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('success:', res.data);
+ return setToken(res.data);
+ })
+ .catch( err => {
+ $log.error('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('success', res.data);
+ return setToken(res.data);
+ })
+ .catch( err => {
+ $log.error(err.message);
+ return $q.reject(err);
+ });
+ };
+
+ return service;
+};
diff --git a/app/service/gallery-service.js b/app/service/gallery-service.js
new file mode 100644
index 00000000..355f1c1e
--- /dev/null
+++ b/app/service/gallery-service.js
@@ -0,0 +1,142 @@
+'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('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, galleryData) {
+ $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 a 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.mesage);
+ 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('service.deleteGallery');
+
+ return authService.getToken()
+ .then( token => {
+ let url = `${__API_URL__}/api/gallery/${galleryID}`;
+ let config = {
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
+ };
+
+ 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.galleris.splice(i, 1);
+ break;
+ }
+ }
+ })
+ .catch( err => {
+ $log.error(err.message);
+ return $q.reject(err);
+ });
+ };
+
+ return service;
+};
diff --git a/app/service/pic-service.js b/app/service/pic-service.js
new file mode 100644
index 00000000..18649794
--- /dev/null
+++ b/app/service/pic-service.js
@@ -0,0 +1,45 @@
+'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');
+ $log.log('ugp galleryData', galleryData);
+ $log.log('ugp picData', picData);
+ 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);
+ $log.log('res.data', res.data);
+ return res.data;
+ })
+ .catch( err => {
+ $log.log(err.message);
+ return $q.reject(err);
+ })
+ }
+
+ return service;
+}
diff --git a/app/view/home/_home.scss b/app/view/home/_home.scss
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/app/view/home/_home.scss
@@ -0,0 +1 @@
+
diff --git a/app/view/home/home-controller.js b/app/view/home/home-controller.js
new file mode 100644
index 00000000..737d2bc5
--- /dev/null
+++ b/app/view/home/home-controller.js
@@ -0,0 +1,29 @@
+'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) {
+ if (this.currentGallery._id === gallery._id) {
+ this.currentGallery = null;
+ }
+ };
+
+ this.fetchGalleries();
+
+ $rootScope.$on('$locationChangeSuccess', () => {
+ this.fetchGalleries();
+ })
+};
diff --git a/app/view/home/home.html b/app/view/home/home.html
new file mode 100644
index 00000000..6ce3aff6
--- /dev/null
+++ b/app/view/home/home.html
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Hello
+
This is a place where our info could go
+
+
+
+
+
+ Goodbye
+
+
+
+
+
+ Goodbye for now
+
+
+
+
+
+
+
+ Previous
+
+
+
+ Next
+
+
+
diff --git a/app/view/landing/_landing.scss b/app/view/landing/_landing.scss
new file mode 100644
index 00000000..53563e42
--- /dev/null
+++ b/app/view/landing/_landing.scss
@@ -0,0 +1,18 @@
+@import '../../scss/lib/theme/vars';
+.join-inner {
+
+ div:last-child {
+ margin-top: 1.75vw;
+ text-align: right;
+ font-size: 2vw;
+
+ p {
+ display: inline;
+ margin-right: 1vw;
+ }
+
+ a {
+ margin-right: 1.5vw;
+ }
+ }
+}
diff --git a/app/view/landing/landing-controller.js b/app/view/landing/landing-controller.js
new file mode 100644
index 00000000..783dfec3
--- /dev/null
+++ b/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/app/view/landing/landing.html b/app/view/landing/landing.html
new file mode 100644
index 00000000..e72dd9ae
--- /dev/null
+++ b/app/view/landing/landing.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+
+
+
+
+
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100755
index 00000000..24190d6d
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,23 @@
+'use strict';
+
+const gulp = require('gulp');
+const eslint = require('gulp-eslint');
+const mocha = require('gulp-mocha');
+
+gulp.task('test', function () {
+ gulp.src('./test/*-test.js', { read: false})
+ .pipe(mocha({reporter: 'spec'}));
+});
+
+gulp.task('lint', function() {
+ return gulp.src(['**/*.js', '!node_modules'])
+ .pipe(eslint())
+ .pipe(eslint.format())
+ .pipe(eslint.failAfterError());
+});
+
+gulp.task('dev', function() {
+ gulp.watch(['**/*.js', '!node_modules'], ['lint', 'test']);
+});
+
+gulp.task('default', ['dev']);
diff --git a/karma.conf.js b/karma.conf.js
new file mode 100644
index 00000000..f6f6d029
--- /dev/null
+++ b/karma.conf.js
@@ -0,0 +1,29 @@
+const webpackConfig = require('./webpack.config.js');
+delete webpackConfig.entry;
+
+module.exports = function(config) {
+ config.set({
+ webpack: webpackConfig,
+ 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
+ });
+};
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..d720e561
--- /dev/null
+++ b/package.json
@@ -0,0 +1,59 @@
+{
+ "name": "glgram",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "build": "./node_modules/webpack/bin/webpack.js",
+ "build-watch": "./node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot",
+ "lint": "eslint .",
+ "test": "./node_modules/karma/bin/karma start --single-run",
+ "test-watch": "./node_modules/karma/bin/karma start",
+ "start": "node server.js",
+ "heroku-postbuild": "webpack -p --progress"
+ },
+ "keywords": [],
+ "author": "Gary Lundgren",
+ "license": "ISC",
+ "dependencies": {
+ "angular": "^1.6.3",
+ "angular-animate": "^1.6.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",
+ "express": "^4.15.2",
+ "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.0",
+ "ui-router": "^1.0.0-alpha.3",
+ "url-loader": "^0.5.8",
+ "webpack": "^2.3.2"
+ },
+ "devDependencies": {
+ "angular-mocks": "^1.6.3",
+ "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/server.js b/server.js
new file mode 100644
index 00000000..907833e4
--- /dev/null
+++ b/server.js
@@ -0,0 +1,11 @@
+'use strict';
+
+const express = require('express');
+const app = express();
+const PORT = process.env.PORT || 8080;
+
+app.use(express.static(`${__dirname}/build`));
+
+app.listen(PORT, function() {
+ console.log('server up: ' PORT);
+})
diff --git a/test/auth-service-test.js b/test/auth-service-test.js
new file mode 100644
index 00000000..754a2a7b
--- /dev/null
+++ b/test/auth-service-test.js
@@ -0,0 +1,31 @@
+'use strict';
+
+describe('Auth Service', function() {
+
+ beforeEach( () => {
+ angular.mock.module('glgram');
+ 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/test/edit-gallery-component-test.js b/test/edit-gallery-component-test.js
new file mode 100644
index 00000000..9f429638
--- /dev/null
+++ b/test/edit-gallery-component-test.js
@@ -0,0 +1,78 @@
+'use strict';
+
+describe('Edit Gallery Component', function() {
+
+ beforeEach(() => {
+ angular.mock.module('glgram');
+ angular.mock.inject(($rootScope, $componentController, $httpBackend, authService) => {
+ this.$rootScope = $rootScope;
+ this.$componentController = $componentController;
+ this.$httpBackend = $httpBackend;
+ this.authService = authService;
+ });
+ });
+
+
+
+ it('should contain the proper component bindings', () => {
+ let mockBindings = {
+ gallery: {
+ name: 'test gallery name',
+ desc: 'test gallery 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 = 'http://localhost:5000/api/gallery/12345';
+ let headers = {
+ Authorization: 'Bearer test token',
+ Accept: 'application/json',
+ 'Content-Type': 'application/json'
+ };
+
+ 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();
+
+ expect(editGalleryCtrl.gallery.name).toEqual(mockBindings.gallery.name);
+ expect(editGalleryCtrl.gallery.desc).toEqual(mockBindings.gallery.desc);
+
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+ });
+ });
+});
+
+
+
+
+
+
+
+
+
+ // it('should contain the proper component')
diff --git a/test/example-test.js b/test/example-test.js
new file mode 100644
index 00000000..23ae19f5
--- /dev/null
+++ b/test/example-test.js
@@ -0,0 +1,7 @@
+'use strict';
+
+describe('Example Test', function() {
+ it('should pass this test', () => {
+ expect(true).toEqual(true);
+ });
+});
diff --git a/test/gallery-item-component-test.js b/test/gallery-item-component-test.js
new file mode 100644
index 00000000..df4409e8
--- /dev/null
+++ b/test/gallery-item-component-test.js
@@ -0,0 +1,62 @@
+'use strict';
+
+describe('Gallery Item Component', function() {
+ beforeEach(() => {
+ angular.mock.module('glgram');
+ 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 = 'http://localhost:5000/api/gallery/12345';
+ let headers = {
+ Authorization: 'Bearer test token',
+ Accept: 'application/json, text/plain, */*'
+ };
+
+ let mockBindings = {
+ gallery: {
+ _id: '12345',
+ name: 'test name',
+ desc: 'test description',
+ pics: []
+ },
+ deleteDone: function(data) {
+ expect(data.galleryData._id).toEqual(mockBindings.gallery._id);
+ }
+ };
+
+ this.$httpBackend.expectDELETE(url, headers).respond(204);
+
+ let galleryItemCtrl = this.$componentController('galleryItem', null, mockBindings);
+ galleryItemCtrl.deleteGallery();
+
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+ });
+});
diff --git a/test/gallery-service-test.js b/test/gallery-service-test.js
new file mode 100644
index 00000000..a5153017
--- /dev/null
+++ b/test/gallery-service-test.js
@@ -0,0 +1,41 @@
+'use strict';
+
+describe('Gallery Service', function() {
+
+ beforeEach(() => {
+ angular.mock.module('glgram');
+ 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 gallery', () => {
+ let galleryData = {
+ name: 'example gallery',
+ desc: 'example description'
+ };
+ let headers = {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ Authorization: 'Bearer test token'
+ };
+
+ this.$httpBackend.expectPOST('http://localhost:5000/api/gallery', galleryData, headers)
+ .respond(200, {
+ username: 'testuser',
+ name: galleryData.name,
+ desc: galleryData.desc,
+ pics: []
+ });
+
+ this.galleryService.createGallery(galleryData);
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+ });
+ });
+});
diff --git a/trial.html b/trial.html
new file mode 100644
index 00000000..e69de29b
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 00000000..129cad88
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,50 @@
+'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: [
+ {
+ test: /\.js$/,
+ exclude: /node_modules/,
+ loader: 'babel-loader'
+ },
+ {
+ test: /\.html$/,
+ loader: 'html-loader'
+ },
+ {
+ test: /\.scss$/,
+ loader: ExtractTextPlugin.extract(['css-loader', 'sass-loader'])
+ },
+ {
+ test: /\.png$/,
+ loader: 'url-loader'
+ }
+ ]
+ }
+};