diff --git a/.travis.yml b/.travis.yml index 0ff1f46fec..ff4e63f2ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,7 @@ before_script: script: - ng lint - if [[ "$TRAVIS_OS_NAME" = "linux" ]]; then karma start karma.conf.js --single-run; fi -- ng e2e +- if [[ "$TRAVIS_OS_NAME" = "linux" ]]; then ng e2e before_deploy: - if [ ! -z "$TRAVIS_TAG" ]; then npm run build-electron; fi deploy: diff --git a/src/app/actions/layout/layout.ts b/src/app/actions/layout/layout.ts index a008df8fff..8836174c86 100644 --- a/src/app/actions/layout/layout.ts +++ b/src/app/actions/layout/layout.ts @@ -29,4 +29,8 @@ export class NotifyExperimentalAction implements Action { constructor(public windowId: string) {} } -export type Action = StartLoadingAction | StopLoadingAction | SetWindowNameAction | NotifyExperimentalAction; +export type Action = + | StartLoadingAction + | StopLoadingAction + | SetWindowNameAction + | NotifyExperimentalAction; diff --git a/src/app/actions/settings/settings.ts b/src/app/actions/settings/settings.ts new file mode 100644 index 0000000000..b5ad2bca7c --- /dev/null +++ b/src/app/actions/settings/settings.ts @@ -0,0 +1,33 @@ +import { Action } from '@ngrx/store'; + +export const SHOW_SETTINGS = 'SHOW_SETTINGS'; +export const HIDE_SETTINGS = 'HIDE_SETTINGS'; + +export const SET_THEME = 'SET_THEME'; +export const SET_LANGUAGE = 'SET_LANGUAGE'; + +export class ShowSettingsAction implements Action { + readonly type = SHOW_SETTINGS; +} + +export class HideSettingsAction implements Action { + readonly type = HIDE_SETTINGS; +} + +export class SetThemeAction implements Action { + readonly type = SET_THEME; + + constructor(public payload: { value: string }) {} +} + +export class SetLanguageAction implements Action { + readonly type = SET_LANGUAGE; + + constructor(public payload: { value: string }) { } +} + +export type Action = + | ShowSettingsAction + | HideSettingsAction + | SetThemeAction + | SetLanguageAction; diff --git a/src/app/components/index.ts b/src/app/components/index.ts index a4a4a878aa..18bcefa69d 100644 --- a/src/app/components/index.ts +++ b/src/app/components/index.ts @@ -19,30 +19,32 @@ import { WindowSwitcherComponent } from './window-switcher/window-switcher.compo import { SubscriptionUrlDialogComponent } from './subscription-url-dialog/subscription-url-dialog.component'; import { SubscriptionResultItemComponent } from './subscription-result-item/subscription-result-item.component'; import { HistoryDialogComponent } from './history-dialog/history-dialog.component'; +import { SettingsDialogComponent } from './settings-dialog/settings-dialog.component'; const COMPONENTS = [ - QueryEditorComponent, - QueryResultComponent, - ActionBarComponent, - SetVariableDialogComponent, - ForkRepoComponent, - WindowSwitcherComponent, - SubscriptionUrlDialogComponent, - SubscriptionResultItemComponent, - UrlBoxComponent, - HistoryDialogComponent + QueryEditorComponent, + QueryResultComponent, + ActionBarComponent, + SetVariableDialogComponent, + ForkRepoComponent, + WindowSwitcherComponent, + SubscriptionUrlDialogComponent, + SubscriptionResultItemComponent, + UrlBoxComponent, + HistoryDialogComponent, + SettingsDialogComponent ]; @NgModule({ - imports: [ - CommonModule, - FormsModule, - CodemirrorModule, - PipesModule, - SharedModule, - ClarityModule - ], - declarations: COMPONENTS, - exports: [...COMPONENTS ] + imports: [ + CommonModule, + FormsModule, + CodemirrorModule, + PipesModule, + SharedModule, + ClarityModule + ], + declarations: COMPONENTS, + exports: [...COMPONENTS ] }) export class ComponentModule {} diff --git a/src/app/components/settings-dialog/settings-dialog.component.html b/src/app/components/settings-dialog/settings-dialog.component.html new file mode 100644 index 0000000000..8d792c8637 --- /dev/null +++ b/src/app/components/settings-dialog/settings-dialog.component.html @@ -0,0 +1,34 @@ + + + + + diff --git a/src/app/components/settings-dialog/settings-dialog.component.scss b/src/app/components/settings-dialog/settings-dialog.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/components/settings-dialog/settings-dialog.component.spec.ts b/src/app/components/settings-dialog/settings-dialog.component.spec.ts new file mode 100644 index 0000000000..dc1058076a --- /dev/null +++ b/src/app/components/settings-dialog/settings-dialog.component.spec.ts @@ -0,0 +1,36 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FormsModule } from '@angular/forms'; +import { ClarityModule } from 'clarity-angular'; +import { CodemirrorModule } from 'ng2-codemirror'; +import { TranslateModule } from '@ngx-translate/core'; + +import { SettingsDialogComponent } from './settings-dialog.component'; + +describe('SettingsDialogComponent', () => { + let component: SettingsDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SettingsDialogComponent ], + imports: [ + FormsModule, + CodemirrorModule, + ClarityModule.forRoot(), + TranslateModule.forRoot() + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SettingsDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/settings-dialog/settings-dialog.component.ts b/src/app/components/settings-dialog/settings-dialog.component.ts new file mode 100644 index 0000000000..9b3950f32a --- /dev/null +++ b/src/app/components/settings-dialog/settings-dialog.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +import config from '../../config'; + +@Component({ + selector: 'app-settings-dialog', + templateUrl: './settings-dialog.component.html', + styleUrls: ['./settings-dialog.component.scss'] +}) +export class SettingsDialogComponent implements OnInit { + + @Input() settings = {}; + @Output() toggleDialogChange = new EventEmitter(); + @Output() themeChange = new EventEmitter(); + @Output() languageChange = new EventEmitter(); + + themes = config.themes; + languages = Object.entries(config.languages); + + constructor() {} + + ngOnInit() { + } + + onSelectTheme(theme) { + return this.themeChange.next(theme); + } + + onSelectLanguage(language) { + return this.languageChange.next(language); + } + +} diff --git a/src/app/config.ts b/src/app/config.ts index 2f53d827b8..cf5e47df05 100644 --- a/src/app/config.ts +++ b/src/app/config.ts @@ -5,6 +5,7 @@ export default { add_query_depth_limit: 3, max_windows: 10, default_language: 'en', - languages: ['en'], - query_history_depth: isElectron ? 25 : 7 + languages: { en: 'English' }, + query_history_depth: isElectron ? 25 : 7, + themes: ['light', 'dark'] }; diff --git a/src/app/containers/app/app.component.html b/src/app/containers/app/app.component.html index 0665061ceb..c147c91dd2 100644 --- a/src/app/containers/app/app.component.html +++ b/src/app/containers/app/app.component.html @@ -1,4 +1,4 @@ -
+
@@ -24,14 +24,14 @@ (activeWindowChange)="setActiveWindow($event)" (removeWindowChange)="removeWindow($event)" (windowNameChange)="setWindowName($event)"> -
+
- + @@ -40,7 +40,15 @@ +
+ +
- +
diff --git a/src/app/containers/app/app.component.ts b/src/app/containers/app/app.component.ts index dad4227174..ebcdf31724 100644 --- a/src/app/containers/app/app.component.ts +++ b/src/app/containers/app/app.component.ts @@ -9,6 +9,7 @@ import isElectron from '../../utils/is_electron'; import * as fromRoot from '../../reducers'; import * as fromHeader from '../../reducers/headers/headers'; import * as fromVariable from '../../reducers/variables/variables'; +import * as fromSettings from '../../reducers/settings/settings'; import * as queryActions from '../../actions/query/query'; import * as headerActions from '../../actions/headers/headers'; @@ -18,6 +19,7 @@ import * as layoutActions from '../../actions/layout/layout'; import * as docsActions from '../../actions/docs/docs'; import * as windowsActions from '../../actions/windows/windows'; import * as windowsMetaActions from '../../actions/windows-meta/windows-meta'; +import * as settingsActions from '../../actions/settings/settings'; import { QueryService } from '../../services/query.service'; import { GqlService } from '../../services/gql.service'; @@ -30,7 +32,9 @@ import config from '../../config'; templateUrl: './app.component.html', }) export class AppComponent { - windowIds$: Observable; + windowIds$: Observable; + settings$: Observable; + windowIds = []; windowsArr = []; activeWindowId = ''; @@ -43,6 +47,8 @@ export class AppComponent { private translate: TranslateService, private electron: ElectronService ) { + this.settings$ = this.store.select('settings'); + this.setDefaultLanguage(); this.setAvailableLanguages(); @@ -89,7 +95,7 @@ export class AppComponent { } setAvailableLanguages(): void { - const availableLanguages = config.languages; + const availableLanguages = Object.keys(config.languages); this.translate.addLangs(availableLanguages); } @@ -123,6 +129,22 @@ export class AppComponent { this.store.dispatch(new layoutActions.SetWindowNameAction(windowId, windowName)); } + showSettingsDialog() { + this.store.dispatch(new settingsActions.ShowSettingsAction()); + } + + hideSettingsDialog() { + this.store.dispatch(new settingsActions.HideSettingsAction()); + } + + onThemeChange(theme) { + this.store.dispatch(new settingsActions.SetThemeAction({ value: theme })); + } + + onLanguageChange(language) { + this.store.dispatch(new settingsActions.SetLanguageAction({ value: language })); + } + /** * Transform an object into an array * @param obj diff --git a/src/app/reducers/index.ts b/src/app/reducers/index.ts index bf4f643ebf..11aea2a981 100644 --- a/src/app/reducers/index.ts +++ b/src/app/reducers/index.ts @@ -15,6 +15,7 @@ import * as fromDocs from './docs/docs'; import * as fromWindows from './windows'; import * as fromHistory from './history/history'; import * as fromWindowsMeta from './windows-meta/windows-meta'; +import * as fromSettings from './settings/settings'; export interface PerWindowState { layout: fromLayout.State; @@ -42,6 +43,7 @@ const perWindowReducers = { export interface State { windows: fromWindows.State; windowsMeta: fromWindowsMeta.State; + settings: fromSettings.State; } // Meta reducer to log actions @@ -55,14 +57,15 @@ export const log = (_reducer) => (state: State, action: Action) => { export const keySerializer = (key) => 'altair_' + key; export const metaReducers: MetaReducer[] = [ - localStorageSync({ keys: ['windows', 'windowsMeta'], rehydrate: true, storageKeySerializer: keySerializer }), + localStorageSync({ keys: ['windows', 'windowsMeta', 'settings'], rehydrate: true, storageKeySerializer: keySerializer }), // !environment.production ? storeFreeze : null, log ]; export const reducer: ActionReducerMap = { windows: fromWindows.windows(combineReducers(perWindowReducers)), - windowsMeta: fromWindowsMeta.windowsMetaReducer + windowsMeta: fromWindowsMeta.windowsMetaReducer, + settings: fromSettings.settingsReducer }; export const selectWindowState = (windowId: string) => (state: State) => state.windows[windowId]; diff --git a/src/app/reducers/layout/layout.ts b/src/app/reducers/layout/layout.ts index cd248781dd..93ebd4c0bf 100644 --- a/src/app/reducers/layout/layout.ts +++ b/src/app/reducers/layout/layout.ts @@ -3,24 +3,24 @@ import { Action } from '@ngrx/store'; import * as layout from '../../actions/layout/layout'; export interface State { - isLoading: boolean; - title: string; + isLoading: boolean; + title: string; } const initialState: State = { - isLoading: false, - title: 'New window' + isLoading: false, + title: 'New window', }; export function layoutReducer(state = initialState, action: layout.Action): State { - switch (action.type) { - case layout.START_LOADING: - return Object.assign({}, state, { isLoading: true }); - case layout.STOP_LOADING: - return Object.assign({}, state, { isLoading: false }); - case layout.SET_WINDOW_NAME: - return Object.assign({}, state, { title: action.payload }); - default: - return state; - } + switch (action.type) { + case layout.START_LOADING: + return { ...state, isLoading: true }; + case layout.STOP_LOADING: + return { ...state, isLoading: false }; + case layout.SET_WINDOW_NAME: + return { ...state, title: action.payload }; + default: + return state; + } } diff --git a/src/app/reducers/settings/settings.ts b/src/app/reducers/settings/settings.ts new file mode 100644 index 0000000000..da1a97f7f7 --- /dev/null +++ b/src/app/reducers/settings/settings.ts @@ -0,0 +1,30 @@ +import { Action } from '@ngrx/store'; + +import * as settings from '../../actions/settings/settings'; + +export interface State { + isShown: boolean; + theme: string; + language: string; +} + +const initialState: State = { + isShown: false, + theme: 'light', + language: 'en' +}; + +export function settingsReducer(state = initialState, action: settings.Action): State { + switch (action.type) { + case settings.SHOW_SETTINGS: + return { ...state, isShown: true }; + case settings.HIDE_SETTINGS: + return { ...state, isShown: false }; + case settings.SET_THEME: + return { ...state, theme: action.payload.value }; + case settings.SET_LANGUAGE: + return { ...state, language: action.payload.value }; + default: + return state; + } +} diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index e7279c7c70..78daff7b89 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -46,5 +46,10 @@ "SET_SUBSCRIPTION_URL_TEXT": "Enter the subscription URL for your server below", "HISTORY_TEXT": "History", - "HISTORY_SUB_TEXT": "You can select an item from the history to restore" + "HISTORY_SUB_TEXT": "You can select an item from the history to restore", + + "SETTINGS_TEXT": "Settings", + "SETTINGS_SUB_TEXT": "You can adjust any of the settings below", + "SETTINGS_THEME_TEXT": "Theme", + "SETTINGS_LANGUAGE_TEXT": "Language" } diff --git a/src/scss/_codemirror.scss b/src/scss/_codemirror.scss index 7c3543444f..88cdf40b2b 100644 --- a/src/scss/_codemirror.scss +++ b/src/scss/_codemirror.scss @@ -32,9 +32,9 @@ $result-string: $red; background-color: $theme-off-bg-color; } } -.CodeMirror-lint-mark-error{ - // border-bottom: 1px dotted #f00; -} +// .CodeMirror-lint-mark-error{ +// border-bottom: 1px dotted #f00; +// } .CodeMirror-lint-tooltip{ background: $theme-bg-color; border: 1px solid $theme-off-border-color; diff --git a/src/scss/_globals.scss b/src/scss/_globals.scss index c26c19dc70..81b33a3129 100644 --- a/src/scss/_globals.scss +++ b/src/scss/_globals.scss @@ -1,31 +1,5 @@ /* You can add global styles to this file, and also import other style files */ -:root { - --simple-ease: ease; - --ease-in-quad: cubic-bezier(.55, .085, .68, .53); - --ease-in-cubic: cubic-bezier(.550, .055, .675, .19); - --ease-in-quart: cubic-bezier(.895, .03, .685, .22); - --ease-in-quint: cubic-bezier(.755, .05, .855, .06); - --ease-in-expo: cubic-bezier(.95, .05, .795, .035); - --ease-in-circ: cubic-bezier(.6, .04, .98, .335); - - --ease-out-quad: cubic-bezier(.25, .46, .45, .94); - --ease-out-cubic: cubic-bezier(.215, .61, .355, 1); - --ease-out-quart: cubic-bezier(.165, .84, .44, 1); - --ease-out-quint: cubic-bezier(.23, 1, .32, 1); - --ease-out-expo: cubic-bezier(.19, 1, .22, 1); - --ease-out-circ: cubic-bezier(.075, .82, .165, 1); - - --ease-in-out-quad: cubic-bezier(.455, .03, .515, .955); - --ease-in-out-cubic: cubic-bezier(.645, .045, .355, 1); - --ease-in-out-quart: cubic-bezier(.77, 0, .175, 1); - --ease-in-out-quint: cubic-bezier(.86, 0, .07, 1); - --ease-in-out-expo: cubic-bezier(1, 0, 0, 1); - --ease-in-out-circ: cubic-bezier(.785, .135, .15, .86); - - --app-easing: var(--simple-ease); -} - * { box-sizing: border-box; outline: none; diff --git a/src/scss/_helpers.scss b/src/scss/_helpers.scss index d2fc0c92ed..8a0d2cd0a2 100644 --- a/src/scss/_helpers.scss +++ b/src/scss/_helpers.scss @@ -38,11 +38,11 @@ @mixin show-transition{ visibility: visible; opacity: 1; - transition: visibility 0s var(app-easing) 0s, opacity 300ms; + transition: visibility 0s var(--app-easing) 0s, opacity 300ms; } @mixin hide-transition{ visibility: hidden; opacity: 0; - transition: visibility 0s var(app-easing) 300ms, opacity 300ms; + transition: visibility 0s var(--app-easing) 300ms, opacity 300ms; } diff --git a/src/scss/_layout.scss b/src/scss/_layout.scss index 45260ca254..5e88a3d328 100644 --- a/src/scss/_layout.scss +++ b/src/scss/_layout.scss @@ -1,4 +1,5 @@ .main-container { + color: $theme-font-color; background: $theme-bg-color; } .app-content-area { diff --git a/src/scss/_variables.scss b/src/scss/_variables.scss index 875a638884..78cda7e1e1 100644 --- a/src/scss/_variables.scss +++ b/src/scss/_variables.scss @@ -25,3 +25,30 @@ $dialog-z-index: 10; $baseline-size: $clr-baseline-px; $base-font-size: (14/$clr_baseline) * 1rem; + + +:root { + --simple-ease: ease; + --ease-in-quad: cubic-bezier(.55, .085, .68, .53); + --ease-in-cubic: cubic-bezier(.550, .055, .675, .19); + --ease-in-quart: cubic-bezier(.895, .03, .685, .22); + --ease-in-quint: cubic-bezier(.755, .05, .855, .06); + --ease-in-expo: cubic-bezier(.95, .05, .795, .035); + --ease-in-circ: cubic-bezier(.6, .04, .98, .335); + + --ease-out-quad: cubic-bezier(.25, .46, .45, .94); + --ease-out-cubic: cubic-bezier(.215, .61, .355, 1); + --ease-out-quart: cubic-bezier(.165, .84, .44, 1); + --ease-out-quint: cubic-bezier(.23, 1, .32, 1); + --ease-out-expo: cubic-bezier(.19, 1, .22, 1); + --ease-out-circ: cubic-bezier(.075, .82, .165, 1); + + --ease-in-out-quad: cubic-bezier(.455, .03, .515, .955); + --ease-in-out-cubic: cubic-bezier(.645, .045, .355, 1); + --ease-in-out-quart: cubic-bezier(.77, 0, .175, 1); + --ease-in-out-quint: cubic-bezier(.86, 0, .07, 1); + --ease-in-out-expo: cubic-bezier(1, 0, 0, 1); + --ease-in-out-circ: cubic-bezier(.785, .135, .15, .86); + + --app-easing: var(--simple-ease); +} diff --git a/src/scss/components/_dialog.scss b/src/scss/components/_dialog.scss index d4ec39a923..f421532a4a 100644 --- a/src/scss/components/_dialog.scss +++ b/src/scss/components/_dialog.scss @@ -21,10 +21,29 @@ max-height: 500px; overflow: auto; } +.app-dialog-section { + margin-bottom: 20px; +} .app-dialog-footer { width: 100%; } +.dialog-select { + display: block; + font-size: 14px; + padding: 5px 0; + line-height: 1; + border: none; + border-bottom: 2px solid $theme-border-color; + transition: all .3s ease; + min-width: 250px; + border-radius: 0; + background: transparent; + color: $theme-font-color; + &:focus{ + border-color: $green; + } +} .set-header-input-container { display: flex; flex: 1; diff --git a/src/scss/themes/_dark.scss b/src/scss/themes/_dark.scss index 3042458259..eadefd81b0 100644 --- a/src/scss/themes/_dark.scss +++ b/src/scss/themes/_dark.scss @@ -6,6 +6,7 @@ $theme-off-font-color: $light-grey; $theme-border-color: lighten($black, 15%); $theme-off-border-color: lighten($black, 5%); $clr-modal-bg-color: $theme-bg-color; +$clr-header-textColor: $theme-font-color; $clr-header-bgColor: $theme-off-bg-color; $clr-dropdown-bg-color: $theme-bg-color; $clr-dropdown-bg-hover-color: $theme-off-bg-color;