├── .github
├── banner.png
├── dark.png
└── light.png
├── .gitignore
├── .vscode
└── settings.json
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── appveyor.yml
├── build
├── icon.icns
└── icon.ico
├── gulpfile.js
├── index.html
├── package.json
├── resources
└── audio
│ ├── hark-error.mp3
│ ├── hark-new-items.mp3
│ └── hark-success.mp3
├── src
├── fonts
│ └── themify
│ │ ├── themify.eot
│ │ ├── themify.svg
│ │ ├── themify.ttf
│ │ └── themify.woff
├── js
│ ├── Actions
│ │ ├── Accounts.ts
│ │ ├── App.ts
│ │ ├── AppAlerts.ts
│ │ ├── Authentication.ts
│ │ ├── HawkEye.ts
│ │ ├── NotificationFilters.ts
│ │ ├── Notifications.ts
│ │ ├── Repositories.ts
│ │ ├── RepositoryMuteFilters.ts
│ │ ├── Settings.ts
│ │ ├── Setup.ts
│ │ └── UIActions
│ │ │ ├── Accounts.ts
│ │ │ ├── App.ts
│ │ │ ├── AppAlerts.ts
│ │ │ ├── Notifications.ts
│ │ │ ├── RepositoryMuteFilters.ts
│ │ │ └── Settings.ts
│ ├── App.ts
│ ├── Config
│ │ └── HawkEye.default.ts
│ ├── Constants
│ │ ├── Actions
│ │ │ ├── Accounts.ts
│ │ │ ├── App.ts
│ │ │ ├── AppAlerts.ts
│ │ │ ├── Authentication.ts
│ │ │ ├── Index.ts
│ │ │ ├── NotificationFilter.ts
│ │ │ ├── Notifications.ts
│ │ │ ├── Repositories.ts
│ │ │ ├── RepositoryMuteFilters.ts
│ │ │ ├── Settings.ts
│ │ │ └── Setup.ts
│ │ ├── Lang
│ │ │ └── Date.ts
│ │ ├── Models
│ │ │ ├── AppAlert.ts
│ │ │ ├── NotificationFilterSet.ts
│ │ │ └── Settings.ts
│ │ ├── Resources
│ │ │ └── Sound.ts
│ │ ├── Services
│ │ │ └── GitHub.ts
│ │ ├── State
│ │ │ └── RepositoryMuteFilters.ts
│ │ └── System
│ │ │ └── Electron.ts
│ ├── Core
│ │ ├── InstanceCache.ts
│ │ ├── Interfaces
│ │ │ ├── IInstanceCache.ts
│ │ │ ├── IRequest.ts
│ │ │ ├── IRequestFactory.ts
│ │ │ ├── IRouting.ts
│ │ │ └── IStoreCreator.ts
│ │ ├── Renderer.tsx
│ │ ├── Request.ts
│ │ ├── RequestFactory.ts
│ │ ├── Routing.ts
│ │ └── StoreCreator.ts
│ ├── Electron
│ │ ├── Dialogs
│ │ │ └── Accounts.ts
│ │ ├── Menus
│ │ │ ├── Accounts.ts
│ │ │ ├── Notification.ts
│ │ │ └── Repository.ts
│ │ ├── OAuthBrowserWindow.ts
│ │ └── Tasks
│ │ │ ├── App.ts
│ │ │ ├── Notification.ts
│ │ │ └── Settings.ts
│ ├── Filter
│ │ ├── Filter.ts
│ │ ├── FilterFunctions
│ │ │ └── GitHubNotifications
│ │ │ │ ├── Index.ts
│ │ │ │ ├── Read.ts
│ │ │ │ ├── Reason.ts
│ │ │ │ ├── Repository.ts
│ │ │ │ └── Subject.ts
│ │ └── Interfaces
│ │ │ ├── IFilter.ts
│ │ │ └── IFilterFunction.ts
│ ├── GitHub
│ │ ├── Activity.ts
│ │ ├── Authentication.ts
│ │ ├── GitHub.ts
│ │ ├── Interfaces
│ │ │ ├── IGitHub.ts
│ │ │ ├── IGitHubActivity.ts
│ │ │ ├── IGitHubAuthentication.ts
│ │ │ └── IGitHubUsers.ts
│ │ └── Users.ts
│ ├── HawkEye.ts
│ ├── Helpers
│ │ ├── Lang
│ │ │ ├── Array.ts
│ │ │ ├── Audio.ts
│ │ │ ├── Date.ts
│ │ │ ├── Sort.ts
│ │ │ ├── String.ts
│ │ │ └── Timeout.ts
│ │ ├── Models
│ │ │ ├── Accounts.ts
│ │ │ ├── App.ts
│ │ │ ├── AppAlert.ts
│ │ │ ├── GitHubNotification.ts
│ │ │ ├── GitHubNotificationFilterSet.ts
│ │ │ ├── GitHubRepository.ts
│ │ │ ├── GitHubUser.ts
│ │ │ ├── RepositoryMuteFilters.ts
│ │ │ └── Settings.ts
│ │ ├── Services
│ │ │ └── GitHub.ts
│ │ ├── State
│ │ │ ├── Reducify.ts
│ │ │ └── Store.ts
│ │ └── System
│ │ │ ├── Electron.ts
│ │ │ ├── Environment.ts
│ │ │ └── Scheduler.ts
│ ├── Interfaces
│ │ ├── HawkEye
│ │ │ └── IHawkEyeConfig.ts
│ │ ├── IState
│ │ │ ├── IState.ts
│ │ │ ├── IStateAccounts.ts
│ │ │ ├── IStateApp.ts
│ │ │ ├── IStateAppAlerts.ts
│ │ │ ├── IStateAuthentication.ts
│ │ │ ├── IStateNotificationFilters.ts
│ │ │ ├── IStateNotifications.ts
│ │ │ ├── IStateRepositories.ts
│ │ │ ├── IStateRepositoryMuteFilters.ts
│ │ │ ├── IStateSettings.ts
│ │ │ └── IStateSetup.ts
│ │ └── Models
│ │ │ ├── IAppAlert.ts
│ │ │ ├── IGitHubNotification.ts
│ │ │ ├── IGitHubNotificationFilterSet.ts
│ │ │ ├── IGitHubRepository.ts
│ │ │ ├── IGitHubUser.ts
│ │ │ └── INotificationFilterSet.ts
│ ├── Main.ts
│ ├── Reducers
│ │ ├── Accounts.ts
│ │ ├── App.ts
│ │ ├── AppAlerts.ts
│ │ ├── Authentication.ts
│ │ ├── Index.ts
│ │ ├── NotificationFilters.ts
│ │ ├── Notifications.ts
│ │ ├── Repositories.ts
│ │ ├── RepositoryMuteFilters.ts
│ │ ├── Settings.ts
│ │ └── Setup.ts
│ ├── Routes.ts
│ ├── Scheduler
│ │ ├── Interfaces
│ │ │ ├── IScheduler.ts
│ │ │ └── ISchedulerJobs.ts
│ │ ├── NodeSchedule.ts
│ │ └── Scheduler.ts
│ ├── Services
│ │ ├── GitHubAccountsService.ts
│ │ ├── GitHubAuthenticationService.ts
│ │ ├── GitHubNotificationsService.ts
│ │ └── Interfaces
│ │ │ ├── IGitHubAccountsService.ts
│ │ │ ├── IGitHubAuthenticationService.ts
│ │ │ └── IGitHubNotificationsService.ts
│ └── View
│ │ ├── App.tsx
│ │ ├── Components
│ │ ├── AppAlerts
│ │ │ ├── AppAlert.tsx
│ │ │ └── Index.tsx
│ │ ├── AppBar
│ │ │ ├── Account.tsx
│ │ │ └── Index.tsx
│ │ ├── GenericError.tsx
│ │ ├── NoAccounts
│ │ │ └── Index.tsx
│ │ ├── NoNotifications
│ │ │ └── Index.tsx
│ │ ├── NotificationFilters
│ │ │ ├── Index.tsx
│ │ │ ├── NotificationFilterRepositoryFilter.tsx
│ │ │ └── NotificationFilterStringFilter.tsx
│ │ ├── NotificationsList
│ │ │ └── Index.tsx
│ │ └── ViewBar
│ │ │ └── Index.tsx
│ │ ├── Container.tsx
│ │ ├── Index.tsx
│ │ ├── Settings
│ │ ├── Accounts
│ │ │ ├── RepositoryMuteFilters.tsx
│ │ │ └── View.tsx
│ │ ├── Index.tsx
│ │ └── Sections
│ │ │ ├── Accounts.tsx
│ │ │ ├── App.tsx
│ │ │ ├── Frequency.tsx
│ │ │ ├── Notifications.tsx
│ │ │ └── Sounds.tsx
│ │ └── Ui
│ │ ├── Anchor.tsx
│ │ ├── AppLoading.tsx
│ │ ├── Btn.tsx
│ │ ├── BtnTo.tsx
│ │ ├── Button.tsx
│ │ ├── CenteredBox.tsx
│ │ ├── Icon.tsx
│ │ ├── Index.ts
│ │ ├── Loader.tsx
│ │ ├── Notification.tsx
│ │ ├── ProfilePicture.tsx
│ │ ├── RoundedBtnSet.tsx
│ │ ├── Scroll.tsx
│ │ └── Toggle.tsx
└── scss
│ ├── _variables.scss
│ ├── base
│ ├── _fonts.scss
│ ├── _form.scss
│ ├── _images.scss
│ ├── _lists.scss
│ ├── _page.scss
│ └── _typography.scss
│ ├── components
│ ├── _app-alert.scss
│ ├── _app.scss
│ ├── _btn.scss
│ ├── _loader.scss
│ ├── _no-outline.scss
│ ├── _notification.scss
│ ├── _profile-picture.scss
│ ├── _toggle.scss
│ └── _view-bar.scss
│ ├── directives
│ ├── _breakpoints.scss
│ ├── _font-sizes.scss
│ ├── _grid.scss
│ ├── _headings.scss
│ ├── _keyframes.scss
│ ├── _pushes.scss
│ ├── _softs.scss
│ └── _transparents.scss
│ ├── generic
│ ├── _app-drag.scss
│ ├── _backgrounds.scss
│ ├── _borders.scss
│ ├── _clearfix.scss
│ ├── _displays.scss
│ ├── _floats.scss
│ ├── _hards.scss
│ ├── _heights.scss
│ ├── _highers.scss
│ ├── _line-heights.scss
│ ├── _max-widths.scss
│ ├── _min-heights.scss
│ ├── _n-tops.scss
│ ├── _overflows.scss
│ ├── _positionings.scss
│ ├── _pushes.scss
│ ├── _round.scss
│ ├── _softs.scss
│ ├── _text-aligns.scss
│ ├── _text-colors.scss
│ ├── _text-emboss.scss
│ ├── _text-fonts.scss
│ ├── _text-sizes.scss
│ ├── _text-transforms.scss
│ ├── _text-weights.scss
│ ├── _tops.scss
│ └── _widths.scss
│ ├── hawkeye.scss
│ ├── objects
│ ├── _cover-height.scss
│ ├── _dialog.scss
│ ├── _dropdown.scss
│ ├── _fixed-overlay.scss
│ ├── _flexi-side.scss
│ ├── _grid.scss
│ ├── _hard-bottom.scss
│ ├── _hard-left.scss
│ ├── _hard-right.scss
│ ├── _hard-top.scss
│ ├── _hideable-left.scss
│ ├── _img-scale.scss
│ ├── _loading-overlay.scss
│ ├── _media.scss
│ ├── _truncate.scss
│ └── _vscroll.scss
│ ├── themes
│ ├── _dark.scss
│ └── _light.scss
│ └── vendors
│ ├── _themify.scss
│ └── _virtualized.scss
├── tsconfig.json
├── tsconfig.main.json
├── typings.json
├── webpack.config.js
└── webpack.dev.config.js
/.github/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/harksys/HawkEye/77203dc40a14300887c58505707ae33955819b6d/.github/banner.png
--------------------------------------------------------------------------------
/.github/dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/harksys/HawkEye/77203dc40a14300887c58505707ae33955819b6d/.github/dark.png
--------------------------------------------------------------------------------
/.github/light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/harksys/HawkEye/77203dc40a14300887c58505707ae33955819b6d/.github/light.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | typings/*
4 | !typings/custom
5 |
6 | main.js
7 | app.js
8 | hawkeye.css
9 |
10 | src/js/Config/HawkEye.ts
11 |
12 | /*.woff
13 | /*.eot
14 | /*.svg
15 | /*.ttf
16 |
17 | npm-debug.log
18 |
19 | dist
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | // Two spaces for tabs.
3 | "editor.tabSize": 2,
4 | "editor.insertSpaces": true,
5 |
6 | // Ruler at 80 chars
7 | "editor.rulers" : [80],
8 |
9 | // Show whitespaces in the editor pane.
10 | "editor.renderWhitespace": true,
11 |
12 | // When saving, automatically trim whitespace
13 | "files.trimTrailingWhitespace" : true,
14 |
15 | // Exclude folders from being shown in VSC. Just to
16 | // clean the view up a little bit.
17 | "files.exclude": {
18 | "dist" : true,
19 | "node_modules" : true
20 | },
21 |
22 | // Exclude folders from being shown in the search
23 | "search.exclude": {
24 | "**/node_modules": true
25 | },
26 |
27 | "typescript.tsdk": "./node_modules/typescript/lib"
28 | }
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Hawk Eye
2 |
3 | Please search issues and pull requests before adding something new to avoid duplicating
4 | efforts and conversations.
5 |
6 | ## Development setup
7 |
8 | - Fork the [Hawk Eye](https://github.com/harksys/hawkeye) repository in to your own account, and clone to your device.
9 | - Install the project dependencies.
10 |
11 | ```bash
12 | $ npm install
13 | ```
14 |
15 | - Install typing definitions.
16 |
17 | ```bash
18 | $ typings Install
19 | ```
20 |
21 | - You'll need to setup an OAuth GitHub app and add your configuration file under `src/js/Config/HawkEye.ts`. Follow the `HawkEye.default.ts` file for more information.
22 | - Make your changes and build, then run the app.
23 |
24 | ```bash
25 | $ gulp
26 | $ electron .
27 | ```
28 |
29 | - You can package the app using, and run from the dist folder.
30 |
31 | ```bash
32 | $ npm run pack
33 | ```
34 |
35 | - Confirm your changes work, and create a Pull Request to the Hawk Eye repository.
36 |
37 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # MIT License
2 |
3 | Copyright (c) 2017 Hark Systems LTD.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://ci.appveyor.com/project/andrewhathaway/hawkeye)
4 |
5 |
6 | To read about Hawk Eye, visit the [Hark website](https://harksys.com/labs/introducing-hawk-eye-a-useful-app-for-github-notifications).
7 |
8 | ## Features
9 |
10 | Hawk Eye has many features, including:
11 |
12 | - Scheduled notification pulling
13 | - Filter by type, repository and more
14 | - Flexible per-repository mute filters
15 | - Mark single and multiple notifications as read
16 | - Support for multiple accounts
17 | - Light and dark mode
18 | - Responsive design
19 |
20 | ## Usage
21 |
22 | To get started and try out Hawk Eye, download the latest release for your platform on the [Releases Page](https://github.com/harksys/hawkeye/releases). You'll then be able to auto-update when new updates are available. Note: this currently only works for macOS, Windows incoming.
23 |
24 | ## Screenshots
25 |
26 | ### Dark Mode
27 |
28 | 
29 |
30 | ### Light Mode
31 |
32 | 
33 |
34 | ## Contributing
35 |
36 | For information regarding contributing to this project, please read the [Contributing](./CONTRIBUTING.md) document.
37 |
38 | ## License
39 |
40 | [MIT License](./LICENSE.md)
41 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 0.2.{build}
2 |
3 | platform:
4 | - x64
5 |
6 | cache:
7 | - node_modules
8 | - app\node_modules
9 | - '%APPDATA%\npm-cache'
10 | - '%USERPROFILE%\.electron'
11 |
12 | init:
13 | - git config --global core.autocrlf input
14 |
15 | install:
16 | - ps: Install-Product node 6 x64
17 | - git reset --hard HEAD
18 | - npm install npm -g
19 | - npm install electron-builder@next # force install next version to test electron-builder
20 | - npm install
21 | - npm prune
22 |
23 | build_script:
24 | - node --version
25 | - npm --version
26 | - npm run citask
27 |
28 | test: off
--------------------------------------------------------------------------------
/build/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/harksys/HawkEye/77203dc40a14300887c58505707ae33955819b6d/build/icon.icns
--------------------------------------------------------------------------------
/build/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/harksys/HawkEye/77203dc40a14300887c58505707ae33955819b6d/build/icon.ico
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const del = require('del');
2 | const gulp = require('gulp');
3 | const rename = require('gulp-rename');
4 | const runSeq = require('run-sequence');
5 | const typescript = require('gulp-typescript');
6 | const webpack = require('webpack-stream');
7 | const sass = require('gulp-sass');
8 |
9 | const ENV = process.env.NODE_ENV || 'production';
10 |
11 | /**
12 | * Clean Task
13 | *
14 | * Clean up old build. Nice and clean.
15 | */
16 | gulp.task('clean', () => del(['./app.js', './main.js']));
17 |
18 | /**
19 | * Main Task
20 | *
21 | * Build the main file for Electron
22 | */
23 | gulp.task('main', () =>
24 | {
25 | var tsProject = typescript.createProject('tsconfig.main.json');
26 | var res = gulp.src('src/js/Main.ts')
27 | .pipe(tsProject());
28 |
29 | return res.js
30 | .pipe(rename('main.js'))
31 | .pipe(gulp.dest('./'));
32 | });
33 |
34 | /**
35 | * Bundle Task
36 | *
37 | * Bundle our application in to a single file.
38 | */
39 | gulp.task('bundle', () =>
40 | {
41 | var webpackConfig = ENV === 'production'
42 | ? './webpack.config.js'
43 | : './webpack.dev.config.js';
44 |
45 | return gulp.src('./src/js/App.ts')
46 | .pipe(webpack(require(webpackConfig)))
47 | .pipe(gulp.dest('./'));
48 | });
49 |
50 | /*
51 | * Styles Task
52 | *
53 | * Use Node Sass to build our main stylesheet
54 | */
55 | gulp.task('styles', () =>
56 | {
57 | return gulp.src('./src/scss/hawkeye.scss')
58 | .pipe(sass({
59 | outputStyle : 'compressed',
60 | includePaths : [
61 | './node_modules/compass-mixins/lib'
62 | ]
63 | }).on('err', sass.logError))
64 | .pipe(gulp.dest('./'));
65 | });
66 |
67 | /*
68 | * Default Config Task
69 | *
70 | * This is for CI builds to test a project can build
71 | */
72 | gulp.task('configdefault', () =>
73 | {
74 | return gulp.src('./src/js/Config/HawkEye.default.ts')
75 | .pipe(rename('HawkEye.ts'))
76 | .pipe(gulp.dest('./src/js/Config'));
77 | });
78 |
79 | /**
80 | * Default Task
81 | *
82 | * Do all the things.
83 | */
84 | gulp.task('default', cb => runSeq('clean', 'main', 'bundle', 'styles', cb));
85 |
86 | /**
87 | * Watch Task
88 | *
89 | * Watch for changes
90 | */
91 | gulp.task('watch', () =>
92 | {
93 | gulp.watch('./src/js/**/*', {}, () => gulp.start('default'));
94 | gulp.watch('./src/scss/**/*.scss', {}, () => gulp.start('styles'));
95 | });
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | HawkEye
6 |
7 |
8 |
9 |
10 |
20 |
21 |
--------------------------------------------------------------------------------
/resources/audio/hark-error.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/harksys/HawkEye/77203dc40a14300887c58505707ae33955819b6d/resources/audio/hark-error.mp3
--------------------------------------------------------------------------------
/resources/audio/hark-new-items.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/harksys/HawkEye/77203dc40a14300887c58505707ae33955819b6d/resources/audio/hark-new-items.mp3
--------------------------------------------------------------------------------
/resources/audio/hark-success.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/harksys/HawkEye/77203dc40a14300887c58505707ae33955819b6d/resources/audio/hark-success.mp3
--------------------------------------------------------------------------------
/src/fonts/themify/themify.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/harksys/HawkEye/77203dc40a14300887c58505707ae33955819b6d/src/fonts/themify/themify.eot
--------------------------------------------------------------------------------
/src/fonts/themify/themify.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/harksys/HawkEye/77203dc40a14300887c58505707ae33955819b6d/src/fonts/themify/themify.ttf
--------------------------------------------------------------------------------
/src/fonts/themify/themify.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/harksys/HawkEye/77203dc40a14300887c58505707ae33955819b6d/src/fonts/themify/themify.woff
--------------------------------------------------------------------------------
/src/js/Actions/App.ts:
--------------------------------------------------------------------------------
1 | import ActionConstants from 'Constants/Actions/Index';
2 |
3 | export function setCurrentAccountId(currentAccountId: number)
4 | {
5 | return {
6 | type : ActionConstants.app.SET_CURRENT_ACCOUNT_ID,
7 | currentAccountId
8 | };
9 | };
10 |
11 | export function setIsPolling(isPolling: boolean)
12 | {
13 | return {
14 | type : ActionConstants.app.SET_APP_IS_POLLING,
15 | isPolling
16 | };
17 | };
18 |
19 | export function setLastPoll(lastPoll: string)
20 | {
21 | return {
22 | type : ActionConstants.app.SET_APP_LAST_POLL,
23 | lastPoll
24 | };
25 | };
--------------------------------------------------------------------------------
/src/js/Actions/Authentication.ts:
--------------------------------------------------------------------------------
1 | import ActionConstants from 'Constants/Actions/Index';
2 |
3 | export function setIsAuthenticating(isAuthenticating: boolean)
4 | {
5 | return {
6 | type : ActionConstants.authentication.SET_IS_AUTHENTICATING,
7 | isAuthenticating
8 | };
9 | };
--------------------------------------------------------------------------------
/src/js/Actions/HawkEye.ts:
--------------------------------------------------------------------------------
1 | import { getAccountIds } from 'Helpers/Models/Accounts';
2 | import { wait } from 'Helpers/Lang/Timeout';
3 | import { getCurrentPollPeriod } from 'Helpers/Models/Settings';
4 |
5 | import {
6 | setSetupIsLoading,
7 | setSetupRenderApp,
8 | setSetupShowLoading
9 | } from 'Actions/Setup';
10 | import { updateAccounts } from 'Actions/Accounts';
11 |
12 | import {
13 | pollingMethod,
14 | configurePollingScheduler
15 | } from 'Helpers/System/Scheduler';
16 |
17 | /**
18 | * @todo: lol more callback hell...fix this.
19 | */
20 | export function appSetupFlow()
21 | {
22 | return dispatch =>
23 | {
24 | /*
25 | * Render the application, and wait some time
26 | * for completion
27 | */
28 | dispatch(setSetupRenderApp(true));
29 | wait(250)
30 | .then(() =>
31 | {
32 | /*
33 | * Update the accounts we have stored.
34 | */
35 | dispatch(updateAccounts(() =>
36 | {
37 | pollingMethod(getAccountIds());
38 |
39 | /*
40 | * Setup our polling scheduler
41 | */
42 | configurePollingScheduler(getCurrentPollPeriod());
43 |
44 | /*
45 | * Hide the loading page, wait for it to fade out.
46 | * When it has, remove the loading screen from the DOM.
47 | */
48 | dispatch(setSetupShowLoading(false));
49 | wait(250)
50 | .then(() => dispatch(setSetupIsLoading(false)));
51 | }));
52 | });
53 | };
54 | };
--------------------------------------------------------------------------------
/src/js/Actions/NotificationFilters.ts:
--------------------------------------------------------------------------------
1 | import ActionConstants from 'Constants/Actions/Index';
2 |
3 | export function clearFilters(accountId: number)
4 | {
5 | return {
6 | type : ActionConstants.notificationFilter.CLEAR_FILTERS,
7 | accountId
8 | };
9 | };
10 |
11 | /**
12 | * @param {number} accountId
13 | * @param {boolean} read
14 | */
15 | export function setReadFilter(accountId: number, read: boolean)
16 | {
17 | return {
18 | type : ActionConstants.notificationFilter.SET_READ_FILTER,
19 | accountId,
20 | read
21 | };
22 | };
23 |
24 | /**
25 | * @param {string} subjectType
26 | */
27 | export function addSubjectTypeFilter(accountId: number, subjectType: string)
28 | {
29 | return addFilter(accountId, 'subjectType', subjectType);
30 | };
31 |
32 | /**
33 | * @param {string} subjectType
34 | */
35 | export function removeSubjectTypeFilter(accountId: number, subjectType: string)
36 | {
37 | return removeFilter(accountId, 'subjectType', subjectType);
38 | };
39 |
40 | /**
41 | * @param {string} reasonType
42 | */
43 | export function addReasonFilter(accountId: number, reasonType: string)
44 | {
45 | return addFilter(accountId, 'reasonType', reasonType);
46 | };
47 |
48 | /**
49 | * @param {string} reasonType
50 | */
51 | export function removeReasonFilter(accountId: number, reasonType: string)
52 | {
53 | return removeFilter(accountId, 'reasonType', reasonType);
54 | };
55 |
56 | /**
57 | * @param {string|number} id
58 | */
59 | export function addRepositoryFilter(accountId: number, id: string | number)
60 | {
61 | return addFilter(accountId, 'repository', id);
62 | };
63 |
64 | /**
65 | * @param {string|number} id
66 | */
67 | export function removeRepositoryFilter(accountId: number, id: string | number)
68 | {
69 | return removeFilter(accountId, 'repository', id);
70 | };
71 |
72 | /**
73 | * @param {string} area
74 | * @param {string|number} filter
75 | */
76 | export function addFilter(accountId: number, area: string, filter: string | number)
77 | {
78 | return {
79 | type : ActionConstants.notificationFilter.ADD_FILTER,
80 | accountId,
81 | area,
82 | filter,
83 | };
84 | }
85 |
86 | /**
87 | * @param {string} area
88 | * @param {string|number} filter
89 | */
90 | export function removeFilter(accountId: number, area: string, filter: string | number)
91 | {
92 | return {
93 | type : ActionConstants.notificationFilter.REMOVE_FILTER,
94 | accountId,
95 | area,
96 | filter
97 | };
98 | }
--------------------------------------------------------------------------------
/src/js/Actions/Repositories.ts:
--------------------------------------------------------------------------------
1 | import ActionConstants from 'Constants/Actions/Index';
2 |
3 | /**
4 | * @param {IGitHubRepository} repository
5 | */
6 | export function addRepository(repository: IGitHubRepository)
7 | {
8 | return {
9 | type : ActionConstants.repositories.ADD_REPOSITORY,
10 | repository
11 | };
12 | };
--------------------------------------------------------------------------------
/src/js/Actions/RepositoryMuteFilters.ts:
--------------------------------------------------------------------------------
1 | import ActionConstants from 'Constants/Actions/Index';
2 |
3 | export function setupRepositoryMuteFilter(accountId: number, repositoryId: string)
4 | {
5 | return {
6 | type : ActionConstants.repositoryMuteFilters.SETUP_FILTER,
7 | accountId,
8 | repositoryId
9 | };
10 | };
11 |
12 | export function setReasonFilter(accountId: number,
13 | repoId: string,
14 | filterName: string,
15 | enabled: boolean)
16 | {
17 | return {
18 | type : ActionConstants.repositoryMuteFilters.SET_REASON_FILTER,
19 | accountId,
20 | repoId,
21 | filterName,
22 | enabled
23 | };
24 | };
25 |
26 | export function setSubjectFilter(accountId: number,
27 | repoId: string,
28 | filterName: string,
29 | enabled: boolean)
30 | {
31 | return {
32 | type : ActionConstants.repositoryMuteFilters.SET_SUBJECT_FILTER,
33 | accountId,
34 | repoId,
35 | filterName,
36 | enabled
37 | };
38 | };
39 |
40 | export function removeFilter(accountId: number, repoId: string)
41 | {
42 | return {
43 | type : ActionConstants.repositoryMuteFilters.REMOVE_FILTER,
44 | accountId,
45 | repoId
46 | };
47 | };
--------------------------------------------------------------------------------
/src/js/Actions/Settings.ts:
--------------------------------------------------------------------------------
1 | import ActionConstants from 'Constants/Actions/Index';
2 |
3 | /**
4 | * @param {string} period
5 | */
6 | export function updatePollPeriod(period: string)
7 | {
8 | return dispatch => dispatch(updateSettingsValue('pollPeriod', period));
9 | };
10 |
11 | /**
12 | * @param {string} key
13 | * @param {any} value
14 | */
15 | export function updateSettingsValue(key: string, value: any)
16 | {
17 | return {
18 | type : ActionConstants.settings.SET_SETTINGS_VALUE,
19 | key,
20 | value
21 | };
22 | };
23 |
24 | /**
25 | * @param {boolean} enabled
26 | */
27 | export function setNewItemsEnabled(enabled: boolean)
28 | {
29 | return updateSoundSettingsEnabled('newItemsEnabled', enabled);
30 | };
31 |
32 | /**
33 | * @param {boolean} enabled
34 | */
35 | export function setAlertSuccessEnabled(enabled: boolean)
36 | {
37 | return updateSoundSettingsEnabled('alertSuccessEnabled', enabled);
38 | };
39 |
40 | /**
41 | * @param {boolean} enabled
42 | */
43 | export function setAlertErrorEnabled(enabled: boolean)
44 | {
45 | return updateSoundSettingsEnabled('alertErrorEnabled', enabled);
46 | };
47 |
48 |
49 | /**
50 | * @param {string} key
51 | * @param {boolean} enabled
52 | */
53 | export function updateSoundSettingsEnabled(key: string, enabled: boolean)
54 | {
55 | return {
56 | type : ActionConstants.settings.SET_SOUND_SETTINGS_ENABLED,
57 | key,
58 | enabled
59 | };
60 | };
61 |
62 | /**
63 | * @param {string} action
64 | */
65 | export function setNotificationDoubleClickAction(action: string)
66 | {
67 | return {
68 | type : ActionConstants.settings.SET_NOTIFICATIONS_DOUBLE_CLICK_ACTION,
69 | action
70 | };
71 | };
72 |
73 | /**
74 | * @param {boolean} confirm
75 | */
76 | export function setConfirmBeforeMarkingMultipleNotificationsAsRead(confirm: boolean)
77 | {
78 | return {
79 | type : ActionConstants.settings.SET_CONFIRM_BEFORE_MARKING_NOTIFICATIONS_AS_READ,
80 | confirm
81 | };
82 | };
83 |
84 | /**
85 | * @param {string} colorMode
86 | */
87 | export function setColorMode(colorMode: string)
88 | {
89 | return {
90 | type : ActionConstants.settings.SET_COLOR_MODE,
91 | colorMode
92 | };
93 | };
--------------------------------------------------------------------------------
/src/js/Actions/Setup.ts:
--------------------------------------------------------------------------------
1 | import ActionConstants from 'Constants/Actions/Index';
2 |
3 | export function setSetupIsLoading(isLoading: boolean)
4 | {
5 | return {
6 | type : ActionConstants.setup.SET_SETUP_IS_LOADING,
7 | isLoading
8 | };
9 | };
10 |
11 | export function setSetupShowLoading(showLoading: boolean)
12 | {
13 | return {
14 | type : ActionConstants.setup.SET_SETUP_SHOW_LOADING,
15 | showLoading
16 | };
17 | };
18 |
19 | export function setSetupRenderApp(renderApp: boolean)
20 | {
21 | return {
22 | type : ActionConstants.setup.SET_SETUP_RENDER_APP,
23 | renderApp
24 | };
25 | };
--------------------------------------------------------------------------------
/src/js/Actions/UIActions/App.ts:
--------------------------------------------------------------------------------
1 | import { replace } from 'react-router-redux';
2 | import { setCurrentAccountId } from 'Actions/App';
3 |
4 | /**
5 | * @param {number} accountId
6 | */
7 | export function switchAccount(accountId: number)
8 | {
9 | return dispatch =>
10 | {
11 | dispatch(setCurrentAccountId(accountId));
12 | dispatch(replace('/'));
13 | };
14 | };
--------------------------------------------------------------------------------
/src/js/Actions/UIActions/AppAlerts.ts:
--------------------------------------------------------------------------------
1 | import { updateActions } from 'Constants/System/Electron';
2 | import { autoUpdateQuitAndInstall } from 'Electron/Tasks/App';
3 |
4 | /**
5 | * @param {string} actionName
6 | * @param {any={}} actionParams
7 | */
8 | export function handleAppAlertActionClick(actionName: string, actionParams: any = {})
9 | {
10 | return dispatch =>
11 | {
12 | if (actionName !== updateActions.AU_QUIT_INSTALL) {
13 | return;
14 | }
15 |
16 | autoUpdateQuitAndInstall();
17 | };
18 | };
--------------------------------------------------------------------------------
/src/js/Actions/UIActions/Notifications.ts:
--------------------------------------------------------------------------------
1 | import { notificationDoubleClickActions } from 'Constants/Models/Settings';
2 |
3 | import {
4 | getElectron,
5 | openExternalUrl,
6 | getCurrentWindow,
7 | copyStringToClipboard
8 | } from 'Helpers/System/Electron';
9 | import { getNotificationWebUrl } from 'Helpers/Services/GitHub';
10 | import {
11 | getNotificationDoubleClickAction,
12 | shouldConfirmBeforeMarkingNotificationsAsRead
13 | } from 'Helpers/Models/Settings';
14 | import { markNotificationsAsRead } from 'Helpers/Models/GitHubNotification';
15 |
16 | import { clearFilters } from 'Actions/NotificationFilters';
17 |
18 | /**
19 | * @param {number} accountId
20 | * @param {IGitHubNotification} notification
21 | */
22 | export function doubleClickNotification(accountId: number, notification: IGitHubNotification)
23 | {
24 | return dispatch =>
25 | {
26 | let action = getNotificationDoubleClickAction();
27 | if (action === notificationDoubleClickActions.nothing) {
28 | return;
29 | }
30 |
31 | let url = getNotificationWebUrl(notification);
32 | if (action === notificationDoubleClickActions.copyLink) {
33 | copyStringToClipboard(url);
34 | return;
35 | }
36 |
37 | openExternalUrl(url);
38 | };
39 | };
40 |
41 | /**
42 | * @param {number} accountId
43 | * @param {IGitHubNotification[]} notifications
44 | */
45 | export function markMultipleNotificationsAsRead(accountId: number, notifications: IGitHubNotification[])
46 | {
47 | return dispatch =>
48 | {
49 | /*
50 | * If we shouldn't confirm, then just go ahead and clear notificatins
51 | */
52 | if (!shouldConfirmBeforeMarkingNotificationsAsRead()) {
53 | dispatch(clearFilters(accountId));
54 | markNotificationsAsRead(accountId, notifications);
55 | return;
56 | }
57 |
58 | /*
59 | * Ask the user whether we should really go through with this
60 | */
61 | getElectron().remote.dialog.showMessageBox(getCurrentWindow(), {
62 | type : 'question',
63 | title : 'Mark all as read?',
64 | message : 'Are you sure you want to mark all these notifications as read?',
65 | buttons : [
66 | 'Yes',
67 | 'No'
68 | ]
69 | }, index =>
70 | {
71 | /*
72 | * Answered No, so don't!
73 | */
74 | if (index === 1) {
75 | return;
76 | }
77 |
78 | /*
79 | * Answered yes, so lets go ahead
80 | */
81 | dispatch(clearFilters(accountId));
82 | markNotificationsAsRead(accountId, notifications);
83 | });
84 | };
85 | };
--------------------------------------------------------------------------------
/src/js/Actions/UIActions/Settings.ts:
--------------------------------------------------------------------------------
1 | import InstanceCache from 'Core/InstanceCache';
2 |
3 | import { updatePollPeriod } from 'Actions/Settings';
4 | import { configurePollingScheduler } from 'Helpers/System/Scheduler';
5 |
6 | /**
7 | * @param {string} pollPeriod
8 | */
9 | export function configurePollPeriod(pollPeriod: string)
10 | {
11 | let scheduler = InstanceCache.getInstance('IScheduler');
12 |
13 | return dispatch =>
14 | {
15 | dispatch(updatePollPeriod(pollPeriod));
16 | configurePollingScheduler(pollPeriod);
17 | };
18 | };
--------------------------------------------------------------------------------
/src/js/App.ts:
--------------------------------------------------------------------------------
1 | import HawkEye from './HawkEye';
2 |
3 | // ES6 Promise Polyfill
4 | import * as FakePromise from 'es6-promise';
5 | (FakePromise as any).polyfill();
6 |
7 | window.onload = () =>
8 | {
9 | new HawkEye();
10 | }
--------------------------------------------------------------------------------
/src/js/Config/HawkEye.default.ts:
--------------------------------------------------------------------------------
1 | import { gitHubScopes } from 'Constants/Services/GitHub';
2 |
3 | const config: IHawkEyeConfig = {
4 | github : {
5 | clientId : '',
6 | clientSecret : '',
7 | scopes : [
8 | gitHubScopes.notifications
9 | ],
10 | webUrl : 'https://github.com/'
11 | },
12 | appAlerts : {
13 | showFor : 4000
14 | }
15 | };
16 |
17 | export default config;
--------------------------------------------------------------------------------
/src/js/Constants/Actions/Accounts.ts:
--------------------------------------------------------------------------------
1 |
2 | const AccountsActions = {
3 | ADD_ACCOUNT : 'ADD_ACCOUNT',
4 | REMOVE_ACCOUNT : 'REMOVE_ACCOUNT'
5 | };
6 |
7 | export default AccountsActions;
--------------------------------------------------------------------------------
/src/js/Constants/Actions/App.ts:
--------------------------------------------------------------------------------
1 |
2 | const AppActions = {
3 | SET_CURRENT_ACCOUNT_ID : 'SET_CURRENT_ACCOUNT_ID',
4 | SET_APP_IS_POLLING : 'SET_APP_IS_POLLING',
5 | SET_APP_LAST_POLL : 'SET_APP_LAST_POLL'
6 | };
7 |
8 | export default AppActions;
--------------------------------------------------------------------------------
/src/js/Constants/Actions/AppAlerts.ts:
--------------------------------------------------------------------------------
1 |
2 | const AppAlertsActions = {
3 | ADD_APP_ALERT : 'ADD_APP_ALERT',
4 | REMOVE_APP_ALERT : 'REMOVE_APP_ALERT',
5 | SHOW_APP_ALERT : 'SHOW_APP_ALERT',
6 | HIDE_APP_ALERT : 'HIDE_APP_ALERT',
7 | HIDE_ALL_APP_ALERTS : 'HIDE_ALL_APP_ALERTS'
8 | };
9 |
10 | export default AppAlertsActions;
--------------------------------------------------------------------------------
/src/js/Constants/Actions/Authentication.ts:
--------------------------------------------------------------------------------
1 |
2 | const AuthenticationActions = {
3 | SET_IS_AUTHENTICATING : 'SET_IS_AUTHENTICATING'
4 | };
5 |
6 | export default AuthenticationActions;
7 |
--------------------------------------------------------------------------------
/src/js/Constants/Actions/Index.ts:
--------------------------------------------------------------------------------
1 | import AccountsActions from './Accounts';
2 | import AppActions from './App';
3 | import NotificationsActions from './Notifications';
4 | import SettingsActions from './Settings';
5 | import AuthenticationActions from './Authentication';
6 | import AppAlertsActions from './AppAlerts';
7 | import SetupActions from './Setup';
8 | import NotificationFilterActions from './NotificationFilter';
9 | import RepositoryActions from './Repositories';
10 | import RepositoryMuteFilterActions from './RepositoryMuteFilters';
11 |
12 | const ActionConstants = {
13 | accounts : AccountsActions,
14 | app : AppActions,
15 | notifications : NotificationsActions,
16 | settings : SettingsActions,
17 | authentication : AuthenticationActions,
18 | appAlerts : AppAlertsActions,
19 | setup : SetupActions,
20 | notificationFilter : NotificationFilterActions,
21 | repositories : RepositoryActions,
22 | repositoryMuteFilters : RepositoryMuteFilterActions
23 | };
24 |
25 | export default ActionConstants;
--------------------------------------------------------------------------------
/src/js/Constants/Actions/NotificationFilter.ts:
--------------------------------------------------------------------------------
1 |
2 | const NotificationFilterActions = {
3 | ADD_FILTER : 'ADD_FILTER',
4 | REMOVE_FILTER : 'REMOVE_FILTER',
5 | SET_READ_FILTER : 'SET_READ_FILTER',
6 | CLEAR_FILTERS : 'CLEAR_FILTERS'
7 | };
8 |
9 | export default NotificationFilterActions;
--------------------------------------------------------------------------------
/src/js/Constants/Actions/Notifications.ts:
--------------------------------------------------------------------------------
1 |
2 | const NotificationsActions = {
3 | INGEST_NOTIFICATION : 'INGEST_NOTIFICATION',
4 | INGEST_NOTIFICATIONS : 'INGEST_NOTIFICATIONS',
5 | REMOVE_ACCOUNT_NOTIFICATIONS : 'REMOVE_ACCOUNT_NOTIFICATIONS',
6 | MARK_NOTIFICATION_AS_READ : 'MARK_NOTIFICATION_AS_READ'
7 | };
8 |
9 | export default NotificationsActions;
--------------------------------------------------------------------------------
/src/js/Constants/Actions/Repositories.ts:
--------------------------------------------------------------------------------
1 |
2 | const RepositoryActions = {
3 | ADD_REPOSITORY : 'ADD_REPOSITORY'
4 | };
5 |
6 | export default RepositoryActions;
--------------------------------------------------------------------------------
/src/js/Constants/Actions/RepositoryMuteFilters.ts:
--------------------------------------------------------------------------------
1 |
2 | const RepositoryMuteFilterActions = {
3 | SETUP_FILTER : 'SETUP_FILTER',
4 | SET_REASON_FILTER : 'SET_REASON_FILTER',
5 | SET_SUBJECT_FILTER : 'SET_SUBJECT_FILTER',
6 | REMOVE_FILTER : 'REMOVE_FILTER'
7 | };
8 |
9 | export default RepositoryMuteFilterActions;
--------------------------------------------------------------------------------
/src/js/Constants/Actions/Settings.ts:
--------------------------------------------------------------------------------
1 |
2 | const SettingsActions = {
3 | SET_SETTINGS_VALUE : 'SET_SETTINGS_VALUE',
4 | SET_SOUND_SETTINGS_ENABLED : 'SET_SOUND_SETTINGS_ENABLED',
5 | SET_NOTIFICATIONS_DOUBLE_CLICK_ACTION : 'SET_NOTIFICATIONS_DOUBLE_CLICK_ACTION',
6 | SET_CONFIRM_BEFORE_MARKING_NOTIFICATIONS_AS_READ : 'SET_CONFIRM_BEFORE_MARKING_NOTIFICATIONS_AS_READ',
7 | SET_COLOR_MODE : 'SET_COLOR_MODE'
8 | };
9 |
10 | export default SettingsActions;
--------------------------------------------------------------------------------
/src/js/Constants/Actions/Setup.ts:
--------------------------------------------------------------------------------
1 |
2 | const SetupActions = {
3 | SET_SETUP_IS_LOADING : 'SET_SETUP_IS_LOADING',
4 | SET_SETUP_SHOW_LOADING : 'SET_SETUP_SHOW_LOADING',
5 | SET_SETUP_RENDER_APP : 'SET_SETUP_RENDER_APP'
6 | }
7 |
8 | export default SetupActions;
--------------------------------------------------------------------------------
/src/js/Constants/Lang/Date.ts:
--------------------------------------------------------------------------------
1 |
2 | export const cronPeriods = {
3 | fiveMinutes : '*/5 * * * *',
4 | fifteenMinute : '*/15 * * * *',
5 | thirtyMinute : '*/30 * * * *',
6 | fourtyFiveMinute : '*/45 * * * *',
7 | sixtyMinute : '* * /1 * * *'
8 | };
9 |
10 | export const cronPeriodPrettyNames = {
11 | fiveMinutes : '5 Minutes',
12 | fifteenMinute : '15 Minutes',
13 | thirtyMinute : '30 Minutes',
14 | fourtyFiveMinute : '45 Minutes',
15 | sixtyMinute : '1 Hour',
16 | };
--------------------------------------------------------------------------------
/src/js/Constants/Models/AppAlert.ts:
--------------------------------------------------------------------------------
1 |
2 | export const appAlertStatuses = {
3 | success : 'SUCCESS',
4 | error : 'ERROR',
5 | warning : 'WARNING'
6 | };
--------------------------------------------------------------------------------
/src/js/Constants/Models/NotificationFilterSet.ts:
--------------------------------------------------------------------------------
1 |
2 | export const defaultNotificationFilterSet: INotificationFilterSet = {
3 | read : false,
4 | subjectType : [],
5 | reasonType : [],
6 | repository : []
7 | };
--------------------------------------------------------------------------------
/src/js/Constants/Models/Settings.ts:
--------------------------------------------------------------------------------
1 |
2 | export const colorModes = {
3 | light : 'LIGHT',
4 | dark : 'DARK'
5 | };
6 |
7 | export const notificationDoubleClickActions = {
8 | nothing : 'NOTHING',
9 | open : 'OPEN',
10 | copyLink : 'COPYLINK'
11 | };
12 |
13 | export const defaultPollPeriod = 'fifteenMinute';
--------------------------------------------------------------------------------
/src/js/Constants/Resources/Sound.ts:
--------------------------------------------------------------------------------
1 |
2 | export const soundClipPaths = {
3 | harkSuccess : './resources/audio/hark-success.mp3',
4 | harkError : './resources/audio/hark-error.mp3',
5 | harkNewItems : './resources/audio/hark-new-items.mp3'
6 | };
--------------------------------------------------------------------------------
/src/js/Constants/Services/GitHub.ts:
--------------------------------------------------------------------------------
1 |
2 | export const gitHubApiUrl: string = 'https://api.github.com';
3 |
4 |
5 | /*
6 | * GitHub Scope Strings
7 | *
8 | * Most, but cut down to never allow access to sensitive things.
9 | * For example, delete_repo etc.
10 | */
11 | export const gitHubScopes = {
12 | user : 'user',
13 | userEmail : 'user:email',
14 | userFollow : 'user:follow',
15 | publicRepo : 'public_repo',
16 | repo : 'repo',
17 | repoDeployment : 'repo_deployment',
18 | repoStatus : 'repo:status',
19 | gist : 'gist',
20 | notifications : 'notifications'
21 | };
22 |
23 | export const githubNotificationSubjectTypes = {
24 | Issue : 'ISSUE',
25 | PullRequest : 'PULLREQUEST',
26 | Commit : 'COMMIT',
27 | Release : 'RELEASE'
28 | };
29 |
30 | export const githubNotificationSubjectTypeIcons = {
31 | ISSUE : 'issue-opened',
32 | PULLREQUEST : 'git-pull-request',
33 | COMMIT : 'git-commit',
34 | RELEASE : 'tag'
35 | };
36 |
37 | export const gitHubNotificationSubjectTypePrettyNames = {
38 | ISSUE : 'Issue',
39 | PULLREQUEST : 'Pull Request',
40 | COMMIT : 'Commit',
41 | RELEASE : 'Release'
42 | };
43 |
44 | export const gitHubNotificationReasonTypes = {
45 | subscribed : 'SUBSCRIBED',
46 | manual : 'MANUAL',
47 | author : 'AUTHOR',
48 | comment : 'COMMENT',
49 | mention : 'MENTION',
50 | team_mention : 'TEAMMENTION',
51 | state_change : 'STATECHANGE',
52 | assign : 'ASSIGN'
53 | };
54 |
55 | export const gitHubNotificationReasonTypePrettyNames = {
56 | SUBSCRIBED : 'Watching',
57 | MANUAL : 'Watching Thread',
58 | AUTHOR : 'Authored',
59 | COMMENT : 'Commented',
60 | MENTION : 'Mentioned',
61 | TEAMMENTION : 'Team Mention',
62 | STATECHANGE : 'State Changed',
63 | ASSIGN : 'Assigned'
64 | };
--------------------------------------------------------------------------------
/src/js/Constants/State/RepositoryMuteFilters.ts:
--------------------------------------------------------------------------------
1 | import {
2 | gitHubNotificationReasonTypes,
3 | githubNotificationSubjectTypes
4 | } from 'Constants/Services/GitHub';
5 |
6 | export const defaultRepositoryMuteFilter: IStateRepositoryMuteFiltersAccountRepo = {
7 | allowedSubjectTypes : Object.keys(githubNotificationSubjectTypes),
8 | allowedReasons : Object.keys(gitHubNotificationReasonTypes)
9 | };
--------------------------------------------------------------------------------
/src/js/Constants/System/Electron.ts:
--------------------------------------------------------------------------------
1 |
2 | export const updateActions = {
3 | AU_QUIT_INSTALL : 'AU_QUIT_INSTALL',
4 | AU_UPDATE_AVAILABLE : 'AU_UPDATE_AVAILABLE'
5 | };
--------------------------------------------------------------------------------
/src/js/Core/InstanceCache.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | class InstanceCache
4 | {
5 |
6 | private classInstances: any = {};
7 |
8 | constructor()
9 | {
10 | this.classInstances = {};
11 | }
12 |
13 | /**
14 | * @param {string} className
15 | * @param {any} implementation
16 | * @returns InstanceCache
17 | */
18 | public addInstance = (className: string, implementation: any, force: boolean = false): InstanceCache =>
19 | {
20 | // Check the instance hasn't already been assigned, error if so.
21 | // Skip this check if force is true
22 | if (typeof this.classInstances[className] !== 'undefined'
23 | && !force) {
24 | throw new Error('InstanceCache: Instance for class "'
25 | + className
26 | + '" already exists in cache.');
27 | }
28 |
29 | // Add the instances to the cache
30 | this.classInstances[className] = implementation;
31 |
32 | return this;
33 | }
34 |
35 | /**
36 | * @param {string} className
37 | * @returns T
38 | */
39 | public getInstance = (className: string): T =>
40 | {
41 | if (typeof this.classInstances[className] === 'undefined') {
42 | throw new Error('InstanceCache: Class "'
43 | + className
44 | + '" not found in InstanceCache.');
45 | }
46 |
47 | return this.classInstances[className];
48 | }
49 |
50 | }
51 |
52 | export default new InstanceCache();
--------------------------------------------------------------------------------
/src/js/Core/Interfaces/IInstanceCache.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IInstanceCache
3 | {
4 |
5 | addInstance(className: string, implementation: any): IInstanceCache;
6 |
7 | getInstance(className: string): T;
8 |
9 | };
--------------------------------------------------------------------------------
/src/js/Core/Interfaces/IRequest.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IRequest
3 | {
4 |
5 | setHost(host?: string): IRequest;
6 |
7 | setUrl(url?: string): IRequest;
8 |
9 | setMethod(method?: string): IRequest;
10 |
11 | setQuery(query?: any): IRequest;
12 |
13 | setBody(body?: any): IRequest;
14 |
15 | setHeaders(headers?: any): IRequest;
16 |
17 | execute(): Promise;
18 |
19 | sendAsJson(): IRequest;
20 |
21 | };
--------------------------------------------------------------------------------
/src/js/Core/Interfaces/IRequestFactory.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | interface IRequestFactory
4 | {
5 |
6 | getHost(): string;
7 |
8 | newRequest(): IRequest;
9 |
10 | };
--------------------------------------------------------------------------------
/src/js/Core/Interfaces/IRouting.ts:
--------------------------------------------------------------------------------
1 | interface IRouting
2 | {
3 | getHistory(): ReactRouter.History;
4 |
5 | getSyncdHistory(): ReactRouterRedux.ReactRouterReduxHistory;
6 |
7 | syncHistoryWithStore(store: Redux.Store): IRouting;
8 |
9 | getRoutes(): ReactRouter.PlainRoute;
10 |
11 | addRouteConfig(routeConfig: ReactRouter.PlainRoute): IRouting;
12 |
13 | addRouteConfigs(...routeConfigs: ReactRouter.PlainRoute[]): IRouting;
14 | };
--------------------------------------------------------------------------------
/src/js/Core/Interfaces/IStoreCreator.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IStoreCreator
3 | {
4 | getStore(): Redux.Store;
5 | };
--------------------------------------------------------------------------------
/src/js/Core/Renderer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { render } from 'react-dom';
3 |
4 | import Container from 'View/Container';
5 |
6 | export function renderApplication(containerElement: HTMLElement,
7 | storeCreator: IStoreCreator,
8 | routing: IRouting): Promise
9 | {
10 | /*
11 | * Render the application with our routes and store!
12 | */
13 | return new Promise(resolve =>
14 | {
15 | render(
16 | ,
19 | containerElement,
20 | resolve
21 | );
22 | });
23 | };
--------------------------------------------------------------------------------
/src/js/Core/RequestFactory.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import Request from './Request';
5 |
6 | class RequestFactory implements IRequestFactory
7 | {
8 | private host: string = '';
9 |
10 | constructor(host: string)
11 | {
12 | this.host = host;
13 | }
14 |
15 | public getHost = (): string =>
16 | {
17 | return this.host;
18 | };
19 |
20 | public newRequest = (): Request =>
21 | {
22 | return new Request(this.host);
23 | }
24 | };
25 |
26 | export default RequestFactory;
--------------------------------------------------------------------------------
/src/js/Core/Routing.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { hashHistory } from 'react-router';
4 | import { syncHistoryWithStore } from 'react-router-redux';
5 |
6 | class Routing implements IRouting
7 | {
8 | public routes: ReactRouter.PlainRoute;
9 |
10 | public historyInstance: ReactRouter.History;
11 |
12 | public syncdHistory: ReactRouterRedux.ReactRouterReduxHistory;
13 |
14 | public childRoutes: ReactRouter.PlainRoute[];
15 |
16 | /**
17 | * @param {any} appContainer
18 | */
19 | constructor(appContainer: any, indexComponent: any)
20 | {
21 | /*
22 | * Setup the default chilRoutes and
23 | * store the historyInstance
24 | */
25 | this.childRoutes = [];
26 | this.historyInstance = hashHistory;
27 |
28 | /*
29 | * Set root-application routes
30 | * and assign the childRoutes
31 | */
32 | this.routes = {
33 | childRoutes : [{
34 | path : '/',
35 | component : appContainer,
36 | indexRoute : {
37 | component : indexComponent
38 | },
39 | childRoutes : this.childRoutes
40 | }]
41 | };
42 | }
43 |
44 | /**
45 | * @returns ReactRouter
46 | */
47 | public getHistory = (): ReactRouter.History =>
48 | {
49 | return this.historyInstance;
50 | }
51 |
52 | /**
53 | * @param {Redux.Store} store
54 | * @returns IRouting
55 | */
56 | public syncHistoryWithStore = (store: Redux.Store): IRouting =>
57 | {
58 | this.syncdHistory = syncHistoryWithStore(this.historyInstance, store);
59 |
60 | return this;
61 | };
62 |
63 | /**
64 | * @returns ReactRouterRedux
65 | */
66 | public getSyncdHistory = (): ReactRouterRedux.ReactRouterReduxHistory =>
67 | {
68 | return this.syncdHistory;
69 | }
70 |
71 | /**
72 | * @returns ReactRouter
73 | */
74 | public getRoutes = (): ReactRouter.PlainRoute =>
75 | {
76 | return this.routes;
77 | }
78 |
79 | /**
80 | * @param {ReactRouter.PlainRoute} routeConfig
81 | */
82 | public addRouteConfig = (routeConfig: ReactRouter.PlainRoute) =>
83 | {
84 | this.childRoutes.push(routeConfig);
85 |
86 | return this;
87 | }
88 |
89 | /**
90 | * @param {ReactRouter.PlainRoute[]} ...routingConfigurations
91 | */
92 | public addRouteConfigs = (...routingConfigurations: ReactRouter.PlainRoute[]) =>
93 | {
94 | routingConfigurations.forEach(c => this.addRouteConfig(c));
95 |
96 | return this;
97 | }
98 | };
99 |
100 | export default Routing;
--------------------------------------------------------------------------------
/src/js/Core/StoreCreator.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import {
4 | compose,
5 | createStore,
6 | applyMiddleware,
7 | combineReducers
8 | } from 'redux';
9 | import {
10 | routerReducer,
11 | routerMiddleware
12 | } from 'react-router-redux';
13 | import {
14 | persistStore,
15 | autoRehydrate
16 | } from 'redux-persist';
17 | import thunk from 'redux-thunk';
18 |
19 | import Reducers from 'Reducers/Index';
20 | import * as objectAssign from 'object-assign';
21 |
22 | class StoreCreator implements IStoreCreator
23 | {
24 | private store: Redux.Store;
25 |
26 | /**
27 | * @param {ReactRouter.History} historyInstance
28 | */
29 | constructor(historyInstance: ReactRouter.History,
30 | rehydrateCallback: () => void)
31 | {
32 | /*
33 | * Setup the middlewares and reducers
34 | */
35 | const router = routerMiddleware(historyInstance);
36 | const reducers = combineReducers(objectAssign({}, Reducers, {
37 | routing : routerReducer
38 | }));
39 |
40 | /*
41 | * Further middleware and enhancer prep
42 | */
43 | const middlewares = [thunk, router];
44 | const enhancer = compose(applyMiddleware(...middlewares), autoRehydrate());
45 |
46 | /*
47 | * Create our store with the reducers and enhancers.
48 | * Persist this state in localStorage, minus routing!
49 | */
50 | this.store = createStore(reducers, undefined, enhancer) as Redux.Store;
51 | persistStore(this.store, {
52 | blacklist : [
53 | 'routing',
54 | 'authentication',
55 | 'setup',
56 | 'appAlerts',
57 | 'notificationFilters'
58 | ]
59 | }, rehydrateCallback);
60 | }
61 |
62 | /**
63 | * @returns Redux
64 | */
65 | public getStore(): Redux.Store
66 | {
67 | return this.store;
68 | };
69 |
70 | };
71 |
72 | export default StoreCreator;
--------------------------------------------------------------------------------
/src/js/Electron/Menus/Accounts.ts:
--------------------------------------------------------------------------------
1 | import { replace } from 'react-router-redux';
2 |
3 | import {
4 | getNewRemoteElectronMenu,
5 | getNewRemoteElectronMenuItem
6 | } from 'Helpers/System/Electron';
7 | import { dispatch } from 'Helpers/State/Store';
8 |
9 | import { updateAccount } from 'Actions/Accounts';
10 |
11 | export default function createMenu(accountId: number): Electron.Menu
12 | {
13 | let menu = getNewRemoteElectronMenu();
14 |
15 | /*
16 | *
17 | */
18 | menu.append(getNewRemoteElectronMenuItem({
19 | label : 'Update Details',
20 | click : () => dispatch(updateAccount(accountId.toString(), true))
21 | }));
22 |
23 | /*
24 | * Separate!
25 | */
26 | menu.append(getNewRemoteElectronMenuItem({
27 | type : 'separator'
28 | }));
29 |
30 | /*
31 | * Settings
32 | */
33 | menu.append(getNewRemoteElectronMenuItem({
34 | label : 'Manage',
35 | click : () => dispatch(replace('/settings/accounts/' + accountId))
36 | }));
37 |
38 | return menu;
39 | };
--------------------------------------------------------------------------------
/src/js/Electron/Menus/Notification.ts:
--------------------------------------------------------------------------------
1 | import {
2 | openExternalUrl,
3 | copyStringToClipboard,
4 | getNewRemoteElectronMenu,
5 | getNewRemoteElectronMenuItem
6 | } from 'Helpers/System/Electron';
7 | import {
8 | getNotificationWebUrl,
9 | getNotificationSubjectPrettyName
10 | } from 'Helpers/Services/GitHub';
11 | import { dispatch } from 'Helpers/State/Store';
12 |
13 | import { handleMarkNotificationAsRead } from 'Actions/Notifications';
14 |
15 | /**
16 | * @param {string} accountId
17 | * @param {IGitHubNotification} notification
18 | */
19 | export default function createMenu(accountId: string, notification: IGitHubNotification)
20 | {
21 | let menu = getNewRemoteElectronMenu();
22 | let subjectPrettyName = getNotificationSubjectPrettyName(notification.subject.type);
23 |
24 | /*
25 | * Open In Browser
26 | */
27 | menu.append(getNewRemoteElectronMenuItem({
28 | label : 'Open in Browser',
29 | click : () => openExternalUrl(getNotificationWebUrl(notification))
30 | }));
31 |
32 | /*
33 | * Copy X Link
34 | */
35 | menu.append(getNewRemoteElectronMenuItem({
36 | label : 'Copy ' + subjectPrettyName + ' Link',
37 | click : () => copyStringToClipboard(getNotificationWebUrl(notification))
38 | }));
39 |
40 | /*
41 | * Copy X Title
42 | */
43 | menu.append(getNewRemoteElectronMenuItem({
44 | label : 'Copy ' + subjectPrettyName + ' Title',
45 | click : () => copyStringToClipboard(notification.subject.title)
46 | }));
47 |
48 | /*
49 | * Separate!
50 | */
51 | menu.append(getNewRemoteElectronMenuItem({
52 | type : 'separator'
53 | }));
54 |
55 | /*
56 | * Mark Notification as Read
57 | */
58 | menu.append(getNewRemoteElectronMenuItem({
59 | label : 'Mark as Read',
60 | click : () => dispatch(handleMarkNotificationAsRead(accountId, notification.id.toString()))
61 | }))
62 |
63 | return menu;
64 | };
--------------------------------------------------------------------------------
/src/js/Electron/Menus/Repository.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getNewRemoteElectronMenu,
3 | getNewRemoteElectronMenuItem
4 | } from 'Helpers/System/Electron';
5 | import { dispatch } from 'Helpers/State/Store';
6 |
7 | import { setupRepositoryMuteFilter } from 'Actions/UIActions/RepositoryMuteFilters';
8 |
9 | export default function createMenu(accountId: number, repository: IGitHubRepository): Electron.Menu
10 | {
11 | let menu = getNewRemoteElectronMenu();
12 |
13 | menu.append(getNewRemoteElectronMenuItem({
14 | label : 'Edit Mute Filter',
15 | click : () => dispatch(setupRepositoryMuteFilter(
16 | accountId,
17 | repository,
18 | 'settings/accounts/'
19 | + accountId
20 | + '/repo-mute-filter/'
21 | + repository.id
22 | ))
23 | }));
24 |
25 | return menu;
26 | };
--------------------------------------------------------------------------------
/src/js/Electron/Tasks/App.ts:
--------------------------------------------------------------------------------
1 | import { dispatch } from 'Helpers/State/Store';
2 | import { pushAppAlert } from 'Actions/AppAlerts';
3 | import { getElectron } from 'Helpers/System/Electron';
4 | import { updateActions } from 'Constants/System/Electron';
5 | import { createSuccessAppAlert } from 'Helpers/Models/AppAlert';
6 |
7 | /**
8 | */
9 | export function autoUpdateQuitAndInstall()
10 | {
11 | getElectron()
12 | .ipcRenderer
13 | .send(updateActions.AU_QUIT_INSTALL);
14 | };
15 |
16 | export function registerUpdateAvailableHandler()
17 | {
18 | getElectron()
19 | .ipcRenderer
20 | .on(updateActions.AU_UPDATE_AVAILABLE, () => dispatch(pushAppAlert(createSuccessAppAlert(
21 | 'Update available. Click the refresh icon to quit and install',
22 | true, {
23 | stickyActionIcon : 'sticky',
24 | stickyActionName : updateActions.AU_QUIT_INSTALL,
25 | stickyActionParams : {}
26 | }
27 | ))));
28 | };
--------------------------------------------------------------------------------
/src/js/Electron/Tasks/Notification.ts:
--------------------------------------------------------------------------------
1 | import { dispatch } from 'Helpers/State/Store';
2 | import { getElectron } from 'Helpers/System/Electron';
3 |
4 | import { markNotificationAsRead } from 'Actions/Notifications';
5 |
6 | const MarkNotificationAsReadTask = 'MarkNotificationRead';
7 | const MarkNotificationAsReadSuccessTask = 'MarkNotificationReadSuccess';
8 |
9 | /**
10 | * @param {string} token
11 | * @param {string} accountId
12 | * @param {string[]} notificationIds
13 | */
14 | export function markMultipleNotificationsAsRead(token: string,
15 | accountId: number,
16 | notificationIds: string[])
17 | {
18 | let ipc = getElectron().ipcRenderer;
19 |
20 | notificationIds.forEach(id => ipc.send(MarkNotificationAsReadTask, [{
21 | token : token,
22 | accountId : accountId,
23 | notificationId : id,
24 | }]));
25 | };
26 |
27 | /**
28 | */
29 | export function registerMarkNotificationAsReadSuccess()
30 | {
31 | getElectron()
32 | .ipcRenderer
33 | .on(MarkNotificationAsReadSuccessTask, (e, args: { token: string;
34 | accountId: string;
35 | notificationId: string; }) =>
36 | {
37 | dispatch(markNotificationAsRead(args.accountId, args.notificationId));
38 | });
39 | };
--------------------------------------------------------------------------------
/src/js/Electron/Tasks/Settings.ts:
--------------------------------------------------------------------------------
1 | import { replace } from 'react-router-redux';
2 |
3 | import { dispatch } from 'Helpers/State/Store';
4 | import { getElectron } from 'Helpers/System/Electron';
5 |
6 | const PreferencesTask = 'Preferences';
7 |
8 | export function registerPreferencesMenuControl()
9 | {
10 | getElectron()
11 | .ipcRenderer
12 | .on(PreferencesTask, () => dispatch(replace('/settings')));
13 | };
--------------------------------------------------------------------------------
/src/js/Filter/Filter.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import * as clone from 'lodash/clone';
5 |
6 | class Filter implements IFilter
7 | {
8 | private filterFunctions: IFilterFunction[] = [];
9 |
10 | private originalDataSet: T[] = [];
11 |
12 | private filteredDataSet: T[] = [];
13 |
14 | private ruleSet: any = {};
15 |
16 | constructor(dataSet: T[], ruleSet: any)
17 | {
18 | this.originalDataSet = dataSet;
19 | this.ruleSet = ruleSet;
20 | }
21 |
22 | public addFilterFunction = (filterFunc: IFilterFunction): IFilter =>
23 | {
24 | this.filterFunctions.push(filterFunc);
25 |
26 | return this;
27 | }
28 |
29 | public addFilterFunctions = (...filterFunctions: IFilterFunction[]): IFilter =>
30 | {
31 | filterFunctions.forEach(f => this.filterFunctions.push(f));
32 |
33 | return this;
34 | }
35 |
36 | public filter = (): T[] =>
37 | {
38 | this.filteredDataSet = clone(this.originalDataSet);
39 | for (var i = 0; i < this.filterFunctions.length; i++) {
40 | let func = this.filterFunctions[i];
41 |
42 | this.filteredDataSet = func(this.filteredDataSet, this.ruleSet);
43 | }
44 |
45 | return this.filteredDataSet;
46 | }
47 |
48 | };
49 |
50 | export default Filter;
--------------------------------------------------------------------------------
/src/js/Filter/FilterFunctions/GitHubNotifications/Index.ts:
--------------------------------------------------------------------------------
1 | import Read from './Read';
2 | import Reason from './Reason';
3 | import Subject from './Subject';
4 | import Repository from './Repository';
5 |
6 | export {
7 | Read,
8 | Reason,
9 | Subject,
10 | Repository
11 | };
--------------------------------------------------------------------------------
/src/js/Filter/FilterFunctions/GitHubNotifications/Read.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @param {IGitHubNotification[]} input
4 | * @param {INotificationFilterSet} ruleSet
5 | * @returns IGitHubNotification
6 | */
7 | export default function filter(input: IGitHubNotification[], ruleSet: INotificationFilterSet): IGitHubNotification[]
8 | {
9 | return input.filter(n => ruleSet.read != n.unread);
10 | };
--------------------------------------------------------------------------------
/src/js/Filter/FilterFunctions/GitHubNotifications/Reason.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @param {IGitHubNotification[]} input
4 | * @param {INotificationFilterSet} ruleSet
5 | * @returns IGitHubNotification
6 | */
7 | export default function filter(input: IGitHubNotification[], ruleSet: INotificationFilterSet): IGitHubNotification[]
8 | {
9 | if (ruleSet.reasonType.length === 0) {
10 | return input;
11 | }
12 |
13 | return input.filter(n => ruleSet.reasonType.indexOf(n.reason) > -1);
14 | };
--------------------------------------------------------------------------------
/src/js/Filter/FilterFunctions/GitHubNotifications/Repository.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @param {IGitHubNotification[]} input
4 | * @param {INotificationFilterSet} ruleSet
5 | * @returns IGitHubNotification
6 | */
7 | export default function filter(input: IGitHubNotification[], ruleSet: INotificationFilterSet): IGitHubNotification[]
8 | {
9 | if (ruleSet.repository.length === 0) {
10 | return input;
11 | }
12 |
13 | return input.filter(n => ruleSet.repository.indexOf(n.repository.id) > -1);
14 | };
--------------------------------------------------------------------------------
/src/js/Filter/FilterFunctions/GitHubNotifications/Subject.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @param {IGitHubNotification[]} input
4 | * @param {INotificationFilterSet} ruleSet
5 | * @returns IGitHubNotification
6 | */
7 | export default function filter(input: IGitHubNotification[], ruleSet: INotificationFilterSet): IGitHubNotification[]
8 | {
9 | if (ruleSet.subjectType.length === 0) {
10 | return input;
11 | }
12 |
13 | return input.filter(n => ruleSet.subjectType.indexOf(n.subject.type) > -1);
14 | };
--------------------------------------------------------------------------------
/src/js/Filter/Interfaces/IFilter.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | interface IFilter
4 | {
5 | addFilterFunction(filterFunc: IFilterFunction): IFilter;
6 |
7 | addFilterFunctions(...filterFunctions: IFilterFunction[]): IFilter;
8 |
9 | filter(): T[];
10 | };
--------------------------------------------------------------------------------
/src/js/Filter/Interfaces/IFilterFunction.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IFilterFunction
3 | {
4 | (input: T[], ruleSet: any): T[];
5 | };
--------------------------------------------------------------------------------
/src/js/GitHub/Activity.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | class Activity implements IGitHubActivity
4 | {
5 | private requestFactory: IRequestFactory;
6 |
7 | constructor(requestFactory: IRequestFactory)
8 | {
9 | this.requestFactory = requestFactory;
10 | }
11 |
12 | public getNotifications = (token: string,
13 | page: number = 1,
14 | all: boolean = false,
15 | participating: boolean = false,
16 | since?: string,
17 | before?: string): Promise =>
18 | {
19 | let query = {
20 | access_token : token,
21 | all : all,
22 | participating : participating,
23 | page : page
24 | };
25 |
26 | if (typeof since === 'string') {
27 | query['since'] = since;
28 | }
29 |
30 | if (typeof before === 'string') {
31 | query['before'] = before;
32 | }
33 |
34 | return new Promise((resolve, reject) =>
35 | {
36 | this.requestFactory
37 | .newRequest()
38 | .setUrl('/notifications')
39 | .setQuery(query)
40 | .execute()
41 | .then(response => response.json())
42 | .then(res => resolve(res),
43 | err => reject(err));
44 | });
45 | }
46 |
47 | public markThreadAsRead = (token: string, threadId: string): Promise =>
48 | {
49 | return new Promise((resolve, reject) =>
50 | {
51 | this.requestFactory
52 | .newRequest()
53 | .setUrl('/notifications/threads/' + threadId)
54 | .setQuery({
55 | access_token : token
56 | })
57 | .setMethod('post')
58 | .execute()
59 | .then(response =>
60 | {
61 | if (response.status === 205) {
62 | resolve();
63 | return;
64 | }
65 |
66 | reject();
67 | });
68 | });
69 | }
70 | };
71 |
72 | export default Activity;
--------------------------------------------------------------------------------
/src/js/GitHub/Authentication.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import * as QueryString from 'query-string';
4 |
5 | class Authentication implements IGitHubAuthentication
6 | {
7 | private requestFactory: IRequestFactory;
8 |
9 | constructor(requestFactory: IRequestFactory)
10 | {
11 | this.requestFactory = requestFactory;
12 | }
13 |
14 | /**
15 | * @param {string} clientId
16 | * @param {string[]} scopes
17 | * @returns Promise
18 | */
19 | public generateOAuthUrl = (clientId: string, scopes: string[]): Promise =>
20 | {
21 | return new Promise(resolve =>
22 | {
23 | let url = 'https://github.com/login/oauth/authorize?';
24 | let qs = {
25 | client_id : clientId,
26 | scope : scopes.join(' ')
27 | };
28 |
29 | resolve(url + QueryString.stringify(qs));
30 | });
31 | }
32 |
33 | /**
34 | * @param {string} clientId
35 | * @param {string} clientSecret
36 | * @param {string} code
37 | * @returns Promise
38 | */
39 | public authenticateAccessToken = (clientId: string, clientSecret: string, code: string): Promise =>
40 | {
41 | return new Promise((resolve, reject) =>
42 | {
43 | let body = {
44 | client_id : clientId,
45 | client_secret : clientSecret,
46 | code
47 | };
48 |
49 | // @todo: Host set :/ Extract?
50 | this.requestFactory
51 | .newRequest()
52 | .setHost('https://github.com')
53 | .setUrl('/login/oauth/access_token')
54 | .setMethod('post')
55 | .setBody(body)
56 | .sendAsJson()
57 | .execute()
58 | .then(r => r.json())
59 | .then(res =>
60 | {
61 | // @todo: IMPROVE THIS ERROR HANDLING.
62 | if (typeof res.access_token === 'undefined') {
63 | reject(null);
64 | }
65 |
66 | resolve(res.access_token);
67 | }, err => reject(null));
68 | });
69 | }
70 | };
71 |
72 | export default Authentication;
--------------------------------------------------------------------------------
/src/js/GitHub/GitHub.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import Users from './Users';
5 | import Activity from './Activity';
6 | import Authentication from './Authentication';
7 |
8 | class GitHub implements IGitHub
9 | {
10 | private requestFactory: IRequestFactory;
11 |
12 | public activity: IGitHubActivity;
13 |
14 | public users: IGitHubUsers;
15 |
16 | public authentication: IGitHubAuthentication;
17 |
18 | constructor(requestFactory: IRequestFactory)
19 | {
20 | this.requestFactory = requestFactory;
21 |
22 | this.activity = new Activity(this.requestFactory);
23 | this.users = new Users(this.requestFactory);
24 | this.authentication = new Authentication(this.requestFactory);
25 | }
26 | };
27 |
28 | export default GitHub;
29 |
--------------------------------------------------------------------------------
/src/js/GitHub/Interfaces/IGitHub.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 |
5 | interface IGitHub
6 | {
7 | activity: IGitHubActivity;
8 |
9 | users: IGitHubUsers;
10 |
11 | authentication: IGitHubAuthentication;
12 | };
--------------------------------------------------------------------------------
/src/js/GitHub/Interfaces/IGitHubActivity.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IGitHubActivity
3 | {
4 | getNotifications(token: string,
5 | page?: number,
6 | all?: boolean,
7 | participating?: boolean,
8 | since?: string,
9 | before?: string): Promise;
10 |
11 | markThreadAsRead(token: string,
12 | threadId: string): Promise;
13 | };
--------------------------------------------------------------------------------
/src/js/GitHub/Interfaces/IGitHubAuthentication.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IGitHubAuthentication
3 | {
4 | generateOAuthUrl(clientId: string, scopes: string[]): Promise;
5 |
6 | authenticateAccessToken(clientId: string, clientSecret: string, code: string): Promise;
7 | };
--------------------------------------------------------------------------------
/src/js/GitHub/Interfaces/IGitHubUsers.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IGitHubUsers
3 | {
4 | getAuthenticatedUser(token: string): Promise;
5 | };
--------------------------------------------------------------------------------
/src/js/GitHub/Users.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | class Users implements IGitHubUsers
4 | {
5 | private requestFactory: IRequestFactory;
6 |
7 | constructor(requestFactory: IRequestFactory)
8 | {
9 | this.requestFactory = requestFactory;
10 | }
11 |
12 | public getAuthenticatedUser = (token: string): Promise =>
13 | {
14 | return new Promise((resolve, reject) =>
15 | {
16 | this.requestFactory
17 | .newRequest()
18 | .setUrl('/user')
19 | .setQuery({
20 | access_token : token
21 | })
22 | .execute()
23 | .then(response => response.json())
24 | .then(res => resolve(res),
25 | err => reject(err));
26 | });
27 | }
28 |
29 | };
30 |
31 | export default Users;
--------------------------------------------------------------------------------
/src/js/HawkEye.ts:
--------------------------------------------------------------------------------
1 | import InstanceCache from 'Core/InstanceCache';
2 | import { renderApplication } from 'Core/Renderer';
3 |
4 | import App from 'View/App';
5 | import AppIndex from 'View/Index';
6 |
7 | import Routing from 'Core/Routing';
8 | import StoreCreator from 'Core/StoreCreator';
9 | import RequestFactory from 'Core/RequestFactory';
10 |
11 | import GitHub from 'GitHub/GitHub';
12 | import Scheduler from 'Scheduler/Scheduler';
13 |
14 | import { gitHubApiUrl } from 'Constants/Services/GitHub';
15 | import { appSetupFlow } from 'Actions/HawkEye';
16 |
17 | import GitHubAccountsService from 'Services/GitHubAccountsService';
18 | import GitHubAuthenticationService from 'Services/GitHubAuthenticationService';
19 | import GitHubNotificationsService from 'Services/GitHubNotificationsService';
20 |
21 | import Routes from 'Routes';
22 |
23 | class HawkEye
24 | {
25 | private routing: IRouting;
26 |
27 | private storeCreator: IStoreCreator;
28 |
29 | constructor()
30 | {
31 | /*
32 | * Setup Routing and StoreCreator for our application
33 | */
34 | this.routing = new Routing(App, AppIndex);
35 | this.storeCreator = new StoreCreator(this.routing.getHistory(),
36 | this.onRehydrated.bind(this));
37 |
38 | /*
39 | * Sync history with the Store and add
40 | * the routing configuration
41 | */
42 | this.routing
43 | .syncHistoryWithStore(this.storeCreator.getStore())
44 | .addRouteConfigs(...Routes);
45 |
46 | /*
47 | * Bind our Routing and StoreCreator to the cache
48 | */
49 | InstanceCache
50 | .addInstance('IRouting', this.routing)
51 | .addInstance>('IStoreCreator', this.storeCreator)
52 | .addInstance('IScheduler', new Scheduler);
53 |
54 | // @todo: Migrate the lot. ServiceBinder
55 | const gitHubService = new GitHub(new RequestFactory(gitHubApiUrl));
56 |
57 | InstanceCache
58 | .addInstance('IGitHub', gitHubService)
59 | .addInstance('IGitHubAccountsService',
60 | new GitHubAccountsService(gitHubService))
61 | .addInstance('IGitHubAuthenticationService',
62 | new GitHubAuthenticationService(gitHubService))
63 | .addInstance('IGitHubNotificationsService',
64 | new GitHubNotificationsService(gitHubService));
65 |
66 | /*
67 | * Render our application in the container,
68 | * along with our storeCreator and routing instances.
69 | */
70 | renderApplication(
71 | document.getElementById('root'),
72 | this.storeCreator,
73 | this.routing
74 | );
75 | }
76 |
77 | public onRehydrated = () =>
78 | {
79 | this.storeCreator.getStore().dispatch(appSetupFlow());
80 | }
81 | };
82 |
83 | export default HawkEye;
--------------------------------------------------------------------------------
/src/js/Helpers/Lang/Array.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @param {T[]} array
4 | * @returns T
5 | */
6 | export function getLast(array: T[]): T
7 | {
8 | if (array.length === 0) {
9 | return null;
10 | }
11 |
12 | return array[array.length - 1];
13 | };
14 |
15 | /**
16 | * @param {any[]} array
17 | */
18 | export function hasItems(array: any[])
19 | {
20 | return array.length > 0;
21 | }
22 |
23 | /**
24 | * @param {any[]} array
25 | * @param {number} min
26 | */
27 | export function hasMinItems(array: any[], min: number)
28 | {
29 | return array.length > min;
30 | };
31 |
32 | /**
33 | * @param {number} index
34 | */
35 | export function isFirstItem(index: number)
36 | {
37 | return index === 0;
38 | };
39 |
40 | /**
41 | * @param {any[]} array
42 | * @param {number} index
43 | */
44 | export function isLastItem(array: any[], index: number)
45 | {
46 | return array.length === index + 1;
47 | };
48 |
49 | /**
50 | * @param {any[]} array
51 | * @param {(key:any)=>any} getValue
52 | * @returns any
53 | */
54 | export function toObject(array: any[],
55 | getKey: (value: any, key: any) => any,
56 | getValue: (value: any, key: any) => any): any
57 | {
58 | let object = {};
59 |
60 | array.forEach((v, i) => object[getKey(v, i)] = getValue(v, i));
61 |
62 | return object;
63 | };
--------------------------------------------------------------------------------
/src/js/Helpers/Lang/Audio.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @returns boolean
4 | */
5 | export function hasAudioCapability(): boolean
6 | {
7 | return typeof Audio !== 'undefined';
8 | }
9 |
10 | /**
11 | * @param {string} sound
12 | */
13 | export function playSound(sound: string)
14 | {
15 | if (!hasAudioCapability()) {
16 | return;
17 | }
18 |
19 | new Audio(sound).play();
20 | };
--------------------------------------------------------------------------------
/src/js/Helpers/Lang/Date.ts:
--------------------------------------------------------------------------------
1 | import * as Moment from 'moment';
2 |
3 | import { cronPeriods } from 'Constants/Lang/Date';
4 |
5 | /**
6 | * @param {string} name
7 | * @returns string
8 | */
9 | export function getCronPeriodByName(name: string): string
10 | {
11 | return cronPeriods[name] || null;
12 | };
13 |
14 | /**
15 | * @param {} date?
16 | * @param {} format='YYYY-MM-DD'
17 | * @returns string
18 | */
19 | export function formatDate(date?, format = 'YYYY-MM-DD'): string
20 | {
21 | return Moment(date).format(format);
22 | };
23 |
24 | /**
25 | * @param {string|moment.Moment} date
26 | * @returns moment
27 | */
28 | export function toUtc(date: string | moment.Moment): moment.Moment
29 | {
30 | return Moment(date).utc();
31 | };
32 |
33 | /**
34 | * @param {} date?
35 | * @returns string
36 | */
37 | export function formatDateAsUTC(date?): string
38 | {
39 | return formatDate(date, 'YYYY-MM-DDTHH:mm:ss') + 'Z';
40 | };
41 |
42 | /**
43 | * @param {} date?
44 | * @returns moment
45 | */
46 | export function convertUtcToLocal(date?): moment.Moment
47 | {
48 | return Moment(date).utc().local();
49 | };
50 |
51 | export function relativeTime(date: string | moment.Moment): string
52 | {
53 | return Moment(date).fromNow(false);
54 | };
55 |
56 | /**
57 | * @param {string|moment.Moment} date
58 | * @param {string} unit
59 | * @param {number} amount
60 | * @returns moment
61 | */
62 | export function addTime(date: string | moment.Moment, unit: string, amount: number): moment.Moment
63 | {
64 | return Moment(date).add(unit, amount);
65 | };
--------------------------------------------------------------------------------
/src/js/Helpers/Lang/Sort.ts:
--------------------------------------------------------------------------------
1 |
2 | export const sortingMethods = {
3 | dateAsc : sortDateAsc,
4 | dateDesc : sortDateDesc
5 | };
6 |
7 | /**
8 | * @param {string} field?
9 | * @returns any
10 | */
11 | export function sortDateAsc(field?: string): any
12 | {
13 | return (a, b) =>
14 | {
15 | let dateOne = new Date(typeof field !== 'undefined'
16 | ? a[field]
17 | : a);
18 | let dateTwo = new Date(typeof field !== 'undefined'
19 | ? b[field]
20 | : b);
21 |
22 | if (dateOne > dateTwo) return 1;
23 | if (dateOne < dateTwo) return -1;
24 | };
25 | };
26 |
27 | export function sortDateDesc(field?: string): any
28 | {
29 | return (a, b) =>
30 | {
31 | let dateOne = new Date(typeof field !== 'undefined'
32 | ? a[field]
33 | : a);
34 | let dateTwo = new Date(typeof field !== 'undefined'
35 | ? b[field]
36 | : b);
37 |
38 | if (dateOne > dateTwo) return -1;
39 | if (dateOne < dateTwo) return 1;
40 | };
41 | };
--------------------------------------------------------------------------------
/src/js/Helpers/Lang/String.ts:
--------------------------------------------------------------------------------
1 |
2 | const possibleChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
3 |
4 | /**
5 | * @param {number=10} length
6 | * @returns string
7 | */
8 | export function randomString(length: number = 10): string
9 | {
10 | let text: string = '';
11 | for(var i = 0; i < length; i++) {
12 | text += possibleChars.charAt(
13 | Math.floor(Math.random() * possibleChars.length)
14 | );
15 | }
16 |
17 | return text;
18 | }
19 |
20 | /**
21 | */
22 | export function generateId()
23 | {
24 | return randomString() + (new Date().getTime());
25 | };
--------------------------------------------------------------------------------
/src/js/Helpers/Lang/Timeout.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @param {number} duration
4 | * @returns Promise
5 | */
6 | export function wait(duration: number): Promise
7 | {
8 | return new Promise(resolve => setTimeout(resolve, duration));
9 | };
--------------------------------------------------------------------------------
/src/js/Helpers/Models/Accounts.ts:
--------------------------------------------------------------------------------
1 | import { getState } from 'Helpers/State/Store';
2 |
3 | /**
4 | * @returns string
5 | */
6 | export function getAccountIds(): string[]
7 | {
8 | return Object.keys(getState().accounts);
9 | };
10 |
11 | /**
12 | * @param {string} accountId
13 | * @returns string
14 | */
15 | export function getAccountToken(accountId: string): string
16 | {
17 | let account = getAccount(accountId);
18 | if (account === null) {
19 | return null;
20 | }
21 |
22 | return account.token;
23 | };
24 |
25 | /**
26 | * @param {string} accountId
27 | * @returns IStateAccountsAccount
28 | */
29 | export function getAccount(accountId: string): IStateAccountsAccount
30 | {
31 | let accounts = getState().accounts;
32 | if (typeof accounts[accountId] === 'undefined') {
33 | return null;
34 | }
35 |
36 | return accounts[accountId];
37 | };
38 |
39 | /**
40 | * @param {string} accountId
41 | * @returns boolean
42 | */
43 | export function accountAlreadyAdded(accountId: string): boolean
44 | {
45 | return getAccount(accountId) != null;
46 | };
--------------------------------------------------------------------------------
/src/js/Helpers/Models/App.ts:
--------------------------------------------------------------------------------
1 | import { getState } from 'Helpers/State/Store';
2 |
3 | /**
4 | * @returns boolean
5 | */
6 | export function isPolling(): boolean
7 | {
8 | return getState().app.isPolling;
9 | };
10 |
11 | /**
12 | * @returns string
13 | */
14 | export function getLastPoll(): string
15 | {
16 | return getState().app.lastPoll;
17 | };
18 |
19 | /**
20 | * @returns number
21 | */
22 | export function getCurrentAccountId(): number
23 | {
24 | return getState().app.currentAccountId;
25 | };
--------------------------------------------------------------------------------
/src/js/Helpers/Models/GitHubNotificationFilterSet.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @param {IGitHubNotification[]} notifications
4 | * @returns IGitHubNotificationFilterSet
5 | */
6 | export function createGitHubNotificationFilterSet(notifications: IGitHubNotification[],
7 | filterRules: INotificationFilterSet): IGitHubNotificationFilterSet
8 | {
9 | let filterOpts = {
10 | read : 0,
11 | subjectTypes : {},
12 | reasonTypes : {},
13 | repositories : {}
14 | };
15 |
16 | /*
17 | * Go through each and create the numbers
18 | * @todo: change this, perf.
19 | */
20 | notifications
21 | .forEach(notification =>
22 | {
23 | if (!notification.unread) {
24 | filterOpts.read++;
25 | }
26 |
27 | if (filterRules.read
28 | && notification.unread) {
29 | return;
30 | }
31 |
32 | if (!filterRules.read
33 | && !notification.unread) {
34 | return;
35 | }
36 |
37 | /*
38 | * Subject Types
39 | */
40 | if (typeof filterOpts.subjectTypes[notification.subject.type] === 'undefined') {
41 | filterOpts.subjectTypes[notification.subject.type] = 0;
42 | }
43 |
44 | filterOpts.subjectTypes[notification.subject.type]++;
45 |
46 | /*
47 | * Reason Types
48 | */
49 | if (typeof filterOpts.reasonTypes[notification.reason] === 'undefined') {
50 | filterOpts.reasonTypes[notification.reason] = 0;
51 | }
52 |
53 | filterOpts.reasonTypes[notification.reason]++;
54 |
55 | /*
56 | * Repositories
57 | */
58 | if (typeof filterOpts.repositories[notification.repository.id] === 'undefined') {
59 | filterOpts.repositories[notification.repository.id] = {
60 | repository : notification.repository,
61 | count : 0
62 | }
63 | }
64 |
65 | filterOpts.repositories[notification.repository.id].count++;
66 | });
67 |
68 | let notificationFilters: IGitHubNotificationFilterSet = {
69 | read : filterOpts.read,
70 | subjectTypes : Object.keys(filterOpts.subjectTypes)
71 | .map(type => ({
72 | name : type,
73 | count : filterOpts.subjectTypes[type]
74 | })),
75 | reasonTypes : Object.keys(filterOpts.reasonTypes)
76 | .map(type => ({
77 | name : type,
78 | count : filterOpts.reasonTypes[type]
79 | })),
80 | repositories : Object.keys(filterOpts.repositories)
81 | .map(repoId => ({
82 | repository : filterOpts.repositories[repoId].repository,
83 | count : filterOpts.repositories[repoId].count
84 | }))
85 | };
86 |
87 | return notificationFilters;
88 | };
--------------------------------------------------------------------------------
/src/js/Helpers/Models/GitHubRepository.ts:
--------------------------------------------------------------------------------
1 | import { makeGitHubUser } from './GitHubUser';
2 |
3 | /**
4 | * @param {any} repo
5 | * @returns IGitHubRepository
6 | */
7 | export function makeGitHubRepository(repo: any): IGitHubRepository
8 | {
9 | if (typeof repo.id === 'undefined') {
10 | return null;
11 | }
12 |
13 | return {
14 | id : repo.id,
15 | name : repo.name,
16 | fullName : repo.full_name,
17 | private : repo.private,
18 | htmlUrl : repo.html_url,
19 | owner : makeGitHubUser(repo.owner)
20 | };
21 | };
--------------------------------------------------------------------------------
/src/js/Helpers/Models/GitHubUser.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @param {any} user
4 | * @returns IGitHubUser
5 | */
6 | export function makeGitHubUser(user: any): IGitHubUser
7 | {
8 | if (typeof user.id === 'undefined') {
9 | return null;
10 | }
11 |
12 | let newUser: IGitHubUser = {
13 | id : user.id,
14 | avatarUrl : user.avatar_url,
15 | name : user.name,
16 | email : user.email,
17 | createdAt : user.created_at,
18 | username : user.login
19 | };
20 |
21 | return newUser;
22 | };
--------------------------------------------------------------------------------
/src/js/Helpers/Models/RepositoryMuteFilters.ts:
--------------------------------------------------------------------------------
1 | import { getState } from 'Helpers/State/Store';
2 |
3 | /**
4 | * @param {number} accountId
5 | * @returns IStateRepositoryMuteFiltersAccount
6 | */
7 | export function getAccountRepositoryMuteFilters(accountId: string): IStateRepositoryMuteFiltersAccount
8 | {
9 | return getState().repositoryMuteFilters[accountId] || {};
10 | };
--------------------------------------------------------------------------------
/src/js/Helpers/Models/Settings.ts:
--------------------------------------------------------------------------------
1 | import { getState } from 'Helpers/State/Store';
2 |
3 | /**
4 | * @returns string
5 | */
6 | export function getCurrentPollPeriod(): string
7 | {
8 | return getState().settings.pollPeriod;
9 | };
10 |
11 | /**
12 | * @returns boolean
13 | */
14 | export function alertSuccessSoundIsEnabled(): boolean
15 | {
16 | return getState().settings.soundSettings.alertSuccessEnabled;
17 | };
18 |
19 | /**
20 | * @returns boolean
21 | */
22 | export function alertErrorSoundIsEnabled(): boolean
23 | {
24 | return getState().settings.soundSettings.alertErrorEnabled;
25 | };
26 |
27 | /**
28 | * @returns boolean
29 | */
30 | export function newItemsSoundIsEnabled(): boolean
31 | {
32 | return getState().settings.soundSettings.newItemsEnabled;
33 | };
34 |
35 | /**
36 | * @returns string
37 | */
38 | export function getNotificationDoubleClickAction(): string
39 | {
40 | return getState().settings.notifications.doubleClickAction;
41 | };
42 |
43 | /**
44 | * @returns boolean
45 | */
46 | export function shouldConfirmBeforeMarkingNotificationsAsRead(): boolean
47 | {
48 | return getState().settings.notifications.confirmBeforeMarkingMultipleAsRead;
49 | };
--------------------------------------------------------------------------------
/src/js/Helpers/State/Reducify.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @param {any={}} defaultState
4 | * @param {any} reducingMethods
5 | */
6 | const reducify = (defaultState: any = {}, reducingMethods: any) =>
7 | {
8 | return (state = defaultState, action) =>
9 | {
10 | return typeof reducingMethods[action.type] === 'function'
11 | ? reducingMethods[action.type](state, action)
12 | : state;
13 | };
14 | };
15 |
16 | export default reducify;
--------------------------------------------------------------------------------
/src/js/Helpers/State/Store.ts:
--------------------------------------------------------------------------------
1 | import InstanceCache from 'Core/InstanceCache';
2 |
3 | /**
4 | * @returns Redux
5 | */
6 | export function getStore(): Redux.Store
7 | {
8 | return InstanceCache.getInstance>('IStoreCreator')
9 | .getStore();
10 | };
11 |
12 | /**
13 | * @param {any} action
14 | */
15 | export function dispatch(action: any)
16 | {
17 | return getStore().dispatch(action);
18 | };
19 |
20 | /**
21 | * @returns T
22 | */
23 | export function getState(): T
24 | {
25 | return getStore().getState() as T;
26 | };
--------------------------------------------------------------------------------
/src/js/Helpers/System/Electron.ts:
--------------------------------------------------------------------------------
1 | const windowRequire = window['require'] as NodeRequire;
2 |
3 | /**
4 | * @returns Electron
5 | */
6 | export function getElectron(): Electron.ElectronMainAndRenderer
7 | {
8 | return windowRequire('electron') as Electron.ElectronMainAndRenderer;
9 | };
10 |
11 | /**
12 | * @returns Electron
13 | */
14 | export function getNewRemoteElectronMenu(): Electron.Menu
15 | {
16 | let electron = getElectron();
17 |
18 | return new electron.remote.Menu();
19 | };
20 |
21 | /**
22 | * @param {Electron.MenuItemOptions} opts
23 | * @returns Electron
24 | */
25 | export function getNewRemoteElectronMenuItem(opts: Electron.MenuItemOptions): Electron.MenuItem
26 | {
27 | let electron = getElectron();
28 |
29 | return new electron.remote.MenuItem(opts);
30 | };
31 |
32 | /**
33 | * @param {string} string
34 | */
35 | export function copyStringToClipboard(string: string)
36 | {
37 | getElectron().clipboard.writeText(string);
38 | };
39 |
40 | /**
41 | * @param {string} url
42 | * @param {boolean=true} activate
43 | */
44 | export function openExternalUrl(url: string, activate: boolean = true)
45 | {
46 | getElectron().shell.openExternal(url, {
47 | activate : activate
48 | });
49 | };
50 |
51 | /**
52 | * @returns string
53 | */
54 | export function getPlatform(): string
55 | {
56 | return getElectron().remote.getGlobal('process').platform;
57 | };
58 |
59 | /**
60 | * @returns boolean
61 | */
62 | export function isMac(): boolean
63 | {
64 | return getPlatform() === 'darwin';
65 | };
66 |
67 | /**
68 | * @returns Electron
69 | */
70 | export function getCurrentWindow(): Electron.BrowserWindow
71 | {
72 | return getElectron().remote.getCurrentWindow();
73 | };
--------------------------------------------------------------------------------
/src/js/Helpers/System/Environment.ts:
--------------------------------------------------------------------------------
1 | const developmentEnv = 'development';
2 | const productionEnv = 'production';
3 |
4 | /**
5 | * @returns boolean
6 | */
7 | export function isDevelopment(): boolean
8 | {
9 | return isEnv(developmentEnv);
10 | }
11 |
12 | /**
13 | * @returns boolean
14 | */
15 | export function isProduction(): boolean
16 | {
17 | return isEnv(productionEnv);
18 | };
19 |
20 | /**
21 | * @param {string} env
22 | * @returns boolean
23 | */
24 | export function isEnv(env: string): boolean
25 | {
26 | return process.env.NODE_ENV === env;
27 | };
--------------------------------------------------------------------------------
/src/js/Helpers/System/Scheduler.ts:
--------------------------------------------------------------------------------
1 | import InstaceCache from 'Core/InstanceCache';
2 |
3 | import { pollSinceNotifications } from 'Actions/Notifications';
4 |
5 | import {
6 | isPolling,
7 | getLastPoll
8 | } from 'Helpers/Models/App';
9 | import {
10 | getAccountIds,
11 | getAccountToken
12 | } from 'Helpers/Models/Accounts';
13 | import { dispatch } from 'Helpers/State/Store';
14 | import { getCronPeriodByName } from 'Helpers/Lang/Date';
15 |
16 | /**
17 | * @param {string} pollPeriod
18 | */
19 | export function configurePollingScheduler(pollPeriod: string)
20 | {
21 | let scheduler = InstaceCache.getInstance('IScheduler');
22 |
23 | /*
24 | * Clear all the current jobs. We'll reconfigure them below.
25 | */
26 | scheduler.clearAllJobs();
27 |
28 | /*
29 | * Setup the job for polling
30 | */
31 | let name = scheduler.scheduleJob(getCronPeriodByName(pollPeriod), {
32 | accountIds : getAccountIds()
33 | }, () =>
34 | {
35 | if (isPolling()) {
36 | return;
37 | }
38 |
39 | /*
40 | * Get the parameters we passed for this job
41 | */
42 | let params = scheduler.getJobParameters(name);
43 | if (typeof params.accountIds === 'undefined') {
44 | return;
45 | }
46 |
47 | pollingMethod(params.accountIds);
48 | });
49 | };
50 |
51 | /**
52 | * @param {string[]} accountIds
53 | */
54 | export function pollingMethod(accountIds: string[])
55 | {
56 | let lastPoll = getLastPoll();
57 |
58 | /*
59 | * For each of the accounts, poll for
60 | * notifications since we last polled.
61 | */
62 | accountIds.forEach(id => dispatch(pollSinceNotifications(
63 | id, getAccountToken(id), lastPoll, true
64 | )));
65 | };
--------------------------------------------------------------------------------
/src/js/Interfaces/HawkEye/IHawkEyeConfig.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IHawkEyeConfig
3 | {
4 | github: IHawkEyeConfigGitHub;
5 |
6 | appAlerts: IHawkEyeConfigAppAlerts;
7 | };
8 |
9 | interface IHawkEyeConfigGitHub
10 | {
11 | clientId: string;
12 |
13 | clientSecret: string;
14 |
15 | scopes: string[];
16 |
17 | webUrl: string;
18 | };
19 |
20 | interface IHawkEyeConfigAppAlerts
21 | {
22 | showFor: number;
23 | };
--------------------------------------------------------------------------------
/src/js/Interfaces/IState/IState.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 | ///
5 | ///
6 | ///
7 | ///
8 | ///
9 | ///
10 | ///
11 |
12 | interface IState
13 | {
14 | app: IStateApp;
15 |
16 | setup: IStateSetup;
17 |
18 | accounts: IStateAccounts;
19 |
20 | settings: IStateSettings;
21 |
22 | appAlerts: IStateAppAlerts;
23 |
24 | repositories: IStateRepositories;
25 |
26 | notifications: IStateNotifications;
27 |
28 | authentication: IStateAuthentication;
29 |
30 | notificationFilters: IStateNotificationFilters;
31 |
32 | repositoryMuteFilters: IStateRepositoryMuteFilters;
33 | };
--------------------------------------------------------------------------------
/src/js/Interfaces/IState/IStateAccounts.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | interface IStateAccounts
4 | {
5 | [accountId: string]: IStateAccountsAccount;
6 | };
7 |
8 | interface IStateAccountsAccount
9 | {
10 | token: string;
11 |
12 | gitHubUser: IGitHubUser;
13 | };
--------------------------------------------------------------------------------
/src/js/Interfaces/IState/IStateApp.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IStateApp
3 | {
4 | currentAccountId: number;
5 |
6 | isPolling: boolean;
7 |
8 | lastPoll: string;
9 | };
--------------------------------------------------------------------------------
/src/js/Interfaces/IState/IStateAppAlerts.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | interface IStateAppAlerts
4 | {
5 | alerts: IAppAlert[];
6 | };
--------------------------------------------------------------------------------
/src/js/Interfaces/IState/IStateAuthentication.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IStateAuthentication
3 | {
4 | isAuthenticating: boolean;
5 | };
--------------------------------------------------------------------------------
/src/js/Interfaces/IState/IStateNotificationFilters.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IStateNotificationFilters
3 | {
4 | [accountId: string]: INotificationFilterSet;
5 | };
--------------------------------------------------------------------------------
/src/js/Interfaces/IState/IStateNotifications.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | interface IStateNotifications
4 | {
5 | [accountId: string]: IStateNotificationsAccountsNotifications;
6 | };
7 |
8 | interface IStateNotificationsAccountsNotifications
9 | {
10 | [notificationId: string]: IGitHubNotification;
11 | };
--------------------------------------------------------------------------------
/src/js/Interfaces/IState/IStateRepositories.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IStateRepositories
3 | {
4 | [repoId: string]: IGitHubRepository;
5 | };
--------------------------------------------------------------------------------
/src/js/Interfaces/IState/IStateRepositoryMuteFilters.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IStateRepositoryMuteFilters
3 | {
4 | [accountId: number]: IStateRepositoryMuteFiltersAccount;
5 | };
6 |
7 | interface IStateRepositoryMuteFiltersAccount
8 | {
9 | [repoId: string]: IStateRepositoryMuteFiltersAccountRepo;
10 | };
11 |
12 | interface IStateRepositoryMuteFiltersAccountRepo
13 | {
14 | allowedSubjectTypes: string[];
15 |
16 | allowedReasons: string[];
17 | };
--------------------------------------------------------------------------------
/src/js/Interfaces/IState/IStateSettings.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IStateSettings
3 | {
4 | colorMode: string;
5 |
6 | pollPeriod: string;
7 |
8 | soundSettings: IStateSettingsSound;
9 |
10 | accountSettings: IStateSettingsAccountSettings;
11 |
12 | notifications: IStateSettingsNotification;
13 | };
14 |
15 | interface IStateSettingsAccountSettings
16 | {
17 | [accountId: string]: IStateSettingsAccountSettingsItem;
18 | };
19 |
20 | interface IStateSettingsAccountSettingsItem
21 | {
22 |
23 | };
24 |
25 | interface IStateSettingsSound
26 | {
27 | newItemsEnabled: boolean;
28 |
29 | alertSuccessEnabled: boolean;
30 |
31 | alertErrorEnabled: boolean;
32 | };
33 |
34 | interface IStateSettingsNotification
35 | {
36 | doubleClickAction: string;
37 |
38 | confirmBeforeMarkingMultipleAsRead: boolean;
39 | };
--------------------------------------------------------------------------------
/src/js/Interfaces/IState/IStateSetup.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IStateSetup
3 | {
4 | isLoading: boolean;
5 |
6 | showLoading: boolean;
7 |
8 | renderApp: boolean;
9 | };
--------------------------------------------------------------------------------
/src/js/Interfaces/Models/IAppAlert.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IAppAlert
3 | {
4 | id: string;
5 |
6 | index: number;
7 |
8 | status: string;
9 |
10 | message: string;
11 |
12 | show: boolean;
13 |
14 | sticky: boolean;
15 |
16 | stickyActionIcon?: string;
17 |
18 | stickyActionName?: string;
19 |
20 | stickyActionParams?: any;
21 | };
--------------------------------------------------------------------------------
/src/js/Interfaces/Models/IGitHubNotification.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | interface IGitHubNotification
4 | {
5 | id: number;
6 |
7 | reason: string;
8 |
9 | unread: boolean;
10 |
11 | updatedAt: string;
12 |
13 | lastReadAt: string;
14 |
15 | url: string;
16 |
17 | repository: IGitHubRepository;
18 |
19 | subject: IGitHubNotificationSubject;
20 | };
21 |
22 | interface IGitHubNotificationSubject
23 | {
24 | title: string;
25 |
26 | url: string;
27 |
28 | latestCommentUrl: string;
29 |
30 | type: string;
31 | };
--------------------------------------------------------------------------------
/src/js/Interfaces/Models/IGitHubNotificationFilterSet.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IGitHubNotificationFilterSet
3 | {
4 | read: number;
5 |
6 | subjectTypes: IGitHubNotificationFilterSetStringFilter[];
7 |
8 | reasonTypes: IGitHubNotificationFilterSetStringFilter[];
9 |
10 | repositories: IGitHubNotificationFilterSetRepository[];
11 | };
12 |
13 | interface IGitHubNotificationFilterSetStringFilter
14 | {
15 | name: string;
16 |
17 | count: number;
18 | };
19 |
20 | interface IGitHubNotificationFilterSetRepository
21 | {
22 | repository: IGitHubRepository;
23 |
24 | count: number;
25 | };
--------------------------------------------------------------------------------
/src/js/Interfaces/Models/IGitHubRepository.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | interface IGitHubRepository
4 | {
5 | id: number;
6 |
7 | name: string;
8 |
9 | fullName: string;
10 |
11 | private: boolean;
12 |
13 | htmlUrl: string;
14 |
15 | owner: IGitHubUser;
16 | };
--------------------------------------------------------------------------------
/src/js/Interfaces/Models/IGitHubUser.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IGitHubUser
3 | {
4 | id: number;
5 |
6 | avatarUrl: string;
7 |
8 | name: string;
9 |
10 | email: string;
11 |
12 | createdAt: string;
13 |
14 | username: string;
15 | };
--------------------------------------------------------------------------------
/src/js/Interfaces/Models/INotificationFilterSet.ts:
--------------------------------------------------------------------------------
1 |
2 | interface INotificationFilterSet
3 | {
4 | read: boolean;
5 |
6 | subjectType: string[];
7 |
8 | reasonType: string[];
9 |
10 | repository: number[];
11 |
12 | };
--------------------------------------------------------------------------------
/src/js/Reducers/Accounts.ts:
--------------------------------------------------------------------------------
1 | import Reducify from 'Helpers/State/Reducify';
2 | import ActionConstants from 'Constants/Actions/Index';
3 |
4 | import * as omit from 'lodash/omit';
5 | import * as objectAssign from 'object-assign';
6 |
7 | const initialState: IStateAccounts = {
8 |
9 | };
10 |
11 | let reducingMethods = {
12 | [ActionConstants.accounts.ADD_ACCOUNT] : (state: IStateAccounts, action) =>
13 | {
14 | return objectAssign({}, state, {
15 | [action.user.id] : ({
16 | token : action.token,
17 | gitHubUser : action.user
18 | } as IStateAccountsAccount)
19 | });
20 | },
21 | [ActionConstants.accounts.REMOVE_ACCOUNT] : (state: IStateAccounts, action) =>
22 | {
23 | return objectAssign({}, omit(state, action.accountId));
24 | }
25 | };
26 |
27 | export default Reducify(initialState, reducingMethods);
--------------------------------------------------------------------------------
/src/js/Reducers/App.ts:
--------------------------------------------------------------------------------
1 | import Reducify from 'Helpers/State/Reducify';
2 | import ActionConstants from 'Constants/Actions/Index';
3 |
4 | import * as objectAssign from 'object-assign';
5 |
6 | const initialState: IStateApp = {
7 | currentAccountId : null,
8 | isPolling : false,
9 | lastPoll : null
10 | };
11 |
12 | let reducingMethods = {
13 | [ActionConstants.app.SET_CURRENT_ACCOUNT_ID] : (state: IStateApp, action) =>
14 | {
15 | return objectAssign({}, state, {
16 | currentAccountId : action.currentAccountId
17 | });
18 | },
19 | [ActionConstants.app.SET_APP_IS_POLLING] : (state: IStateApp, action) =>
20 | {
21 | return objectAssign({}, state, {
22 | isPolling : action.isPolling
23 | });
24 | },
25 | [ActionConstants.app.SET_APP_LAST_POLL] : (state: IStateApp, action) =>
26 | {
27 | return objectAssign({}, state, {
28 | lastPoll : action.lastPoll
29 | });
30 | }
31 | };
32 |
33 | export default Reducify(initialState, reducingMethods);
--------------------------------------------------------------------------------
/src/js/Reducers/AppAlerts.ts:
--------------------------------------------------------------------------------
1 | import Reducify from 'Helpers/State/Reducify';
2 | import ActionConstants from 'Constants/Actions/Index';
3 |
4 | import * as objectAssign from 'object-assign';
5 |
6 | const initialState: IStateAppAlerts = {
7 | alerts : []
8 | };
9 |
10 | // @todo: Show/hide to be one reducer and two actions
11 | let reducingMethods = {
12 | [ActionConstants.appAlerts.ADD_APP_ALERT] : (state: IStateAppAlerts, action) =>
13 | {
14 | return objectAssign({}, state, {
15 | alerts : [...state.alerts, action.appAlert]
16 | });
17 | },
18 | [ActionConstants.appAlerts.REMOVE_APP_ALERT] : (state: IStateAppAlerts, action) =>
19 | {
20 | return objectAssign({}, state, {
21 | alerts : state.alerts
22 | .filter(alert => alert.id !== action.appAlertId)
23 | });
24 | },
25 | [ActionConstants.appAlerts.SHOW_APP_ALERT] : (state: IStateAppAlerts, action) =>
26 | {
27 | return objectAssign({}, state, {
28 | alerts : state.alerts
29 | .map(alert => alert.id === action.appAlertId
30 | ? objectAssign({}, alert, {
31 | show : true
32 | })
33 | : alert)
34 | });
35 | },
36 | [ActionConstants.appAlerts.HIDE_APP_ALERT] : (state: IStateAppAlerts, action) =>
37 | {
38 | return objectAssign({}, state, {
39 | alerts : state.alerts
40 | .map(alert => alert.id === action.appAlertId
41 | ? objectAssign({}, alert, {
42 | show : false
43 | })
44 | : alert)
45 | });
46 | },
47 | [ActionConstants.appAlerts.HIDE_ALL_APP_ALERTS] : (state: IStateAppAlerts, action) =>
48 | {
49 | return objectAssign({}, state, {
50 | alerts : state.alerts
51 | .map(alert => objectAssign({}, alert, {
52 | show : false
53 | }))
54 | });
55 | },
56 | };
57 |
58 | export default Reducify(initialState, reducingMethods);
--------------------------------------------------------------------------------
/src/js/Reducers/Authentication.ts:
--------------------------------------------------------------------------------
1 | import Reducify from 'Helpers/State/Reducify';
2 | import ActionConstants from 'Constants/Actions/Index';
3 |
4 | import * as objectAssign from 'object-assign';
5 |
6 | const initialState: IStateAuthentication = {
7 | isAuthenticating : false
8 | };
9 |
10 | let reducingMethods = {
11 | [ActionConstants.authentication.SET_IS_AUTHENTICATING] : (state: IStateAuthentication, action) =>
12 | {
13 | return objectAssign({}, state, {
14 | isAuthenticating : action.isAuthenticating
15 | });
16 | }
17 | };
18 |
19 | export default Reducify(initialState, reducingMethods);
--------------------------------------------------------------------------------
/src/js/Reducers/Index.ts:
--------------------------------------------------------------------------------
1 | import App from './App';
2 | import Setup from './Setup';
3 | import Accounts from './Accounts';
4 | import Settings from './Settings';
5 | import AppAlerts from './AppAlerts';
6 | import Repositories from './Repositories';
7 | import Notifications from './Notifications';
8 | import Authentication from './Authentication';
9 | import NotificationFilters from './NotificationFilters';
10 | import RepositoryMuteFilters from './RepositoryMuteFilters';
11 |
12 | export default {
13 | app : App,
14 | setup : Setup,
15 | accounts : Accounts,
16 | settings : Settings,
17 | appAlerts : AppAlerts,
18 | repositories : Repositories,
19 | notifications : Notifications,
20 | authentication : Authentication,
21 | notificationFilters : NotificationFilters,
22 | repositoryMuteFilters : RepositoryMuteFilters
23 | };
--------------------------------------------------------------------------------
/src/js/Reducers/Repositories.ts:
--------------------------------------------------------------------------------
1 | import Reducify from 'Helpers/State/Reducify';
2 | import ActionConstants from 'Constants/Actions/Index';
3 |
4 | import * as omit from 'lodash/omit';
5 | import * as objectAssign from 'object-assign';
6 |
7 | const initialState: IStateRepositories = {
8 |
9 | };
10 |
11 | let reducingMethods = {
12 | [ActionConstants.repositories.ADD_REPOSITORY] : (state: IStateRepositories, action: { repository: IGitHubRepository; }) =>
13 | {
14 | return objectAssign({}, state, {
15 | [action.repository.id] : action.repository
16 | });
17 | }
18 | };
19 |
20 | export default Reducify(initialState, reducingMethods);
--------------------------------------------------------------------------------
/src/js/Reducers/Settings.ts:
--------------------------------------------------------------------------------
1 | import Reducify from 'Helpers/State/Reducify';
2 |
3 | import { cronPeriods } from 'Constants/Lang/Date';
4 | import ActionConstants from 'Constants/Actions/Index';
5 | import {
6 | colorModes,
7 | defaultPollPeriod,
8 | notificationDoubleClickActions
9 | } from 'Constants/Models/Settings';
10 |
11 |
12 | import * as objectAssign from 'object-assign';
13 |
14 | const initialState: IStateSettings = {
15 | colorMode : colorModes.dark,
16 | pollPeriod : defaultPollPeriod,
17 | accountSettings : {},
18 | soundSettings : {
19 | newItemsEnabled : true,
20 | alertSuccessEnabled : true,
21 | alertErrorEnabled : true
22 | },
23 | notifications : {
24 | doubleClickAction : notificationDoubleClickActions.open,
25 | confirmBeforeMarkingMultipleAsRead : true
26 | }
27 | };
28 |
29 | let reducingMethods = {
30 | [ActionConstants.settings.SET_SETTINGS_VALUE] : (state: IStateSettings, action) =>
31 | {
32 | return objectAssign({}, state, {
33 | [action.key] : action.value
34 | });
35 | },
36 | [ActionConstants.settings.SET_SOUND_SETTINGS_ENABLED] : (state: IStateSettings, action: { key: string;
37 | enabled: boolean; }) =>
38 | {
39 | return objectAssign({}, state, {
40 | soundSettings : objectAssign({}, state.soundSettings, {
41 | [action.key] : action.enabled
42 | })
43 | });
44 | },
45 | [ActionConstants.settings.SET_NOTIFICATIONS_DOUBLE_CLICK_ACTION] : (state: IStateSettings, action: { action: string }) =>
46 | {
47 | return objectAssign({}, state, {
48 | notifications : objectAssign({}, state.notifications, {
49 | doubleClickAction : action.action
50 | })
51 | })
52 | },
53 | [ActionConstants.settings.SET_CONFIRM_BEFORE_MARKING_NOTIFICATIONS_AS_READ] : (state: IStateSettings, action: { confirm: boolean }) =>
54 | {
55 | return objectAssign({}, state, {
56 | notifications : objectAssign({}, state.notifications, {
57 | confirmBeforeMarkingMultipleAsRead : action.confirm
58 | })
59 | })
60 | },
61 | [ActionConstants.settings.SET_COLOR_MODE] : (state: IStateSettings, action : { colorMode: string }) =>
62 | {
63 | return objectAssign({}, state, {
64 | colorMode : action.colorMode
65 | });
66 | }
67 | };
68 |
69 | export default Reducify(initialState, reducingMethods);
--------------------------------------------------------------------------------
/src/js/Reducers/Setup.ts:
--------------------------------------------------------------------------------
1 | import Reducify from 'Helpers/State/Reducify';
2 | import ActionConstants from 'Constants/Actions/Index';
3 |
4 | import * as objectAssign from 'object-assign';
5 |
6 | const initialState: IStateSetup = {
7 | isLoading : true,
8 | showLoading : true,
9 | renderApp : false
10 | };
11 |
12 | let reducingMethods = {
13 | [ActionConstants.setup.SET_SETUP_IS_LOADING] : (state: IStateSetup, action) =>
14 | {
15 | return objectAssign({}, state, {
16 | isLoading : action.isLoading
17 | });
18 | },
19 | [ActionConstants.setup.SET_SETUP_SHOW_LOADING] : (state: IStateSetup, action) =>
20 | {
21 | return objectAssign({}, state, {
22 | showLoading : action.showLoading
23 | });
24 | },
25 | [ActionConstants.setup.SET_SETUP_RENDER_APP] : (state: IStateSetup, action) =>
26 | {
27 | return objectAssign({}, state, {
28 | renderApp : action.renderApp
29 | });
30 | }
31 | };
32 |
33 | export default Reducify(initialState, reducingMethods);
--------------------------------------------------------------------------------
/src/js/Routes.ts:
--------------------------------------------------------------------------------
1 | import Index from 'View/Index';
2 |
3 | import SettingsIndex from 'View/Settings/Index';
4 |
5 | import SettingsAccountView from 'View/Settings/Accounts/View';
6 | import SettingsAccountRepositoryMuteFilter from 'View/Settings/Accounts/RepositoryMuteFilters';
7 |
8 | const routes: ReactRouter.PlainRoute[] = [{
9 | path : 'settings',
10 | component : SettingsIndex
11 | }, {
12 | path : 'settings/accounts/:accountId',
13 | component : SettingsAccountView
14 | }, {
15 | path : 'settings/accounts/:accountId/repo-mute-filter/:repoId',
16 | component : SettingsAccountRepositoryMuteFilter
17 | }
18 | ];
19 |
20 | export default routes;
--------------------------------------------------------------------------------
/src/js/Scheduler/Interfaces/IScheduler.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IScheduler
3 | {
4 | scheduleJob(time: string, parameters: any, method: () => any): string;
5 |
6 | getJobParameters(name: string): any;
7 |
8 | cancelJob(name: string);
9 |
10 | clearAllJobs();
11 | };
--------------------------------------------------------------------------------
/src/js/Scheduler/Interfaces/ISchedulerJobs.ts:
--------------------------------------------------------------------------------
1 | //import * as nodeSchedule from 'node-schedule';
2 |
3 | interface ISchedulerJobs
4 | {
5 | [jobId: string]: ISchedulerJobsJob;
6 | };
7 |
8 | interface ISchedulerJobsJob
9 | {
10 | parameters: any;
11 |
12 | job: any;
13 | };
--------------------------------------------------------------------------------
/src/js/Scheduler/NodeSchedule.ts:
--------------------------------------------------------------------------------
1 | const windowRequire = window['require'] as NodeRequire;
2 |
3 | import * as nodeSchedule from 'node-schedule';
4 |
5 | /**
6 | * @returns any
7 | */
8 | export function getNodeSchedule(): any
9 | {
10 | return windowRequire('node-schedule');
11 | };
12 |
13 | /**
14 | * @param {string} name
15 | * @param {()=>{}} event
16 | * @returns nodeSchedule
17 | */
18 | export function scheduleJob(name: string, event: () => {}): nodeSchedule.Job
19 | {
20 | return getNodeSchedule().scheduleJob(name, event) as nodeSchedule.Job;
21 | };
22 |
23 | /**
24 | * @param {nodeSchedule.Job} job
25 | * @returns boolean
26 | */
27 | export function cancelJob(job: nodeSchedule.Job): boolean
28 | {
29 | return job.cancel();
30 | };
31 |
32 | /**
33 | * @returns nodeSchedule
34 | */
35 | export function getScheduledJobs(): { [jobName: string]: nodeSchedule.Job; }
36 | {
37 | return getNodeSchedule().scheduledJobs;
38 | };
--------------------------------------------------------------------------------
/src/js/Scheduler/Scheduler.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import {
5 | cancelJob,
6 | scheduleJob,
7 | getScheduledJobs
8 | } from './NodeSchedule';
9 | import { generateId } from 'Helpers/Lang/String';
10 |
11 | class Scheduler implements IScheduler
12 | {
13 | private jobs: ISchedulerJobs = {};
14 |
15 | constructor()
16 | {
17 | /*
18 | * Cancel any currently running jobs. This is
19 | * in case of a refresh and not a restart.
20 | */
21 | this.cancelRunningJobs();
22 | this.jobs = {};
23 | }
24 |
25 | /**
26 | * @param {string} time
27 | * @param {any} parameters
28 | * @param {()=>{}} method
29 | * @returns string
30 | */
31 | public scheduleJob = (time: string, parameters: any, method: () => any): string =>
32 | {
33 | let name = this.makeJobName();
34 | let job = scheduleJob(time, method);
35 |
36 | this.jobs[name] = {
37 | job : job,
38 | parameters : parameters
39 | } as ISchedulerJobsJob;
40 |
41 | return name;
42 | };
43 |
44 | /**
45 | * @param {string} name
46 | * @returns any
47 | */
48 | public getJobParameters = (name: string): any =>
49 | {
50 | return typeof this.jobs[name] !== 'undefined'
51 | ? this.jobs[name].parameters
52 | : null;
53 | }
54 |
55 | /**
56 | * @param {string} name
57 | */
58 | public cancelJob = (name: string) =>
59 | {
60 | if (typeof this.jobs[name] === 'undefined') {
61 | return;
62 | }
63 |
64 | this.jobs[name].job.cancel();
65 | }
66 |
67 | /**
68 | */
69 | public clearAllJobs = () =>
70 | {
71 | this.cancelRunningJobs();
72 | this.jobs = {};
73 | }
74 |
75 | /**
76 | * @returns string
77 | */
78 | private makeJobName = (): string =>
79 | {
80 | let name = generateId();
81 | while (typeof this.jobs[name] !== 'undefined') {
82 | name = generateId();
83 | }
84 |
85 | return name;
86 | };
87 |
88 | /**
89 | */
90 | private cancelRunningJobs = () =>
91 | {
92 | let jobs = getScheduledJobs();
93 |
94 | Object.keys(jobs)
95 | .map(k => jobs[k].cancel());
96 | };
97 | };
98 |
99 | export default Scheduler;
--------------------------------------------------------------------------------
/src/js/Services/GitHubAccountsService.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | class GitHubAccountsService implements IGitHubAccountsService
5 | {
6 | private gitHub: IGitHub;
7 |
8 | constructor(gitHub: IGitHub)
9 | {
10 | this.gitHub = gitHub;
11 | }
12 | };
13 |
14 | export default GitHubAccountsService;
--------------------------------------------------------------------------------
/src/js/Services/GitHubAuthenticationService.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import {
5 | getGitHubScopes,
6 | getGitHubClientId,
7 | getGitHubClientSecret
8 | } from 'Helpers/Services/GitHub';
9 |
10 | class GitHubAuthenticationService implements IGitHubAuthenticationService
11 | {
12 | private gitHub: IGitHub;
13 |
14 | constructor(gitHub: IGitHub)
15 | {
16 | this.gitHub = gitHub;
17 | }
18 |
19 | /**
20 | * @returns Promise
21 | */
22 | public generateOAuthUrl = (): Promise =>
23 | {
24 | return this.gitHub
25 | .authentication
26 | .generateOAuthUrl(getGitHubClientId(), getGitHubScopes());
27 |
28 | }
29 |
30 | /**
31 | * @param {string} code
32 | * @returns Promise
33 | */
34 | public authenticateAccessToken = (code: string): Promise =>
35 | {
36 | return new Promise((resolve, reject) =>
37 | {
38 | this.gitHub
39 | .authentication
40 | .authenticateAccessToken(getGitHubClientId(), getGitHubClientSecret(), code)
41 | .then(res => resolve(res),
42 | err => reject(err));
43 | });
44 | };
45 |
46 | /**
47 | * @param {string} token
48 | * @returns Promise
49 | */
50 | public getAuthenticatedUser = (token: string): Promise =>
51 | {
52 | return new Promise((resolve, reject) =>
53 | {
54 | this.gitHub
55 | .users
56 | .getAuthenticatedUser(token)
57 | .then(res => resolve(res),
58 | err => reject(err));
59 | });
60 | }
61 | };
62 |
63 | export default GitHubAuthenticationService;
--------------------------------------------------------------------------------
/src/js/Services/Interfaces/IGitHubAccountsService.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IGitHubAccountsService
3 | {
4 |
5 | };
--------------------------------------------------------------------------------
/src/js/Services/Interfaces/IGitHubAuthenticationService.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IGitHubAuthenticationService
3 | {
4 | generateOAuthUrl(): Promise;
5 |
6 | authenticateAccessToken(code: string): Promise;
7 |
8 | getAuthenticatedUser(token: string): Promise;
9 | };
--------------------------------------------------------------------------------
/src/js/Services/Interfaces/IGitHubNotificationsService.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IGitHubNotificationsService
3 | {
4 | getNotifications(token: string,
5 | page?: number,
6 | all?: boolean,
7 | participating?: boolean,
8 | since?: string,
9 | before?: string): Promise;
10 |
11 | getAllNotificationsSince(token: string, since: string, all?: boolean): Promise;
12 |
13 | getAllNotificationsBefore(token: string, before: string, all?: boolean): Promise;
14 |
15 | markNotificationAsThread(token: string, threadId: string): Promise;
16 | };
--------------------------------------------------------------------------------
/src/js/View/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Link } from 'react-router';
3 |
4 | import { registerUpdateAvailableHandler } from 'Electron/Tasks/App';
5 | import { registerPreferencesMenuControl } from 'Electron/Tasks/Settings';
6 | import { registerMarkNotificationAsReadSuccess } from 'Electron/Tasks/Notification';
7 |
8 | import AppBar from './Components/AppBar/Index';
9 | import AppAlerts from './Components/AppAlerts/Index';
10 |
11 | class App extends React.Component
12 | {
13 | render()
14 | {
15 | return (
16 |
17 |
18 |
21 |
22 |
25 | {this.props.children}
26 |
27 |
28 |
29 | );
30 | }
31 |
32 | componentDidMount()
33 | {
34 | registerUpdateAvailableHandler();
35 | registerPreferencesMenuControl();
36 | registerMarkNotificationAsReadSuccess();
37 | }
38 | };
39 |
40 | export default App;
--------------------------------------------------------------------------------
/src/js/View/Components/AppAlerts/Index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { connect } from 'react-redux';
3 |
4 | import AppAlert from './AppAlert';
5 |
6 | interface IAppAlertsProps
7 | {
8 | appAlerts?: IAppAlert[];
9 | };
10 |
11 | class AppAlerts extends React.Component
12 | {
13 | render()
14 | {
15 | return (
16 |
17 | {this.props.appAlerts
18 | .map(a =>
)}
20 |
21 | );
22 | }
23 | };
24 |
25 | export default connect<{}, {}, IAppAlertsProps>(
26 | (state: IState, props: IAppAlertsProps) => ({
27 | appAlerts : state.appAlerts.alerts
28 | })
29 | )(AppAlerts);
--------------------------------------------------------------------------------
/src/js/View/Components/AppBar/Account.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import createMenu from 'Electron/Menus/Accounts';
4 |
5 | import {
6 | Btn,
7 | ProfilePicture
8 | } from 'View/Ui/Index';
9 |
10 | interface IAppBarAccountProps
11 | {
12 | account: IStateAccountsAccount;
13 |
14 | currentAccountId: number;
15 |
16 | className?: string;
17 |
18 | onClick?(account: IStateAccountsAccount);
19 | };
20 |
21 | class AppBarAccount extends React.Component
22 | {
23 | static defaultProps = {
24 | onClick : () => {}
25 | };
26 |
27 | handleRightClick(e)
28 | {
29 | e.preventDefault();
30 |
31 | try {
32 | createMenu(this.props.account.gitHubUser.id)
33 | .popup(e.clientX, e.clientY);
34 | } catch (e) {}
35 | }
36 |
37 | render()
38 | {
39 | return (
40 |
46 |
49 |
50 | );
51 | }
52 | };
53 |
54 | export default AppBarAccount;
--------------------------------------------------------------------------------
/src/js/View/Components/AppBar/Index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { connect } from 'react-redux';
3 |
4 | import { dispatch } from 'Helpers/State/Store';
5 | import { isMac } from 'Helpers/System/Electron';
6 | import { switchAccount } from 'Actions/UIActions/App';
7 |
8 | import {
9 | Icon,
10 | BtnTo,
11 | Scroll,
12 | } from 'View/Ui/Index';
13 |
14 | import Account from './Account';
15 |
16 | interface IAppBarProps
17 | {
18 | app?: IStateApp;
19 |
20 | accounts?: IStateAccountsAccount[];
21 | };
22 |
23 | class AppBar extends React.Component
24 | {
25 | handleAccountClick(account: IStateAccountsAccount)
26 | {
27 | dispatch(switchAccount(account.gitHubUser.id));
28 | }
29 |
30 | render()
31 | {
32 | return (
33 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | {this.props.accounts
44 | .map((account, i, a) =>
45 | (
46 |
55 | ))}
56 |
57 |
58 |
59 |
60 |
62 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | );
71 | }
72 | };
73 |
74 | export default connect(
75 | (state: IState, props: IAppBarProps) => ({
76 | app : state.app,
77 | accounts : Object.keys(state.accounts)
78 | .map(id => state.accounts[id])
79 |
80 | })
81 | )(AppBar as any);
82 |
--------------------------------------------------------------------------------
/src/js/View/Components/GenericError.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import {
4 | BtnTo,
5 | CenteredBox
6 | } from 'View/Ui/Index';
7 |
8 | interface IGenericErrorProps
9 | {
10 | title: string;
11 |
12 | description: string;
13 |
14 | buttonText: string;
15 |
16 | buttonUrl: string;
17 | };
18 |
19 | class GenericError extends React.Component
20 | {
21 | render()
22 | {
23 | return (
24 |
25 |
26 | {this.props.title}
27 |
28 |
29 | {this.props.description}
30 |
31 |
33 | {this.props.buttonText}
34 |
35 |
36 | );
37 | };
38 | };
39 |
40 | export default GenericError;
--------------------------------------------------------------------------------
/src/js/View/Components/NoAccounts/Index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { connect } from 'react-redux';
3 |
4 | import { dispatch } from 'Helpers/State/Store';
5 | import { handleAddAccountClick } from 'Actions/UIActions/Accounts';
6 |
7 | import {
8 | Btn,
9 | CenteredBox
10 | } from 'View/Ui/Index';
11 |
12 | interface INoAccountsProps
13 | {
14 | authentication?: IStateAuthentication;
15 | };
16 |
17 | class NoAccounts extends React.Component
18 | {
19 | render()
20 | {
21 | return (
22 |
23 |
24 | {'Welcome to Hawk Eye'}
25 |
26 |
27 | {'Let\'s get started by adding a GitHub account.'}
28 |
29 |
31 | {'Add an Account'}
32 |
33 |
34 | );
35 | }
36 |
37 | handleAddAccountClick()
38 | {
39 | if (this.props.authentication.isAuthenticating) {
40 | return;
41 | }
42 |
43 | dispatch(handleAddAccountClick());
44 | }
45 | };
46 |
47 | export default connect<{}, {}, INoAccountsProps>(
48 | (state: IState, props: INoAccountsProps) => ({
49 | authentication : state.authentication
50 | })
51 | )(NoAccounts);
--------------------------------------------------------------------------------
/src/js/View/Components/NoNotifications/Index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { dispatch } from 'Helpers/State/Store';
4 | import { clearFilters } from 'Actions/NotificationFilters';
5 |
6 | import {
7 | Btn,
8 | CenteredBox
9 | } from 'View/Ui/Index';
10 |
11 | interface INoNotificationsProps
12 | {
13 | accountId: number;
14 | };
15 |
16 | class NoNotifications extends React.Component
17 | {
18 | handleClearFiltersClick()
19 | {
20 | dispatch(clearFilters(this.props.accountId));
21 | }
22 |
23 | render()
24 | {
25 | return (
26 |
27 |
28 | {'Quiver Zero!'}
29 |
30 |
31 | {'Hah, get it? But well done!'}
32 |
33 |
35 | {'Clear Filters'}
36 |
37 |
38 | );
39 | }
40 | };
41 |
42 | export default NoNotifications;
--------------------------------------------------------------------------------
/src/js/View/Components/NotificationFilters/NotificationFilterRepositoryFilter.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import createMenu from 'Electron/Menus/Repository';
4 |
5 | import { Btn } from 'View/Ui/Index';
6 |
7 | interface INotificationFilterRepositoryFilterProps
8 | {
9 | accountId: number;
10 |
11 | repositoryFilters: IGitHubNotificationFilterSetRepository[];
12 |
13 | className?: string;
14 |
15 | getTitle(): string;
16 |
17 | getFilterTitle(filter: IGitHubNotificationFilterSetRepository): string;
18 |
19 | getFilterIsActive(filter: IGitHubNotificationFilterSetRepository): boolean;
20 |
21 | onClick(filter: IGitHubNotificationFilterSetRepository): void;
22 | };
23 |
24 | class NotificationFilterRepositoryFilter extends React.Component
25 | {
26 | handleRightClick(repository: IGitHubRepository, e)
27 | {
28 | e.preventDefault();
29 |
30 | try {
31 | createMenu(this.props.accountId, repository)
32 | .popup(e.clientX, e.clientY);
33 | } catch (e) {}
34 | }
35 |
36 | render()
37 | {
38 | return (
39 |
40 |
41 |
42 |
43 | {this.props.repositoryFilters
44 | .map(filter =>
45 | (
46 |
48 |
54 | {this.props.getFilterTitle(filter)}
55 | {filter.count}
56 |
57 |
58 | ))}
59 |
60 | );
61 | }
62 | };
63 |
64 | export default NotificationFilterRepositoryFilter;
--------------------------------------------------------------------------------
/src/js/View/Components/NotificationFilters/NotificationFilterStringFilter.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { Btn } from 'View/Ui/Index';
4 |
5 | interface INotificationFilterStringFilterProps
6 | {
7 | stringFilters: IGitHubNotificationFilterSetStringFilter[];
8 |
9 | className?: string;
10 |
11 | getTitle(): string;
12 |
13 | getFilterTitle(filter: IGitHubNotificationFilterSetStringFilter): string;
14 |
15 | getFilterIsActive(filter: IGitHubNotificationFilterSetStringFilter): boolean;
16 |
17 | onClick(filter: IGitHubNotificationFilterSetStringFilter): void;
18 | };
19 |
20 | class NotificationFilterStringFilter extends React.Component
21 | {
22 | render()
23 | {
24 | return (
25 |
26 |
27 |
28 |
29 | {this.props.stringFilters
30 | .map(filter =>
31 | (
32 |
34 |
39 | {this.props.getFilterTitle(filter)}
40 | {filter.count}
41 |
42 |
43 | ))}
44 |
45 | )
46 | }
47 | };
48 |
49 | export default NotificationFilterStringFilter;
--------------------------------------------------------------------------------
/src/js/View/Components/NotificationsList/Index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import {
4 | AutoSizer,
5 | Collection
6 | } from 'react-virtualized';
7 | import { Notification } from 'View/Ui/Index';
8 |
9 | interface INotificationsListProps
10 | {
11 | accountId: string;
12 |
13 | notifications: IGitHubNotification[];
14 |
15 | disableMarkAsRead?: boolean;
16 |
17 | onClick?(notification: IGitHubNotification);
18 |
19 | onDoubleClick?(notification: IGitHubNotification);
20 | };
21 |
22 | class NotificationsList extends React.Component
23 | {
24 | static defaultProps = {
25 | onClick : () => {},
26 | onDoubleClick : () => {},
27 | };
28 |
29 | render()
30 | {
31 | return (
32 |
33 | {({ height, width }) => {
34 | return width === 0
35 | ?
36 | : }}
42 |
43 | );
44 | }
45 |
46 | cellSizeCalculator(width: number, o: any)
47 | {
48 | return {
49 | width : width,
50 | height : 100,
51 | x : 0,
52 | y : o.index * 100
53 | };
54 | };
55 |
56 | renderRow(notifications: IGitHubNotification[], o: any)
57 | {
58 | return (
59 |
61 |
66 |
67 | );
68 | };
69 | };
70 |
71 | export default NotificationsList;
--------------------------------------------------------------------------------
/src/js/View/Components/ViewBar/Index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { Link } from 'react-router';
4 | import { Icon } from 'View/Ui/Index';
5 |
6 | interface IViewBarProps
7 | {
8 | title: string;
9 |
10 | backLink?: string;
11 |
12 | getLeftContent?(): any;
13 |
14 | getRightContent?(): any;
15 | };
16 |
17 | class ViewBar extends React.Component
18 | {
19 | render()
20 | {
21 | return (
22 |
23 |
24 |
25 | {typeof this.props.backLink === 'string'
26 | ?
28 |
29 |
30 | : undefined}
31 | {typeof this.props.getLeftContent === 'function'
32 | ?
33 | {this.props.getLeftContent()}
34 |
35 | : undefined}
36 |
37 | {this.props.title}
38 |
39 | {typeof this.props.getRightContent === 'function'
40 | ?
41 | {this.props.getRightContent()}
42 |
43 | : undefined}
44 |
45 |
46 |
47 | {this.props.children}
48 |
49 |
50 | );
51 | }
52 | };
53 |
54 | export default ViewBar;
--------------------------------------------------------------------------------
/src/js/View/Container.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { connect } from 'react-redux';
3 | import { Provider } from 'react-redux';
4 | import { Router } from 'react-router';
5 |
6 | import { colorModes } from 'Constants/Models/Settings';
7 |
8 | import { AppLoading } from 'View/Ui/Index';
9 |
10 | interface IContainerProps
11 | {
12 | store: Redux.Store;
13 |
14 | history: ReactRouterRedux.ReactRouterReduxHistory;
15 |
16 | routes: ReactRouter.PlainRoute;
17 |
18 | setup?: IStateSetup;
19 |
20 | settings?: IStateSettings;
21 | };
22 |
23 | class Container extends React.Component
24 | {
25 | render()
26 | {
27 | return (
28 |
32 | {this.props.setup.isLoading
33 | ?
34 | : undefined}
35 | {this.props.setup.renderApp
36 | ? (
37 |
38 |
40 |
41 | )
42 | : undefined}
43 |
44 | );
45 | }
46 | };
47 |
48 | export default connect<{}, {}, IContainerProps>(
49 | (state: IState) => ({
50 | setup : state.setup,
51 | settings : state.settings
52 | })
53 | )(Container);
--------------------------------------------------------------------------------
/src/js/View/Settings/Index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Link } from 'react-router';
3 | import { connect } from 'react-redux';
4 |
5 | import AppSection from './Sections/App';
6 | import SoundsSection from './Sections/Sounds';
7 | import AccountsSection from './Sections/Accounts';
8 | import FrequencySection from './Sections/Frequency';
9 | import NotificationsSection from './Sections/Notifications';
10 |
11 | import { Scroll } from 'View/Ui/Index';
12 | import ViewBar from 'View/Components/ViewBar/Index';
13 |
14 | interface ISettingsIndexProps
15 | {
16 | settings: IStateSettings;
17 | };
18 |
19 | class SettingsIndex extends React.Component
20 | {
21 | render()
22 | {
23 | return (
24 |
26 |
27 |
28 |
29 |
30 |
31 |
34 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | );
57 | }
58 | };
59 |
60 | export default connect(
61 | (state: IState) => ({
62 | settings : state.settings
63 | })
64 | )(SettingsIndex);
--------------------------------------------------------------------------------
/src/js/View/Settings/Sections/Accounts.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { connect } from 'react-redux';
3 |
4 | import { dispatch } from 'Helpers/State/Store';
5 | import { handleAddAccountClick } from 'Actions/UIActions/Accounts';
6 |
7 | import {
8 | Btn,
9 | BtnTo
10 | } from 'View/Ui/Index';
11 |
12 | interface IAccountsSettingsSectionProps
13 | {
14 | settings: IStateSettings;
15 |
16 | accounts?: IStateAccountsAccount[];
17 |
18 | authentication?: IStateAuthentication;
19 | };
20 |
21 | class AccountsSettingsSection extends React.Component
22 | {
23 | handleClick()
24 | {
25 | if (this.props.authentication.isAuthenticating) {
26 | return;
27 | }
28 |
29 | dispatch(handleAddAccountClick());
30 | }
31 |
32 | render()
33 | {
34 | return (
35 |
36 |
37 |
38 | {this.props.accounts
39 | .map((acc, i) =>
40 | (
41 |
50 | {'@' + acc.gitHubUser.username}
51 |
52 | ))}
53 | 0
55 | ? ' btn--hard-top'
56 | : undefined)}
57 | onClick={this.handleClick.bind(this)}>
58 | {'Add Account'}
59 |
60 |
61 |
62 | );
63 | }
64 | };
65 |
66 | export default connect<{}, {}, IAccountsSettingsSectionProps>(
67 | (state: IState) => ({
68 | authentication : state.authentication,
69 | accounts : Object.keys(state.accounts)
70 | .map(id => state.accounts[id])
71 | })
72 | )(AccountsSettingsSection);
--------------------------------------------------------------------------------
/src/js/View/Settings/Sections/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { dispatch } from 'Helpers/State/Store';
4 | import { setColorMode } from 'Actions/Settings';
5 | import { colorModes } from 'Constants/Models/Settings';
6 |
7 | import { Toggle } from 'View/Ui/Index';
8 |
9 | interface IAppSettingsSectionProps
10 | {
11 | settings: IStateSettings;
12 | };
13 |
14 | class AppSettingsSection extends React.Component
15 | {
16 | handleColorModeChange(mode)
17 | {
18 | dispatch(setColorMode(mode));
19 | }
20 |
21 | render()
22 | {
23 | return (
24 |
25 |
26 |
27 |
38 |
39 |
40 | );
41 | }
42 | };
43 |
44 | export default AppSettingsSection;
--------------------------------------------------------------------------------
/src/js/View/Settings/Sections/Frequency.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { dispatch } from 'Helpers/State/Store';
4 | import { cronPeriodPrettyNames } from 'Constants/Lang/Date';
5 | import { configurePollPeriod } from 'Actions/UIActions/Settings';
6 |
7 | import { Button } from 'View/Ui/Index';
8 |
9 | interface IFrequencySettingsSectionProps
10 | {
11 | settings: IStateSettings;
12 | };
13 |
14 | class FrequencySettingsSection extends React.Component
15 | {
16 | render()
17 | {
18 | return (
19 |
20 |
21 |
24 | {Object.keys(cronPeriodPrettyNames)
25 | .map((name, i, a) =>
26 | (
27 |
45 | ))}
46 |
47 |
48 | );
49 | }
50 | };
51 |
52 | export default FrequencySettingsSection;
--------------------------------------------------------------------------------
/src/js/View/Settings/Sections/Sounds.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import {
4 | setNewItemsEnabled,
5 | setAlertErrorEnabled,
6 | setAlertSuccessEnabled
7 | } from 'Actions/Settings';
8 | import { dispatch } from 'Helpers/State/Store';
9 |
10 | import { Toggle } from 'View/Ui/Index';
11 |
12 | interface ISoundSettingsSectionProps
13 | {
14 | settings: IStateSettings;
15 | };
16 |
17 | class SoundSettingsSection extends React.Component
18 | {
19 | handleNewItemsEnabledChange(value: boolean)
20 | {
21 | dispatch(setNewItemsEnabled(value));
22 | }
23 |
24 | handleOtherSoundsChange(value: boolean)
25 | {
26 | dispatch(setAlertSuccessEnabled(value));
27 | dispatch(setAlertErrorEnabled(value));
28 | }
29 |
30 | render()
31 | {
32 | return (
33 |
34 |
35 |
36 |
47 |
48 |
49 |
50 |
62 |
63 |
64 | );
65 | }
66 | };
67 |
68 | export default SoundSettingsSection;
--------------------------------------------------------------------------------
/src/js/View/Ui/Anchor.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | interface IAnchorProps
4 | {
5 | className?: string;
6 |
7 | href?: string;
8 |
9 | preventDefault?: boolean;
10 |
11 | onClick?(): void;
12 |
13 | onContextMenu?(): void;
14 | };
15 |
16 | class Anchor extends React.Component
17 | {
18 | static defaultProps = {
19 | href : '#',
20 | preventDefault : true,
21 | onClick : () => {},
22 | onContextMenu : () => {}
23 | };
24 |
25 | handleClick(e)
26 | {
27 | if (this.props.preventDefault) {
28 | e.preventDefault();
29 | }
30 |
31 | this.props.onClick();
32 | }
33 |
34 | render()
35 | {
36 | return (
37 |
41 | {this.props.children}
42 |
43 | );
44 | }
45 | };
46 |
47 | export default Anchor;
--------------------------------------------------------------------------------
/src/js/View/Ui/AppLoading.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import {
4 | Loader,
5 | CenteredBox
6 | } from 'View/Ui/Index';
7 |
8 | interface IAppLoadingProps
9 | {
10 | show: boolean;
11 | };
12 |
13 | const AppLoading: React.SFC = ({ show }) =>
14 | (
15 |
19 |
20 |
22 |
23 |
24 | );
25 |
26 | export default AppLoading;
--------------------------------------------------------------------------------
/src/js/View/Ui/Btn.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { Anchor } from './Index';
4 |
5 | interface IBtnProps
6 | {
7 | className?: string;
8 |
9 | children?: any;
10 |
11 | onClick?(): void;
12 |
13 | onContextMenu?(): void;
14 | };
15 |
16 | const Btn: React.SFC = props =>
17 | (
18 |
24 | {props.children}
25 |
26 | );
27 |
28 | export default Btn;
--------------------------------------------------------------------------------
/src/js/View/Ui/BtnTo.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Link } from 'react-router';
3 |
4 | interface IBtnToProps
5 | {
6 | to: string;
7 |
8 | className?: string;
9 |
10 | children?: any;
11 | };
12 |
13 | const BtnTo: React.SFC = props =>
14 | (
15 |
20 | {props.children}
21 |
22 | );
23 |
24 | export default BtnTo;
--------------------------------------------------------------------------------
/src/js/View/Ui/Button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | interface IButtonProps
4 | {
5 | onClick(): void;
6 |
7 | className?: string;
8 |
9 | children?: any;
10 | };
11 |
12 | const Button: React.SFC = props =>
13 | (
14 |
21 | );
22 |
23 | export default Button;
--------------------------------------------------------------------------------
/src/js/View/Ui/CenteredBox.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | interface ICenteredBoxProps
4 | {
5 | children?: any;
6 |
7 | childClassName?: string;
8 | };
9 |
10 | const CenteredBox: React.SFC = props =>
11 | (
12 |
13 |
17 | {props.children}
18 |
19 |
20 | );
21 |
22 | export default CenteredBox;
--------------------------------------------------------------------------------
/src/js/View/Ui/Icon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | interface IIconProps
4 | {
5 | icon: string;
6 |
7 | className?: string;
8 | };
9 |
10 | const Icon: React.SFC = (props) =>
11 | (
12 |
17 | );
18 |
19 | export default Icon;
--------------------------------------------------------------------------------
/src/js/View/Ui/Index.ts:
--------------------------------------------------------------------------------
1 | import Btn from './Btn';
2 | import Icon from './Icon';
3 | import BtnTo from './BtnTo';
4 | import Scroll from './Scroll';
5 | import Anchor from './Anchor';
6 | import Button from './Button';
7 | import Loader from './Loader';
8 | import Toggle from './Toggle';
9 | import AppLoading from './AppLoading';
10 | import CenteredBox from './CenteredBox';
11 | import Notification from './Notification';
12 | import RoundedBtnSet from './RoundedBtnSet';
13 | import ProfilePicture from './ProfilePicture';
14 |
15 | export {
16 | Btn,
17 | Icon,
18 | BtnTo,
19 | Scroll,
20 | Anchor,
21 | Button,
22 | Loader,
23 | Toggle,
24 | AppLoading,
25 | CenteredBox,
26 | Notification,
27 | RoundedBtnSet,
28 | ProfilePicture
29 | };
--------------------------------------------------------------------------------
/src/js/View/Ui/Loader.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | interface ILoaderProps
4 | {
5 | size?: string;
6 |
7 | default?: boolean
8 | };
9 |
10 | const Loader: React.SFC = props =>
11 | (
12 |
19 | {'Loading'}
20 |
21 | );
22 |
23 | export default Loader;
--------------------------------------------------------------------------------
/src/js/View/Ui/ProfilePicture.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | interface IProfilePictureProps
4 | {
5 | picture: string;
6 | };
7 |
8 | const ProfilePicture: React.SFC = ({ picture }) =>
9 | (
10 |
12 | );
13 |
14 | export default ProfilePicture;
--------------------------------------------------------------------------------
/src/js/View/Ui/RoundedBtnSet.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import {
4 | isLastItem,
5 | hasMinItems,
6 | isFirstItem
7 | } from 'Helpers/Lang/Array';
8 |
9 | import { Btn } from './Index';
10 |
11 | interface IRoundedBtnSetProps
12 | {
13 | className?: string;
14 |
15 | buttons: IRoundedBtnSetBtn[];
16 | };
17 |
18 | interface IRoundedBtnSetBtn
19 | {
20 | key: string;
21 |
22 | text: string;
23 |
24 | className?: string;
25 |
26 | onClick?();
27 | };
28 |
29 | class RoundedBtnSet extends React.Component
30 | {
31 | render()
32 | {
33 | return (
34 |
35 | {this.props.buttons
36 | .map((btn, i, a) =>
37 | {
38 | let className = (btn.className || '')
39 | + (hasMinItems(a, 1)
40 | && isFirstItem(i)
41 | ? ' btn--hard-bottom'
42 | : '')
43 | + (hasMinItems(a, 1)
44 | && isLastItem(a, i)
45 | ? ' btn--hard-top'
46 | : '')
47 | + (hasMinItems(a, 1)
48 | && !isFirstItem(i)
49 | && !isLastItem(a, i)
50 | ? ' btn--hard'
51 | : '')
52 |
53 | return (
54 |
57 | {btn.text}
58 |
59 | );
60 | })}
61 |
62 | );
63 | }
64 | };
65 |
66 | export default RoundedBtnSet;
--------------------------------------------------------------------------------
/src/js/View/Ui/Scroll.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | interface IScrollProps
4 | {
5 | className?: string;
6 |
7 | children?: any;
8 | };
9 |
10 | const Scroll: React.SFC = props =>
11 | (
12 |
16 |
17 | {props.children}
18 |
19 |
20 | );
21 |
22 | export default Scroll;
--------------------------------------------------------------------------------
/src/js/View/Ui/Toggle.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | interface IToggleProps
4 | {
5 | value: any;
6 |
7 | options: IToggleOption[];
8 |
9 | className?: string;
10 |
11 | onChange?(value: any): void;
12 | };
13 |
14 | interface IToggleOption
15 | {
16 | index: number;
17 |
18 | text: string;
19 |
20 | value: any;
21 | };
22 |
23 | class Toggle extends React.Component
24 | {
25 | handleOptionClick(value, e)
26 | {
27 | e.preventDefault();
28 |
29 | this.props.onChange(value);
30 | }
31 |
32 | render()
33 | {
34 | return (
35 |
39 | {this.props.options
40 | .sort((a, b) => a.index - b.index)
41 | .map((option, i, a)=>
42 | (
43 |
56 | {option.text}
57 |
58 | ))}
59 |
60 | );
61 | }
62 | };
63 |
64 | export default Toggle;
--------------------------------------------------------------------------------
/src/scss/base/_fonts.scss:
--------------------------------------------------------------------------------
1 | //@import url(https://fonts.googleapis.com/css?family=Roboto:400,700,500,300);
2 | @import url('https://fonts.googleapis.com/css?family=Noto+Sans:400, 700');
3 |
4 | /**
5 | * Themify Icon Font
6 | */
7 | @font-face {
8 | font-family: 'themify';
9 | src:url('./src/fonts/themify/themify.eot?-fvbane');
10 | src:url('./src/fonts/themify/themify.eot?#iefix-fvbane') format('embedded-opentype'),
11 | url('./src/fonts/themify/themify.woff?-fvbane') format('woff'),
12 | url('./src/fonts/themify/themify.ttf?-fvbane') format('truetype'),
13 | url('./src/fonts/themify/themify.svg?-fvbane#themify') format('svg');
14 |
15 | font-weight: normal;
16 | font-style: normal;
17 | }
--------------------------------------------------------------------------------
/src/scss/base/_form.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Form Base
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Base form/input styles
6 | */
7 |
8 | input, textarea, button {
9 | resize: none;
10 | outline: none;
11 | @include border-radius(0);
12 | }
--------------------------------------------------------------------------------
/src/scss/base/_images.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Images
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Base image styles
6 | */
7 |
8 | img {
9 | display: block;
10 | }
--------------------------------------------------------------------------------
/src/scss/base/_lists.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Base Lists
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Base list styles and standardising
6 | */
7 |
8 | /*
9 | * Reset lists and make them consistent
10 | */
11 | ul, ol {
12 | margin-bottom: 0;
13 | padding-left: 1em;
14 | }
15 |
16 | ul {
17 | list-style-type: disc;
18 | }
19 |
20 | ol {
21 | list-style-type: decimal;
22 |
23 | ol {
24 | list-style-type: lower-alpha;
25 | }
26 | }
27 |
28 | /*
29 | * Clean List, no funny business
30 | */
31 | .clean-list {
32 | padding-left: 0;
33 | list-style-type: none;
34 | }
35 |
36 | /**
37 | * Inline List
38 | */
39 | .inline-list li {
40 | display: inline;
41 | }
--------------------------------------------------------------------------------
/src/scss/base/_page.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Page Base
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Base page styles
6 | */
7 |
8 | * { @include box-sizing(border-box); }
9 |
10 | html, body {
11 | height: 100%;
12 | min-height: 100%;
13 | }
14 |
15 | body, input, textarea, button {
16 | font: {
17 | size: $base-font-size;
18 | family: $base-body-font;
19 | weight: $body-font-weight;
20 | }
21 |
22 | -webkit-font-smoothing: antialiased;
23 | }
24 |
25 | body {
26 | min-width: $min-page-width;
27 | }
28 |
29 | /*
30 | * Disable text selection across the app
31 | */
32 | :not(input):not(textarea),
33 | :not(input):not(textarea)::after,
34 | :not(input):not(textarea)::before {
35 | -webkit-user-select: none;
36 | user-select: none;
37 | cursor: default;
38 | }
39 | input, button, textarea, :focus {
40 | outline: none; // You should add some other style for :focus to help UX/a11y
41 | }
42 |
43 | a:not([draggable=true]), img:not([draggable=true]) {
44 | -webkit-user-drag: none;
45 | user-drag: none; /* Technically not supported in Electron yet */
46 |
47 | i {
48 | cursor: default;
49 | }
50 | }
51 | a[href^="http://"],
52 | a[href^="https://"],
53 | a[href^="ftp://"] {
54 | -webkit-user-drag: auto;
55 | user-drag: auto; /* Technically not supported in Electron yet */
56 | }
--------------------------------------------------------------------------------
/src/scss/base/_typography.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Typography
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Typography base
6 | */
7 |
8 | /*
9 | * Paragraphs
10 | */
11 | p {
12 | line-height: $text-line-height;
13 | }
14 |
15 | /*
16 | * Anchors
17 | */
18 | a {
19 | font-size: inherit;
20 | text-decoration: none;
21 |
22 | &:hover {
23 | cursor: pointer;
24 | }
25 | }
26 |
27 | /*
28 | * Headings
29 | */
30 | @include headings() {
31 | font-weight: $header-font-weight;
32 | line-height: $header-line-height;
33 | }
34 |
35 | h1 { font-size: get-font-size(alpha); }
36 | h2 { font-size: get-font-size(beta); }
37 | h3 { font-size: get-font-size(gamma); }
38 | h4 { font-size: get-font-size(delta); }
39 | h5 { font-size: get-font-size(epsilon); }
40 | h6 { font-size: get-font-size(zeta); }
41 |
42 | /*
43 | * Labels
44 | */
45 | label {
46 | display: block;
47 | font: {
48 | size: $label-size;
49 | weight: $label-font-weight;
50 | };
51 | text-transform: $label-text-transform;
52 | }
--------------------------------------------------------------------------------
/src/scss/components/_app-alert.scss:
--------------------------------------------------------------------------------
1 | .app-alert-container {
2 | left: 0;
3 | top: -25px;
4 | width: 100%;
5 | height: 25px;
6 | z-index: 9999999;
7 | position: absolute;
8 | }
9 |
10 | .app-alert {
11 | left: 0;
12 | top: 0px;
13 | width: 100%;
14 | height: 25px;
15 | padding: 0 10px;
16 | position: absolute;
17 | @include transition(top .25s ease);
18 | }
19 |
20 | .app-alert__inner {
21 | width: 100%;
22 | height: 25px;
23 | text-align: center;
24 | @include border-radius(0 0 5px 5px);
25 |
26 | p {
27 | color: white;
28 | font-size: 13px;
29 | line-height: 25px;
30 | font-weight: bold;
31 | }
32 | }
33 |
34 | .app-alert--show {
35 | top: 25px;
36 | }
37 |
38 | .app-alert-pad {
39 | padding: 0 10px; //@hack
40 | }
41 |
42 | // Statuses
43 | .app-alert--success .app-alert__inner { background: $background-success; }
44 | .app-alert--warning .app-alert__inner { background: $background-warning; }
45 | .app-alert--error .app-alert__inner { background: $background-error; }
46 |
47 | /*
48 | * Actions
49 | */
50 | .app-alert__inner__action {
51 | float: left;
52 | width: 25px;
53 | height: 25px;
54 | display: block;
55 | color: white;
56 | font-size: 12px;
57 | line-height: 25px;
58 | text-align: center;
59 | @include transition(background .25s ease);
60 |
61 | i {
62 |
63 | }
64 |
65 | &:hover {
66 | background: trans-dark(.2);
67 | }
68 | }
69 |
70 | .app-alert__inner__action--last {
71 | @include border-radius(0 0 5px 0);
72 | }
--------------------------------------------------------------------------------
/src/scss/components/_app.scss:
--------------------------------------------------------------------------------
1 | .app {
2 | width: 100%;
3 | height: 100%;
4 | position: relative;
5 | @include transition(background .25s ease);
6 | }
7 |
8 | .app__loading {
9 | top: 0;
10 | left: 0;
11 | opacity: 0;
12 | width: 100%;
13 | height: 100%;
14 | position: absolute;
15 | z-index: 9999999999; // lol
16 | background: $app-loading-background;
17 | @include transition(opacity .25s ease);
18 | }
19 |
20 | .app__loading--show {
21 | opacity: 1;
22 | }
23 |
24 | .app--dark { background: $app-dark-background; }
25 | .app--light { background: $app-light-background; }
--------------------------------------------------------------------------------
/src/scss/components/_btn.scss:
--------------------------------------------------------------------------------
1 | .btn {
2 | border: 0;
3 | margin: 0;
4 | width: 100%;
5 | display: block;
6 | text-align: center;
7 |
8 | cursor: pointer!important;
9 | font-size: $button-font-size;
10 | padding: $button-padding;
11 | text-shadow: $button-text-shadow;
12 | text-transform: $button-text-transform;
13 | @include border-radius($button-border-radius);
14 | @include transition(all $button-transition-speed);
15 | }
16 |
17 | /*
18 | * Square Sizes
19 | */
20 | @each $size-name, $size in $btn-square-sizes {
21 | .btn--square-#{$size-name} {
22 | padding: 0;
23 | width: $size;
24 | height: $size;
25 | line-height: $size;
26 | }
27 | }
28 |
29 | /*
30 | * Backgrounds
31 | */
32 | @each $bg-name, $bgs in $btn-backgrounds {
33 | .btn--#{$bg-name} {
34 | background: nth($bgs, 1);
35 |
36 | &:hover {
37 | background: nth($bgs, 2);
38 | }
39 | }
40 | }
41 |
42 | .btn--transparent {
43 | background: transparent;
44 |
45 | &:hover {
46 | background: transparent;
47 | }
48 | }
49 |
50 | .btn--error {
51 | color: white;
52 | }
53 |
54 | /*
55 | * Pill
56 | */
57 | .btn--pill {
58 | @extend .truncate;
59 | padding: $btn-pill-padding;
60 | font-size: $btn-pill-font-size;
61 | text-align: $btn-pill-text-align;
62 | text-transform: $btn-pill-text-transform;
63 | }
64 |
65 | .btn--pill-has-count {
66 | position: relative;
67 | padding-right: 60px;
68 | }
69 |
70 | .btn--pill__count {
71 | top: 3px;
72 | right: 20px;
73 | width: 25px;
74 | height: 17px;
75 | display: block;
76 | font-size: 10px;
77 | line-height: 17px;
78 | text-align: center;
79 | position: absolute;
80 | @include border-radius(10px);
81 | }
82 |
83 | /*
84 | * Small Icon
85 | */
86 | .btn--small-icon {
87 | padding: 0;
88 | width: 16px;
89 | height: 16px;
90 | text-align: center;
91 | @include border-radius(3px);
92 |
93 | i {
94 | font-size: 8px;
95 | line-height: 16px;
96 | font-weight: bold;
97 | }
98 | }
99 |
100 | /*
101 | * Hard Corners
102 | */
103 | .btn--hard { @include border-radius(0); }
104 | .btn--hard-top { @include border-radius(0 0 $button-border-radius $button-border-radius); }
105 | .btn--hard-bottom { @include border-radius($button-border-radius $button-border-radius 0 0); }
106 | .btn--hard-left { @include border-radius($button-border-radius 0 0 $button-border-radius); }
107 | .btn--hard-right { @include border-radius(0 $button-border-radius $button-border-radius 0); }
108 |
109 | /*
110 | * Mods
111 | */
112 | .btn .profile-picture {
113 | @include border-radius($button-border-radius - 1px);
114 | }
--------------------------------------------------------------------------------
/src/scss/components/_loader.scss:
--------------------------------------------------------------------------------
1 | .loader,
2 | .loader:after {
3 | width: 40px;
4 | height: 40px;
5 | @include border-radius(50%);
6 | }
7 | .loader {
8 | margin: 0 auto;
9 | font-size: 10px;
10 | position: relative;
11 | text-indent: -9999em;
12 |
13 | border-top-color: rgba(255, 255, 255, 0.2);
14 | border-right-color: rgba(255, 255, 255, 0.2);
15 | border-bottom-color: rgba(255, 255, 255, 0.2);
16 | border-left-color: white;
17 |
18 | border-top-width: 5px;
19 | border-right-width: 5px;
20 | border-bottom-width: 5px;
21 | border-left-width: 5px;
22 |
23 | border-top-style: solid;
24 | border-right-style: solid;
25 | border-bottom-style: solid;
26 | border-left-style: solid;
27 |
28 | -webkit-transform: translateZ(0);
29 | -ms-transform: translateZ(0);
30 | transform: translateZ(0);
31 | -webkit-animation: load8 1.1s infinite linear;
32 | animation: load8 1.1s infinite linear;
33 | }
34 |
35 | @-webkit-keyframes load8 {
36 | 0% {
37 | -webkit-transform: rotate(0deg);
38 | transform: rotate(0deg);
39 | }
40 | 100% {
41 | -webkit-transform: rotate(360deg);
42 | transform: rotate(360deg);
43 | }
44 | }
45 | @keyframes load8 {
46 | 0% {
47 | -webkit-transform: rotate(0deg);
48 | transform: rotate(0deg);
49 | }
50 | 100% {
51 | -webkit-transform: rotate(360deg);
52 | transform: rotate(360deg);
53 | }
54 | }
55 |
56 | @each $size-name, $size in $loader-sizes {
57 | .loader--#{$size-name},
58 | .loader--#{$size-name}:after {
59 | width: $size;
60 | height: $size;
61 | }
62 | }
63 |
64 | .loader--small {
65 | border-top-width: 2px;
66 | border-right-width: 2px;
67 | border-bottom-width: 2px;
68 | border-left-width: 2px;
69 | }
70 |
71 | .loader--default {
72 | border-top-color: rgba(255, 255, 255, 0.2)!important;
73 | border-right-color: rgba(255, 255, 255, 0.2)!important;
74 | border-bottom-color: rgba(255, 255, 255, 0.2)!important;
75 | border-left-color: white!important;
76 | }
--------------------------------------------------------------------------------
/src/scss/components/_no-outline.scss:
--------------------------------------------------------------------------------
1 | .no-outline * {
2 | outline: none!important;
3 | }
--------------------------------------------------------------------------------
/src/scss/components/_notification.scss:
--------------------------------------------------------------------------------
1 |
2 | .notification {
3 | width: 100%;
4 | height: 100%;
5 | position: relative;
6 | @include transition(background .25s ease);
7 | }
8 |
9 | .notification__text {
10 | display: block;
11 | display: -webkit-box;
12 |
13 | max-height: 14 * 1.5 * 2px;
14 | font-size: 14px;
15 | line-height: 1.3em;
16 | margin-bottom: 5px;
17 | -webkit-line-clamp: 2;
18 | -webkit-box-orient: vertical;
19 | overflow: hidden;
20 | text-overflow: ellipsis;
21 | }
22 |
23 | .notification-check {
24 | @include transition(color .25s ease);
25 | }
--------------------------------------------------------------------------------
/src/scss/components/_profile-picture.scss:
--------------------------------------------------------------------------------
1 | .profile-picture {
2 | @include border-radius(5px);
3 | }
--------------------------------------------------------------------------------
/src/scss/components/_toggle.scss:
--------------------------------------------------------------------------------
1 | .toggle {
2 | width: 100%;
3 | display: inline-block;
4 | @include border-radius($toggle-border-radius);
5 | }
6 |
7 | .toggle__option {
8 | width: 50%;
9 | text-align: center;
10 | font: {
11 | size: $toggle-option-font-size;
12 | weight: $toggle-option-font-weight;
13 | }
14 | display: inline-block;
15 | padding: $toggle-option-padding;
16 | text-transform: $toggle-option-text-transform;
17 | @include transition(all .25s ease);
18 | }
19 |
20 | .toggle__option--first {
21 | @include border-radius($toggle-border-radius 0 0 $toggle-border-radius);
22 | }
23 |
24 | .toggle__option--last {
25 | @include border-radius(0 $toggle-border-radius $toggle-border-radius 0);
26 | }
--------------------------------------------------------------------------------
/src/scss/components/_view-bar.scss:
--------------------------------------------------------------------------------
1 | //@todo: Variable this up
2 | .view-bar {
3 | width: 100%;
4 | height: 100%;
5 | position: relative;
6 | @extend .app-drag;
7 | }
8 |
9 | .view-bar__title {
10 | font-size: 14px;
11 | line-height: 40px;
12 | text-align: center;
13 |
14 | }
15 |
16 | .view-bar__back-link {
17 | top: 0;
18 | left: 0;
19 | width: 35px;
20 | height: 40px;
21 | text-align: right;
22 | position: absolute;
23 |
24 | i {
25 | width: 35px;
26 | height: 40px;
27 | font-size: 18px;
28 | line-height: 40px;
29 | }
30 | }
31 |
32 | .view-bar__left-content {
33 | top: 0;
34 | left: 0;
35 | width: 40px;
36 | height: 40px;
37 | position: absolute;
38 | }
39 |
40 | .view-bar__right-content {
41 | top: 0;
42 | right: 0;
43 | width: 40px;
44 | height: 40px;
45 | position: absolute;
46 | }
--------------------------------------------------------------------------------
/src/scss/directives/_breakpoints.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Breakpoint Directives
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Methods and helpers for working
6 | * with our defined breakpoints.
7 | */
8 |
9 | @mixin breakpoint($breakpoint-name) {
10 |
11 | @if $breakpoint-name == mobile {
12 | @media (max-width: $mobile-max-breakpoint) {
13 | @content
14 | }
15 | }
16 |
17 | @else if $breakpoint-name == tablet {
18 | @media (min-width: $tablet-min-breakpoint) and (max-width: $tablet-max-breakpoint) {
19 | @content;
20 | }
21 | }
22 |
23 | @else if $breakpoint-name == desktop {
24 | @media (min-width: $desktop-min-breakpoint) {
25 | @content;
26 | }
27 | }
28 |
29 | @else if $breakpoint-name == max {
30 | @media (min-width: $max-min-breakpoint) {
31 | @content;
32 | }
33 | }
34 |
35 | }
36 |
37 | @function prefix-breakpoint-class($class-name) {
38 | @return $class-name + $breakpoint-separator;
39 | };
--------------------------------------------------------------------------------
/src/scss/directives/_font-sizes.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Font Size Directives
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Methods to associate with our
6 | * font-size values.
7 | */
8 |
9 | /*
10 | * get-font-size
11 | *
12 | * Retrive the font size for a font size name
13 | */
14 | @function get-font-size($size-name: 'alpha') {
15 | @return map-get($font-sizes, $size-name);
16 | }
--------------------------------------------------------------------------------
/src/scss/directives/_grid.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Grid Directives
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Helpers for grid methods
6 | */
7 |
8 | @function get-grid-gutter($gutter-name: default) {
9 | @return map-get($grid-gutters, $gutter-name);
10 | };
11 |
12 | @mixin silent-relative() {
13 | @if $grid-use-silent == true {
14 | position: relative;
15 | };
16 | };
--------------------------------------------------------------------------------
/src/scss/directives/_headings.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Headings Directives
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Headings directives
6 | */
7 |
8 | /**
9 | * headings-each
10 | *
11 | * Duplicate a block of code, for headings
12 | * from x to y.
13 | */
14 | @mixin headings-each($from: 1, $to: 6){
15 | @for $i from $from through $to{
16 | h#{$i}{
17 | @content;
18 | };
19 | };
20 | };
21 |
22 | /**
23 | * headings
24 | */
25 | @mixin headings() {
26 | h1, h2, h3,
27 | h4, h5, h6 {
28 | @content;
29 | }
30 | }
31 |
32 | @mixin heading($i) {
33 | h#{$i} {
34 | @content;
35 | }
36 | }
--------------------------------------------------------------------------------
/src/scss/directives/_keyframes.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Keyframes Directives
3 | * Jan 2106 - Andrew Hathaway
4 | *
5 | * Directives to help with keyframes
6 | */
7 |
8 | /**
9 | * Keyframes mixin
10 | *
11 | * Usage: @include keyframes([name], { CONTENT });
12 | */
13 | @mixin keyframes($name) {
14 | @-webkit-keyframes #{$name} {
15 | @content;
16 | }
17 |
18 | @-moz-keyframes #{$name} {
19 | @content;
20 | }
21 |
22 | @-ms-keyframes #{$name} {
23 | @content;
24 | }
25 |
26 | @-o-keyframes #{$name} {
27 | @content;
28 | }
29 |
30 | @keyframes #{$name} {
31 | @content;
32 | }
33 | }
--------------------------------------------------------------------------------
/src/scss/directives/_pushes.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Push Directives
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Methods associated with our values
6 | * for pushes.
7 | */
8 |
9 | /**
10 | * get-push-value
11 | *
12 | * Retreive the push value for a spacingName
13 | */
14 | @function get-push-value($spacing-name: 'alpha') {
15 | @return map-get($base-push-values, $spacing-name);
16 | }
--------------------------------------------------------------------------------
/src/scss/directives/_softs.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Soft Directives
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Methods associated with our values
6 | * for soft values.
7 | */
8 |
9 | /**
10 | * get-soft-value
11 | *
12 | * Retreive the sot value for a spacingName
13 | */
14 | @function get-soft-value($spacing-name: 'alpha') {
15 | @return map-get($base-soft-values, $spacing-name);
16 | }
--------------------------------------------------------------------------------
/src/scss/directives/_transparents.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Transparent Directives
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Methods to help with transparancies
6 | */
7 |
8 | /**
9 | * trans-dark
10 | *
11 | * Simple method to help with dark overlays etc
12 | */
13 | @function trans-dark($percentage: 0.5) {
14 | @return rgba(0, 0, 0, $percentage);
15 | }
16 |
17 | /**
18 | * trans-light
19 | *
20 | * Simple method to help with light overlays etc
21 | */
22 | @function trans-light($percentage: 0.5) {
23 | @return rgba(255, 255, 255, $percentage);
24 | }
--------------------------------------------------------------------------------
/src/scss/generic/_app-drag.scss:
--------------------------------------------------------------------------------
1 | .app-drag {
2 | -ms-overflow-style: scrollbar;
3 | -webkit-app-region:drag;
4 | }
5 |
6 | .app-drag--none {
7 | -webkit-app-region: no-drag;
8 | }
--------------------------------------------------------------------------------
/src/scss/generic/_backgrounds.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Backgrounds
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Background helpers
6 | */
7 |
8 | @mixin generate-backgrounds($pre-class-name: null) {
9 | .#{$pre-class-name}bg--none { background: transparent!important; }
10 | .#{$pre-class-name}bg--white { background: white!important; }
11 | .#{$pre-class-name}bg--black { background: black!important; }
12 |
13 | .#{$pre-class-name}bg--success { background: $background-success!important; }
14 | .#{$pre-class-name}bg--error { background: $background-error! important; }
15 | .#{$pre-class-name}bg--warning { background: $background-warning!important; }
16 |
17 | @each $name, $color in $backgrounds {
18 | .#{$pre-class-name}bg--#{$name} { background: $color!important; }
19 | }
20 | }
21 |
22 | /*
23 | * Generate defafult backgrounds
24 | */
25 | @include generate-backgrounds();
26 |
27 | /*
28 | * Generate backgrounds for any
29 | * breakpoints in the variables
30 | */
31 | @each $breakpoint-name in $breakpoint-backgrounds {
32 | @include breakpoint($breakpoint-name) {
33 | @include generate-backgrounds(prefix-breakpoint-class($breakpoint-name));
34 | }
35 | };
--------------------------------------------------------------------------------
/src/scss/generic/_borders.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Borders
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Border helpers
6 | */
7 |
8 | @mixin generate-borders($pre-class-name: null) {
9 | .#{$pre-class-name}border-solid { border-style: solid!important; }
10 | .#{$pre-class-name}border-dashed { border-style: dashed!important; }
11 |
12 | .#{$pre-class-name}border-single { border-width: 1px!important; }
13 |
14 | .#{$pre-class-name}border-top-single { border-top-width: 1px!important; }
15 | .#{$pre-class-name}border-bottom-single { border-bottom-width: 1px!important; }
16 |
17 | .#{$pre-class-name}border-mid-grey { border-color: #D6D6D6!important; }
18 | .#{$pre-class-name}border-light-grey { border-color: #EEE!important; }
19 | .#{$pre-class-name}border-black { border-color: black!important; }
20 | }
21 |
22 | /*
23 | * Generate defafult borders
24 | */
25 | @include generate-borders();
26 |
27 | /*
28 | * Generate borders for any
29 | * breakpoints in the variables
30 | */
31 | @each $breakpoint-name in $breakpoint-borders {
32 | @include breakpoint($breakpoint-name) {
33 | @include generate-borders(prefix-breakpoint-class($breakpoint-name));
34 | }
35 | };
--------------------------------------------------------------------------------
/src/scss/generic/_clearfix.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Clearfix
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * The clear fix object. Extend this
6 | * instead of having it appear in markup
7 | * majority of the time.
8 | */
9 |
10 | .clear {
11 |
12 | &:after {
13 | content: '';
14 | display: table;
15 | clear: both;
16 | }
17 |
18 | }
--------------------------------------------------------------------------------
/src/scss/generic/_displays.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Displays
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Display helpers
6 | */
7 |
8 | @mixin generate-displays($pre-class-name: null) {
9 | .#{$pre-class-name}display--hidden { display: none!important; }
10 | .#{$pre-class-name}display--block { display: block!important; }
11 | .#{$pre-class-name}display--inline { display: inline!important; }
12 | .#{$pre-class-name}display--inline-block { display: inline-block!important; }
13 | .#{$pre-class-name}display--table { display: table!important; }
14 | .#{$pre-class-name}display--table-cell { display: table-cell!important; }
15 | }
16 |
17 | /*
18 | * Generate default displays
19 | */
20 | @include generate-displays();
21 |
22 | /*
23 | * Generate displays for any breakpoints
24 | * setup in the variables.
25 | */
26 | @each $breakpoint-name in $breakpoint-displays {
27 | @include breakpoint($breakpoint-name) {
28 | @include generate-displays(prefix-breakpoint-class($breakpoint-name));
29 | };
30 | };
--------------------------------------------------------------------------------
/src/scss/generic/_floats.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Floats
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Float helpers
6 | */
7 |
8 | @mixin generate-floats($pre-class-name: null) {
9 | .#{$pre-class-name}float--left { float: left!important; }
10 | .#{$pre-class-name}float--right { float: right!important; }
11 | .#{$pre-class-name}float--none { float: none!important; }
12 | }
13 |
14 | /*
15 | * Generate default floats
16 | */
17 | @include generate-floats();
18 |
19 | /*
20 | * Generate floats for any breakpoints
21 | * setup in the variables.
22 | */
23 | @each $breakpoint-name in $breakpoint-floats {
24 | @include breakpoint($breakpoint-name) {
25 | @include generate-floats(prefix-breakpoint-class($breakpoint-name));
26 | }
27 | }
--------------------------------------------------------------------------------
/src/scss/generic/_hards.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Hards
3 | * March 2016 - Andrew Hathaway
4 | *
5 | * Generate hard (padding) rules for values.
6 | */
7 | @mixin generate-hard-rules($class-pre-name) {
8 | .#{$class-pre-name}hard { padding: 0!important; }
9 | .#{$class-pre-name}hard--top { padding-top: 0!important; }
10 | .#{$class-pre-name}hard--bottom { padding-bottom: 0!important; }
11 | .#{$class-pre-name}hard--left { padding-left: 0!important; }
12 | .#{$class-pre-name}hard--right { padding-right: 0!important; }
13 | .#{$class-pre-name}hard--ends { padding-top: 0!important;
14 | padding-bottom: 0!important; }
15 | .#{$class-pre-name}hard--sides { padding-left: 0!important;
16 | padding-right: 0!important; }
17 | };
18 |
19 | /*
20 | * Generate the standard hards
21 | */
22 | @include generate-hard-rules(null);
23 |
24 | /*
25 | * For each breakpoint, generate the hards.
26 | */
27 | @each $breakpoint-name in $breakpoint-hards {
28 | @include breakpoint($breakpoint-name) {
29 | @include generate-hard-rules(prefix-breakpoint-class($breakpoint-name));
30 | }
31 | };
--------------------------------------------------------------------------------
/src/scss/generic/_heights.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Heights
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Height helpers
6 | */
7 |
8 | @mixin generate-heights($pre-class-name: null) {
9 | /*
10 | * None-fractional heights
11 | */
12 |
13 | .#{$pre-class-name}height--full { height: 100%!important; }
14 | .#{$pre-class-name}height--auto { height: auto!important; }
15 |
16 | .#{$pre-class-name}height--20 { height: 20px!important; }
17 | .#{$pre-class-name}height--30 { height: 30px!important; }
18 | .#{$pre-class-name}height--40 { height: 40px!important; }
19 | .#{$pre-class-name}height--80 { height: 80px!important; }
20 | .#{$pre-class-name}height--90 { height: 90px!important; }
21 | .#{$pre-class-name}height--100 { height: 100px!important; }
22 | .#{$pre-class-name}height--300 { height: 300px!important; }
23 | .#{$pre-class-name}height--340 { height: 340px!important; }
24 | .#{$pre-class-name}height--400 { height: 400px!important; }
25 | }
26 |
27 | /*
28 | * Generate defafult heights
29 | */
30 | @include generate-heights();
31 |
32 | /*
33 | * Generate heights for any
34 | * breakpoints in the variables
35 | */
36 | @each $breakpoint-name in $breakpoint-heights {
37 | @include breakpoint($breakpoint-name) {
38 | @include generate-heights(prefix-breakpoint-class($breakpoint-name));
39 | }
40 | };
--------------------------------------------------------------------------------
/src/scss/generic/_highers.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * Highers
3 | * March 2016 - Andrew Hathaway
4 | *
5 | * Generate z-index helpers
6 | */
7 | .higher { z-index: $higher; }
8 |
9 | @mixin generate-higher-rule($higher-name: 'alpha', $sizing-value: $higher) {
10 | .higher--#{$higher-name} { z-index: $sizing-value; }
11 | };
12 |
13 | @each $sizing-name, $sizing-value in $higher-sizes {
14 | @include generate-higher-rule($sizing-name, $sizing-value);
15 | };
--------------------------------------------------------------------------------
/src/scss/generic/_line-heights.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Line Heights
3 | * May 2016 - Andrew Hathaway
4 | *
5 | * Line Heights
6 | */
7 |
8 | @mixin generate-line-heights($pre-class-name: null) {
9 | .#{$pre-class-name}line-height--none { line-height: 1em!important; }
10 | .#{$pre-class-name}line-height--1-3 { line-height: 1.3em!important; }
11 |
12 | .#{$pre-class-name}line-height--20 { line-height: 20px!important; }
13 | .#{$pre-class-name}line-height--25 { line-height: 25px!important; }
14 | .#{$pre-class-name}line-height--30 { line-height: 30px!important; }
15 | .#{$pre-class-name}line-height--40 { line-height: 40px!important; }
16 | }
17 |
18 | @include generate-line-heights();
19 |
20 | @each $breakpoint-name in $breakpoint-line-heights {
21 | @include breakpoint($breakpoint-name) {
22 | @include generate-line-heights(prefix-breakpoint-class($breakpoint-name));
23 | }
24 | };
--------------------------------------------------------------------------------
/src/scss/generic/_max-widths.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Max Widths
3 | * Mar 2016 - Andrew Hathaway
4 | *
5 | * Max-Width helpers
6 | */
7 |
8 | @mixin generate-max-widths($pre-class-name: null) {
9 | /*
10 | * None-fractional max-widths
11 | */
12 | .#{$pre-class-name}max-width--200 { max-width: 200px!important; }
13 | .#{$pre-class-name}max-width--240 { max-width: 240px!important; }
14 | .#{$pre-class-name}max-width--290 { max-width: 290px!important; }
15 | .#{$pre-class-name}max-width--450 { max-width: 450px!important; }
16 | .#{$pre-class-name}max-width--540 { max-width: 540px!important; }
17 |
18 | .#{$pre-class-name}max-width--35p { max-width: 35%!important; }
19 | .#{$pre-class-name}max-width--50p { max-width: 50%!important; }
20 | .#{$pre-class-name}max-width--75p { max-width: 75%!important; }
21 | }
22 |
23 | /*
24 | * Generate defafult max-widths
25 | */
26 | @include generate-max-widths();
27 |
28 | /*
29 | * Generate max-widths for any
30 | * breakpoints in the variables
31 | */
32 | @each $breakpoint-name in $breakpoint-max-widths {
33 | @include breakpoint($breakpoint-name) {
34 | @include generate-max-widths(prefix-breakpoint-class($breakpoint-name));
35 | }
36 | };
--------------------------------------------------------------------------------
/src/scss/generic/_min-heights.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Min Height
3 | * Mar 2016 - Andrew Hathaway
4 | *
5 | * Min-Height helpers
6 | */
7 |
8 | @mixin generate-min-heights($pre-class-name: null) {
9 | /*
10 | * None-fractional min-heights
11 | */
12 | .#{$pre-class-name}min-height--full { min-height: 100%!important; }
13 | }
14 |
15 | /*
16 | * Generate defafult min-heights
17 | */
18 | @include generate-min-heights();
19 |
20 | /*
21 | * Generate min-heights for any
22 | * breakpoints in the variables
23 | */
24 | @each $breakpoint-name in $breakpoint-min-heights {
25 | @include breakpoint($breakpoint-name) {
26 | @include generate-min-heights(prefix-breakpoint-class($breakpoint-name));
27 | }
28 | };
--------------------------------------------------------------------------------
/src/scss/generic/_n-tops.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Negative Tops
3 | *
4 | */
5 |
6 | @mixin generate-n-tops($pre-class-name: null) {
7 | @each $name, $size in $n-top-sizes {
8 | .#{$pre-class-name}n-top--#{$name} { top: $size!important; }
9 | }
10 | };
11 |
12 | @include generate-n-tops();
13 |
14 | @each $breakpoint-name in $breakpoint-n-tops {
15 | @include breakpoint($breakpoint-name) {
16 | @include generate-n-tops(prefix-breakpoint-class($breakpoint-name));
17 | }
18 | }
--------------------------------------------------------------------------------
/src/scss/generic/_overflows.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Overflows
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Overflow helpers
6 | */
7 |
8 | @mixin generate-overflows($pre-class-name: null) {
9 | .#{$pre-class-name}overflow--visible { overflow: visible!important; }
10 | .#{$pre-class-name}overflow--hidden { overflow: hidden!important; }
11 | .#{$pre-class-name}overflow--scroll { overflow: scroll!important; }
12 | .#{$pre-class-name}overflow--xscroll { overflow-x: scroll!important; }
13 | .#{$pre-class-name}overflow--yscroll { overflow-y: scroll!important; }
14 | }
15 |
16 | /*
17 | * Generate default overflows
18 | */
19 | @include generate-overflows();
20 |
21 | /*
22 | * Generate overflows for any breakpoints
23 | * setup in the variables
24 | */
25 | @each $breakpoint-name in $breakpoint-overflows {
26 | @include breakpoint($breakpoint-name) {
27 | @include generate-overflows(prefix-breakpoint-class($breakpoint-name));
28 | };
29 | };
--------------------------------------------------------------------------------
/src/scss/generic/_positionings.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Positionings
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Generate position rules
6 | */
7 |
8 | @mixin generate-positionings($class-pre-name: null) {
9 | .#{$class-pre-name}position--static { position: static!important; }
10 | .#{$class-pre-name}position--fixed { position: fixed!important; }
11 | .#{$class-pre-name}position--relative { position: relative!important; }
12 | .#{$class-pre-name}position--absolute { position: absolute!important; }
13 | }
14 |
15 | /*
16 | * Generate default positionings
17 | */
18 | @include generate-positionings();
19 |
20 | /*
21 | * Generate positionings for any breakpoints
22 | * setup in the variables
23 | */
24 | @each $breakpoint-name in $breakpoint-positionings {
25 | @include breakpoint($breakpoint-name) {
26 | @include generate-positionings(prefix-breakpoint-class($breakpoint-name));
27 | }
28 | }
--------------------------------------------------------------------------------
/src/scss/generic/_pushes.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Pushes
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Generate push rules for required values
6 | */
7 |
8 | @mixin generate-push-rules($class-pre-name: null, $spacing-name: 'alpha') {
9 | .#{$class-pre-name}push-#{$spacing-name} { margin: get-push-value($spacing-name)!important; }
10 | .#{$class-pre-name}push-#{$spacing-name}--top { margin-top: get-push-value($spacing-name)!important; }
11 | .#{$class-pre-name}push-#{$spacing-name}--bottom { margin-bottom: get-push-value($spacing-name)!important; }
12 | .#{$class-pre-name}push-#{$spacing-name}--left { margin-left: get-push-value($spacing-name)!important; }
13 | .#{$class-pre-name}push-#{$spacing-name}--right { margin-right: get-push-value($spacing-name)!important; }
14 | .#{$class-pre-name}push-#{$spacing-name}--ends { margin-top: get-push-value($spacing-name)!important;
15 | margin-bottom: get-push-value($spacing-name)!important; }
16 | .#{$class-pre-name}push-#{$spacing-name}--sides { margin-left: get-push-value($spacing-name)!important;
17 | margin-right: get-push-value($spacing-name)!important; }
18 | }
19 |
20 | @mixin generate-push-autos($class-pre-name: null) {
21 | .#{$class-pre-name}push-auto--sides {
22 | margin-left: auto!important;
23 | margin-right: auto!important;
24 | }
25 | }
26 |
27 | /*
28 | * Generate a set of push rules for the
29 | * defined push-values.
30 | */
31 | @each $spacing-name, $spacing-value in $base-push-values {
32 | @include generate-push-rules(null, $spacing-name);
33 | }
34 |
35 | /*
36 | * Generate default push-auto rules
37 | */
38 | @include generate-push-autos();
39 |
40 | /*
41 | * Generate a set of push rules for the breakpoints
42 | * defined in each sets list. Wrap them in a breakpoint
43 | * and prefix the class with the breakpoint name and -.
44 | */
45 | @each $spacing-name, $breakpoint-list in $breakpoint-pushes {
46 | @each $breakpoint-name in $breakpoint-list {
47 | @include breakpoint($breakpoint-name) {
48 | @include generate-push-rules(prefix-breakpoint-class($breakpoint-name), $spacing-name);
49 | };
50 | };
51 | };
52 |
53 | /*
54 | * Generate the set of push autos for
55 | * breakpoints defined in the variables.
56 | */
57 | @each $breakpoint-name in $breakpoint-push-autos {
58 | @include breakpoint($breakpoint-name) {
59 | @include generate-push-autos(prefix-breakpoint-class($breakpoint-name));
60 | };
61 | };
--------------------------------------------------------------------------------
/src/scss/generic/_round.scss:
--------------------------------------------------------------------------------
1 | .round { @include border-radius($default-border-radius); }
--------------------------------------------------------------------------------
/src/scss/generic/_softs.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Softs
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Generate soft (padding) rules for
6 | * required values
7 | */
8 | @mixin generate-soft-rules($class-pre-name, $spacing-name: 'alpha') {
9 | .#{$class-pre-name}soft-#{$spacing-name} { padding: get-soft-value($spacing-name)!important; }
10 | .#{$class-pre-name}soft-#{$spacing-name}--top { padding-top: get-soft-value($spacing-name)!important; }
11 | .#{$class-pre-name}soft-#{$spacing-name}--bottom { padding-bottom: get-soft-value($spacing-name)!important; }
12 | .#{$class-pre-name}soft-#{$spacing-name}--left { padding-left: get-soft-value($spacing-name)!important; }
13 | .#{$class-pre-name}soft-#{$spacing-name}--right { padding-right: get-soft-value($spacing-name)!important; }
14 | .#{$class-pre-name}soft-#{$spacing-name}--ends { padding-top: get-soft-value($spacing-name)!important;
15 | padding-bottom: get-soft-value($spacing-name)!important; }
16 | .#{$class-pre-name}soft-#{$spacing-name}--sides { padding-left: get-soft-value($spacing-name)!important;
17 | padding-right: get-soft-value($spacing-name)!important; }
18 | }
19 |
20 | /*
21 | * Generate a set of soft rules for the
22 | * defined soft-values.
23 | */
24 | @each $spacing-name, $spacing-value in $base-soft-values {
25 | @include generate-soft-rules(null, $spacing-name);
26 | };
27 |
28 | /*
29 | * Generate a set of soft rules for the breakpoints
30 | * defined in each sets list. Wrap them in a breakpoint
31 | * and prefix the class with the breakpoint name and -.
32 | */
33 | @each $spacing-name, $breakpoint-list in $breakpoint-softs {
34 | @each $breakpoint-name in $breakpoint-list {
35 | @include breakpoint($breakpoint-name) {
36 | @include generate-soft-rules(prefix-breakpoint-class($breakpoint-name), $spacing-name);
37 | };
38 | };
39 | };
--------------------------------------------------------------------------------
/src/scss/generic/_text-aligns.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Text Aligns
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Text align helpers
6 | */
7 |
8 | @mixin generate-text-aligns($pre-class-name: null) {
9 | .#{$pre-class-name}text--center { text-align: center!important; }
10 | .#{$pre-class-name}text--left { text-align: left!important; }
11 | .#{$pre-class-name}text--right { text-align: right!important; }
12 | }
13 |
14 | /*
15 | * Generate default text aligns
16 | */
17 | @include generate-text-aligns();
18 |
19 | /*
20 | * Generate text aligns for any
21 | * breakpoints in the variables
22 | */
23 | @each $breakpoint-name in $breakpoint-text-aligns {
24 | @include breakpoint($breakpoint-name) {
25 | @include generate-text-aligns(prefix-breakpoint-class($breakpoint-name));
26 | };
27 | };
--------------------------------------------------------------------------------
/src/scss/generic/_text-colors.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Text Colors
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Text color helpers
6 | */
7 |
8 | @mixin generate-text-colors($pre-class-name: null) {
9 | .#{$pre-class-name}text--white { color: white!important; }
10 | .#{$pre-class-name}text--error { color: $background-error!important; }
11 | }
12 |
13 | /*
14 | * Generate default text aligns
15 | */
16 | @include generate-text-colors();
17 |
18 | /*
19 | * Generate text colors for any
20 | * breakpoints in the variables
21 | */
22 | @each $breakpoint-name in $breakpoint-text-colors {
23 | @include breakpoint($breakpoint-name) {
24 | @include generate-text-colors(prefix-breakpoint-class($breakpoint-name));
25 | };
26 | };
--------------------------------------------------------------------------------
/src/scss/generic/_text-emboss.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Text Embossings
3 | * May 2016 - Andrew Hathaway
4 | *
5 | * Text Embossings
6 | */
7 | @mixin generate-text-embossings($pre-class-name: null) {
8 | .#{$pre-class-name}text--emboss { text-shadow: 0 1px rgba(0, 0, 0, .4); }
9 | }
10 |
11 | @include generate-text-embossings();
12 |
13 | @each $breakpoint-name in $breakpoint-text-embossings {
14 | @include breakpoint($breakpoint-name) {
15 | @include generate-text-embossings(prefix-breakpoint-class($breakpoint-name));
16 | }
17 | };
--------------------------------------------------------------------------------
/src/scss/generic/_text-fonts.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Text Fonts
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Text font helpers
6 | */
7 |
8 | @mixin generate-text-fonts($pre-class-name: null) {
9 | .#{$pre-class-name}text--primary { font-family: $primary-font!important; }
10 | }
11 |
12 | /*
13 | * Generate default text fonts
14 | */
15 | @include generate-text-fonts();
16 |
17 | /*
18 | * Generate text fonts for any
19 | * breakpoints in the variables
20 | */
21 | @each $breakpoint-name in $breakpoint-text-fonts {
22 | @include breakpoint($breakpoint-name) {
23 | @include generate-text-fonts(prefix-breakpoint-class($breakpoint-name));
24 | };
25 | };
--------------------------------------------------------------------------------
/src/scss/generic/_text-sizes.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Text Sizes
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Text size helpers
6 | */
7 |
8 | @mixin generate-text-sizes($pre-class-name: null) {
9 | @each $size-name, $font-size in $font-sizes {
10 | .#{$pre-class-name}text--#{$size-name} { font-size: $font-size!important; }
11 | };
12 | };
13 |
14 | /*
15 | * Generate the default text sizes
16 | */
17 | @include generate-text-sizes();
18 |
19 | /*
20 | * Generate a set of text size rules for the breakpoints
21 | * defined in each sets list. Wrap them in a breakpoint
22 | * and prefix them with the class and the breakpoint name.
23 | */
24 | @each $space-name, $breakpoint-list in $breakpoint-font-sizes {
25 | @each $breakpoint-name in $breakpoint-list {
26 | @include breakpoint($breakpoint-name) {
27 | @include generate-text-sizes(prefix-breakpoint-class($breakpoint-name));
28 | };
29 | };
30 | };
--------------------------------------------------------------------------------
/src/scss/generic/_text-transforms.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Text Transforms
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Text font helpers
6 | */
7 |
8 | @mixin generate-text-transforms($pre-class-name: null) {
9 | .#{$pre-class-name}text--no-transform { text-transform: none!important; }
10 | .#{$pre-class-name}text--uppercase { text-transform: uppercase!important; }
11 | .#{$pre-class-name}text--lowercase { text-transform: lowercase!important; }
12 | .#{$pre-class-name}text--capitalize { text-transform: capitalize!important; }
13 | }
14 |
15 | /*
16 | * Generate default text transforms
17 | */
18 | @include generate-text-transforms();
19 |
20 | /*
21 | * Generate text transforms for any
22 | * breakpoints in the variables
23 | */
24 | @each $breakpoint-name in $breakpoint-text-transforms {
25 | @include breakpoint($breakpoint-name) {
26 | @include generate-text-transforms(prefix-breakpoint-class($breakpoint-name));
27 | };
28 | };
--------------------------------------------------------------------------------
/src/scss/generic/_text-weights.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Text Weights
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Text weight helpers
6 | */
7 |
8 | @mixin generate-text-weights($pre-class-name: null) {
9 | .#{$pre-class-name}text--bold { font-weight: bold!important; }
10 | .#{$pre-class-name}text--normal { font-weight: normal!important; }
11 | }
12 |
13 | /*
14 | * Generate default text weights
15 | */
16 | @include generate-text-weights();
17 |
18 | /*
19 | * Generate text weights for any
20 | * breakpoints in the variables
21 | */
22 | @each $breakpoint-name in $breakpoint-text-weights {
23 | @include breakpoint($breakpoint-name) {
24 | @include generate-text-weights(prefix-breakpoint-class($breakpoint-name));
25 | };
26 | };
--------------------------------------------------------------------------------
/src/scss/generic/_tops.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Negative Tops
3 | *
4 | */
5 |
6 | @mixin generate-tops($pre-class-name: null) {
7 | @each $name, $size in $top-sizes {
8 | .#{$pre-class-name}top--#{$name} { top: $size!important; }
9 | }
10 | };
11 |
12 | @include generate-tops();
13 |
14 | @each $breakpoint-name in $breakpoint-tops {
15 | @include breakpoint($breakpoint-name) {
16 | @include generate-tops(prefix-breakpoint-class($breakpoint-name));
17 | }
18 | }
--------------------------------------------------------------------------------
/src/scss/objects/_cover-height.scss:
--------------------------------------------------------------------------------
1 | .cover-height {
2 | width: 100%;
3 | overflow: hidden;
4 | max-height: 120px;
5 | @include transition(max-height .25s ease);
6 | }
7 |
8 | .cover-height--130 {
9 | max-height: 130px;
10 | }
11 |
12 | .cover-height--show {
13 | max-height: 9000px!important;
14 | }
15 |
16 | .cover-height--to-470.cover-height--show {
17 | max-height: 470px!important;
18 | }
--------------------------------------------------------------------------------
/src/scss/objects/_dialog.scss:
--------------------------------------------------------------------------------
1 |
2 | .dialog {
3 | width: 100%;
4 | height: 100%;
5 | display: table;
6 | }
7 |
8 | .dialog__cell {
9 | text-align: center;
10 | display: table-cell;
11 | vertical-align: middle;
12 | }
--------------------------------------------------------------------------------
/src/scss/objects/_dropdown.scss:
--------------------------------------------------------------------------------
1 | .dropdown {
2 | position: relative;
3 | }
4 |
5 | .dropdown__menu {
6 | top: 100%;
7 | right: 0;
8 | display: none;
9 | position: absolute;
10 | }
11 |
12 | .dropdown--open .dropdown__menu {
13 | display: block;
14 | }
15 |
16 | .dropdown__menu--center {
17 | left: 50%;
18 | right: auto;
19 | }
--------------------------------------------------------------------------------
/src/scss/objects/_fixed-overlay.scss:
--------------------------------------------------------------------------------
1 |
2 | .fixed-overlay {
3 | width: 100%;
4 | height: 100%;
5 | position: fixed;
6 | z-index: 99999999999999;
7 | }
--------------------------------------------------------------------------------
/src/scss/objects/_flexi-side.scss:
--------------------------------------------------------------------------------
1 | .flexi-side {
2 | width: 100%;
3 | display: table;
4 | position: relative;
5 | }
6 |
7 | .flexi-side__side,
8 | .flexi-side__content {
9 | display: table-cell;
10 | vertical-align: middle;
11 | }
12 |
13 | .flexi-side__side {
14 | height: 100%;
15 | }
16 |
17 | .flexi-side__content {
18 | width: 100%;
19 | }
--------------------------------------------------------------------------------
/src/scss/objects/_grid.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Grid System
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * Awesome grid system. Split out
6 | * from CSSWizardry Grids.
7 | */
8 |
9 | .grid {
10 | list-style: none;
11 | margin: 0;
12 | padding: 0;
13 | margin-left: get-grid-gutter() * -1;
14 | }
15 |
16 | @if $grid-use-markup-fix != true {
17 | /* Opera hack */
18 | .opera:-o-prefocus,
19 | .grid{
20 | word-spacing:-0.43em;
21 | }
22 | }
23 |
24 | .grid__item {
25 | display: inline-block;
26 | padding-left: get-grid-gutter();
27 | vertical-align: top;
28 |
29 | @if $grid-mobile-first == true {
30 | width: 100%;
31 | }
32 |
33 | @if $grid-use-markup-fix != true {
34 | letter-spacing: normal;
35 | word-spacing: normal;
36 | }
37 | }
38 |
39 | /**
40 | * Modifiers
41 | */
42 |
43 | /*
44 | * Reversed grids allow you to structure your source in the opposite order to
45 | * how your rendered layout will appear. Extends `.grid`.
46 | */
47 | .grid--rev {
48 | direction: rtl;
49 | text-align: left;
50 |
51 | > .grid__item {
52 | direction: ltr;
53 | text-align: left;
54 | }
55 | }
56 |
57 | /*
58 | * Gutterless grids have all the properties of regular grids, minus any spacing.
59 | * Extends `.grid`.
60 | */
61 | .grid--full {
62 | margin-left: 0;
63 |
64 | > .grid__item {
65 | padding-left: 0;
66 | }
67 | }
68 |
69 | /*
70 | * Align the entire grid to the right. Extends `.grid`.
71 | */
72 | .grid--right {
73 | text-align: right;
74 |
75 | > .grid__item {
76 | text-align: left;
77 | }
78 | }
79 |
80 | /*
81 | * Centered grids align grid items centrally without needing to use push or pull
82 | * classes. Extends `.grid`.
83 | */
84 | .grid--center {
85 | text-align: center;
86 |
87 | > .grid__item {
88 | text-align: left;
89 | }
90 | }
91 |
92 | /*
93 | * Align grid cells vertically (`.grid--middle` or `.grid--bottom`). Extends
94 | * `.grid`.
95 | */
96 | .grid--middle {
97 | > .grid__item {
98 | vertical-align: middle;
99 | }
100 | }
101 |
102 | .grid--bottom {
103 | > .grid__item {
104 | vertical-align: bottom;
105 | }
106 | }
107 |
108 | /*
109 | * Generate gutter methods for any
110 | * others than the default.
111 | *
112 | * SIDENOTE: WHY CANNOT SASS JUST GET @CONTINUE
113 | */
114 | @each $gutter-name, $gutter-value in $grid-gutters {
115 | @if $gutter-name != default {
116 | .grid--#{$gutter-name} {
117 | margin-left: -$gutter-value;
118 |
119 | > .grid__item {
120 | padding-left: $gutter-value;
121 | }
122 | }
123 | }
124 | }
--------------------------------------------------------------------------------
/src/scss/objects/_hard-bottom.scss:
--------------------------------------------------------------------------------
1 | .hard-bottom {
2 | width: 100%;
3 | height: 100%;
4 | position: relative; //@todo: Mixin these rules? fullRelative()
5 | }
6 |
7 | .hard-bottom__bottom {
8 | bottom: 0;
9 | width: 100%;
10 | position: absolute;
11 | height: $hard-bottom-width;
12 | }
13 |
14 | .hard-bottom__content {
15 | width: 100%;
16 | height: 100%;
17 | padding-bottom: $hard-bottom-width;
18 | }
19 |
20 | @mixin generate-hard-bottoms($post-class-name: null) {
21 | .hard-bottom--#{$post-class-name} {
22 | > .hard-bottom__bottom { height: map-get($hard-bottom-sizes, $post-class-name); }
23 | > .hard-bottom__content { padding-bottom: map-get($hard-bottom-sizes, $post-class-name); }
24 | }
25 | };
26 |
27 | @each $hard-name, $hard-size in $hard-bottom-sizes {
28 | @include generate-hard-bottoms($hard-name);
29 | }
--------------------------------------------------------------------------------
/src/scss/objects/_hard-left.scss:
--------------------------------------------------------------------------------
1 | .hard-left {
2 | width: 100%;
3 | height: 100%;
4 | overflow: hidden;
5 | position: relative; //@todo: Mixin these rules? fullRelative()
6 | }
7 |
8 | .hard-left__left {
9 | top: 0;
10 | left: 0;
11 | bottom: 0;
12 | height: 100%;
13 | position: absolute;
14 | width: $hard-left-width;
15 | @include transition(left .25s ease-in-out);
16 | }
17 |
18 | .hard-left__content {
19 | width: 100%;
20 | height: 100%;
21 | padding-left: $hard-left-width;
22 | @include transition(padding-left .25s ease-in-out);
23 | }
24 |
25 | .hard-left--pull {
26 | > .hard-left__left { left: -$hard-left-width; }
27 | > .hard-left__content { padding-left: 0; }
28 | }
29 |
30 | @mixin generate-hards($post-class-name: null) {
31 | .hard-left--#{$post-class-name} {
32 | > .hard-left__left { width: map-get($hard-left-sizes, $post-class-name); }
33 | > .hard-left__content { padding-left: map-get($hard-left-sizes, $post-class-name)!important; }
34 |
35 | &.hard-left--pull {
36 | > .hard-left__left { left: -map-get($hard-left-sizes, $post-class-name); }
37 | > .hard-left__content { padding-left: 0; }
38 | }
39 | }
40 | };
41 |
42 | @each $hard-name, $hard-size in $hard-left-sizes {
43 | @include generate-hards($hard-name);
44 | }
--------------------------------------------------------------------------------
/src/scss/objects/_hard-right.scss:
--------------------------------------------------------------------------------
1 | .hard-right {
2 | width: 100%;
3 | height: 100%;
4 | overflow: hidden;
5 | position: relative; //@todo: Mixin these rules? fullRelative()
6 | }
7 |
8 | .hard-right--allow-overflow {
9 | overflow: visible;
10 | }
11 |
12 | .hard-right__right {
13 | top: 0;
14 | right: 0;
15 | bottom: 0;
16 | height: 100%;
17 | position: absolute;
18 | width: $hard-right-width;
19 | @include transition(right .25s ease-in-out);
20 | }
21 |
22 | .hard-right__content {
23 | width: 100%;
24 | height: 100%;
25 | padding-right: $hard-right-width;
26 | @include transition(padding-right .25s ease-in-out);
27 | }
28 |
29 | .hard-right--pull {
30 | overflow: hidden;
31 |
32 | > .hard-right__right { right: -$hard-right-width; }
33 | > .hard-right__content { padding-right: 0; }
34 | }
35 |
36 | @mixin generate-hards($post-class-name: null) {
37 | .hard-right--#{$post-class-name} {
38 | > .hard-right__right { width: map-get($hard-right-sizes, $post-class-name); }
39 | > .hard-right__content { padding-right: map-get($hard-right-sizes, $post-class-name)!important; }
40 |
41 | &.hard-right--pull {
42 | > .hard-right__right { right: map-get($hard-right-sizes, $post-class-name); }
43 | > .hard-right__content { padding-right: 0; }
44 | }
45 | }
46 | };
47 |
48 | @each $hard-name, $hard-size in $hard-right-sizes {
49 | @include generate-hards($hard-name);
50 | }
--------------------------------------------------------------------------------
/src/scss/objects/_hard-top.scss:
--------------------------------------------------------------------------------
1 | .hard-top {
2 | width: 100%;
3 | height: 100%;
4 | position: relative; //@todo: Mixin these rules? fullRelative()
5 | }
6 |
7 | .hard-top__top {
8 | top: 0;
9 | top: 0;
10 | bottom: 0;
11 | z-index: 9;
12 | width: 100%;
13 | position: absolute;
14 | height: $hard-top-width;
15 | }
16 |
17 | .hard-top__content {
18 | width: 100%;
19 | height: 100%;
20 | padding-top: $hard-top-width;
21 | }
22 |
23 | @mixin generate-hard-tops($post-class-name: null) {
24 | .hard-top--#{$post-class-name} {
25 | > .hard-top__top { height: map-get($hard-top-sizes, $post-class-name); }
26 | > .hard-top__content { padding-top: map-get($hard-top-sizes, $post-class-name); }
27 | }
28 | };
29 |
30 | @each $hard-name, $hard-size in $hard-top-sizes {
31 | @include generate-hard-tops($hard-name);
32 | }
33 |
34 | .hard-top--disable {
35 | .hard-top__top { display: none!important; }
36 | .hard-top__content { padding-top: 0px!important; }
37 | }
--------------------------------------------------------------------------------
/src/scss/objects/_hideable-left.scss:
--------------------------------------------------------------------------------
1 |
2 | .hideable-left {
3 | width: 100%;
4 | height: 100%;
5 | position: relative;
6 | }
7 |
8 | .hideable-left__left {
9 | top: 0;
10 | left: 0;
11 | bottom: 0;
12 | height: 100%;
13 | width: 200px;
14 | z-index: 9999;
15 | position: absolute;
16 | }
17 |
18 | .hideable-left__content {
19 | width: 100%;
20 | height: 100%;
21 | position: relative;
22 | padding-left: 200px;
23 | };
24 |
25 | @include breakpoint(mobile) {
26 | .hideable-left__content {
27 | padding-left: 0;
28 | }
29 | .hideable-left__left {
30 | display: none;
31 | }
32 | }
--------------------------------------------------------------------------------
/src/scss/objects/_img-scale.scss:
--------------------------------------------------------------------------------
1 | .img-scale {
2 | width: 100%;
3 | height: auto;
4 | }
--------------------------------------------------------------------------------
/src/scss/objects/_loading-overlay.scss:
--------------------------------------------------------------------------------
1 | .loading-overlay {
2 | position: relative;
3 | }
4 |
5 | .loading-overlay__overlay {
6 | top: 0;
7 | left: 0;
8 | z-index: 2;
9 | width: 100%;
10 | height: 100%;
11 | position: absolute;
12 | }
--------------------------------------------------------------------------------
/src/scss/objects/_media.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Media Object
3 | * Jan 2016 - Andrew Hathaway
4 | *
5 | * The media object:
6 | * http://www.stubbornella.org/content/2010/06/25/the-media-object-saves-hundreds-of-lines-of-code/
7 | */
8 |
9 | .media {
10 | @extend .clear;
11 | @extend .display--block;
12 | }
13 |
14 | .media__img {
15 | float: left;
16 | margin-right: 20px; // @todo: replace
17 | }
18 |
19 | .media--right .media__img {
20 | float: right;
21 | margin-left: 20px; // @todo: replace
22 | margin-right: 0;
23 | }
24 |
25 | .media__body {
26 | overflow: hidden;
27 |
28 | &, & > :last-child {
29 | margin-bottom: 0;
30 | }
31 | }
--------------------------------------------------------------------------------
/src/scss/objects/_truncate.scss:
--------------------------------------------------------------------------------
1 | .truncate {
2 | width: 100%;
3 | white-space: nowrap;
4 | overflow: hidden;
5 | text-overflow: ellipsis;
6 | }
--------------------------------------------------------------------------------
/src/scss/objects/_vscroll.scss:
--------------------------------------------------------------------------------
1 | .vscroll {
2 | width: 100%;
3 | height: 100%;
4 | overflow-y: auto;
5 | position: relative;
6 | }
7 |
8 | .vscroll__content {
9 | top: 0;
10 | left: 0;
11 | right: 0;
12 | bottom: 0;
13 | width: 100%;
14 | height: 100%;
15 | position: absolute;
16 | }
--------------------------------------------------------------------------------
/src/scss/vendors/_virtualized.scss:
--------------------------------------------------------------------------------
1 | /* Collection default theme */
2 |
3 | .ReactVirtualized__Collection {
4 | }
5 |
6 | .ReactVirtualized__Collection__innerScrollContainer {
7 | }
8 |
9 | /* Grid default theme */
10 |
11 | .ReactVirtualized__Grid {
12 | }
13 |
14 | .ReactVirtualized__Grid__innerScrollContainer {
15 | }
16 |
17 | /* Table default theme */
18 |
19 | .ReactVirtualized__Table {
20 | }
21 |
22 | .ReactVirtualized__Table__Grid {
23 | }
24 |
25 | .ReactVirtualized__Table__headerRow {
26 | font-weight: 700;
27 | text-transform: uppercase;
28 | display: flex;
29 | flex-direction: row;
30 | align-items: center;
31 | }
32 | .ReactVirtualized__Table__row {
33 | display: flex;
34 | flex-direction: row;
35 | align-items: center;
36 | }
37 |
38 | .ReactVirtualized__Table__headerTruncatedText {
39 | display: inline-block;
40 | max-width: 100%;
41 | white-space: nowrap;
42 | text-overflow: ellipsis;
43 | overflow: hidden;
44 | }
45 |
46 | .ReactVirtualized__Table__headerColumn,
47 | .ReactVirtualized__Table__rowColumn {
48 | margin-right: 10px;
49 | min-width: 0px;
50 | }
51 | .ReactVirtualized__Table__rowColumn {
52 | text-overflow: ellipsis;
53 | white-space: nowrap;
54 | }
55 |
56 | .ReactVirtualized__Table__headerColumn:first-of-type,
57 | .ReactVirtualized__Table__rowColumn:first-of-type {
58 | margin-left: 10px;
59 | }
60 | .ReactVirtualized__Table__sortableHeaderColumn {
61 | cursor: pointer;
62 | }
63 |
64 | .ReactVirtualized__Table__sortableHeaderIconContainer {
65 | display: flex;
66 | align-items: center;
67 | }
68 | .ReactVirtualized__Table__sortableHeaderIcon {
69 | flex: 0 0 24px;
70 | height: 1em;
71 | width: 1em;
72 | fill: currentColor;
73 | }
74 |
75 | /* List default theme */
76 |
77 | .ReactVirtualized__List {
78 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions" : {
3 | "target" : "ES5",
4 | "sourceMap" : true,
5 | "module" : "commonjs",
6 | "jsx" : "react",
7 | "baseUrl" : "./src/js"
8 | },
9 | "exclude" : [
10 | "node_modules"
11 | ]
12 | }
--------------------------------------------------------------------------------
/tsconfig.main.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions" : {
3 | "target" : "ES5",
4 | "sourceMap" : true,
5 | "module" : "commonjs",
6 | "moduleResolution" : "node",
7 | "baseUrl" : "./src/js"
8 | },
9 | "exclude" : [
10 | "node_modules"
11 | ]
12 | }
--------------------------------------------------------------------------------
/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hawkeye",
3 | "dependencies": {
4 | "form-data": "registry:npm/form-data#1.0.0+20160812072516",
5 | "lodash": "registry:npm/lodash#4.0.0+20161015015725",
6 | "redux-persist": "registry:npm/redux-persist#3.2.2+20160817092816",
7 | "request": "registry:dt/request#0.0.0+20161128184003"
8 | },
9 | "globalDependencies": {
10 | "async": "registry:dt/async#2.0.1+20161211174247",
11 | "electron": "registry:dt/electron#1.4.8+20161220141501",
12 | "electron-window-state": "registry:dt/electron-window-state#2.0.0+20161102134001",
13 | "es2015-symbol": "registry:env/es2015-symbol#1.0.0+20160526151700",
14 | "es6-shim": "registry:dt/es6-shim#0.31.2+20160726072212",
15 | "history": "registry:dt/history#1.8.0+20160316155526",
16 | "moment": "registry:dt/moment#2.11.1+20161010105546",
17 | "node": "registry:dt/node#6.0.0+20161121110008",
18 | "node-schedule": "registry:dt/node-schedule#1.1.0+20161119044246",
19 | "object-assign": "registry:dt/object-assign#4.0.1+20161006140353",
20 | "query-string": "registry:dt/query-string#3.0.0+20161129181732",
21 | "react": "registry:dt/react#0.14.0+20161203201537",
22 | "react-dom": "registry:dt/react-dom#0.14.0+20160412154040",
23 | "react-redux": "registry:dt/react-redux#4.4.0+20160908183346",
24 | "react-router": "registry:dt/react-router#2.0.0+20161117134353",
25 | "react-router-redux": "registry:dt/react-router-redux#4.0.0+20160930121107",
26 | "react-router/history": "registry:dt/react-router/history#2.0.0+20160830150755",
27 | "react-virtualized": "registry:dt/react-virtualized#0.0.0+20161117143210",
28 | "redux": "registry:dt/redux#3.5.2+20160703092728",
29 | "redux-thunk": "registry:dt/redux-thunk#2.1.0+20160703120921",
30 | "whatwg-fetch": "registry:dt/whatwg-fetch#0.0.0+20161120230953",
31 | "whatwg-streams": "registry:dt/whatwg-streams#0.0.0+20161214010628"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 |
4 | module.exports = {
5 | entry : './src/js/App.ts',
6 | output : {
7 | path : path.resolve('./'),
8 | filename : 'app.min.js'
9 | },
10 | resolve : {
11 | root : [
12 | path.resolve('./node_modules'),
13 | path.resolve('./src/js')
14 | ],
15 | extensions : [
16 | '',
17 | '.webpack.js',
18 | '.web.js',
19 | '.ts',
20 | '.js',
21 | '.tsx'
22 | ]
23 | },
24 | module : {
25 | loaders : [{
26 | test : /\.tsx?$/,
27 | loader : 'ts-loader?sourceMap=false&configFileName=tsconfig.json'
28 | }, {
29 | test : /\.css$/,
30 | loader : 'style!css'
31 | }, {
32 | test : /\.(otf|eot|svg|ttf|woff|woff2).*$/,
33 | loader : 'url?limit=8192'
34 | }]
35 | },
36 | plugins : [
37 | new webpack.ProvidePlugin({
38 | 'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch'
39 | }),
40 | new webpack.optimize.UglifyJsPlugin({
41 | minimize : true,
42 | output : {
43 | comments : false
44 | },
45 | compress : {
46 | warnings: false
47 | }
48 | }),
49 | new webpack.DefinePlugin({
50 | 'process.env.NODE_ENV': '"production"'
51 | })
52 | ]
53 | };
--------------------------------------------------------------------------------
/webpack.dev.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 |
4 | module.exports = {
5 | entry : './src/js/App.ts',
6 | output : {
7 | path : path.resolve('./'),
8 | filename : 'app.js'
9 | },
10 | resolve : {
11 | root : [
12 | path.resolve('./node_modules'),
13 | path.resolve('./src/js')
14 | ],
15 | extensions : [
16 | '',
17 | '.webpack.js',
18 | '.web.js',
19 | '.ts',
20 | '.js',
21 | '.tsx'
22 | ]
23 | },
24 | module : {
25 | loaders : [{
26 | test : /\.tsx?$/,
27 | loader : 'ts-loader?sourceMap=false&configFileName=tsconfig.json'
28 | }, {
29 | test : /\.css$/,
30 | loader : 'style!css'
31 | }, {
32 | test : /\.(otf|eot|svg|ttf|woff|woff2).*$/,
33 | loader : 'url?limit=8192'
34 | }]
35 | },
36 | plugins : [
37 | new webpack.ProvidePlugin({
38 | 'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch'
39 | }),
40 | new webpack.DefinePlugin({
41 | 'process.env.NODE_ENV': '"development"'
42 | })
43 | ]
44 | };
--------------------------------------------------------------------------------