diff --git a/electron/src/tsconfig.json b/electron/src/tsconfig.json index 92b896e6..ff03177a 100644 --- a/electron/src/tsconfig.json +++ b/electron/src/tsconfig.json @@ -17,6 +17,7 @@ "jquery", "core-js", "select2", + "file-saver", "electron", "usb" ] diff --git a/electron/src/webpack.config.js b/electron/src/webpack.config.js index aa1eecf1..cc4300ee 100644 --- a/electron/src/webpack.config.js +++ b/electron/src/webpack.config.js @@ -27,7 +27,8 @@ module.exports = { modules: [path.join(rootDir, "node_modules")], alias: { jquery: 'jquery/dist/jquery.min.js', - select2: 'select2/dist/js/select2.full.min.js' + select2: 'select2/dist/js/select2.full.min.js', + 'file-saver': 'filesaver.js/FileSaver.min.js' } }, module: { diff --git a/package.json b/package.json index 78257094..a6e78e5f 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@ngrx/store-log-monitor": "3.0.2", "@types/core-js": "0.9.35", "@types/electron": "^1.4.32", + "@types/file-saver": "0.0.0", "@types/jquery": "^2.0.40", "@types/node": "^7.0.5", "@types/usb": "^1.1.2", @@ -47,8 +48,10 @@ "@ngrx/store": "2.2.1", "bootstrap": "^3.3.7", "browser-stdout": "^1.3.0", + "buffer": "^5.0.3", "core-js": "2.4.1", "dragula": "^3.7.2", + "filesaver.js": "^0.2.0", "font-awesome": "^4.6.3", "jquery": "3.1.1", "json-loader": "^0.5.4", diff --git a/shared/src/components/keymap/edit/keymap-edit.component.html b/shared/src/components/keymap/edit/keymap-edit.component.html index 6d39d7b2..3f0dd2da 100644 --- a/shared/src/components/keymap/edit/keymap-edit.component.html +++ b/shared/src/components/keymap/edit/keymap-edit.component.html @@ -1,5 +1,5 @@ diff --git a/shared/src/components/keymap/edit/keymap-edit.component.ts b/shared/src/components/keymap/edit/keymap-edit.component.ts index b9317969..2bf948bf 100644 --- a/shared/src/components/keymap/edit/keymap-edit.component.ts +++ b/shared/src/components/keymap/edit/keymap-edit.component.ts @@ -4,14 +4,18 @@ import { ActivatedRoute } from '@angular/router'; import '@ngrx/core/add/operator/select'; import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/first'; import 'rxjs/add/operator/let'; +import 'rxjs/add/operator/map'; import 'rxjs/add/operator/publishReplay'; import 'rxjs/add/operator/switchMap'; -import { Observable } from 'rxjs/Observable'; + +import { saveAs } from 'file-saver'; import { Keymap } from '../../../config-serializer/config-items/Keymap'; import { AppState } from '../../../store'; -import { getKeymap, getKeymaps } from '../../../store/reducers/user-configuration'; +import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration'; @Component({ selector: 'keymap-edit', @@ -41,4 +45,35 @@ export class KeymapEditComponent { .map((keymaps: Keymap[]) => keymaps.length > 1); } + downloadKeymap() { + const exportableJSON$: Observable = this.keymap$ + .switchMap(keymap => this.toExportableJSON(keymap)) + .map(exportableJSON => JSON.stringify(exportableJSON)); + + this.keymap$ + .combineLatest(exportableJSON$) + .first() + .subscribe(latest => { + const keymap = latest[0]; + const exportableJSON = latest[1]; + const fileName = keymap.name + '_keymap.json'; + saveAs(new Blob([exportableJSON], { type: 'application/json' }), fileName); + }); + } + + private toExportableJSON(keymap: Keymap): Observable { + return this.store + .let(getUserConfiguration()) + .first() + .map(userConfiguration => { + return { + site: 'https://ultimatehackingkeyboard.com', + description: 'Ultimate Hacking Keyboard keymap', + keyboardModel: 'UHK60', + dataModelVersion: userConfiguration.dataModelVersion, + objectType: 'keymap', + objectValue: keymap.toJsonObject() + }; + }); + } } diff --git a/shared/src/components/keymap/header/keymap-header.component.html b/shared/src/components/keymap/header/keymap-header.component.html index 572f4b2b..b1d7122e 100644 --- a/shared/src/components/keymap/header/keymap-header.component.html +++ b/shared/src/components/keymap/header/keymap-header.component.html @@ -34,5 +34,6 @@ data-original-title="Duplicate keymap" (click)="duplicateKeymap()" > + \ No newline at end of file diff --git a/shared/src/components/keymap/header/keymap-header.component.scss b/shared/src/components/keymap/header/keymap-header.component.scss index ce3eda58..e33b1461 100644 --- a/shared/src/components/keymap/header/keymap-header.component.scss +++ b/shared/src/components/keymap/header/keymap-header.component.scss @@ -43,6 +43,18 @@ } } +.layer__download { + top: 10px; + font-size: 0.8em; + position: relative; + margin-right: 10px; + + &:hover { + cursor: pointer; + color: $icon-hover; + } +} + .pane-title { margin-bottom: 1em; diff --git a/shared/src/components/keymap/header/keymap-header.component.ts b/shared/src/components/keymap/header/keymap-header.component.ts index 7ed69ba1..fde6edcd 100644 --- a/shared/src/components/keymap/header/keymap-header.component.ts +++ b/shared/src/components/keymap/header/keymap-header.component.ts @@ -3,6 +3,8 @@ import { Component, ElementRef, Input, + Output, + EventEmitter, OnChanges, Renderer, SimpleChanges, @@ -25,6 +27,7 @@ import { KeymapActions } from '../../../store/actions'; export class KeymapHeaderComponent implements OnChanges { @Input() keymap: Keymap; @Input() deletable: boolean; + @Output() downloadClick = new EventEmitter(); @ViewChild('name') keymapName: ElementRef; @ViewChild('abbr') keymapAbbr: ElementRef; @@ -87,4 +90,8 @@ export class KeymapHeaderComponent implements OnChanges { setTrashTitle(): void { this.trashTitle = this.deletable ? '' : 'The last keymap cannot be deleted.'; } + + onDownloadIconClick(): void { + this.downloadClick.emit(); + } } diff --git a/shared/src/config-serializer/config-items/Module.ts b/shared/src/config-serializer/config-items/Module.ts index 39f194c5..22cff961 100644 --- a/shared/src/config-serializer/config-items/Module.ts +++ b/shared/src/config-serializer/config-items/Module.ts @@ -3,6 +3,7 @@ import { UhkBuffer } from '../UhkBuffer'; import { Helper as KeyActionHelper, KeyAction, NoneAction } from './key-action'; import { Keymap } from './Keymap'; import { Macro } from './Macro'; +import { PlayMacroAction, SwitchLayerAction } from './key-action'; enum PointerRole { none, @@ -54,7 +55,7 @@ export class Module { id: this.id, pointerRole: PointerRole[this.pointerRole], keyActions: this.keyActions.map(keyAction => { - if (keyAction) { + if (keyAction && (macros || !(keyAction instanceof PlayMacroAction || keyAction instanceof SwitchLayerAction))) { return keyAction.toJsonObject(macros); } }) diff --git a/web/src/tsconfig.json b/web/src/tsconfig.json index ce2ea70b..b4d65ff0 100644 --- a/web/src/tsconfig.json +++ b/web/src/tsconfig.json @@ -16,7 +16,8 @@ "node", "jquery", "core-js", - "select2" + "select2", + "file-saver" ] }, "exclude": [ diff --git a/web/src/vendor.ts b/web/src/vendor.ts new file mode 100644 index 00000000..91896cec --- /dev/null +++ b/web/src/vendor.ts @@ -0,0 +1,3 @@ +import './shared/vendor.ts'; + +import 'buffer'; diff --git a/web/src/webpack.config.js b/web/src/webpack.config.js index bc39e051..3086cea6 100644 --- a/web/src/webpack.config.js +++ b/web/src/webpack.config.js @@ -11,7 +11,7 @@ console.log(rootDir, __dirname); module.exports = { entry: { polyfills: path.resolve(rootDir, 'src/shared/polyfills.ts'), - vendor: path.resolve(rootDir, 'src/shared/vendor.ts'), + vendor: path.resolve(rootDir, 'src/vendor.ts'), app: path.resolve(rootDir, 'src/main.ts') }, output: { @@ -24,7 +24,8 @@ module.exports = { modules: [path.join(rootDir, "node_modules")], alias: { jquery: 'jquery/dist/jquery.min.js', - select2: 'select2/dist/js/select2.full.min.js' + select2: 'select2/dist/js/select2.full.min.js', + 'file-saver': 'filesaver.js/FileSaver.min.js' } }, module: {