Skip to content

Commit

Permalink
Added window import and export functionality.
Browse files Browse the repository at this point in the history
Closes #217.
  • Loading branch information
imolorhe committed Feb 4, 2018
1 parent e784e94 commit d0f795e
Show file tree
Hide file tree
Showing 17 changed files with 238 additions and 40 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"build": "ng build -prod --aot",
"test": "ng test",
"test-build": "ng lint && npm run build && karma start karma.conf.js --single-run",
"local-verify": "ng test --single-run && ng lint && ng e2e && yarn build",
"lint": "ng lint",
"e2e": "ng e2e",
"heroku-postbuild": "npm run build",
Expand Down Expand Up @@ -72,6 +73,7 @@
"electron-squirrel-startup": "^1.0.0",
"electron-updater": "^2.18.2",
"express": "^4.16.2",
"file-dialog": "^0.0.7",
"graphql": "0.9.3",
"graphql-query-compress": "^0.9.6",
"marked": "^0.3.12",
Expand Down
9 changes: 9 additions & 0 deletions src/app/actions/headers/headers.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Action } from '@ngrx/store';

import * as fromHeaders from '../../reducers/headers/headers';

export const ADD_HEADER = 'ADD_HEADER';
export const REMOVE_HEADER = 'REMOVE_HEADER';
export const EDIT_HEADER_KEY = 'EDIT_HEADER_KEY';
export const EDIT_HEADER_VALUE = 'EDIT_HEADER_VALUE';
export const SET_HEADERS = 'SET_HEADERS';

export class AddHeaderAction implements Action {
readonly type = ADD_HEADER;
Expand All @@ -29,4 +32,10 @@ export class EditHeaderValueAction implements Action {
constructor(public payload: any, public windowId: string) {}
}

export class SetHeadersAction implements Action {
readonly type = SET_HEADERS;

constructor(public payload: { headers: Array<fromHeaders.Header> }, public windowId: string) { }
}

export type Action = AddHeaderAction | RemoveHeaderAction | EditHeaderKeyAction | EditHeaderValueAction;
27 changes: 21 additions & 6 deletions src/app/actions/windows/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,36 @@ export const ADD_WINDOW = 'ADD_WINDOW';
export const SET_WINDOWS = 'SET_WINDOWS';
export const REMOVE_WINDOW = 'REMOVE_WINDOW';

export const EXPORT_WINDOW = 'EXPORT_WINDOW';
export const IMPORT_WINDOW = 'IMPORT_WINDOW';

export class AddWindowAction implements Action {
readonly type = ADD_WINDOW;
readonly type = ADD_WINDOW;

constructor(public payload: any) {}
constructor(public payload: any) {}
}
export class SetWindowsAction implements Action {
readonly type = SET_WINDOWS;
readonly type = SET_WINDOWS;

constructor(public payload: Array<any>) {}
constructor(public payload: Array<any>) {}
}

export class RemoveWindowAction implements Action {
readonly type = REMOVE_WINDOW;
readonly type = REMOVE_WINDOW;

constructor(public payload: any) {}
}

export type Action = AddWindowAction | SetWindowsAction | RemoveWindowAction;
export class ExportWindowAction implements Action {
readonly type = EXPORT_WINDOW;

constructor(public payload: { windowId: string }) { }
}

export class ImportWindowAction implements Action {
readonly type = IMPORT_WINDOW;

constructor(public payload?: any) { }
}

export type Action = AddWindowAction | SetWindowsAction | RemoveWindowAction | ExportWindowAction | ImportWindowAction;
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { TranslateModule } from '@ngx-translate/core';
import { SharedModule } from '../../../shared/shared.module';
import { DocViewerFieldComponent } from './doc-viewer-field.component';
import { DocViewerTypeComponent } from '../doc-viewer-type/doc-viewer-type.component';

Expand All @@ -15,7 +16,8 @@ describe('DocViewerFieldComponent', () => {
DocViewerTypeComponent
],
imports: [
TranslateModule.forRoot()
TranslateModule.forRoot(),
SharedModule
]
})
.compileComponents();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { TranslateModule } from '@ngx-translate/core';
import { SharedModule } from '../../../shared/shared.module';
import { DocViewerTypeComponent } from './doc-viewer-type.component';

describe('DocViewerTypeComponent', () => {
Expand All @@ -11,7 +12,8 @@ describe('DocViewerTypeComponent', () => {
TestBed.configureTestingModule({
declarations: [ DocViewerTypeComponent ],
imports: [
TranslateModule.forRoot()
TranslateModule.forRoot(),
SharedModule
]
})
.compileComponents();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { TranslateModule } from '@ngx-translate/core';
import { SortablejsModule } from 'angular-sortablejs';
import { ContextMenuModule } from 'ngx-contextmenu';
import { WindowSwitcherComponent } from './window-switcher.component';

describe('WindowSwitcherComponent', () => {
Expand All @@ -13,7 +14,8 @@ describe('WindowSwitcherComponent', () => {
declarations: [ WindowSwitcherComponent ],
imports: [
TranslateModule.forRoot(),
SortablejsModule.forRoot({})
SortablejsModule.forRoot({}),
ContextMenuModule.forRoot()
]
})
.compileComponents();
Expand Down
3 changes: 2 additions & 1 deletion src/app/containers/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@
<clr-icon shape="caret down"></clr-icon>
</button>
<clr-dropdown-menu clrPosition="bottom-right" *clrIfOpen>
<button type="button" clrDropdownItem (click)="importWindow()" track-id="import_window">{{ 'IMPORT_WINDOW_TEXT' | translate }}</button>
<button type="button" clrDropdownItem (click)="showSettingsDialog()" track-id="show_settings">{{ 'SETTINGS_TEXT' | translate }}</button>
<button type="button" (click)="externalLink($event, 'https://github.com/imolorhe/altair')" clrDropdownItem track-id="view_on_github">{{ 'VIEW_ON_GITHUB_TEXT' | translate }}</button>
<button type="button" clrDropdownItem (click)="externalLink($event, 'https://github.com/imolorhe/altair')" track-id="view_on_github">{{ 'VIEW_ON_GITHUB_TEXT' | translate }}</button>
</clr-dropdown-menu>
</clr-dropdown>
</div>
Expand Down
1 change: 1 addition & 0 deletions src/app/containers/app/app.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('AppComponent', () => {
subscribe: () => Observable.empty(),
select: () => Observable.empty(),
map: () => Observable.empty(),
first: () => Observable.empty(),
dispatch: () => {}
} }
];
Expand Down
8 changes: 6 additions & 2 deletions src/app/containers/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export class AppComponent {
});

if (!this.windowIds.length) {
this.windowService.newWindow();
this.windowService.newWindow().subscribe();
}
}

Expand Down Expand Up @@ -147,7 +147,7 @@ export class AppComponent {
}

newWindow() {
this.windowService.newWindow();
this.windowService.newWindow().first().subscribe();
}

setActiveWindow(windowId) {
Expand All @@ -168,6 +168,10 @@ export class AppComponent {
this.store.dispatch(new windowsMetaActions.RepositionWindowAction({ currentPosition, newPosition }));
}

importWindow() {
this.store.dispatch(new windowsActions.ImportWindowAction());
}

showSettingsDialog() {
this.store.dispatch(new settingsActions.ShowSettingsAction());
}
Expand Down
5 changes: 5 additions & 0 deletions src/app/containers/window/window.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -148,5 +148,10 @@ <h3 class="modal-title">
<clr-icon clrVerticalNavIcon shape="history"></clr-icon>
{{ 'HISTORY_TEXT' | translate }}
</a>
<div class="nav-divider"></div>
<a clrVerticalNavLink (click)="exportWindowData()" title="{{ 'EXPORT_WINDOW_TEXT' | translate }}" track-id="export_window">
<clr-icon clrVerticalNavIcon shape="export"></clr-icon>
{{ 'EXPORT_WINDOW_TEXT' | translate }}
</a>
</clr-vertical-nav>
</div>
9 changes: 9 additions & 0 deletions src/app/containers/window/window.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import * as docsActions from '../../actions/docs/docs';
import * as layoutActions from '../../actions/layout/layout';
import * as schemaActions from '../../actions/gql-schema/gql-schema';
import * as historyActions from '../../actions/history/history';
import * as windowActions from '../../actions/windows/windows';

import { QueryService, GqlService, NotifyService } from '../../services';
import { graphql } from 'graphql';
Expand Down Expand Up @@ -276,6 +277,14 @@ export class WindowComponent implements OnInit {
}
}

/**
* Export the data in the current window
*/
exportWindowData() {
this.store.dispatch(new windowActions.ExportWindowAction({ windowId: this.windowId }));
}


trackByFn(index, item) {
return index;
}
Expand Down
94 changes: 89 additions & 5 deletions src/app/effects/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,124 @@ import { Effect, Actions, toPayload } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';

import * as fromRoot from '../reducers';
import * as fromWindows from '../reducers/windows';

import * as queryActions from '../actions/query/query';
import * as headerActions from '../actions/headers/headers';
import * as variableActions from '../actions/variables/variables';
import * as layoutActions from '../actions/layout/layout';
import * as windowActions from '../actions/windows/windows';
import * as windowsMetaActions from '../actions/windows-meta/windows-meta';

import { WindowService } from '../services/window.service';

import { downloadJson, openFile } from '../utils';

@Injectable()
export class WindowsEffects {

@Effect() // add
// Updates windowsMeta with window IDs when a window is added
@Effect()
addWindowID$: Observable<Action> = this.actions$
.ofType(windowActions.ADD_WINDOW)
.withLatestFrom(this.store, (action: windowActions.Action, state) => {
return { windows: state.windows, windowIds: state.windowsMeta.windowIds, action };
}).switchMap(data => {
})
.switchMap(data => {
const windowIds = Object.keys(data.windows);
const metaWindowIds = data.windowIds;
const newWindowIds = [...metaWindowIds, ...windowIds.filter(id => !metaWindowIds.includes(id))]

return Observable.of(new windowsMetaActions.SetWindowIdsAction({ ids: newWindowIds }));
});

@Effect() // remove
// Updates windowsMeta with window IDs when a window is removed
@Effect()
removeWindowID$: Observable<Action> = this.actions$
.ofType(windowActions.REMOVE_WINDOW)
.withLatestFrom(this.store, (action: windowActions.Action, state) => {
return { windows: state.windows, windowIds: state.windowsMeta.windowIds, action };
}).switchMap(data => {
})
.switchMap(data => {
const windowIds = Object.keys(data.windows);
const metaWindowIds = data.windowIds;
const newWindowIds = metaWindowIds.filter(id => windowIds.includes(id));
return Observable.of(new windowsMetaActions.SetWindowIdsAction({ ids: newWindowIds }));
});

// Exports the window data
@Effect()
exportWindow$: Observable<Action> = this.actions$
.ofType(windowActions.EXPORT_WINDOW)
.withLatestFrom(this.store, (action: windowActions.Action, state) => {
return { data: state.windows[action.payload.windowId], windowId: action.payload.windowId, action };
})
.switchMap(data => {
this.windowService.getWindowExportData(data.windowId).subscribe(exportData => {
downloadJson(exportData, data.data.layout.title, { fileType: 'agq' });
});
return Observable.empty();
});

@Effect()
importWindow$: Observable<Action> = this.actions$
.ofType(windowActions.IMPORT_WINDOW)
.switchMap(action => {
openFile().then((data: string) => {
try {
// Verify file's content
if (!data) {
throw new Error('File is empty.');
}
const parsed: fromWindows.ExportWindowState = JSON.parse(data);
if (!parsed.version || !parsed.type || parsed.type !== 'window') {
throw new Error('File is not a valid Altair file.');
}
// Importing window data...
// Add new window
// Set window name
// Set API URL
// Set query
// Set headers
// Set variables
// Set subscription URL
this.windowService.newWindow().subscribe(newWindow => {
const windowId = newWindow.windowId;

if (parsed.windowName) {
this.store.dispatch(new layoutActions.SetWindowNameAction(windowId, parsed.windowName));
}

if (parsed.apiUrl) {
this.store.dispatch(new queryActions.SetUrlAction({ url: parsed.apiUrl }, windowId));
}

if (parsed.query) {
this.store.dispatch(new queryActions.SetQueryAction(parsed.query, windowId));
}

if (parsed.headers.length) {
this.store.dispatch(new headerActions.SetHeadersAction({ headers: parsed.headers }, windowId));
}

if (parsed.variables) {
this.store.dispatch(new variableActions.UpdateVariablesAction(parsed.variables, windowId));
}

if (parsed.subscriptionUrl) {
this.store.dispatch(new queryActions.SetSubscriptionUrlAction({ subscriptionUrl: parsed.subscriptionUrl }, windowId));
}
});
} catch (err) {
console.log('The file is invalid.', err);
}
});
return Observable.empty();
});

constructor(
private actions$: Actions,
private store: Store<fromRoot.State>
private store: Store<fromRoot.State>,
private windowService: WindowService
) {}
}
11 changes: 11 additions & 0 deletions src/app/reducers/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ export interface State {
[id: string]: fromRoot.PerWindowState;
}

export interface ExportWindowState {
version: 1;
type: 'window';
windowName: string;
apiUrl: string;
query: string;
headers: Array<{key: string, value: string}>;
variables: string;
subscriptionUrl: string;
}

/**
* Metareducer to add new window and pass the correct window state to the main reducer
* @param reducer
Expand Down
Loading

0 comments on commit d0f795e

Please sign in to comment.