From c3ed69a712e89a0fc4d8fe0316f790d132d8cfff Mon Sep 17 00:00:00 2001 From: Rokt33r Date: Thu, 4 Jun 2015 19:49:13 +0900 Subject: [PATCH] popup window added --- .gitignore | 1 + Gulpfile.js | 4 +- build.config.js | 4 + electron_src/popup/index.html | 55 +++++++ electron_src/popup/popup.js | 149 ++++++++++++++++++ electron_src/popup/popup.scss | 75 +++++++++ electron_src/popup/services/snippet.js | 55 +++++++ gulp-electron.js | 125 +++++++++++++++ main.js | 201 ++++++++++++++++++++++++- package.json | 1 + src/index.html | 2 +- 11 files changed, 664 insertions(+), 8 deletions(-) create mode 100644 electron_src/popup/index.html create mode 100644 electron_src/popup/popup.js create mode 100644 electron_src/popup/popup.scss create mode 100644 electron_src/popup/services/snippet.js create mode 100644 gulp-electron.js diff --git a/.gitignore b/.gitignore index 7d5b7a94..16d12354 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /build /node_modules +/electron_build diff --git a/Gulpfile.js b/Gulpfile.js index 553197d9..64bf6bf2 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -47,7 +47,7 @@ gulp.task('tpls', function(){ }) gulp.task('index', function () { - var files = globby.sync(['build/**/*', '!build/vendor/**/*']) + var files = globby.sync(['build/**/*', '!build/vendor/**/*', '!build/electron/**/*']) var filter = function (files, ext) { return files.filter(function (file) { @@ -118,3 +118,5 @@ gulp.task('default', function (cb) { runSequence('del', 'build', 'watch', cb) }) + +require('./gulp-electron')(gulp) diff --git a/build.config.js b/build.config.js index 4d42660a..bdc3fda9 100644 --- a/build.config.js +++ b/build.config.js @@ -44,6 +44,10 @@ module.exports = { name:'moment', src:'node_modules/moment/moment.js', cdn:'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.3/moment.min.js' + }, + { + name:'angular-hotkeys', + src:'node_modules/angular-hotkeys/build/hotkeys.js' } ] } diff --git a/electron_src/popup/index.html b/electron_src/popup/index.html new file mode 100644 index 00000000..62339b93 --- /dev/null +++ b/electron_src/popup/index.html @@ -0,0 +1,55 @@ + + + + + CodeXen App + + + + + + + + + + + + + + +
+ +
+ +
+ + +
+
+ + + +
+
+
+
+ + + + + + + + + diff --git a/electron_src/popup/popup.js b/electron_src/popup/popup.js new file mode 100644 index 00000000..5dca4039 --- /dev/null +++ b/electron_src/popup/popup.js @@ -0,0 +1,149 @@ +// document.getElementById('search-input').focus() + +var remote = require('remote') +var ipc = require('ipc') + +var SEARCH_INPUT = 1 +var RESULT_LIST = 2 +var RESULT_DETAIL = 3 + +angular.module('codexen.popup', [ + 'ui.ace', + 'satellizer', + 'cfp.hotkeys' +]) +.controller('PopUpController', function ($scope, Snippet, $auth, $window, hotkeys, $document, $filter) { + + // For Dev + $scope.toggleDev = function () { + remote.getCurrentWindow().toggleDevTools() + } + + // Setup Events + remote.getCurrentWindow().on('focus', function () { + $scope.$apply(focusSearchInput) + }) + + hotkeys.bindTo($scope) + .add('down', function (e) { + if ($scope.isFocusing === RESULT_LIST) selectNextItem() + e.preventDefault() + }) + .add('up', function (e) { + if ($scope.isFocusing === RESULT_LIST) selectPriorItem() + e.preventDefault() + }) + .add('right', function (e) { + if ($scope.isFocusing === RESULT_LIST) focusDetail() + }) + .add('left', function (e) { + if ($scope.isFocusing === RESULT_DETAIL) focusList() + }) + .add('esc', function (e) { + switch ($scope.isFocusing) { + case RESULT_LIST: + focusSearchInput() + break + case RESULT_DETAIL: + focusList() + break + case SEARCH_INPUT: + hidePopUp() + } + }) + .add('tab', function (e) { + if ($scope.isFocusing === RESULT_LIST) focusDetail() + + }) + + // Init Data + $scope.snippets = [] + + var userId = $auth.getPayload().sub + Snippet.findByUser(userId) + .success(function (data) { + $scope.snippets = data.snippets + $scope.selectedItem = $scope.snippets[0] + filterList() + }) + function filterList (needle) { + $scope.filteredSnippets = $filter('filter')($scope.snippets, needle) + } + $scope.filterList = filterList + + $scope.isFocusing = 0 + $scope.selectIndex = 0 + + $scope.selectItem = selectItem + function selectItem (index) { + $scope.selectIndex = index + $scope.selectedItem = $scope.filteredSnippets[index] + } + + function selectNextItem () { + if ($scope.selectIndex >= ($scope.filteredSnippets.length -1)) { + return + } + selectItem(++$scope.selectIndex) + } + + function selectPriorItem () { + if ($scope.selectIndex == 0) { + focusSearchInput() + return + } + selectItem(--$scope.selectIndex) + } + + function focusSearchInput () { + $scope.isFocusing = SEARCH_INPUT + document.getElementById('search-input').focus() + } + + function focusList () { + $scope.isFocusing = RESULT_LIST + document.getElementById('search-input').blur() + } + $scope.focusList = focusList + + function focusDetail () { + $scope.isFocusing = RESULT_DETAIL + } + + function hidePopUp () { + remote.getCurrentWindow().hide() + } +}) +.directive('resultItem', function () { + return { + link: function (scope, el, attr) { + + } + } +}) +.directive('searchInput', function () { + return { + restrict: 'A', + link: function (scope, el, attr) { + el.on('keydown', function (e) { + + // Down key => Focus on Result list + if (e.keyCode === 40) { + scope.focusList() + e.preventDefault() + } + + // Esc key => Dismiss popup + if (e.keyCode === 27) { + ipc.send('hidePopUp') + e.preventDefault() + } + + // TODO: Tab key => Auto complete + if (e.keyCode === 9) { + e.preventDefault() + } + }) + } + } +}) diff --git a/electron_src/popup/popup.scss b/electron_src/popup/popup.scss new file mode 100644 index 00000000..ef1e8856 --- /dev/null +++ b/electron_src/popup/popup.scss @@ -0,0 +1,75 @@ +@import "../../src/variables"; +@import "../../src/mixins"; + +$selected-color: white; +$selected-bg: $brand-primary; + +$focused-shadow-color: $brand-primary; + + +.popup-body{ + .search-block{ + padding: 5px; + height:44px; + position:absolute; + top: 0; + width: 100%; + } + .result-block{ + position:absolute; + top: 44px; + bottom: 0; + width: 100%; + overflow: hidden; + .result-list{ + margin: 0; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 40%; + overflow-y: auto; + overflow-x: hidden; + list-style:none; + padding: 0; + &.focused{ + border: solid 1px $brand-primary; + } + li{ + a{ + display:block; + padding: 5px 10px; + border-bottom: 1px solid $border-color; + + &.selected{ + color: $selected-color; + background-color: $selected-bg; + } + + &:hover{ + } + } + } + border-right: 1px solid $border-color; + } + .result-detail{ + &.focused{ + border: solid 1px $brand-primary; + } + position: absolute; + left: 40%; + top: 0; + bottom: 0; + width: 60%; + overflow-y: auto; + overflow-x: hidden; + .result-detail-cotent{ + position: absolute; + top: 34px; + bottom: 0; + width: 100%; + } + } + } + +} diff --git a/electron_src/popup/services/snippet.js b/electron_src/popup/services/snippet.js new file mode 100644 index 00000000..84bafbf7 --- /dev/null +++ b/electron_src/popup/services/snippet.js @@ -0,0 +1,55 @@ +/* global angular */ +angular.module('codexen.popup') + .constant('apiUrl', 'http://localhost:8000/') + .config(function ($authProvider, $httpProvider) { + $authProvider.baseUrl = 'http://localhost:8000/' + + $httpProvider.defaults.useXDomain = true + delete $httpProvider.defaults.headers.common['X-Requested-With'] + }) + + +angular.module('codexen.popup') + .factory('Snippet', function ($http, apiUrl) { + var findByUser = function (user) { + var url = apiUrl + 'snippets/search' + + return $http.get(url, { + params: { + user: user + } + }) + } + + var create = function (params) { + var url = apiUrl + 'snippets/create' + + return $http.post(url, params) + } + + var show = function (id) { + var url = apiUrl + 'snippets/id/' + id + + return $http.get(url) + } + + var update = function (id, params) { + var url = apiUrl + 'snippets/id/' + id + + return $http.put(url, params) + } + + var destroy = function (id) { + var url = apiUrl + 'snippets/id/' + id + + return $http.delete(url) + } + + return { + findByUser: findByUser, + create: create, + show: show, + delete: destroy, + update: update + } + }) diff --git a/gulp-electron.js b/gulp-electron.js new file mode 100644 index 00000000..7f1cd38d --- /dev/null +++ b/gulp-electron.js @@ -0,0 +1,125 @@ +var sass = require('gulp-sass') +var autoprefixer = require('gulp-autoprefixer') +var templateCache = require('gulp-angular-templatecache') +var globby = require('globby') +var template = require('gulp-template') +var concat = require('gulp-concat') +var del = require('del') +var runSequence = require('run-sequence') +var merge = require('merge-stream') + +var changed = require('gulp-changed') +var cached = require('gulp-cached') +var remember = require('gulp-remember') +var livereload = require('gulp-livereload') +var childProcess = require('child_process') + +var config = require('./build.config.js') + +// for Dist +var rev = require('gulp-rev') +var ngAnnotate = require('gulp-ng-annotate') +var uglify = require('gulp-uglify') +var minifyCss = require('gulp-minify-css') + +module.exports = function (gulp) { + + + /* + * Electron build + */ + gulp.task('elec-js', function(){ + var src = gulp.src(['src/**/*.js']) + .pipe(changed('electron_build')) + .pipe(gulp.dest('electron_build')) + var elecSrc = gulp.src(['electron_src/**/*.js']) + .pipe(changed('electron_build/electron')) + .pipe(gulp.dest('electron_build/electron')) + var elecHtml = gulp.src(['electron_src/**/*.html']) + .pipe(changed('electron_build/electron')) + .pipe(gulp.dest('electron_build/electron')) + + return merge(src, elecSrc, elecHtml) + }) + + gulp.task('elec-sass', function () { + return gulp.src(['src/**/*.scss', 'electron_src/**/*.scss']) + .pipe(cached('styles')) + .pipe(sass().on('error', sass.logError)) + .pipe(autoprefixer()) + .pipe(remember('styles')) + .pipe(concat('all.css')) + .pipe(gulp.dest('electron_build')) + }) + + gulp.task('elec-tpls', function(){ + return gulp.src('src/**/*.tpl.html') + .pipe(templateCache()) + .pipe(gulp.dest('electron_build')) + }) + + gulp.task('elec-index', function () { + var files = globby.sync(['electron_build/**/*', '!electron_build/vendor/**/*', '!electron_build/electron/**/*']) + + var filter = function (files, ext) { + return files.filter(function (file) { + var reg = new RegExp('.+\.' + ext + '$') + return file.match(reg) + }).map(function (file) { + return file.replace('electron_build/', '') + }) + } + var scripts = filter(files, 'js') + var styles = filter(files, 'css') + + return gulp.src('src/index.html') + .pipe(template({ + scripts: scripts, + styles: styles, + env: 'build' + })) + .pipe(gulp.dest('electron_build')) + }) + + gulp.task('elec-vendor', function () { + var vendors = config.vendors + + var vendorFiles = vendors.map(function (vendor) { + return vendor.src + }) + + return gulp.src(vendorFiles) + .pipe(gulp.dest('electron_build/vendor')) + }) + + gulp.task('elec-resources', function () { + return gulp.src('resources/**/*') + .pipe(changed('electron_build/resources')) + .pipe(gulp.dest('electron_build/resources')) + }) + + gulp.task('elec-build', function (cb) { + runSequence(['elec-js', 'elec-sass', 'elec-tpls', 'elec-vendor', 'elec-resources'], 'elec-index', cb) + }) + + gulp.task('elec-watch', function (cb) { + gulp.watch(['src/**/*.js', 'electron_src/**/*.js', 'electron_src/**/*.html'], ['elec-js']) + + gulp.watch(['src/**/*.scss', 'electron_src/**/*.scss'], ['elec-sass']) + + gulp.watch('src/**/*.tpl.html', ['elec-tpls']) + + gulp.watch(['electron_build/**/*', '!electron_build/vendor/**/*', '!electron_build/electron/**/*'], ['elec-index']) + + livereload.listen() + }) + + gulp.task('elec-del', function (cb) { + del(['electron_build/**/*'], cb) + }) + + gulp.task('elec', function (cb) { + runSequence('elec-del', 'elec-build', 'elec-watch', cb) + }) + +} diff --git a/main.js b/main.js index bb1ba6c4..f1106629 100644 --- a/main.js +++ b/main.js @@ -8,20 +8,209 @@ require('crash-reporter').start() // be closed automatically when the javascript object is GCed. var mainWindow = null -console.log(process.platform) -// Quit when all windows are closed. app.on('window-all-closed', function () { - if (process.platform !== 'darwin') - app.quit() + if (process.platform !== 'darwin') app.quit() }) + app.on('ready', function () { + mainWindow = new BrowserWindow({width: 800, height: 600}) - mainWindow.loadUrl('file://' + __dirname + '/build/index.html') + mainWindow.loadUrl('file://' + __dirname + '/electron_build/index.html') - mainWindow.openDevTools() + // mainWindow.openDevTools() mainWindow.on('closed', function () { + console.log('main closed') mainWindow = null }) + + var globalShortcut = require('global-shortcut') + + var popUpWindow = new BrowserWindow({ + width: 600, + height: 400, + show: false, + frame: false + }) + + popUpWindow.loadUrl('file://' + __dirname + '/electron_build/electron/popup/index.html') + + app.on('activate-with-no-open-windows', function () { + if (mainWindow == null) { + console.log('new WIndow!') + mainWindow = new BrowserWindow({width: 800, height: 600}) + + mainWindow.loadUrl('file://' + __dirname + '/electron_build/index.html') + + mainWindow.on('closed', function () { + mainWindow = null + }) + } + mainWindow.show() + }) + + popUpWindow.on('blur', function () { + popUpWindow.hide() + }) + + var hidePopUp = function () { + if(fromMain){ + + } else { + mainWindow.hide() + Menu.sendActionToFirstResponder('hide:'); + } + + popUpWindow.hide() + } + + var ipc = require('ipc') + ipc.on('hidePopUp', function () { + hidePopUp() + }) + + var fromMain + // Register a 'ctrl+x' shortcut listener. + var ret = globalShortcut.register('ctrl+tab+shift', function () { + + if (popUpWindow.isVisible()) { + hidePopUp() + return + } + fromMain = mainWindow.isFocused() + popUpWindow.show() + + }) + if (!ret) console.log('registerion fails') + + + // MENU + var Menu = require('menu') + var template = [ + { + label: 'Electron', + submenu: [ + { + label: 'About Electron', + selector: 'orderFrontStandardAboutPanel:' + }, + { + type: 'separator' + }, + { + label: 'Services', + submenu: [] + }, + { + type: 'separator' + }, + { + label: 'Hide Electron', + accelerator: 'Command+H', + selector: 'hide:' + }, + { + label: 'Hide Others', + accelerator: 'Command+Shift+H', + selector: 'hideOtherApplications:' + }, + { + label: 'Show All', + selector: 'unhideAllApplications:' + }, + { + type: 'separator' + }, + { + label: 'Quit', + accelerator: 'Command+Q', + click: function() { app.quit(); } + }, + ] + }, + { + label: 'Edit', + submenu: [ + { + label: 'Undo', + accelerator: 'Command+Z', + selector: 'undo:' + }, + { + label: 'Redo', + accelerator: 'Shift+Command+Z', + selector: 'redo:' + }, + { + type: 'separator' + }, + { + label: 'Cut', + accelerator: 'Command+X', + selector: 'cut:' + }, + { + label: 'Copy', + accelerator: 'Command+C', + selector: 'copy:' + }, + { + label: 'Paste', + accelerator: 'Command+V', + selector: 'paste:' + }, + { + label: 'Select All', + accelerator: 'Command+A', + selector: 'selectAll:' + }, + ] + }, + { + label: 'View', + submenu: [ + { + label: 'Reload', + accelerator: 'Command+R', + click: function() { BrowserWindow.getFocusedWindow().reloadIgnoringCache(); } + }, + { + label: 'Toggle DevTools', + accelerator: 'Alt+Command+I', + click: function() { BrowserWindow.getFocusedWindow().toggleDevTools(); } + }, + ] + }, + { + label: 'Window', + submenu: [ + { + label: 'Minimize', + accelerator: 'Command+M', + selector: 'performMiniaturize:' + }, + { + label: 'Close', + accelerator: 'Command+W', + selector: 'performClose:' + }, + { + type: 'separator' + }, + { + label: 'Bring All to Front', + selector: 'arrangeInFront:' + }, + ] + }, + { + label: 'Help', + submenu: [] + }, + ]; + + menu = Menu.buildFromTemplate(template); + + Menu.setApplicationMenu(menu); }) diff --git a/package.json b/package.json index 4d343b01..38eb81f5 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@rokt33r/angular-ui-ace": "^0.2.3", "angular": "^1.3.15", "angular-bootstrap": "^0.12.0", + "angular-hotkeys": "^1.4.5", "angular-md5": "^0.1.7", "angular-sanitize": "^1.3.15", "angular-ui-router": "^0.2.15", diff --git a/src/index.html b/src/index.html index 98617a69..33c9d9c0 100644 --- a/src/index.html +++ b/src/index.html @@ -7,7 +7,7 @@ - + Codexen!