├── .babelrc ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── actions │ ├── app.actions.ts │ └── slides.actions.ts ├── app.global.scss ├── app.html ├── app.icns ├── constants │ ├── app.constants.ts │ ├── slides.constants.ts │ └── slides.enums.ts ├── index.tsx ├── main.development.js ├── modules │ ├── App │ │ ├── App.tsx │ │ ├── EditView │ │ │ ├── EditView.tsx │ │ │ ├── SettingsMenu │ │ │ │ ├── SettingsMenu.tsx │ │ │ │ └── settings-menu.scss │ │ │ └── edit-view.scss │ │ └── FullscreenView │ │ │ ├── FullscreenView.tsx │ │ │ └── fullscreen-view.scss │ ├── ControlPanel │ │ ├── ControlPanel.tsx │ │ └── control-panel.scss │ ├── DummySlide │ │ └── DummySlide.tsx │ ├── MiniSlidesPanel │ │ ├── MiniSlidesPanel.tsx │ │ └── mini-slide-panel.scss │ ├── Scale │ │ └── Scale.tsx │ ├── SmartSlide │ │ ├── SmartSlide.tsx │ │ └── smart-slide.scss │ ├── ToolBar │ │ ├── ToolBar.tsx │ │ └── toolbar.scss │ ├── UtilitiesMenu │ │ ├── DefaultOptions │ │ │ ├── DefaultOptions.tsx │ │ │ ├── Options │ │ │ │ ├── BackgroundColor.tsx │ │ │ │ ├── DuplicateSlide.tsx │ │ │ │ ├── MoveSlide.tsx │ │ │ │ └── SelectTransitions.tsx │ │ │ └── options.scss │ │ ├── UtilitiesMenu.tsx │ │ └── utilities-menu.scss │ └── index.ts ├── package.json ├── plugins │ └── node_modules │ │ ├── devdecks-code-editor │ │ ├── CodeEditor.tsx │ │ ├── Options │ │ │ ├── CodeEdit.tsx │ │ │ ├── CodeHighlightSubmit.tsx │ │ │ ├── CodeImportExport.tsx │ │ │ ├── CodeLang.tsx │ │ │ ├── CodeRun.tsx │ │ │ ├── CodeTheme.tsx │ │ │ ├── ToggleConsole.tsx │ │ │ └── lanuages.ts │ │ ├── OptionsMenu.tsx │ │ ├── code-editor.scss │ │ └── index.ts │ │ ├── devdecks-image │ │ ├── AddImage.tsx │ │ ├── AddImageDialog.tsx │ │ ├── OptionsMenu.tsx │ │ ├── add-image.scss │ │ └── index.ts │ │ └── devdecks-textbox │ │ ├── OptionsMenu.tsx │ │ ├── TextBox.tsx │ │ ├── fonts │ │ ├── Coda.ttf │ │ ├── Droid Sans.ttf │ │ ├── Electrolize.ttf │ │ ├── Exo.ttf │ │ ├── Hind.ttf │ │ ├── Lobster.ttf │ │ ├── Orbitron.ttf │ │ ├── Oxygen.ttf │ │ ├── Raleway.ttf │ │ ├── Roboto.ttf │ │ ├── Satisfy.ttf │ │ ├── Tangerine.ttf │ │ ├── Ubuntu.ttf │ │ └── _index.scss │ │ ├── index.ts │ │ ├── options │ │ ├── FontBackgroundColor.tsx │ │ ├── FontColor.tsx │ │ ├── FontFamily.tsx │ │ ├── FontSize.tsx │ │ ├── FontStyles.tsx │ │ └── Utils.tsx │ │ └── text-box.scss ├── reducers │ ├── app.reducer.ts │ ├── index.ts │ └── slides.reducer.ts ├── store │ ├── configureStore.development.js │ ├── configureStore.js │ ├── configureStore.production.js │ └── reducers.js ├── theme │ ├── _global.scss │ ├── _mixins.scss │ ├── _reset.scss │ ├── _utils.scss │ ├── _variables.scss │ ├── mixins │ │ └── _toolbar.scss │ └── toolbar.scss └── utils │ └── requireContext.ts ├── docs ├── API.md ├── Create_Plugin.md ├── Example.md └── README.md ├── package.json ├── resources ├── icon.icns ├── icon.ico ├── icon.png └── icons │ ├── 1024x1024.png │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ └── 96x96.png ├── server.js ├── test ├── .eslintrc ├── components │ ├── ControlPanel.spec.tsx │ └── SmartSlide-spec.tsx ├── e2e.spec.js ├── reducers │ └── slides │ │ ├── actions │ │ ├── addPluginToCurrentSlide-spec.ts │ │ ├── addSlide-spec.ts │ │ ├── deleteCurrentPlugin-spec.ts │ │ ├── deleteSlide-spec.ts │ │ ├── duplicateSlide-spec.ts │ │ ├── moveSlideDown-spec.ts │ │ ├── moveSlideUp-spec.ts │ │ ├── openFile-spec.ts │ │ ├── openNewDeck-spec.ts │ │ └── updateCurrentPlugin-spec.ts │ │ ├── slidesReducer.spec.ts │ │ └── test.dd └── setup.ts ├── tsconfig.json ├── tslint.json ├── webpack.config.base.js ├── webpack.config.development.js ├── webpack.config.electron.js ├── webpack.config.eslint.js ├── webpack.config.production.js └── webpack.config.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "plugins": ["add-module-exports"], 4 | "env": { 5 | "production": { 6 | "presets": ["react-optimize"], 7 | "plugins": ["babel-plugin-dev-expression"] 8 | }, 9 | "development": { 10 | "plugins": [ 11 | ["react-transform", { 12 | "transforms": [{ 13 | "transform": "react-transform-hmr", 14 | "imports": ["react"], 15 | "locals": ["module"] 16 | }] 17 | }] 18 | ] 19 | }, 20 | "test": { 21 | "plugins": [ 22 | ["webpack-loaders", { "config": "webpack.config.test.js", "verbose": false }] 23 | ] 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "env": { 5 | "browser": true, 6 | "mocha": true, 7 | "node": true 8 | }, 9 | "rules": { 10 | "arrow-parens": ["off"], 11 | "consistent-return": "off", 12 | "comma-dangle": "off", 13 | "generator-star-spacing": "off", 14 | "import/no-unresolved": ["error", { "ignore": ["electron"] }], 15 | "import/no-extraneous-dependencies": "off", 16 | "no-use-before-define": "off", 17 | "promise/param-names": 2, 18 | "promise/always-return": 2, 19 | "promise/catch-or-return": 2, 20 | "promise/no-native": 0, 21 | "react/jsx-no-bind": "off", 22 | "react/jsx-filename-extension": ["error", { "extensions": [".ts", ".tsx"] }], 23 | "react/prefer-stateless-function": "off" 24 | }, 25 | "plugins": [ 26 | "import", 27 | "promise", 28 | "react" 29 | ], 30 | "settings": { 31 | "import/resolver": { 32 | "webpack": { 33 | "config": "webpack.config.eslint.js" 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # * text eol=lf 2 | *.png binary 3 | *.ico binary 4 | *.icns binary 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | app/node_modules 29 | 30 | # OSX 31 | .DS_Store 32 | 33 | # App packaged 34 | release 35 | app/main.js 36 | app/main.js.map 37 | app/bundle.js 38 | app/bundle.js.map 39 | app/style.css 40 | app/style.css.map 41 | build 42 | dist 43 | main.js 44 | main.js.map 45 | 46 | # VSCODE 47 | /.vscode 48 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 7 5 | - 6 6 | - "node" 7 | 8 | cache: 9 | directories: 10 | - node_modules 11 | 12 | script: 13 | - npm test 14 | - npm run package 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present C. T. Lin 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. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DevDecks 2 | An open-source, desktop presentation app for developers. Built in Electron with React-Redux/TypeScript. 3 | 4 | ![Code Highlighting](http://i.giphy.com/26xBAr6CtQO0USZQA.gif) 5 | 6 | ### Docs & Help 7 | * [Guides and API docs](docs/README.md) 8 | 9 | ### Installation 10 | - To install the application for use: 11 | 1. download at: www.devdecks.io 12 | 13 | - To contribute to Devdecks development: 14 | 1. Create a local copy of devdecks (forked, cloned, or downloaded). 15 | 2. Navigate to the devdecks folder then ```npm install``` the dependencies. This may take a few minutes. 16 | 3. To launch the application, run ```npm run dev```. This may take a moment. Leave Terminal open while you are working on devdecks. 17 | 18 | - To create your own installation file 19 | - ```npm run package``` for macs 20 | - ```npm run package-win``` for windows 21 | - ```npm run package-linux``` for linux 22 | 23 | ### Getting Started 24 | - The two buttons on the top right allow you to add a slide and go into presentation mode. 25 | - To add content to a slide, use the three buttons above the main slide container. Currently text, image, and code content can be added to slide decks. 26 | - Once content is added, it can be moved and resized by dragging. 27 | - When a component is active, options will appear on the right sidebar. 28 | - If no component is selected, options for the current slide will appear, including the ability to reorder and delete the current slide [KNOWN ISSUE]. 29 | - Save or open your devdeck files through the menu bar or by pressing ⌘+S or ⌘+O, respectively. 30 | 31 | ### Known Issues 32 | - Once components are added to the slide, user cannot access current slide options unless user clicks on that slide on the mini-slides thumbnail. 33 | - Undo/redo causes issues with current slide number being out of sync with state when adding/deleting or switching slides (current fix is to restart app) - app still works 34 | - It takes more ‘undo’ action calls than should be necessary to undo an action. This is because the undo function is listening to more than just the actions that render a visible difference on the document 35 | 36 | ### User Feedback 37 | [Report bugs or request features](https://goo.gl/forms/W3b5t1DeYldvA8dO2) email: chad.devdecks@gmail.com Your feedback will be greatly appreciated! 38 | 39 | ### Upcoming Features 40 | Feel free to work on any of these features! 41 | - Allowing 3rd party plugins to be downloaded and consumed 42 | - Shapes -------------------------------------------------------------------------------- /app/actions/app.actions.ts: -------------------------------------------------------------------------------- 1 | import * as constants from 'constants/app.constants'; 2 | 3 | export function addThemeColor(color: string) { 4 | return { 5 | type: constants.ADD_THEME_COLOR, 6 | color, 7 | }; 8 | } 9 | 10 | export function goToSlide(slideNumber: number, maxSlides?: number) { 11 | return { 12 | type: constants.GO_TO_SLIDE, 13 | slideNumber, 14 | maxSlides, 15 | }; 16 | } 17 | 18 | export function leftArrowPrev() { 19 | return { 20 | type: constants.LEFT_ARROW_PREV, 21 | }; 22 | } 23 | 24 | export function rightArrowNext() { 25 | return { 26 | type: constants.RIGHT_ARROW_NEXT, 27 | }; 28 | } 29 | 30 | export function saveLastSlideDimensions() { 31 | return { 32 | type: constants.SAVE_LAST_SLIDE_DIMENSION, 33 | }; 34 | } 35 | 36 | export function setActivePlugin(moduleName?: string, pluginNumber?: number, slideNumber?: number) { 37 | const isPluginDeleted = !moduleName || pluginNumber === undefined || slideNumber === undefined; 38 | return { 39 | type: constants.SET_ACTIVE_PLUGIN, 40 | newActivePlugin: isPluginDeleted ? null : { moduleName, pluginNumber, slideNumber }, 41 | }; 42 | } 43 | 44 | export function toggleFullScreen() { 45 | return { 46 | type: constants.TOGGLE_FULLSCREEN, 47 | }; 48 | } 49 | 50 | export function toggleGuidelines() { 51 | return { 52 | type: constants.TOGGLE_GUIDELINES, 53 | }; 54 | } 55 | 56 | export function updateDeviceDimension(newDeviceDimension: { width: number, height: number }) { 57 | return { 58 | type: constants.UPDATE_DEVICE_DIMENSION, 59 | newDeviceDimension, 60 | }; 61 | } 62 | 63 | export function updateSlidesDimension(slidesDimension: { width: number; height: number; }) { 64 | return { 65 | type: constants.UPDATE_SLIDES_DIMENSION, 66 | slidesDimension, 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /app/actions/slides.actions.ts: -------------------------------------------------------------------------------- 1 | import * as constants from '../constants/slides.constants'; 2 | 3 | export function addPluginToCurrentSlide(plugin: any, slideNumber: number) { 4 | return { 5 | type: constants.ADD_PLUGIN_TO_CURRENT_SLIDE, 6 | plugin, 7 | slideNumber, 8 | }; 9 | } 10 | 11 | export function addSlide(currentSlide: number) { 12 | return { 13 | type: constants.ADD_SLIDE, 14 | currentSlide, 15 | }; 16 | } 17 | 18 | export function deleteCurrentPlugin(pluginNumber: any, slideNumber: number) { 19 | return { 20 | type: constants.DELETE_CURRENT_PLUGIN, 21 | pluginNumber, 22 | slideNumber, 23 | }; 24 | } 25 | 26 | export function deleteSlide(slideToDelete: number) { 27 | return { 28 | type: constants.DELETE_SLIDE, 29 | slideToDelete, 30 | }; 31 | } 32 | 33 | export function duplicateSlide(slideToDuplicate: number) { 34 | return { 35 | type: constants.DUPLICATE_SLIDE, 36 | slideToDuplicate, 37 | } 38 | } 39 | 40 | export function moveSlideDown(slideNumber: number) { 41 | return { 42 | type: constants.MOVE_SLIDE_DOWN, 43 | slideNumber, 44 | }; 45 | } 46 | 47 | export function moveSlideUp(slideNumber: number) { 48 | return { 49 | type: constants.MOVE_SLIDE_UP, 50 | slideNumber, 51 | }; 52 | } 53 | 54 | export function openFile(buffer_data: Buffer) { 55 | return { 56 | type: constants.OPEN_FILE, 57 | buffer_data, 58 | }; 59 | } 60 | 61 | export function openNewDeck() { 62 | return { 63 | type: constants.OPEN_NEW_DECK 64 | }; 65 | } 66 | 67 | export function updateCurrentPlugin(pluginNumber: number, slideNumber: number, changes: Object) { 68 | return { 69 | type: constants.UPDATE_CURRENT_PLUGIN, 70 | changes, 71 | pluginNumber, 72 | slideNumber, 73 | }; 74 | } 75 | 76 | export function updateCurrentSlide(slideNumber: number, changes: Object) { 77 | return { 78 | type: constants.UPDATE_CURRENT_SLIDE, 79 | changes, 80 | slideNumber, 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /app/app.global.scss: -------------------------------------------------------------------------------- 1 | @import './theme/_global'; 2 | @import './theme/_mixins'; 3 | @import './theme/_reset'; 4 | @import './theme/_variables'; 5 | @import './theme/_utils'; 6 | -------------------------------------------------------------------------------- /app/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DevDecks 6 | 8 | 19 | 20 | 21 |
22 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/app.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/app/app.icns -------------------------------------------------------------------------------- /app/constants/app.constants.ts: -------------------------------------------------------------------------------- 1 | export const ADD_THEME_COLOR = 'ADD_THEME_COLOR'; 2 | export const GO_TO_SLIDE = 'GO_TO_SLIDE'; 3 | export const LEFT_ARROW_PREV = 'LEFT_ARROW_PREV'; 4 | export const RIGHT_ARROW_NEXT = 'RIGHT_ARROW_NEXT'; 5 | export const SAVE_LAST_SLIDE_DIMENSION = 'SAVE_LAST_SLIDE_DIMENSION'; 6 | export const SET_ACTIVE_PLUGIN = 'SET_ACTIVE_PLUGIN'; 7 | export const TOGGLE_FULLSCREEN = 'TOGGLE_FULLSCREEN'; 8 | export const TOGGLE_GUIDELINES = 'TOGGLE_GUIDELINES'; 9 | export const UPDATE_DEVICE_DIMENSION = 'UPDATE_DEVICE_DIMENSION'; 10 | export const UPDATE_SLIDES_DIMENSION = 'UPDATE_SLIDES_DIMENSION'; 11 | -------------------------------------------------------------------------------- /app/constants/slides.constants.ts: -------------------------------------------------------------------------------- 1 | export const ADD_PLUGIN_TO_CURRENT_SLIDE = 'ADD_PLUGIN_TO_CURRENT_SLIDE'; 2 | export const ADD_SLIDE = 'ADD_SLIDE'; 3 | export const DELETE_CURRENT_PLUGIN = 'DELETE_CURRENT_PLUGIN'; 4 | export const DELETE_SLIDE = 'DELETE_SLIDE'; 5 | export const DUPLICATE_SLIDE = 'DUPLICATE_SLIDE'; 6 | export const MOVE_SLIDE_DOWN = 'MOVE_SLIDE_DOWN'; 7 | export const MOVE_SLIDE_UP = 'MOVE_SLIDE_UP'; 8 | export const OPEN_FILE = 'OPEN_FILE'; 9 | export const OPEN_NEW_DECK = 'OPEN_NEW_DECK'; 10 | export const UPDATE_CURRENT_PLUGIN = 'UPDATE_CURRENT_PLUGIN'; 11 | export const UPDATE_CURRENT_SLIDE = 'UPDATE_CURRENT_SLIDE'; 12 | -------------------------------------------------------------------------------- /app/constants/slides.enums.ts: -------------------------------------------------------------------------------- 1 | export enum EDirection { 2 | RIGHT, 3 | LEFT, 4 | } 5 | -------------------------------------------------------------------------------- /app/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import './app.global.scss'; 5 | 6 | import { 7 | App 8 | } from './modules'; 9 | 10 | const configureStore = require('./store/configureStore'); 11 | const store = configureStore(); 12 | 13 | render( 14 | 15 | 16 | , 17 | document.getElementById('root') 18 | ); 19 | -------------------------------------------------------------------------------- /app/main.development.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { app, BrowserWindow, Menu, shell } from 'electron'; 3 | 4 | const PLATFORM = process.platform; 5 | 6 | let menu; 7 | let template; 8 | let mainWindow = null; 9 | 10 | if (process.env.NODE_ENV === 'production') { 11 | const sourceMapSupport = require('source-map-support'); // eslint-disable-line 12 | sourceMapSupport.install(); 13 | } 14 | 15 | if (process.env.NODE_ENV === 'development') { 16 | require('electron-debug')(); // eslint-disable-line global-require 17 | const path = require('path'); // eslint-disable-line 18 | const p = path.join(__dirname, '..', 'app', 'node_modules'); // eslint-disable-line 19 | require('module').globalPaths.push(p); // eslint-disable-line 20 | } 21 | 22 | app.on('window-all-closed', () => { 23 | if (PLATFORM !== 'darwin') app.quit(); 24 | }); 25 | 26 | 27 | const installExtensions = async () => { 28 | if (process.env.NODE_ENV === 'development') { 29 | const installer = require('electron-devtools-installer'); // eslint-disable-line global-require 30 | 31 | const extensions = [ 32 | 'REACT_DEVELOPER_TOOLS', 33 | 'REDUX_DEVTOOLS' 34 | ]; 35 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS; 36 | for (const name of extensions) { // eslint-disable-line 37 | try { 38 | await installer.default(installer[name], forceDownload); 39 | } catch (e) {} // eslint-disable-line 40 | } 41 | } 42 | }; 43 | 44 | app.on('ready', async () => { 45 | await installExtensions(); 46 | 47 | mainWindow = new BrowserWindow({ 48 | icon: path.resolve('resources/icon.png'), 49 | minWidth: 800, 50 | minHeight: 600, 51 | show: false, 52 | title: 'DevDecks', 53 | }); 54 | 55 | mainWindow.loadURL(`file://${__dirname}/app.html`); 56 | 57 | // NOTE: BOTH 58 | mainWindow.webContents.on('did-finish-load', () => { 59 | mainWindow.show(); 60 | mainWindow.focus(); 61 | }); 62 | 63 | mainWindow.on('closed', () => { 64 | mainWindow = null; 65 | }); 66 | 67 | if (process.env.NODE_ENV === 'development') { 68 | mainWindow.openDevTools(); 69 | mainWindow.webContents.on('context-menu', (e, props) => { 70 | const { x, y } = props; 71 | 72 | Menu.buildFromTemplate([{ 73 | label: 'Inspect element', 74 | click() { 75 | mainWindow.inspectElement(x, y); 76 | } 77 | }]).popup(mainWindow); 78 | }); 79 | } 80 | 81 | if (PLATFORM === 'darwin') { 82 | template = [{ 83 | label: 'DevDecks', 84 | submenu: [{ 85 | label: 'About DevDecks', 86 | selector: 'orderFrontStandardAboutPanel:' 87 | }, { 88 | type: 'separator' 89 | }, { 90 | label: 'Hide DevDecks', 91 | accelerator: 'Command+H', 92 | selector: 'hide:' 93 | }, { 94 | label: 'Hide Others', 95 | accelerator: 'Command+Shift+H', 96 | selector: 'hideOtherApplications:' 97 | }, { 98 | label: 'Show All', 99 | selector: 'unhideAllApplications:' 100 | }, { 101 | type: 'separator' 102 | }, { 103 | label: 'Quit', 104 | accelerator: 'Command+Q', 105 | click() { 106 | app.quit(); 107 | } 108 | }] 109 | }, { 110 | label: 'File', 111 | submenu: [{ 112 | label: 'New', 113 | accelerator: 'Command+N', 114 | click() { 115 | mainWindow.send('newDeck'); 116 | }, 117 | }, { 118 | label: 'Open', 119 | accelerator: 'Command+O', 120 | click() { 121 | mainWindow.send('openFile'); 122 | }, 123 | }, { 124 | label: 'Save', 125 | accelerator: 'Command+S', 126 | click() { 127 | mainWindow.send('saveFile'); 128 | } 129 | }, { 130 | label: 'Save As...', 131 | accelerator: 'Shift+Command+S', 132 | click() { 133 | mainWindow.send('saveFileAs'); 134 | } 135 | }] 136 | }, { 137 | label: 'Edit', 138 | submenu: [{ 139 | label: 'Undo', 140 | accelerator: 'Command+Z', 141 | selector: 'undo:', 142 | click() { 143 | mainWindow.send('undo'); 144 | }, 145 | }, { 146 | label: 'Redo', 147 | accelerator: 'Shift+Command+Z', 148 | selector: 'redo:', 149 | click() { 150 | mainWindow.send('redo'); 151 | }, 152 | }, { 153 | type: 'separator' 154 | }, { 155 | label: 'Cut', 156 | accelerator: 'Command+X', 157 | selector: 'cut:' 158 | }, { 159 | label: 'Copy', 160 | accelerator: 'Command+C', 161 | selector: 'copy:' 162 | }, { 163 | label: 'Paste', 164 | accelerator: 'Command+V', 165 | selector: 'paste:' 166 | }, { 167 | label: 'Select All', 168 | accelerator: 'Command+A', 169 | selector: 'selectAll:' 170 | }] 171 | }, { 172 | label: 'Slides', 173 | submenu: [{ 174 | label: 'Add Slide', 175 | accelerator: 'Command+Plus', 176 | click() { 177 | mainWindow.send('addSlide'); 178 | } 179 | }, { 180 | label: 'Delete Slide', 181 | accelerator: 'Command+Backspace', 182 | click() { 183 | mainWindow.send('deleteSlide'); 184 | } 185 | }, { 186 | label: 'Duplicate Slide', 187 | accelerator: 'Command+D', 188 | click() { 189 | mainWindow.send('duplicateSlide'); 190 | } 191 | }, { 192 | type: 'separator' 193 | }, { 194 | label: 'Move Current Slide Up', 195 | accelerator: 'Alt+Up', 196 | click() { 197 | mainWindow.send('moveSlideUp'); 198 | } 199 | }, { 200 | label: 'Move Current Slide Down', 201 | accelerator: 'Alt+Down', 202 | click() { 203 | mainWindow.send('moveSlideDown'); 204 | } 205 | }] 206 | }, { 207 | label: 'View', 208 | submenu: (process.env.NODE_ENV === 'development') ? [{ 209 | label: 'Reload', 210 | accelerator: 'Command+R', 211 | click() { 212 | mainWindow.webContents.reload(); 213 | } 214 | }, { 215 | label: 'Toggle Full Screen', 216 | accelerator: 'Ctrl+Command+F', 217 | click() { 218 | mainWindow.send('toggleFullScreen'); 219 | } 220 | }, { 221 | label: 'Toggle Developer Tools', 222 | accelerator: 'Alt+Command+I', 223 | click() { 224 | mainWindow.toggleDevTools(); 225 | } 226 | }] : [{ 227 | label: 'Toggle Developer Tools', 228 | accelerator: 'Alt+Command+I', 229 | click() { 230 | mainWindow.toggleDevTools(); 231 | } 232 | }, { 233 | label: 'Toggle Full Screen', 234 | accelerator: 'Ctrl+Command+F', 235 | click() { 236 | mainWindow.send('toggleFullScreen'); 237 | } 238 | }] 239 | }, { 240 | label: 'Window', 241 | submenu: [{ 242 | label: 'Minimize', 243 | accelerator: 'Command+M', 244 | selector: 'performMiniaturize:' 245 | }, { 246 | label: 'Close', 247 | accelerator: 'Command+W', 248 | selector: 'performClose:' 249 | }, { 250 | type: 'separator' 251 | }, { 252 | label: 'Bring All to Front', 253 | selector: 'arrangeInFront:' 254 | }] 255 | }, { 256 | label: 'Help', 257 | submenu: [{ 258 | label: 'GitHub', 259 | click() { 260 | shell.openExternal('https://github.com/Team-CHAD/DevDecks'); 261 | } 262 | }] 263 | }]; 264 | 265 | menu = Menu.buildFromTemplate(template); 266 | Menu.setApplicationMenu(menu); 267 | } else { 268 | template = [{ 269 | label: '&File', 270 | submenu: [{ 271 | label: '&New', 272 | accelerator: 'Ctrl+N', 273 | click() { 274 | mainWindow.send('newDeck'); 275 | } 276 | }, { 277 | label: '&Open', 278 | accelerator: 'Ctrl+O', 279 | click() { 280 | mainWindow.send('openFile'); 281 | } 282 | }, { 283 | label: '&Save', 284 | accelerator: 'Ctrl+S', 285 | click() { 286 | mainWindow.send('saveFile'); 287 | } 288 | }, { 289 | label: 'Save As...', 290 | accelerator: 'Ctrl+Shift+S', 291 | click() { 292 | mainWindow.send('saveFileAs'); 293 | } 294 | }, { 295 | label: '&Close', 296 | accelerator: 'Ctrl+W', 297 | click() { 298 | mainWindow.close(); 299 | } 300 | }] 301 | }, { 302 | label: 'Edit', 303 | submenu: [{ 304 | label: 'Undo', 305 | accelerator: 'Ctrl+Z', 306 | click() { 307 | mainWindow.send('undo'); 308 | }, 309 | }, { 310 | label: 'Redo', 311 | accelerator: 'Shift+Ctrl+Z', 312 | click() { 313 | mainWindow.send('redo'); 314 | }, 315 | }] 316 | }, { 317 | label: 'Slides', 318 | submenu: [{ 319 | label: 'Add Slide', 320 | accelerator: 'Ctrl+Plus', 321 | click() { 322 | mainWindow.send('addSlide'); 323 | } 324 | }, { 325 | label: 'Delete Slide', 326 | accelerator: 'Ctrl+Shift+Backspace', 327 | click() { 328 | mainWindow.send('deleteSlide'); 329 | } 330 | }, { 331 | label: 'Duplicate Slide', 332 | accelerator: 'Ctrl+D', 333 | click() { 334 | mainWindow.send('duplicateSlide'); 335 | } 336 | }, { 337 | type: 'separator' 338 | }, { 339 | label: 'Move Current Slide Up', 340 | accelerator: 'Alt+Up', 341 | click() { 342 | mainWindow.send('moveSlideUp'); 343 | } 344 | }, { 345 | label: 'Move Current Slide Down', 346 | accelerator: 'Alt+Down', 347 | click() { 348 | mainWindow.send('moveSlideDown'); 349 | } 350 | }] 351 | }, { 352 | label: '&View', 353 | submenu: (process.env.NODE_ENV === 'development') ? [{ 354 | label: '&Reload', 355 | accelerator: 'Ctrl+R', 356 | click() { 357 | mainWindow.webContents.reload(); 358 | } 359 | }, { 360 | label: 'Toggle &Full Screen', 361 | accelerator: 'F11', 362 | click() { 363 | mainWindow.send('toggleFullScreen'); 364 | } 365 | }, { 366 | label: 'Toggle &Developer Tools', 367 | accelerator: 'Alt+Ctrl+I', 368 | click() { 369 | mainWindow.toggleDevTools(); 370 | } 371 | }] : [{ 372 | label: 'Toggle &Developer Tools', 373 | accelerator: 'Alt+Ctrl+I', 374 | click() { 375 | mainWindow.toggleDevTools(); 376 | } 377 | }, { 378 | label: 'Toggle &Full Screen', 379 | accelerator: 'F11', 380 | click() { 381 | mainWindow.send('toggleFullScreen'); 382 | } 383 | }] 384 | }, { 385 | label: 'Help', 386 | submenu: [{ 387 | label: 'GitHub', 388 | click() { 389 | shell.openExternal('https://github.com/Team-CHAD/DevDecks'); 390 | } 391 | }] 392 | }]; 393 | menu = Menu.buildFromTemplate(template); 394 | mainWindow.setMenu(menu); 395 | } 396 | }); 397 | -------------------------------------------------------------------------------- /app/modules/App/App.tsx: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as React from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { ipcRenderer, remote } from 'electron'; 5 | import { EDirection } from 'constants/slides.enums'; 6 | import '@blueprintjs/core/dist/blueprint.css'; 7 | 8 | import { 9 | goToSlide, 10 | leftArrowPrev, 11 | rightArrowNext, 12 | setActivePlugin, 13 | toggleFullScreen, 14 | updateDeviceDimension, 15 | updateSlidesDimension, 16 | } from 'actions/app.actions'; 17 | 18 | import { 19 | addSlide, 20 | deleteSlide, 21 | duplicateSlide, 22 | moveSlideDown, 23 | moveSlideUp, 24 | openFile, 25 | openNewDeck, 26 | } from 'actions/slides.actions'; 27 | 28 | import EditView from './EditView/EditView'; 29 | import FullscreenView from './FullscreenView/FullscreenView'; 30 | 31 | const throttle = require('lodash.throttle'); 32 | const { ActionCreators } = require('redux-undo'); 33 | 34 | const PLATFORM = process.platform; 35 | const TITLE = 'DevDecks'; 36 | 37 | interface IDimensions { 38 | width: number; 39 | height: number; 40 | } 41 | 42 | interface AppComponentProps { 43 | deviceDimension: IDimensions; 44 | direction: EDirection; 45 | isDragging: boolean; 46 | isFullScreen: boolean; 47 | lastSavedSlideDimensions: IDimensions; 48 | maxSlides: number; 49 | slide: Object; 50 | slides: Array; 51 | slideNumber: number; 52 | slidesDimension: IDimensions; 53 | 54 | addSlide: Function; 55 | deleteSlide: Function; 56 | duplicateSlide: Function; 57 | goToSlide: Function; 58 | leftArrowPrev: Function; 59 | moveSlideDown: Function; 60 | moveSlideUp: Function; 61 | openFile: Function; 62 | openNewDeck: Function; 63 | rightArrowNext: Function; 64 | setActivePlugin: Function; 65 | toggleFullScreen: any; 66 | updateDeviceDimension: Function; 67 | updateSlidesDimension: Function; 68 | redo: any; 69 | undo: any; 70 | clearHist: Function; 71 | } 72 | 73 | interface AppComponentStates { 74 | representedFilename: string; 75 | } 76 | 77 | class AppComponent extends React.Component { 78 | public constructor() { 79 | super(); 80 | this.handleAddSlide = this.handleAddSlide.bind(this); 81 | this.handleDeleteSlide = this.handleDeleteSlide.bind(this); 82 | this.handleDuplicateSlide = this.handleDuplicateSlide.bind(this); 83 | this.handleMoveSlideDown = this.handleMoveSlideDown.bind(this); 84 | this.handleMoveSlideUp = this.handleMoveSlideUp.bind(this); 85 | this.handleNewDeck = this.handleNewDeck.bind(this); 86 | this.handleOpenFile = this.handleOpenFile.bind(this); 87 | this.handleSaveDialog = this.handleSaveDialog.bind(this); 88 | this.handleSaveFile = this.handleSaveFile.bind(this); 89 | this.handleSaveFileAs = this.handleSaveFileAs.bind(this); 90 | this.handleSlidesTransition = this.handleSlidesTransition.bind(this); 91 | this.handleResize = throttle(this.handleResize.bind(this), 300); 92 | this.state = { 93 | representedFilename: '', 94 | } 95 | } 96 | 97 | private handleAddSlide() { 98 | const { addSlide, goToSlide, slideNumber } = this.props; 99 | addSlide(slideNumber); 100 | goToSlide(slideNumber + 1); 101 | } 102 | 103 | private handleDeleteSlide() { 104 | const { maxSlides, slideNumber, addSlide, deleteSlide, goToSlide, setActivePlugin } = this.props; 105 | setActivePlugin(); 106 | if (maxSlides - 1 < 1) { 107 | addSlide(); 108 | deleteSlide(slideNumber - 1); 109 | goToSlide(0); 110 | } else if (slideNumber === maxSlides - 1) { 111 | goToSlide(maxSlides - 2); 112 | deleteSlide(slideNumber); 113 | } else { 114 | deleteSlide(slideNumber); 115 | goToSlide(slideNumber); 116 | } 117 | } 118 | 119 | private handleDuplicateSlide() { 120 | const { slideNumber, duplicateSlide, goToSlide } = this.props; 121 | duplicateSlide(slideNumber); 122 | goToSlide(slideNumber + 1); 123 | } 124 | 125 | private handleMoveSlideDown() { 126 | const { goToSlide, slideNumber, moveSlideDown } = this.props; 127 | moveSlideDown(slideNumber); 128 | goToSlide(slideNumber - 1); 129 | } 130 | 131 | private handleMoveSlideUp() { 132 | const { maxSlides, slideNumber, goToSlide, moveSlideUp } = this.props; 133 | moveSlideUp(slideNumber); 134 | goToSlide(slideNumber + 1, maxSlides); 135 | } 136 | 137 | private handleNewDeck() { 138 | const { goToSlide, openNewDeck, clearHist } = this.props; 139 | const options: any = { 140 | type: 'warning', 141 | buttons: ['cancel', 'exit without save', 'save'], 142 | defaultId: 2, 143 | message: 'Save before exiting?', 144 | cancelId: 0, 145 | noLink: true, 146 | } 147 | remote.dialog.showMessageBox(options, (response: number) => { 148 | if (response === 0) return; 149 | if (response === 1) { 150 | goToSlide(0); 151 | openNewDeck(); 152 | clearHist(); 153 | return; 154 | } 155 | if (response === 2) { 156 | goToSlide(0); 157 | this.handleSaveFile(); 158 | openNewDeck(); 159 | clearHist(); 160 | return; 161 | } 162 | }); 163 | } 164 | 165 | private handleResize(): void { 166 | const slidesElement = document.getElementById('edit-slide-view'); 167 | if (!slidesElement) return null; 168 | 169 | const { updateSlidesDimension } = this.props; 170 | const { clientWidth: width, clientHeight: height } = slidesElement; 171 | 172 | updateSlidesDimension({ width, height }); 173 | } 174 | 175 | private handleOpenFile() { 176 | const { clearHist, goToSlide, openFile } = this.props; 177 | const options: any = { 178 | filters: [ 179 | { 180 | name: 'DevDecks', 181 | extensions: ['dd'] 182 | } 183 | ] 184 | }; 185 | remote.dialog.showOpenDialog(options, (filePaths: string[]) => { 186 | if (!filePaths) return; 187 | fs.readFile(filePaths[0], (err: Error, data: Buffer) => { 188 | if (err) return; 189 | 190 | // Ensures that slide 0 is active before rendering new slides 191 | // This prevents an error when previous active slide does not exist 192 | goToSlide(0); 193 | 194 | openFile(data); 195 | this.setState({ representedFilename: filePaths[0] }); 196 | remote.getCurrentWindow().setTitle(`${filePaths[0]} - ${TITLE}`); 197 | clearHist(); 198 | }); 199 | }); 200 | } 201 | 202 | private handleSaveDialog() { 203 | const { slides } = this.props; 204 | const { representedFilename } = this.state; 205 | const data = JSON.stringify(slides); 206 | 207 | const options: any = { 208 | filters: [ 209 | { 210 | name: 'DevDecks', 211 | extensions: ['dd'] 212 | } 213 | ] 214 | }; 215 | 216 | remote.dialog.showSaveDialog(options, (filename: string) => { 217 | if (!filename) return; 218 | fs.writeFile(filename, data); 219 | 220 | this.setState({ representedFilename: filename }) 221 | remote.getCurrentWindow().setTitle(`${filename} - ${TITLE}`) 222 | }); 223 | } 224 | 225 | private handleSaveFile() { 226 | const { slides } = this.props; 227 | const { representedFilename } = this.state; 228 | const data = JSON.stringify(slides); 229 | 230 | if (representedFilename) fs.writeFile(representedFilename, data); 231 | else this.handleSaveDialog(); 232 | } 233 | 234 | private handleSaveFileAs() { 235 | this.handleSaveDialog(); 236 | } 237 | 238 | private handleSlidesTransition(event: any): void { 239 | const { 240 | isFullScreen, 241 | leftArrowPrev, 242 | rightArrowNext, 243 | slideNumber, 244 | slides, 245 | toggleFullScreen 246 | } = this.props; 247 | 248 | if (!isFullScreen) return null; 249 | 250 | const isBeginning = slides[slideNumber - 1] === undefined ? true : false; 251 | const isLast = slides[slideNumber + 1] === undefined ? true : false; 252 | 253 | if (event.keyCode === 37 && !isBeginning) leftArrowPrev(); 254 | else if (event.keyCode === 39 && !isLast) rightArrowNext(); 255 | else if (event.keyCode === 27) toggleFullScreen(); 256 | } 257 | 258 | public componentWillMount() { 259 | const { toggleFullScreen, redo, undo } = this.props; 260 | ipcRenderer.on('addSlide', this.handleAddSlide); 261 | ipcRenderer.on('deleteSlide', this.handleDeleteSlide); 262 | ipcRenderer.on('duplicateSlide', this.handleDuplicateSlide); 263 | ipcRenderer.on('moveSlideDown', this.handleMoveSlideUp); 264 | ipcRenderer.on('moveSlideUp', this.handleMoveSlideDown); 265 | ipcRenderer.on('newDeck', this.handleNewDeck); 266 | ipcRenderer.on('openFile', this.handleOpenFile); 267 | ipcRenderer.on('saveFile', this.handleSaveFile); 268 | ipcRenderer.on('saveFileAs', this.handleSaveFileAs); 269 | ipcRenderer.on('toggleFullScreen', toggleFullScreen); 270 | ipcRenderer.on('redo', redo); 271 | ipcRenderer.on('undo', undo); 272 | window.addEventListener('keydown', this.handleSlidesTransition); 273 | } 274 | 275 | public componentDidMount(): void { 276 | const { deviceDimension, updateSlidesDimension } = this.props; 277 | const slidesElement = document.getElementById('edit-slide-view'); 278 | const { clientWidth, clientHeight } = slidesElement; 279 | 280 | window.addEventListener('resize', this.handleResize); 281 | 282 | updateSlidesDimension({ width: clientWidth, height: (deviceDimension.height * clientWidth) / deviceDimension.width }); 283 | } 284 | 285 | public componentWillUnMount() { 286 | ipcRenderer.removeAllListeners(); 287 | window.removeEventListener('keydown', this.handleSlidesTransition); 288 | window.removeEventListener('resize', this.handleResize); 289 | } 290 | 291 | public render() { 292 | const { 293 | deviceDimension, 294 | direction, 295 | isDragging, 296 | isFullScreen, 297 | lastSavedSlideDimensions, 298 | slide, 299 | slides, 300 | slideNumber, 301 | slidesDimension, 302 | 303 | leftArrowPrev, 304 | rightArrowNext, 305 | toggleFullScreen, 306 | undo, 307 | redo, 308 | updateDeviceDimension, 309 | } = this.props; 310 | 311 | return ( 312 |
313 | { 314 | isFullScreen ? 315 | : 320 | 328 | } 329 |
330 | ); 331 | } 332 | } 333 | 334 | const mapStateToProps= (state: any) => ({ 335 | deviceDimension: state.app.present.deviceDimension, 336 | direction: state.app.present.direction, 337 | isDragging: state.app.present.isDragging, 338 | isFullScreen: state.app.present.isFullScreen, 339 | lastSavedSlideDimensions: state.app.present.lastSavedSlideDimensions, 340 | maxSlides: state.slides.present.length, 341 | slide: state.slides.present[state.app.present.currentSlide], 342 | slideNumber: state.app.present.currentSlide, 343 | slides: state.slides.present, 344 | slidesDimension: state.app.present.slidesDimension, 345 | }); 346 | 347 | const mapDispatchToProps = (dispatch: any) => ({ 348 | addSlide: (currentSlide: number) => dispatch(addSlide(currentSlide)), 349 | deleteSlide: (currentSlide: number) => dispatch(deleteSlide(currentSlide)), 350 | duplicateSlide: (slideToDuplicate: number) => dispatch(duplicateSlide(slideToDuplicate)), 351 | goToSlide: (slideNumber: number, maxSlides: number) => dispatch(goToSlide(slideNumber, maxSlides)), 352 | moveSlideDown: (slideNumber: number) => dispatch(moveSlideDown(slideNumber)), 353 | moveSlideUp: (slideNumber: number) => dispatch(moveSlideUp(slideNumber)), 354 | leftArrowPrev: () => dispatch(leftArrowPrev()), 355 | openFile: (newStateFromFile: Buffer) => dispatch(openFile(newStateFromFile)), 356 | openNewDeck: () => dispatch(openNewDeck()), 357 | rightArrowNext: () => dispatch(rightArrowNext()), 358 | setActivePlugin: () => dispatch(setActivePlugin()), 359 | toggleFullScreen: () => dispatch(toggleFullScreen()), 360 | updateDeviceDimension: ( 361 | newDeviceDimension: { 362 | width: number, 363 | height: number 364 | } 365 | ) => dispatch(updateDeviceDimension(newDeviceDimension)), 366 | updateSlidesDimension: ( 367 | slidesDimension: { 368 | width: number, 369 | height: number 370 | } 371 | ) => dispatch(updateSlidesDimension(slidesDimension)), 372 | undo: () => dispatch(ActionCreators.undo()), 373 | redo: () => dispatch(ActionCreators.redo()), 374 | clearHist: () => dispatch(ActionCreators.clearHistory()), 375 | }); 376 | 377 | const App = connect(mapStateToProps, mapDispatchToProps)(AppComponent as any); 378 | 379 | export { App }; 380 | -------------------------------------------------------------------------------- /app/modules/App/EditView/EditView.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import './edit-view.scss'; 3 | 4 | import { 5 | MiniSlidesPanel, 6 | Scale, 7 | SmartSlide, 8 | ToolBar, 9 | UtilitiesMenu, 10 | } from 'modules'; 11 | 12 | import SettingsMenu from './SettingsMenu/SettingsMenu'; 13 | 14 | interface IDimensions { 15 | width: number; 16 | height: number; 17 | } 18 | 19 | interface EditViewProps { 20 | deviceDimension: IDimensions; 21 | isDragging: boolean; 22 | lastSavedSlideDimensions: IDimensions; 23 | slide: any; 24 | slidesDimension: IDimensions; 25 | thumbnailsDimension: IDimensions; 26 | updateDeviceDimension: Function; 27 | } 28 | 29 | const EditView = ({ 30 | deviceDimension, 31 | isDragging, 32 | lastSavedSlideDimensions, 33 | slide, 34 | slidesDimension, 35 | thumbnailsDimension, 36 | updateDeviceDimension, 37 | }: EditViewProps) => { 38 | const EDIT_VIEW_WIDTH = '100vw'; 39 | const UTILITIES_MENU_WIDTH = 295; 40 | 41 | const scale = Math.min( slidesDimension.width / deviceDimension.width, slidesDimension.height / deviceDimension.height); 42 | const { r, g, b, a } = slide.state.backgroundColor; 43 | 44 | return ( 45 |
46 | 47 | 48 | 49 |
50 | 51 | 57 | 58 |
59 |
66 | 67 | 68 | 69 |
72 |
75 |
76 |
77 | 78 |
79 | 80 | 81 | 82 |
83 | ); 84 | }; 85 | 86 | export default EditView; 87 | -------------------------------------------------------------------------------- /app/modules/App/EditView/SettingsMenu/SettingsMenu.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Button, Menu, MenuItem, Popover, Position } from '@blueprintjs/core'; 3 | import './settings-menu.scss'; 4 | 5 | interface SettingsMenu { 6 | deviceDimension: { 7 | width: number; 8 | height: number; 9 | }; 10 | updateDeviceDimension: Function; 11 | } 12 | 13 | const SettingsMenu = ({ deviceDimension, updateDeviceDimension }: SettingsMenu) => { 14 | const screenSizes = [[1920, 1080], [1366, 768], [1280, 1024], [1280, 800], [1024, 768]]; 15 | 16 | const screenSizeSelection = ( 17 | 18 | { 19 | screenSizes.map((screenSize, key) => ( 20 | updateDeviceDimension({ width: screenSize[0], height: screenSize[1] }) }/> 24 | )) 25 | } 26 | 27 | ); 28 | 29 | return ( 30 |
31 | 34 |
40 | ); 41 | }; 42 | 43 | export default SettingsMenu; 44 | -------------------------------------------------------------------------------- /app/modules/App/EditView/SettingsMenu/settings-menu.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/app/modules/App/EditView/SettingsMenu/settings-menu.scss -------------------------------------------------------------------------------- /app/modules/App/EditView/edit-view.scss: -------------------------------------------------------------------------------- 1 | /* NOTE: Variables do not work within calc function */ 2 | 3 | #container { 4 | background-color: rgba(0, 0, 0, 1); 5 | height: 100vh; 6 | } 7 | 8 | #main-content-wrapper { 9 | margin: 0 1em; 10 | } 11 | 12 | #menu-bar-wrapper { 13 | margin: 5px 0; 14 | } 15 | 16 | #edit-slide-view { 17 | position: relative; 18 | border: 2px dashed rgba(100, 100, 100, .5); 19 | } 20 | 21 | .vertical-guideline { 22 | border-left: 2px dotted rgba(255, 128, 128, .3); 23 | position: relative; 24 | float: left; 25 | right: -50%; 26 | } 27 | 28 | .horizontal-guideline { 29 | border-top: 2px dotted rgba(255, 128, 128, .3); 30 | position: absolute; 31 | top: 50%; 32 | bottom: 50%; 33 | } -------------------------------------------------------------------------------- /app/modules/App/FullscreenView/FullscreenView.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { DummySlide } from 'modules'; 3 | import { EDirection } from 'constants/slides.enums'; 4 | import './fullscreen-view.scss'; 5 | 6 | const ReactTransitions = require('react-transitions'); 7 | require('react-transitions/dist/animations.css'); 8 | 9 | interface FullScreenViewProps { 10 | deviceDimension: { 11 | width: number; 12 | height: number; 13 | }; 14 | direction: EDirection; 15 | isFullscreen: boolean; 16 | slide: any; 17 | } 18 | 19 | const FullScreenView = ({ 20 | deviceDimension, 21 | direction, 22 | isFullscreen, 23 | slide 24 | }: FullScreenViewProps) => { 25 | const { r, g, b, a } = slide.state.backgroundColor; 26 | const { state: { transition } } = slide; 27 | return ( 28 |
29 | 37 |
44 | 47 |
48 |
49 |
50 | ); 51 | }; 52 | 53 | export default FullScreenView; 54 | -------------------------------------------------------------------------------- /app/modules/App/FullscreenView/fullscreen-view.scss: -------------------------------------------------------------------------------- 1 | #fullscreen-view { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | height: 100vh; 6 | background-color: #000; 7 | overflow: hidden; 8 | } 9 | -------------------------------------------------------------------------------- /app/modules/ControlPanel/ControlPanel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Button } from '@blueprintjs/core'; 4 | import { 5 | goToSlide, 6 | saveLastSlideDimensions, 7 | toggleFullScreen, 8 | } from 'actions/app.actions'; 9 | import { addSlide } from 'actions/slides.actions'; 10 | import './control-panel.scss'; 11 | 12 | interface ControlPanelProps { 13 | currentSlide: number; 14 | numberOfSlides: number; 15 | 16 | addSlide: Function; 17 | goToSlide: Function; 18 | saveLastSlideDimensions: Function; 19 | toggleFullScreen: Function; 20 | } 21 | 22 | export class ControlPanelComponent extends React.Component { 23 | render() { 24 | const { 25 | currentSlide, 26 | numberOfSlides, 27 | 28 | addSlide, 29 | goToSlide, 30 | saveLastSlideDimensions, 31 | toggleFullScreen, 32 | } = this.props; 33 | 34 | return ( 35 |
36 |
51 | ); 52 | } 53 | } 54 | 55 | const mapStateToProps = (state: any) => ({ 56 | currentSlide: state.app.present.currentSlide, 57 | numberOfSlides: state.slides.present.length, 58 | }); 59 | 60 | const mapDispatchToProps = (dispatch: any) => ({ 61 | addSlide: (currentSlide: number) => dispatch(addSlide(currentSlide)), 62 | goToSlide: (slideNumber: number) => dispatch(goToSlide(slideNumber)), 63 | saveLastSlideDimensions: () => dispatch(saveLastSlideDimensions()), 64 | toggleFullScreen: () => dispatch(toggleFullScreen()), 65 | }); 66 | 67 | const ControlPanel = connect(mapStateToProps, mapDispatchToProps)(ControlPanelComponent as any); 68 | 69 | export { ControlPanel }; 70 | -------------------------------------------------------------------------------- /app/modules/ControlPanel/control-panel.scss: -------------------------------------------------------------------------------- 1 | #control-panel-container { 2 | width: 100%; 3 | background-color: #000; 4 | text-align: center; 5 | margin-bottom: 20px; 6 | padding: 6px 0; 7 | 8 | .pt-button { 9 | margin: 0 5px; 10 | opacity: 0.6; 11 | 12 | &:hover { 13 | opacity: 1.0; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /app/modules/DummySlide/DummySlide.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import req from 'utils/requireContext'; 3 | 4 | interface DummySlideProps { 5 | isFullscreen?: boolean; 6 | slide: any; 7 | slidesDimension?: any; 8 | } 9 | 10 | const DummySlide = ({ 11 | isFullscreen, 12 | slide, 13 | slidesDimension 14 | }: DummySlideProps) => ( 15 |
16 | { 17 | slide.plugins.map((plugin: any, key: number) => { 18 | // When plugin is deleted from plugins array, their position is not 19 | // removed rather the value is set to null 20 | if (!plugin) return null; 21 | 22 | const { moduleName, state: { width, height, left, top } } = plugin; 23 | const Plugin = req(moduleName).component; 24 | 25 | return ( 26 |
27 | 31 |
32 | ); 33 | }) 34 | } 35 |
36 | ); 37 | 38 | export { DummySlide }; 39 | -------------------------------------------------------------------------------- /app/modules/MiniSlidesPanel/MiniSlidesPanel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { DummySlide, Scale } from 'modules'; 4 | import { goToSlide, setActivePlugin } from 'actions/app.actions'; 5 | import './mini-slide-panel.scss'; 6 | 7 | interface IDimensions { 8 | width: number; 9 | height: number; 10 | } 11 | 12 | interface MiniSlidesPanelProps { 13 | currentSlideNumber: number; 14 | deviceDimension: IDimensions; 15 | slidesDimension: IDimensions; 16 | slides: any; 17 | goToSlide: Function; 18 | setActivePlugin: Function; 19 | } 20 | 21 | class MiniSlidesPanelComponent extends React.Component { 22 | render() { 23 | const THUMBNAILS_SCALE = 15; 24 | 25 | const { 26 | currentSlideNumber, 27 | deviceDimension, 28 | slides, 29 | slidesDimension, 30 | goToSlide, 31 | setActivePlugin, 32 | } = this.props; 33 | 34 | const thumbnailsDimension = { 35 | width: deviceDimension.width / THUMBNAILS_SCALE, 36 | height: deviceDimension.height / THUMBNAILS_SCALE 37 | }; 38 | 39 | const scale = Math.min( 40 | thumbnailsDimension.width / deviceDimension.width, 41 | thumbnailsDimension.height / deviceDimension.height 42 | ); 43 | 44 | return ( 45 |
    46 | { 47 | slides.map((slide: any, key: number) => { 48 | const { r, g, b, a } = slide.state.backgroundColor; 49 | return ( 50 |
  • 54 | { key } 55 |
    goToSlide(key) }> 63 | 64 | 67 | 68 |
    69 |
  • 70 | ); 71 | }) 72 | } 73 |
74 | ); 75 | } 76 | } 77 | 78 | const mapStateToProps = (state: any, props: any) => ({ 79 | currentSlideNumber: state.app.present.currentSlide, 80 | deviceDimension: state.app.present.deviceDimension, 81 | slides: state.slides.present, 82 | slidesDimension: state.app.present.slidesDimension, 83 | }); 84 | 85 | const mapDispatchToProps = (dispatch: any) => ({ 86 | goToSlide: (slideNumber: number) => dispatch(goToSlide(slideNumber)), 87 | setActivePlugin: () => dispatch(setActivePlugin()), 88 | }); 89 | 90 | const MiniSlidesPanel = connect(mapStateToProps, mapDispatchToProps)(MiniSlidesPanelComponent as any); 91 | 92 | export { MiniSlidesPanel }; 93 | -------------------------------------------------------------------------------- /app/modules/MiniSlidesPanel/mini-slide-panel.scss: -------------------------------------------------------------------------------- 1 | $mini-slides-padding: 10px 0px; 2 | $mini-slide-border: 2px solid #106BA3; 3 | 4 | #mini-slide-panel { 5 | list-style-type: none; 6 | overflow-y: auto; 7 | 8 | .mini-slide-item { 9 | padding: $mini-slides-padding; 10 | 11 | &.active { 12 | background-color: #5C7080; 13 | } 14 | } 15 | 16 | .mini-slide-content { 17 | position: relative; 18 | background-color: white; 19 | box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3); 20 | box-sizing: initial; 21 | margin: 0 auto; 22 | } 23 | 24 | .mini-slide-counter { 25 | float: left; 26 | font-weight: 600; 27 | margin-left: 5px; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/modules/Scale/Scale.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface ScaleProps { 4 | children?: any; 5 | isFullScreen: boolean; 6 | scale: number; 7 | } 8 | 9 | const Scale = ({ children, isFullScreen, scale }: ScaleProps) => { 10 | return ( 11 |
12 | { children } 13 |
14 | ); 15 | }; 16 | 17 | export { Scale }; 18 | -------------------------------------------------------------------------------- /app/modules/SmartSlide/SmartSlide.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { connect } from 'react-redux'; 3 | import req from 'utils/requireContext'; 4 | import { goToSlide, setActivePlugin, toggleGuidelines } from 'actions/app.actions'; 5 | import { updateCurrentPlugin } from 'actions/slides.actions'; 6 | import './smart-slide.scss'; 7 | 8 | const Rnd = require('react-rnd'); 9 | const classNames = require('classnames'); 10 | 11 | interface SmartSlideProps { 12 | currentSelectedPlugin?: { 13 | moduleName: string; 14 | pluginNumber: number; 15 | slideNumber: number; 16 | }; 17 | goToSlide?: Function; 18 | isInPresenterMode?: boolean; 19 | scale: number; 20 | setActivePlugin?: Function; 21 | slide?: any; 22 | slidesDimension?: { 23 | width: number; 24 | height: number; 25 | }; 26 | slideNumber?: number; 27 | toggleGuidelines?: Function; 28 | updateCurrentPlugin?: Function; 29 | } 30 | 31 | export class SmartSlideComponent extends React.Component { 32 | rnd: any = {}; 33 | 34 | // This is to update the position via Rnd updatePosition API 35 | // Otherwise, there is a bug with slides rendering its position 36 | // based on the last movement of the last plugin 37 | // Added functionality to make sure that the current slide changes to the correct slide 38 | // when undoing actions with the undo functionality 39 | public componentDidUpdate({ slideNumber: _slideNumber }: { slideNumber: number }): void { 40 | const { currentSelectedPlugin, goToSlide, slide, slideNumber } = this.props; 41 | 42 | if (slideNumber !== _slideNumber) { 43 | for (const key in this.rnd) { 44 | if (!this.rnd[key]) return null; 45 | const { state: { left: x, top: y } } = this.props.slide.plugins[key]; 46 | this.rnd[key].updatePosition({ x, y }); 47 | } 48 | } 49 | if (currentSelectedPlugin === null) return null; 50 | if (slideNumber !== currentSelectedPlugin.slideNumber) { 51 | goToSlide(currentSelectedPlugin.slideNumber); 52 | } 53 | } 54 | public componentWillUpdate({ slideNumber: _slideNumber }: { slideNumber: number }): void { 55 | const { currentSelectedPlugin, goToSlide, slide, slideNumber } = this.props; 56 | if (!slide) goToSlide(0); 57 | } 58 | 59 | public render() { 60 | const { 61 | currentSelectedPlugin, 62 | isInPresenterMode, 63 | scale, 64 | setActivePlugin, 65 | slide, 66 | slidesDimension, 67 | slideNumber, 68 | toggleGuidelines, 69 | updateCurrentPlugin, 70 | } = this.props; 71 | 72 | return ( 73 |
74 | { 75 | slide.plugins.map((plugin: any, key: number) => { 76 | // When plugin is deleted from plugins array, their position is not removed rather the value is set to null 77 | if (!plugin) return null; 78 | 79 | const { moduleName, state } = plugin; 80 | const Plugin = req(moduleName).component; 81 | 82 | const { 83 | width, 84 | height, 85 | left, 86 | top, 87 | 88 | isResizable, 89 | forceDynamicHeight, 90 | lockAspectRatio, 91 | } = state; 92 | 93 | const className = classNames({ 94 | 'editing': ( 95 | currentSelectedPlugin !== null 96 | ? currentSelectedPlugin.pluginNumber === key && currentSelectedPlugin.slideNumber === slideNumber 97 | : false 98 | ), 99 | 'force-dynamic-height': forceDynamicHeight, 100 | 'rnd': true, 101 | }); 102 | 103 | return ( 104 | this.rnd[key] = c } 107 | className={ className } 108 | lockAspectRatio={ lockAspectRatio } 109 | initial={{ 110 | width, 111 | height: '100%', 112 | x: left, 113 | y: top 114 | }} 115 | bounds={{ 116 | top: 0, 117 | left: 0, 118 | right: (slidesDimension.width / scale) - width, 119 | bottom: (slidesDimension.height / scale) - height 120 | }} 121 | // Resizing T, L, TR, BL, TL results in unwanted movements 122 | isResizable={ isResizable ? isResizable : { 123 | top: false, 124 | right: true, 125 | bottom: true, 126 | left: false, 127 | topRight: false, 128 | bottomRight: true, 129 | bottomLeft: false, 130 | topLeft: false 131 | }} 132 | moveGrid={[((slidesDimension.width / scale) - width)/100, ((slidesDimension.height / scale) - height)/100]} 133 | onClick={() => { 134 | if (!currentSelectedPlugin) setActivePlugin(plugin.moduleName, key, slideNumber); 135 | else { 136 | const { 137 | pluginNumber: _pluginNumber, 138 | slideNumber: _slideNumber 139 | } = currentSelectedPlugin; 140 | 141 | if (_slideNumber !== slideNumber || _pluginNumber !== key) setActivePlugin(plugin.moduleName, key, slideNumber); 142 | } 143 | }} 144 | onResizeStop={( 145 | direction: string, 146 | styleSize: Object, 147 | clientSize: Object 148 | ) => updateCurrentPlugin(key, slideNumber, clientSize)} 149 | onDragStart={toggleGuidelines} 150 | onDragStop={(e: any, { position }: { position: { left: number; top: number; } }) => { 151 | const deltaX = Math.abs((top - position.top) / top); 152 | const deltaY = Math.abs((left - position.left) / left); 153 | if (deltaX > 0 || deltaY > 0) updateCurrentPlugin(key, slideNumber, position); 154 | toggleGuidelines(); 155 | }} > 156 | 160 | 161 | ); 162 | })} 163 |
164 | ); 165 | } 166 | } 167 | 168 | const mapStateToProps = (state: any, props: any) => ({ 169 | currentSelectedPlugin: state.app.present.currentSelectedPlugin, 170 | isInPresenterMode: state.app.present.isInPresenterMode, 171 | slide: state.slides.present[state.app.present.currentSlide], 172 | slideNumber: state.app.present.currentSlide, 173 | slidesDimension: state.app.present.slidesDimension, 174 | }); 175 | 176 | const mapDispatchToProps = (dispatch: any) => ({ 177 | goToSlide: (slideNumber: number) => dispatch(goToSlide(slideNumber)), 178 | setActivePlugin: ( 179 | moduleName: string, 180 | pluginNumber: number, 181 | slideNumber: number 182 | ) => dispatch(setActivePlugin(moduleName, pluginNumber, slideNumber)), 183 | updateCurrentPlugin: ( 184 | pluginNumber: number, 185 | slideNumber: number, 186 | changes: Object 187 | ) => dispatch(updateCurrentPlugin(pluginNumber, slideNumber, changes)), 188 | toggleGuidelines: () => dispatch(toggleGuidelines()), 189 | }); 190 | 191 | const SmartSlideConnect = connect(mapStateToProps, mapDispatchToProps)(SmartSlideComponent as any); 192 | 193 | export { SmartSlideConnect as SmartSlide }; 194 | -------------------------------------------------------------------------------- /app/modules/SmartSlide/smart-slide.scss: -------------------------------------------------------------------------------- 1 | #current-slide-view { 2 | height: calc(100vh - 200px); 3 | width: calc(100vw - 275px); 4 | border: 1px solid black; 5 | margin-right: 1em; 6 | } 7 | 8 | .rnd { 9 | &.editing { 10 | border: 1px solid #1baee1; 11 | } 12 | 13 | &.force-dynamic-height { 14 | height: 100% !important; 15 | } 16 | } -------------------------------------------------------------------------------- /app/modules/ToolBar/ToolBar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import req from 'utils/requireContext'; 4 | import { addPluginToCurrentSlide } from 'actions/slides.actions'; 5 | import './toolbar.scss'; 6 | 7 | import { 8 | Intent, 9 | Menu as ToolBarMenu, 10 | MenuItem as ToolBarItem, 11 | Popover, 12 | PopoverInteractionKind, 13 | Position 14 | } from '@blueprintjs/core'; 15 | 16 | // NOTE: Hard coded for now. This should dynamically get required 17 | // plugins 18 | const installedPlugins = [ 19 | 'devdecks-textbox', 20 | 'devdecks-image', 21 | 'devdecks-code-editor', 22 | ]; 23 | 24 | interface ToolBarComponentProps { 25 | addPluginToCurrentSlide: React.MouseEventHandler; 26 | slidesDimension: { 27 | width: number; 28 | height: number; 29 | }; 30 | slideNumber: number; 31 | } 32 | 33 | class ToolBarComponent extends React.Component { 34 | public render() { 35 | const { addPluginToCurrentSlide, slidesDimension, slideNumber } = this.props; 36 | return ( 37 |
38 | 39 | { 40 | installedPlugins.map((installedPlugin: string, key: number) => { 41 | const plugin = req(installedPlugin); 42 | const { icon, state, text, tooltip } = plugin; 43 | return ( 44 | 50 | 66 | 67 | ); 68 | }) 69 | } 70 | 71 |
72 | ); 73 | } 74 | } 75 | 76 | const mapStateToProps = (state: any) => ({ 77 | slidesDimension: state.app.present.slidesDimension, 78 | slideNumber: state.app.present.currentSlide 79 | }); 80 | 81 | const mapDispatchToProps = (dispatch: any) => ({ 82 | addPluginToCurrentSlide: (plugin: any, slideNumber: number) => dispatch(addPluginToCurrentSlide(plugin, slideNumber)), 83 | }); 84 | 85 | const ToolBar = connect(mapStateToProps, mapDispatchToProps)(ToolBarComponent as any); 86 | 87 | export { ToolBar }; 88 | -------------------------------------------------------------------------------- /app/modules/ToolBar/toolbar.scss: -------------------------------------------------------------------------------- 1 | @import '../../theme/toolbar.scss'; 2 | 3 | #toolbar { 4 | flex: 1 0 70%; 5 | } -------------------------------------------------------------------------------- /app/modules/UtilitiesMenu/DefaultOptions/DefaultOptions.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import BackgroundColor from './Options/BackgroundColor'; 3 | import DuplicateSlide from './Options/DuplicateSlide'; 4 | import MoveSlide from './Options/MoveSlide'; 5 | import SelectTransitions from './Options/SelectTransitions'; 6 | import './options.scss'; 7 | 8 | interface DefaultOptionsProps { 9 | currentSlideNumber: number; 10 | maxSlides: number; 11 | slide: any; 12 | theme: Object; 13 | 14 | addThemeColor: Function; 15 | duplicateSlide: Function; 16 | goToSlide: Function; 17 | moveSlideDown: Function; 18 | moveSlideUp: Function; 19 | updateCurrentSlide: Function; 20 | } 21 | 22 | const DefaultOptions = ({ 23 | currentSlideNumber, 24 | maxSlides, 25 | slide, 26 | theme, 27 | 28 | addThemeColor, 29 | duplicateSlide, 30 | goToSlide, 31 | moveSlideDown, 32 | moveSlideUp, 33 | updateCurrentSlide 34 | }: DefaultOptionsProps) => ( 35 |
36 |

Slide Options

37 |
38 | 44 |
45 | 50 |
51 | 56 |
57 | 60 |
61 | ); 62 | 63 | export default DefaultOptions; 64 | -------------------------------------------------------------------------------- /app/modules/UtilitiesMenu/DefaultOptions/Options/BackgroundColor.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { SketchPicker } from 'react-color'; 3 | import { Button } from '@blueprintjs/core'; 4 | 5 | interface BackgroundColorProps { 6 | slide: any; 7 | theme: any; 8 | addThemeColor: Function; 9 | updateCurrentSlide: Function; 10 | } 11 | 12 | const BackgroundColor = ({ slide, theme, addThemeColor, updateCurrentSlide }: BackgroundColorProps) => { 13 | const { colors } = theme; 14 | return ( 15 |