├── .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 | 
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 |
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 | {
40 | addSlide(currentSlide);
41 | goToSlide(currentSlide + 1);
42 | }} />
43 | {
47 | saveLastSlideDimensions();
48 | toggleFullScreen();
49 | }} />
50 |
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 |
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 |
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 |
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 |
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 |
16 | Slide Color
17 |
22 | updateCurrentSlide({
23 | backgroundColor: color.rgb,
24 | backgroundHexColor: color.hex
25 | })} />
26 | addThemeColor(slide.state.backgroundHexColor || '#fff')} />
30 |
31 | );
32 | };
33 |
34 | export default BackgroundColor;
35 |
--------------------------------------------------------------------------------
/app/modules/UtilitiesMenu/DefaultOptions/Options/DuplicateSlide.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Button } from '@blueprintjs/core';
3 |
4 | interface DuplicateSlideProps {
5 | currentSlideNumber: number;
6 | maxSlides: number;
7 | goToSlide: Function;
8 | duplicateSlide: Function;
9 | }
10 |
11 | const DuplicateSlide = ({ currentSlideNumber, maxSlides, duplicateSlide, goToSlide }: DuplicateSlideProps) => {
12 | return (
13 | {
16 | duplicateSlide(currentSlideNumber);
17 | goToSlide(currentSlideNumber + 1);
18 | }} />
19 | );
20 | };
21 |
22 | export default DuplicateSlide;
23 |
--------------------------------------------------------------------------------
/app/modules/UtilitiesMenu/DefaultOptions/Options/MoveSlide.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Button, Classes } from '@blueprintjs/core';
3 |
4 | interface MoveSlideProps {
5 | currentSlideNumber: number;
6 | maxSlides: number;
7 | goToSlide: Function;
8 | moveSlideDown: Function;
9 | moveSlideUp: Function;
10 | }
11 |
12 | const MoveSlide = ({
13 | currentSlideNumber,
14 | maxSlides,
15 | goToSlide,
16 | moveSlideDown,
17 | moveSlideUp,
18 | }: MoveSlideProps) => (
19 |
20 |
21 | {
25 | moveSlideUp();
26 | goToSlide(currentSlideNumber + 1, maxSlides);
27 | }} />
28 |
29 |
30 | {
34 | moveSlideDown();
35 | goToSlide(currentSlideNumber - 1);
36 | }} />
37 |
38 |
39 | );
40 |
41 | export default MoveSlide;
42 |
--------------------------------------------------------------------------------
/app/modules/UtilitiesMenu/DefaultOptions/Options/SelectTransitions.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as Select from 'react-select';
3 | import 'react-select/dist/react-select.css';
4 |
5 | const { Transitions } = require('react-transitions');
6 |
7 | interface SelectTransitionsProps {
8 | slide: any;
9 | updateCurrentSlide: Function;
10 | }
11 |
12 | const SelectTransitions = ({ slide, updateCurrentSlide }: SelectTransitionsProps) => {
13 | const { transition } = slide.state;
14 |
15 | // Odd transition at element 26
16 | // Removing this transition so we can dynamically produce
17 | // left and right directions in pairs
18 | const oddTransitions: any = [
19 | 'rotate-fall-scale-up',
20 | 'rotate-out-newspaper-rotate-in-newspaper',
21 | 'scale-down-center-scale-up-center',
22 | 'sides',
23 | 'slide',
24 | ];
25 |
26 | const customTransitions = Transitions.filter((transition: string) => {
27 | if (!oddTransitions.includes(transition)) return true;
28 | });
29 |
30 | const options = customTransitions.reduce((
31 | transitions: any,
32 | current: string,
33 | i: number,
34 | ) => {
35 | if (i % 2 === 0) {
36 | transitions.pair.push(current);
37 | } else {
38 | transitions.pair.push(current);
39 | transitions._transitions.push({
40 | value: transitions.pair[0],
41 | label: transitions.pair[0],
42 | pair: transitions.pair,
43 | });
44 | transitions.pair = [];
45 | }
46 |
47 | return transitions;
48 | }, { _transitions: [], pair: [] })._transitions;
49 |
50 | return (
51 |
52 | Transitions
53 |
60 | updateCurrentSlide({
61 | transition: {
62 | right: val.pair[0],
63 | left: val.pair[1],
64 | }
65 | })
66 | }>
67 |
68 |
69 | );
70 | }
71 |
72 | export default SelectTransitions;
73 |
--------------------------------------------------------------------------------
/app/modules/UtilitiesMenu/DefaultOptions/options.scss:
--------------------------------------------------------------------------------
1 | #utilities-menu-default-options {
2 | #move-slide-container {
3 | display: flex;
4 | list-style-type: none;
5 | justify-content: center;
6 |
7 | li {
8 | margin: 0 5px;
9 | }
10 | }
11 |
12 | .sketch-picker {
13 | margin: 5px auto 0;
14 | }
15 | }
--------------------------------------------------------------------------------
/app/modules/UtilitiesMenu/UtilitiesMenu.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { connect } from 'react-redux';
3 | import req from 'utils/requireContext';
4 | import { Button, Intent } from '@blueprintjs/core';
5 | import { ControlPanel } from 'modules';
6 | import DefaultOptions from './DefaultOptions/DefaultOptions';
7 | import './utilities-menu.scss';
8 |
9 | import {
10 | addThemeColor,
11 | goToSlide,
12 | setActivePlugin,
13 | } from 'actions/app.actions';
14 |
15 | import {
16 | addSlide,
17 | deleteCurrentPlugin,
18 | deleteSlide,
19 | duplicateSlide,
20 | moveSlideDown,
21 | moveSlideUp,
22 | updateCurrentPlugin,
23 | updateCurrentSlide
24 | } from 'actions/slides.actions';
25 |
26 | interface UtilitiesMenuParentProps {
27 | styles: Object;
28 | }
29 |
30 | interface UtilitiesMenuProps extends UtilitiesMenuParentProps {
31 | currentSlideNumber: number;
32 | maxSlides: number;
33 | moduleName: string;
34 | pluginNumber: number;
35 | pluginState: Object;
36 | slide: Object;
37 | slideNumber: number;
38 | theme: any;
39 |
40 | addThemeColor: Function;
41 | addSlide: Function;
42 | deleteCurrentPlugin: Function;
43 | deleteSlide: Function;
44 | duplicateSlide: Function;
45 | goToSlide: Function;
46 | moveSlideDown: Function;
47 | moveSlideUp: Function;
48 | setActivePlugin: Function;
49 | updateCurrentPlugin: Function;
50 | updateCurrentSlide: Function;
51 | }
52 |
53 | class UtilitiesMenu extends React.Component {
54 | render() {
55 | const {
56 | currentSlideNumber,
57 | maxSlides,
58 | moduleName,
59 | pluginNumber,
60 | pluginState,
61 | slide,
62 | slideNumber,
63 | styles,
64 | theme,
65 |
66 | addThemeColor,
67 | addSlide,
68 | deleteCurrentPlugin,
69 | deleteSlide,
70 | duplicateSlide,
71 | goToSlide,
72 | moveSlideDown,
73 | moveSlideUp,
74 | setActivePlugin,
75 | updateCurrentPlugin,
76 | updateCurrentSlide,
77 | } = this.props;
78 |
79 | let PluginOptions: any;
80 | if (moduleName && pluginNumber !== undefined && slideNumber !== undefined) {
81 | PluginOptions = req(moduleName).optionsMenuComponent;
82 | }
83 |
84 | return (
85 |
144 | );
145 | }
146 | }
147 |
148 | const mapStateToProps = (state: any, props: UtilitiesMenuParentProps) => {
149 | const currentSelectedPlugin = state.app.present.currentSelectedPlugin;
150 | const { currentSlide: currentSlideNumber } = state.app.present;
151 |
152 | if (currentSelectedPlugin) {
153 | var { moduleName, pluginNumber, slideNumber } = currentSelectedPlugin;
154 | var pluginState = state.slides.present[slideNumber].plugins[pluginNumber].state;
155 | }
156 |
157 | return {
158 | currentSlideNumber,
159 | moduleName,
160 | pluginNumber,
161 | pluginState,
162 | slideNumber,
163 | maxSlides: state.slides.present.length,
164 | slide: state.slides.present[currentSlideNumber],
165 | theme: state.app.present.theme,
166 | };
167 | };
168 |
169 | const mapDispatchToProps = (dispatch: any) => ({
170 | addThemeColor: (color: string) => dispatch(addThemeColor(color)),
171 | addSlide: (currentSlide: number) => dispatch(addSlide(currentSlide)),
172 | deleteSlide: (currentSlide: number) => dispatch(deleteSlide(currentSlide)),
173 | deleteCurrentPlugin: (pluginNumber: number, pluginSlideNumber: number) => dispatch(deleteCurrentPlugin(pluginNumber, pluginSlideNumber)),
174 | duplicateSlide: (slideToDuplicate: number) => dispatch(duplicateSlide(slideToDuplicate)),
175 | goToSlide: (slideNumber: number, maxSlides: number) => dispatch(goToSlide(slideNumber, maxSlides)),
176 | moveSlideDown: (slideNumber: number) => dispatch(moveSlideDown(slideNumber)),
177 | moveSlideUp: (slideNumber: number) => dispatch(moveSlideUp(slideNumber)),
178 | setActivePlugin: () => dispatch(setActivePlugin()),
179 | updateCurrentPlugin: (pluginNumber: number, pluginSlideNumber: number, changes: any) => dispatch(updateCurrentPlugin(pluginNumber, pluginSlideNumber, changes)),
180 | updateCurrentSlide: (slideNumber: number, changes: Object) => dispatch(updateCurrentSlide(slideNumber, changes)),
181 | });
182 |
183 | const UtilitiesMenuConnect = connect(mapStateToProps, mapDispatchToProps)(UtilitiesMenu as any);
184 |
185 | export { UtilitiesMenuConnect as UtilitiesMenu };
186 |
--------------------------------------------------------------------------------
/app/modules/UtilitiesMenu/utilities-menu.scss:
--------------------------------------------------------------------------------
1 | #utilities-menu-container {
2 | background-color: #293742;
3 | max-height: 100vh;
4 | overflow-x: hidden;
5 | overflow-y: auto;
6 | text-align: center;
7 | }
8 |
9 | #options-list {
10 | margin-left: 10px;
11 | margin-right: 10px;
12 | }
13 |
14 | #delete-button-devdecks {
15 | width: 100%;
16 | margin: 0 auto 15px;
17 | }
--------------------------------------------------------------------------------
/app/modules/index.ts:
--------------------------------------------------------------------------------
1 | export * from './App/App';
2 | export * from './ControlPanel/ControlPanel';
3 | export * from './DummySlide/DummySlide';
4 | export * from './MiniSlidesPanel/MiniSlidesPanel';
5 | export * from './UtilitiesMenu/UtilitiesMenu';
6 | export * from './Scale/Scale';
7 | export * from './SmartSlide/SmartSlide';
8 | export * from './ToolBar/ToolBar';
9 |
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "devdecks",
3 | "productName": "DevDecks",
4 | "version": "0.1.0",
5 | "description": "An open-source and extensible, standalone presentation app for developers",
6 | "main": "./main.js",
7 | "author": {
8 | "name": "CHAD",
9 | "email": "chad.devdecks@gmail.com"
10 | },
11 | "license": "MIT",
12 | "dependencies": {
13 | "@blueprintjs/core": "^1.0.1",
14 | "brace": "^0.9.0",
15 | "classnames": "^2.2.5",
16 | "electron-debug": "^1.0.1",
17 | "lodash.clonedeep": "^4.5.0",
18 | "lodash.throttle": "^4.1.1",
19 | "react": "^15.3.2",
20 | "react-ace": "^4.1.0",
21 | "react-color": "^2.10.0",
22 | "react-dom": "^15.3.2",
23 | "react-redux": "^4.4.5",
24 | "react-rnd": "^4.2.0",
25 | "react-transitions": "0.0.2",
26 | "redux": "^3.6.0",
27 | "redux-undo": "^1.0.0-beta9-2",
28 | "source-map-support": "^0.4.6"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-code-editor/CodeEditor.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import CodeRun from './Options/CodeRun';
3 | import './code-editor.scss';
4 |
5 | import {
6 | Popover,
7 | Position,
8 | } from '@blueprintjs/core';
9 |
10 | const classNames = require('classnames');
11 | const Highlight = require('react-highlight');
12 | require('highlight.js/styles/xcode.css');
13 |
14 | interface CodeEditorProps {
15 | isFullscreen?: boolean;
16 | pluginState: any;
17 | updateCurrentPlugin?: any;
18 | }
19 |
20 | class CodeEditor extends React.Component {
21 | codeBlocks: any[] = [];
22 |
23 | constructor() {
24 | super();
25 | this.handleCodeHighlight = this.handleCodeHighlight.bind(this);
26 | this.state = {
27 | activeItem: 0,
28 | };
29 | }
30 |
31 | private handleCodeHighlight(e: any) {
32 | const { pluginState: { highlightedItems } } = this.props;
33 | const { activeItem } = this.state;
34 | switch (e.keyCode) {
35 | // UP
36 | case 38: {
37 | if (activeItem > 0) this.setState({ activeItem: activeItem - 1 });
38 | break;
39 | }
40 |
41 | // DOWN
42 | case 40: {
43 | if (activeItem < highlightedItems.length - 1) this.setState({ activeItem: activeItem + 1 });
44 | break;
45 | }
46 | }
47 | };
48 |
49 | public componentDidUpdate() {
50 | const { codeBlocks } = this;
51 | const { pluginState: { highlightedItems } } = this.props;
52 | const { activeItem } = this.state;
53 | const activeBlock = highlightedItems[activeItem];
54 | if (activeBlock) {
55 | const scrollToCodeBlock = codeBlocks[activeBlock.start]
56 | scrollToCodeBlock ? scrollToCodeBlock.scrollIntoViewIfNeeded(true) : null;
57 | }
58 | }
59 |
60 | public render() {
61 | const {
62 | isFullscreen,
63 | pluginState,
64 | updateCurrentPlugin,
65 | } = this.props;
66 |
67 | const { activeItem } = this.state;
68 |
69 | const {
70 | fontSize,
71 | height,
72 | highlightedItems,
73 | isOpen,
74 | language,
75 | showLineNumbers,
76 | snippet,
77 | theme
78 | } = pluginState;
79 |
80 | const codeLineNumberClass = classNames({
81 | 'code-line-number': true,
82 | 'show': !isFullscreen || showLineNumbers,
83 | });
84 |
85 | const activeBlock = highlightedItems[activeItem];
86 | const code = (
87 | snippet.split('\n').map((line: string, i: number) => {
88 | if (!activeBlock){
89 | return (
90 |
94 | {i}
95 |
96 | {line}
97 |
98 |
99 | );
100 | } else if (i >= activeBlock.start && i <= activeBlock.end) {
101 | if (i == activeBlock.end) {
102 | return (
103 |
110 | this.codeBlocks[i] = c}
112 | className="code"
113 | id={`code-highlight-${i}`} >
114 | {i}
115 |
116 | {line}
117 |
118 |
119 |
120 | );
121 | }
122 |
123 | return (
124 | this.codeBlocks[i] = c}
126 | key={i}
127 | className="code"
128 | id={`code-highlight-${i}`} >
129 | {i}
130 |
131 | {line}
132 |
133 |
134 | );
135 | }
136 | return (
137 |
141 | {i}
142 |
143 | {line}
144 |
145 |
146 | );
147 | })
148 | );
149 |
150 | return (
151 | {} }
156 | onKeyDown={this.handleCodeHighlight} >
157 | {
158 | snippet
159 | ?
160 | {code}
161 | {
162 | isFullscreen
163 | ?
166 | : null
167 | }
168 |
169 | :
172 |
173 | }
174 |
175 | );
176 | }
177 | }
178 |
179 | export default CodeEditor;
180 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-code-editor/Options/CodeEdit.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Button, Dialog, Intent } from '@blueprintjs/core';
3 |
4 | const brace = require('brace');
5 | const AceEditor = require('react-ace').default;
6 |
7 | //languages
8 | require('brace/mode/c_cpp');
9 | require('brace/mode/clojure');
10 | require('brace/mode/css');
11 | require('brace/mode/django');
12 | require('brace/mode/golang');
13 | require('brace/mode/haskell');
14 | require('brace/mode/html');
15 | require('brace/mode/java');
16 | require('brace/mode/javascript');
17 | require('brace/mode/json');
18 | require('brace/mode/less');
19 | require('brace/mode/objectivec');
20 | require('brace/mode/perl');
21 | require('brace/mode/php');
22 | require('brace/mode/python');
23 | require('brace/mode/ruby');
24 | require('brace/mode/scala');
25 | require('brace/mode/scss');
26 | require('brace/mode/sql');
27 | require('brace/mode/swift');
28 | require('brace/mode/typescript');
29 |
30 | //themes
31 | require('brace/theme/xcode');
32 |
33 | interface CodeEditProps {
34 | pluginState: any;
35 | updateCurrentPlugin: Function;
36 | }
37 |
38 | interface CodeEditStates {
39 | value: string;
40 | }
41 |
42 | class CodeEdit extends React.Component {
43 | constructor() {
44 | super();
45 | this.state = {
46 | value: '',
47 | };
48 | }
49 |
50 | componentWillReceiveProps() {
51 | const { pluginState: { snippet } } = this.props;
52 | if (this.state.value !== snippet) {
53 | this.setState({ value: snippet });
54 | }
55 | }
56 |
57 | render() {
58 | const { pluginState, updateCurrentPlugin } = this.props;
59 | const { value } = this.state;
60 | const { fontSize, isOpen, snippet, snippetEval, theme } = pluginState;
61 |
62 | let { language } = pluginState;
63 | switch (language) {
64 | case 'CPP': {
65 | language = 'c_cpp';
66 | break;
67 | }
68 | case 'Go': {
69 | language = 'golang';
70 | break;
71 | }
72 | case 'HTMLBars': {
73 | language = 'html';
74 | break;
75 | }
76 | }
77 |
78 | return (
79 |
80 | updateCurrentPlugin({ isOpen: true })} />
84 |
85 | updateCurrentPlugin({ isOpen: false })} >
91 |
92 |
this.setState({ value: snippet })} />
104 |
105 |
106 |
107 | updateCurrentPlugin({
111 | isOpen: false,
112 | snippet: this.state.value
113 | })} />
114 |
115 |
116 |
117 |
118 | );
119 | }
120 | }
121 |
122 | export default CodeEdit;
123 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-code-editor/Options/CodeHighlightSubmit.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import {
4 | Alert,
5 | Button,
6 | Intent,
7 | Tag,
8 | } from '@blueprintjs/core';
9 |
10 | const cloneDeep = require('lodash.clonedeep');
11 |
12 | const { SortableContainer, SortableElement, arrayMove } = require('react-sortable-hoc');
13 |
14 | const SortableItem = SortableElement((props: any) => {
15 | const { highlightedItems, position, value, updateCurrentPlugin } = props;
16 | return (
17 |
18 | {
20 | const _highlightedItems = cloneDeep(highlightedItems);
21 | _highlightedItems.splice(position, 1);
22 | updateCurrentPlugin({ highlightedItems: _highlightedItems });
23 | }}>
24 | {value}
25 |
26 |
27 | );
28 | });
29 |
30 | const SortableList = SortableContainer((props: any) => {
31 | const { highlightedItems, updateCurrentPlugin } = props;
32 | return (
33 |
34 | {
35 | props.items.map((value: any, index: number) =>
36 |
43 | )
44 | }
45 |
46 | );
47 | });
48 |
49 | interface CodeHighlightSubmitProps {
50 | pluginState: any;
51 | updateCurrentPlugin: Function;
52 | }
53 |
54 | interface CodeHighlightSubmitStates {
55 | alertMessage?: string;
56 | isAlert?: boolean;
57 | start?: string;
58 | end?: string;
59 | text?: string;
60 | }
61 |
62 | class CodeHighlightSubmit extends React.Component {
63 | maxLines: number = 0;
64 |
65 | constructor() {
66 | super();
67 | this.handleSort = this.handleSort.bind(this);
68 | this.validateHighlightBlock = this.validateHighlightBlock.bind(this);
69 | this.state = {
70 | alertMessage: '',
71 | isAlert: false,
72 | start: '',
73 | end: '',
74 | text: '',
75 | };
76 | }
77 |
78 | private handleSort({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }) {
79 | const { pluginState: { highlightedItems }, updateCurrentPlugin } = this.props;
80 | updateCurrentPlugin({ highlightedItems: arrayMove(highlightedItems, oldIndex, newIndex) });
81 | }
82 |
83 | private validateHighlightBlock() {
84 | const { pluginState: { highlightedItems, snippet } } = this.props;
85 | const { start, end, text } = this.state;
86 |
87 | const max = snippet.split('\n').length - 1;
88 | if (!start || +start < 0 || +start > max) {
89 | return {
90 | success: false,
91 | message: 'Start value cannot be less than 0 or greater than number of lines',
92 | };
93 | }
94 | if (!end || +end < +start || +end > max) {
95 | return {
96 | success: false,
97 | message: 'End value cannot be less than start value or greater than number of lines',
98 | };
99 | }
100 | return {
101 | success: true,
102 | };
103 | }
104 |
105 | public componentWillReceiveProps(nextProps: CodeHighlightSubmitProps) {
106 | const { snippet } = nextProps.pluginState;
107 | this.maxLines = snippet.split('\n').length - 1;
108 | }
109 |
110 | public render() {
111 | const { pluginState: { highlightedItems, snippet }, updateCurrentPlugin } = this.props;
112 | const disabled = snippet ? false : true;
113 | const items = highlightedItems.map((highlightedItem: any) => `${highlightedItem.start}-${highlightedItem.end}`);
114 |
115 | return (
116 |
186 | );
187 | }
188 | }
189 |
190 | export default CodeHighlightSubmit;
191 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-code-editor/Options/CodeImportExport.tsx:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import * as React from 'react';
3 | import { remote } from 'electron';
4 | import { Button } from '@blueprintjs/core';
5 |
6 | interface CodeImportExportProps {
7 | pluginState: any;
8 | updateCurrentPlugin: Function;
9 | }
10 |
11 | const CodeImportExport = ({ pluginState, updateCurrentPlugin }: CodeImportExportProps) => {
12 | const { snippet } = pluginState;
13 |
14 | const selectCodeFileToImport: React.MouseEventHandler = () => {
15 | remote.dialog.showOpenDialog((filePaths: string[]) => {
16 | if (!filePaths) return;
17 | fs.readFile(filePaths[0], (err: any, data: any) => {
18 | if (err) return;
19 | updateCurrentPlugin({ snippet: data.toString() });
20 | });
21 | });
22 | };
23 |
24 | const selectCodeFileToExport: React.MouseEventHandler = () => {
25 | remote.dialog.showSaveDialog((filename: string) => {
26 | if (!filename) return;
27 | fs.writeFile(filename, snippet);
28 | });
29 | };
30 |
31 | return (
32 |
33 |
37 |
38 |
42 |
43 | );
44 | };
45 |
46 |
47 | export default CodeImportExport;
48 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-code-editor/Options/CodeLang.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import languages from './lanuages';
3 |
4 | interface CodeLangProps {
5 | pluginState: any;
6 | updateCurrentPlugin: Function;
7 | }
8 |
9 | const CodeLang = ({ pluginState, updateCurrentPlugin }: CodeLangProps) => {
10 | const langSelection = (
11 | updateCurrentPlugin({ language: e.target.value })}>
14 | {
15 | languages.map((language: string, key: number) => (
16 |
19 | { language }
20 |
21 | ))
22 | }
23 |
24 | );
25 |
26 | return (
27 |
28 |
29 | Language
30 |
31 | { langSelection }
32 |
33 |
34 |
35 | );
36 | }
37 |
38 | export default CodeLang;
39 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-code-editor/Options/CodeRun.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { remote } from 'electron';
3 | import { Button, Intent } from '@blueprintjs/core';
4 |
5 | interface CodeRunProps {
6 | pluginState: any;
7 | styles?: any;
8 | }
9 |
10 | const CodeRun = ({ pluginState, styles }: CodeRunProps) => {
11 | const { snippet } = pluginState;
12 | const webContents = remote.getCurrentWebContents();
13 | return (
14 | {
19 | const isDevToolsOpened = webContents.isDevToolsOpened();
20 | if (!isDevToolsOpened) webContents.toggleDevTools();
21 | eval(snippet);
22 | }} />
23 | );
24 | };
25 |
26 | export default CodeRun;
27 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-code-editor/Options/CodeTheme.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | interface CodeThemeProps {
4 | pluginState: any;
5 | updateCurrentPlugin: Function;
6 | }
7 |
8 | const CodeTheme = ({ pluginState, updateCurrentPlugin }: CodeThemeProps) => {
9 | const themes = ['Ambiance', 'Chaos', 'Chrome', 'Clouds', 'Cobalt', 'Eclipse', 'iPlastic', 'Monokai', 'Textmate', 'Tomorrow', 'Twilight', 'XCode'];
10 |
11 | const themeSelection = (
12 | updateCurrentPlugin({ theme: e.target.value })}>
15 | {
16 | themes.map((theme, key) => (
17 |
20 | { theme }
21 |
22 | ))
23 | }
24 |
25 | );
26 |
27 | return (
28 |
29 |
30 | Theme
31 |
32 | { themeSelection }
33 |
34 |
35 |
36 | );
37 | }
38 |
39 | export default CodeTheme;
40 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-code-editor/Options/ToggleConsole.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { remote } from 'electron';
3 | import { Button, Intent } from '@blueprintjs/core';
4 |
5 | interface ToggleConsoleProps {
6 | pluginState: any;
7 | updateCurrentPlugin: Function;
8 | }
9 |
10 | const ToggleConsole = ({ pluginState, updateCurrentPlugin }: ToggleConsoleProps) => {
11 | const webContents = remote.getCurrentWebContents();
12 | const isDevToolsOpened = webContents.isDevToolsOpened();
13 | return (
14 |
15 | webContents.toggleDevTools() } />
19 |
20 | );
21 | }
22 |
23 | export default ToggleConsole;
24 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-code-editor/Options/lanuages.ts:
--------------------------------------------------------------------------------
1 | export default [
2 | 'Clojure',
3 | 'CPP',
4 | 'CSS',
5 | 'Djanjo',
6 | 'Go',
7 | 'Haskell',
8 | 'HTMLBars',
9 | 'Java',
10 | 'JavaScript',
11 | 'JSON',
12 | 'LESS',
13 | 'ObjectiveC',
14 | 'Perl',
15 | 'PHP',
16 | 'Python',
17 | 'Ruby',
18 | 'Scala',
19 | 'SCSS',
20 | 'SQL',
21 | 'Swift',
22 | 'TypeScript',
23 | ];
24 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-code-editor/OptionsMenu.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Checkbox } from '@blueprintjs/core';
3 | import CodeEdit from './Options/CodeEdit';
4 | import CodeHighlightSubmit from './Options/CodeHighlightSubmit';
5 | import CodeLang from './Options/CodeLang';
6 | import CodeRun from './Options/CodeRun';
7 | import CodeTheme from './Options/CodeTheme';
8 | import CodeImportExport from './Options/CodeImportExport';
9 | import ToggleConsole from './Options/ToggleConsole';
10 |
11 | interface OptionsMenuProps {
12 | moduleName: string;
13 | pluginNumber: number;
14 | pluginState: any;
15 | slideNumber: number;
16 | updateCurrentPlugin: Function;
17 | }
18 |
19 | const OptionsMenu = ({
20 | moduleName,
21 | pluginNumber,
22 | pluginState,
23 | slideNumber,
24 | updateCurrentPlugin
25 | }: OptionsMenuProps) => (
26 |
27 | Code Editor Options
28 |
29 |
32 |
33 | {/*
34 |
37 | */}
38 |
39 |
40 |
41 |
44 |
45 |
46 |
47 |
52 |
53 |
54 |
55 | updateCurrentPlugin({ showLineNumbers: !pluginState.showLineNumbers })} />
59 |
60 |
61 |
62 |
63 |
66 |
67 |
68 |
69 |
72 |
73 |
74 | );
75 |
76 | export default OptionsMenu;
77 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-code-editor/code-editor.scss:
--------------------------------------------------------------------------------
1 | #codeeditor-container {
2 | display: flex;
3 | overflow: auto;
4 |
5 | &::-webkit-scrollbar {
6 | display: none;
7 | }
8 |
9 | &:focus {
10 | border: none;
11 | outline: none;
12 | }
13 |
14 | .pt-icon-code {
15 | align-self: center;
16 | background-color: rgba(50, 50, 50, .2);
17 | }
18 |
19 | .code {
20 | display: flex;
21 | position: relative;
22 | }
23 |
24 | .code-line-number {
25 | visibility: hidden;
26 | position: absolute;
27 |
28 | &.show {
29 | align-self: center;
30 | color: mediumpurple;
31 | font-size: 1.2em;
32 | font-weight: bold;
33 | visibility: initial;
34 | }
35 | }
36 |
37 | // Hides the popover if there is no content
38 | // with the highlight
39 | .hide-popover {
40 | display: none;
41 | }
42 |
43 | .highlighted, .non-highlighted {
44 | font-size: 2.5em;
45 | }
46 |
47 | .highlighted {
48 | font-weight: 600;
49 | }
50 |
51 | .non-highlighted {
52 | opacity: 0.3;
53 | }
54 |
55 | .pt-popover-target {
56 | width: 100%;
57 | }
58 |
59 | .pt-popover-content {
60 | font-size: 2em;
61 | }
62 |
63 | pre {
64 | width: 100%;
65 | box-shadow: none;
66 | margin: 0;
67 | padding: 0;
68 | }
69 | }
70 |
71 | #codeeditor-options-container {
72 | ul {
73 | list-style-type: none;
74 | margin: 0 auto !important;
75 |
76 | li {
77 | margin: 5px 0;
78 | }
79 | }
80 | }
81 |
82 | #code-editor-highlight-code-container {
83 | .pt-input {
84 | width: 100%;
85 | }
86 |
87 | input[type=number]::-webkit-inner-spin-button,
88 | input[type=number]::-webkit-outer-spin-button {
89 | -webkit-appearance: none;
90 | margin: 0;
91 | }
92 | }
93 |
94 | .code-editor-draggable-items {
95 | list-style-type: none;
96 | }
97 |
98 | .code-editor-code-edit {
99 | width: 700px;
100 | height: 400px;
101 | }
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-code-editor/index.ts:
--------------------------------------------------------------------------------
1 | import CodeEditor from './CodeEditor';
2 | import CodeEditorOptions from './OptionsMenu';
3 |
4 | export default {
5 | component: CodeEditor,
6 | icon: 'code',
7 | moduleName: 'devdecks-code-editor',
8 | tooltip: 'Code Editor',
9 | optionsMenuComponent: CodeEditorOptions,
10 | state: {
11 | highlightedItems: [],
12 | language: 'JavaScript',
13 | showLineNumbers: true,
14 | snippet: '',
15 | theme: 'monokai',
16 | width: 1279,
17 | height: 600,
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-image/AddImage.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | // For select image file dialog prompt
4 | import selectImageFile from './AddImageDialog';
5 |
6 | interface AddImageProps {
7 | pluginState: any;
8 | updateCurrentPlugin: Function;
9 | }
10 |
11 | const AddImage = ({ pluginState, updateCurrentPlugin }: AddImageProps) => {
12 |
13 | /**
14 | * Determine what image component to render
15 | *
16 | * Given image URL: Render the online image
17 | * Given image buffer string: Render the image added from the file system
18 | * Default behavior: Render the placeholder image
19 | */
20 | const { width, imageBufferString, imageUrl } = pluginState;
21 | let componentToRender: any;
22 | if (imageUrl && /https?:\/\/.+\.(gif|jpg|jpeg|png|svg)$/.test(imageUrl)) {
23 | componentToRender = (
24 |
29 | );
30 | } else if (imageBufferString) {
31 | componentToRender = (
32 |
37 | );
38 | } else {
39 | componentToRender = (
40 | selectImageFile(updateCurrentPlugin) } />
45 | );
46 | }
47 |
48 | return (
49 | { componentToRender }
50 | );
51 | }
52 |
53 | export default AddImage;
54 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-image/AddImageDialog.tsx:
--------------------------------------------------------------------------------
1 | // For Electron's file system dialog prompt
2 | // Docs: https://github.com/electron/electron/blob/master/docs/api/dialog.md
3 | const dialog = require('electron').remote.dialog;
4 | const fs = require('fs');
5 | const dialogOptions: any = {
6 | filters: [
7 | {
8 | name: 'Images',
9 | extensions: [ 'jpeg', 'jpg', 'gif', 'png' ]
10 | }
11 | ]
12 | };
13 |
14 | /**
15 | * Opens up a native file system dialog to allow user to select an image file.
16 | * Image is saved to plugin state as a base 64 string buffer.
17 | */
18 | export default function(updateCurrentPlugin: Function) {
19 | dialog.showOpenDialog(dialogOptions, (filePaths: string[]) => {
20 | if (!filePaths) return;
21 | fs.readFile(filePaths[0], (err: any, data: any) => {
22 | if (err) return;
23 | const imageBufferString: string = new Buffer(data).toString('base64');
24 | updateCurrentPlugin({ imageBufferString, width: 355 });
25 | });
26 | });
27 | }
28 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-image/OptionsMenu.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Button, EditableText, Intent } from '@blueprintjs/core';
3 | import './add-image.scss';
4 |
5 | // For select image file dialog prompt
6 | import selectImageFile from './AddImageDialog.tsx'
7 |
8 | interface OptionsMenuProps {
9 | moduleName: string;
10 | pluginNumber: number;
11 | pluginState: any;
12 | slideNumber: number;
13 | updateCurrentPlugin: Function;
14 | }
15 |
16 | class OptionsMenu extends React.Component {
17 | render() {
18 | const {
19 | moduleName,
20 | pluginNumber,
21 | pluginState,
22 | slideNumber,
23 | updateCurrentPlugin
24 | } = this.props;
25 |
26 | return (
27 |
28 |
Image Options
29 |
30 | { selectImageFile(updateCurrentPlugin) } }>
33 | Upload Image
34 |
35 |
36 | updateCurrentPlugin({ imageUrl: value })} />
42 |
43 | );
44 | }
45 | }
46 |
47 | export default OptionsMenu;
48 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-image/add-image.scss:
--------------------------------------------------------------------------------
1 | #devdeck-add-image {
2 | #add-image-button {
3 | width: 100%;
4 | }
5 |
6 | .add-image-url {
7 | display: flex;
8 | justify-content: flex-start;
9 | }
10 | }
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-image/index.ts:
--------------------------------------------------------------------------------
1 | import Image from './AddImage';
2 | import ImageOptions from './OptionsMenu';
3 |
4 | export default {
5 | component: Image,
6 | icon: 'media',
7 | moduleName: 'devdecks-image',
8 | tooltip: 'Image',
9 | optionsMenuComponent: ImageOptions,
10 | state: {
11 | height: 200,
12 | lockAspectRatio: false,
13 | value: '',
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/OptionsMenu.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import FontBackgroundColor from './options/FontBackgroundColor';
4 | import FontColor from './options/FontColor';
5 | import FontSize from './options/FontSize';
6 | import FontFamily from './options/FontFamily';
7 | import FontStyles from './options/FontStyles';
8 | import Utils from './options/Utils';
9 |
10 | interface OptionsMenuProps {
11 | moduleName: string;
12 | pluginNumber: number;
13 | pluginState: any;
14 | slide: Object;
15 | slideNumber: number;
16 | theme: any;
17 | addThemeColor: Function;
18 | updateCurrentPlugin: Function;
19 | }
20 |
21 | class OptionsMenu extends React.Component {
22 | render() {
23 | const {
24 | moduleName,
25 | pluginNumber,
26 | pluginState,
27 | slide,
28 | slideNumber,
29 | theme,
30 | addThemeColor,
31 | updateCurrentPlugin
32 | } = this.props;
33 |
34 | return (
35 |
36 | Text Box Options
37 |
38 |
39 |
42 |
43 |
44 |
47 |
48 |
49 |
52 |
53 |
54 |
57 |
58 |
59 |
60 |
65 |
66 |
67 |
73 |
74 |
75 | );
76 | };
77 | }
78 |
79 | export default OptionsMenu;
80 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/TextBox.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import './text-box.scss';
3 |
4 | const ContentEditable = require("react-contenteditable");
5 |
6 | interface TextBoxProps {
7 | disabled: boolean;
8 | pluginState: any;
9 | updateCurrentPlugin: Function;
10 | }
11 |
12 | const TextBox = ({ disabled, pluginState, updateCurrentPlugin }: TextBoxProps) => {
13 | const {
14 | fontBackgroundColor,
15 | fontColor,
16 | fontSize,
17 | fontFamily,
18 | textAlignment,
19 | value,
20 | width,
21 | } = pluginState;
22 |
23 | return (
24 |
33 | {
38 | if (!value) {
39 | updateCurrentPlugin({
40 | isOrderedList: false,
41 | isUnorderedList: false,
42 | value: e.target.value,
43 | });
44 | } else {
45 | updateCurrentPlugin({ value: e.target.value });
46 | }
47 | }}
48 | />
49 |
50 | );
51 | }
52 |
53 | export default TextBox;
54 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/fonts/Coda.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/app/plugins/node_modules/devdecks-textbox/fonts/Coda.ttf
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/fonts/Droid Sans.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/app/plugins/node_modules/devdecks-textbox/fonts/Droid Sans.ttf
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/fonts/Electrolize.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/app/plugins/node_modules/devdecks-textbox/fonts/Electrolize.ttf
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/fonts/Exo.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/app/plugins/node_modules/devdecks-textbox/fonts/Exo.ttf
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/fonts/Hind.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/app/plugins/node_modules/devdecks-textbox/fonts/Hind.ttf
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/fonts/Lobster.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/app/plugins/node_modules/devdecks-textbox/fonts/Lobster.ttf
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/fonts/Orbitron.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/app/plugins/node_modules/devdecks-textbox/fonts/Orbitron.ttf
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/fonts/Oxygen.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/app/plugins/node_modules/devdecks-textbox/fonts/Oxygen.ttf
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/fonts/Raleway.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/app/plugins/node_modules/devdecks-textbox/fonts/Raleway.ttf
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/fonts/Roboto.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/app/plugins/node_modules/devdecks-textbox/fonts/Roboto.ttf
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/fonts/Satisfy.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/app/plugins/node_modules/devdecks-textbox/fonts/Satisfy.ttf
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/fonts/Tangerine.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/app/plugins/node_modules/devdecks-textbox/fonts/Tangerine.ttf
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/fonts/Ubuntu.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/app/plugins/node_modules/devdecks-textbox/fonts/Ubuntu.ttf
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/fonts/_index.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Coda';
3 | src: url('./Coda.ttf');
4 | }
5 |
6 | @font-face {
7 | font-family: 'Droid Sans';
8 | src: url('./Droid Sans.ttf');
9 | }
10 |
11 | @font-face {
12 | font-family: 'Electrolize';
13 | src: url('./Electrolize.ttf');
14 | }
15 |
16 | @font-face {
17 | font-family: 'Exo';
18 | src: url('./Exo.ttf');
19 | }
20 |
21 | @font-face {
22 | font-family: 'Hind';
23 | src: url('./Hind.ttf');
24 | }
25 |
26 | @font-face {
27 | font-family: 'Lobster';
28 | src: url('./Lobster.ttf');
29 | }
30 |
31 | @font-face {
32 | font-family: 'Orbitron';
33 | src: url('./Orbitron.ttf');
34 | }
35 |
36 | @font-face {
37 | font-family: 'Oxygen';
38 | src: url('./Oxygen.ttf');
39 | }
40 |
41 | @font-face {
42 | font-family: 'Raleway';
43 | src: url('./Raleway.ttf');
44 | }
45 |
46 | @font-face {
47 | font-family: 'Roboto';
48 | src: url('./Roboto.ttf');
49 | }
50 |
51 | @font-face {
52 | font-family: 'Satisfy';
53 | src: url('./Satisfy.ttf');
54 | }
55 |
56 | @font-face {
57 | font-family: 'Tangerine';
58 | src: url('./Tangerine.ttf');
59 | }
60 |
61 | @font-face {
62 | font-family: 'Ubuntu';
63 | src: url('./Ubuntu.ttf');
64 | }
65 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/index.ts:
--------------------------------------------------------------------------------
1 | import TextBox from './TextBox';
2 | import TextBoxOptions from './OptionsMenu';
3 |
4 | export default {
5 | component: TextBox,
6 | icon: 'new-text-box',
7 | moduleName: 'devdecks-textbox',
8 | tooltip: 'Text Box',
9 | optionsMenuComponent: TextBoxOptions,
10 | state: {
11 | width: 500,
12 | height: 100,
13 | forceDynamicHeight: true,
14 | fontBackgroundColor: 'rgba(255,255,255,0)',
15 | fontBackgroundColorHex: '#fff',
16 | fontColor: '#000',
17 | fontColorHex: '#000',
18 | fontSize: 700,
19 | fontFamily: 'Arial',
20 | isResizable: {
21 | top: false,
22 | right: true,
23 | bottom: false,
24 | left: true,
25 | topRight: false,
26 | bottomRight: false,
27 | bottomLeft: false,
28 | topLeft: false
29 | },
30 | textAlignment: 'center',
31 | value: 'Text',
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/options/FontBackgroundColor.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { SketchPicker } from 'react-color';
3 | import { Button, Popover, PopoverInteractionKind, Position } from '@blueprintjs/core';
4 |
5 | interface FontBackgroundColor {
6 | pluginState: any;
7 | slide: any;
8 | theme: any;
9 | addThemeColor: Function;
10 | updateCurrentPlugin: Function;
11 | }
12 |
13 | const FontBackgroundColor = ({
14 | pluginState,
15 | slide,
16 | theme,
17 | addThemeColor,
18 | updateCurrentPlugin
19 | }: FontBackgroundColor) => {
20 | const { fontBackgroundColor, fontBackgroundColorHex, fontColor } = pluginState;
21 | const { colors } = theme;
22 |
23 | const content = (
24 |
25 | {
30 | updateCurrentPlugin({
31 | fontBackgroundColor: `rgba(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, ${color.rgb.a})`,
32 | fontBackgroundColorHex: color.hex
33 | });
34 | }} />
35 | addThemeColor(fontBackgroundColorHex)} />
42 |
43 | );
44 |
45 | return (
46 |
49 |
54 |
55 | );
56 | };
57 |
58 | export default FontBackgroundColor;
59 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/options/FontColor.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { SketchPicker } from 'react-color';
3 | import { Button, Popover, PopoverInteractionKind, Position } from '@blueprintjs/core';
4 |
5 | interface FontColor {
6 | pluginState: any;
7 | theme: any;
8 | addThemeColor: Function;
9 | updateCurrentPlugin: Function;
10 | }
11 |
12 | const FontColor = ({ pluginState, theme, addThemeColor, updateCurrentPlugin }: FontColor) => {
13 | const { fontBackgroundColor, fontColor, fontColorHex } = pluginState;
14 | const { colors } = theme;
15 |
16 | const content = (
17 |
18 |
23 | updateCurrentPlugin({
24 | fontColor: `rgba(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, ${color.rgb.a})`,
25 | fontColorHex: color.hex
26 | })} />
27 | addThemeColor(fontColorHex)} />
34 |
35 | );
36 |
37 | return (
38 |
41 |
43 |
44 | );
45 | };
46 |
47 | export default FontColor;
48 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/options/FontFamily.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import '../fonts/_index.scss';
3 |
4 | interface FontFamilyProps {
5 | pluginState: any;
6 | updateCurrentPlugin: Function;
7 | }
8 |
9 | const FontFamily = ({ pluginState, updateCurrentPlugin }: FontFamilyProps) => {
10 | const { FontFamily } = pluginState;
11 |
12 | const fonts = [
13 | 'Arial', 'Coda', 'Courier New', 'Droid Sans', 'Electrolize', 'Exo',
14 | 'Hind', 'Lobster', 'Orbitron', 'Oxygen', 'Raleway', 'Roboto',
15 | 'Satisfy', 'Tangerine', 'Times New Roman', 'Ubuntu'];
16 |
17 | const fontFamilySelection = (
18 | updateCurrentPlugin({ fontFamily: e.target.value })}>
21 | {
22 | fonts.map((fontFamily, key) => (
23 |
26 | {fontFamily}
27 |
28 | ))
29 | }
30 |
31 | );
32 |
33 | return (
34 |
35 | Font Family
36 |
37 | {fontFamilySelection}
38 |
39 |
40 | );
41 | }
42 |
43 | export default FontFamily;
44 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/options/FontSize.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Menu, MenuDivider, MenuItem, Popover, Position } from "@blueprintjs/core";
3 |
4 | interface FontSizeProps {
5 | pluginState: any;
6 | updateCurrentPlugin: Function;
7 | }
8 |
9 | const FontSize = ({ pluginState, updateCurrentPlugin }: FontSizeProps) => {
10 | const DEFAULT_SIZE = 100;
11 | const MAGNIFIER = 7;
12 |
13 | const { fontSize } = pluginState;
14 |
15 | const fontSizes = [50, 75, 90, 100, 125, 150, 175, 200, 250, 275, 300];
16 | const fontSelection = (
17 | updateCurrentPlugin({ fontSize: e.target.value * MAGNIFIER })}>
20 | {
21 | fontSizes.map((fontSize, key) => (
22 |
25 | { fontSize }
26 |
27 | ))
28 | }
29 |
30 | );
31 |
32 | return (
33 |
34 | Font Size
35 |
36 | { fontSelection }
37 |
38 |
39 | );
40 | }
41 |
42 | export default FontSize;
43 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/options/FontStyles.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Button, Intent } from "@blueprintjs/core";
3 |
4 | interface FontStylesProps {
5 | pluginState: any;
6 | updateCurrentPlugin: any;
7 | }
8 |
9 | const FontStyles = ({ pluginState, updateCurrentPlugin }: FontStylesProps) => {
10 | const {
11 | isBold,
12 | isItalic,
13 | isOrderedList,
14 | isUnorderedList,
15 | isUnderline,
16 | textAlignment,
17 | } = pluginState;
18 |
19 | return (
20 |
21 | {
25 | document.execCommand('bold');
26 | updateCurrentPlugin({ isBold: document.queryCommandState('bold') || isBold })
27 | }} />
28 | {
32 | document.execCommand('italic');
33 | updateCurrentPlugin({ isItalic: document.queryCommandState('italic') || isItalic })
34 | }} />
35 | {
39 | document.execCommand('underline');
40 | updateCurrentPlugin({ isUnderline: document.queryCommandState('underline') || isUnderline })
41 | }} />
42 |
43 | {
47 | if (isOrderedList || isUnorderedList) return updateCurrentPlugin({ textAlignment: null });
48 | textAlignment === 'left'
49 | ? updateCurrentPlugin({ textAlignment: null })
50 | : updateCurrentPlugin({ textAlignment: 'left' })}
51 | } />
52 | {
56 | if (isOrderedList || isUnorderedList) return updateCurrentPlugin({ textAlignment: null });
57 | textAlignment === 'center'
58 | ? updateCurrentPlugin({ textAlignment: null })
59 | : updateCurrentPlugin({ textAlignment: 'center' })}
60 | } />
61 | {
65 | if (isOrderedList || isUnorderedList) return updateCurrentPlugin({ textAlignment: null });
66 | textAlignment === 'right'
67 | ? updateCurrentPlugin({ textAlignment: null })
68 | : updateCurrentPlugin({ textAlignment: 'right' })}
69 | } />
70 |
71 | );
72 | };
73 |
74 | export default FontStyles;
75 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/options/Utils.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Button, Intent } from "@blueprintjs/core";
3 |
4 | interface UtilsProps {
5 | pluginState: any;
6 | updateCurrentPlugin: any;
7 | }
8 |
9 | const Utils = ({ pluginState, updateCurrentPlugin }: UtilsProps) => {
10 | const { isOrderedList, isUnorderedList } = pluginState;
11 | return (
12 |
13 | {
17 | document.execCommand('insertOrderedList');
18 | updateCurrentPlugin({
19 | isOrderedList: document.queryCommandState('insertOrderedList'),
20 | isUnorderedList: null,
21 | textAlignment: null,
22 | });
23 | }} />
24 |
25 | {
29 | document.execCommand('insertUnorderedList');
30 | updateCurrentPlugin({
31 | isUnorderedList: document.queryCommandState('insertUnorderedList'),
32 | isOrderedList: null,
33 | textAlignment: null,
34 | });
35 | }} />
36 |
37 | );
38 | };
39 |
40 | export default Utils;
41 |
--------------------------------------------------------------------------------
/app/plugins/node_modules/devdecks-textbox/text-box.scss:
--------------------------------------------------------------------------------
1 | .devdecks-textbox-container {
2 | .editable-content-editable {
3 | outline: 0px solid transparent;
4 | }
5 |
6 | span, b, i, u {
7 | font-size: 1em !important;
8 | }
9 | }
--------------------------------------------------------------------------------
/app/reducers/app.reducer.ts:
--------------------------------------------------------------------------------
1 | import { remote } from 'electron';
2 | import * as constants from 'constants/app.constants';
3 | import { EDirection }from 'constants/slides.enums';
4 |
5 | const cloneDeep = require('lodash.clonedeep');
6 | const undoable = require('redux-undo').default;
7 |
8 | interface IDimensions {
9 | width: number;
10 | height: number;
11 | }
12 |
13 | interface InitialAppState {
14 | deviceDimension: IDimensions;
15 | currentSlide: number;
16 | currentSelectedPlugin: any;
17 | isDragging: boolean;
18 | isFullScreen: boolean;
19 | lastSavedSlideDimensions: IDimensions;
20 | direction: EDirection;
21 | slidesDimension: IDimensions;
22 | theme: {
23 | colors: string[];
24 | };
25 | }
26 |
27 | const deviceDimension = {
28 | width: 1280,
29 | height: 800
30 | };
31 |
32 | const initialAppState: InitialAppState = {
33 | deviceDimension,
34 | currentSlide: 0,
35 | currentSelectedPlugin: null,
36 | isDragging: false,
37 | isFullScreen: false,
38 | lastSavedSlideDimensions: deviceDimension,
39 | direction: EDirection.RIGHT,
40 | slidesDimension: {
41 | width: deviceDimension.width * .75,
42 | height: deviceDimension.height * .75
43 | },
44 | theme: {
45 | colors: ['#ffeb3b', '#0062A3'],
46 | }
47 | };
48 |
49 | const appReducer = (state: any = initialAppState, action: any) => {
50 | switch (action.type) {
51 | case constants.ADD_THEME_COLOR: {
52 | const { color } = action;
53 |
54 | const newState = cloneDeep(state);
55 | newState.theme.colors.push(color);
56 |
57 | return newState;
58 | }
59 |
60 | case constants.GO_TO_SLIDE: {
61 | const { maxSlides, slideNumber } = action;
62 | if (slideNumber < 1) return Object.assign({}, state, {
63 | currentSelectedPlugin: null,
64 | currentSlide: 0,
65 | });
66 | if (slideNumber >= maxSlides) return state;
67 | return Object.assign({}, state, {
68 | currentSelectedPlugin: null,
69 | currentSlide: slideNumber,
70 | });
71 | }
72 |
73 | case constants.LEFT_ARROW_PREV: {
74 | const currentSlide: number = state.currentSlide - 1;
75 | return Object.assign({}, state, {
76 | currentSlide,
77 | direction: EDirection.LEFT
78 | });
79 | }
80 |
81 | case constants.RIGHT_ARROW_NEXT: {
82 | const currentSlide: number = state.currentSlide + 1;
83 | return Object.assign({}, state, {
84 | currentSlide,
85 | direction: EDirection.RIGHT
86 | });
87 | }
88 |
89 | case constants.SAVE_LAST_SLIDE_DIMENSION: {
90 | const slideElement = document.getElementById('edit-slide-view');
91 | const { clientWidth: width, clientHeight: height } = slideElement;
92 | return Object.assign({}, state, { lastSavedSlideDimensions: { width, height } });
93 | }
94 |
95 | case constants.SET_ACTIVE_PLUGIN: {
96 | return Object.assign({}, state, { currentSelectedPlugin: action.newActivePlugin });
97 | }
98 |
99 | case constants.TOGGLE_FULLSCREEN: {
100 | const window = remote.getCurrentWindow();
101 | if (state.isFullScreen) {
102 | window.setMenuBarVisibility(true);
103 | window.setFullScreen(false)
104 | } else {
105 | window.setMenuBarVisibility(false);
106 | window.setFullScreen(true);
107 | }
108 |
109 | return Object.assign({}, state, { isFullScreen: !state.isFullScreen });
110 | }
111 |
112 | case constants.TOGGLE_GUIDELINES: {
113 | return Object.assign({}, state, { isDragging: !state.isDragging });
114 | }
115 |
116 | case constants.UPDATE_DEVICE_DIMENSION: {
117 | return Object.assign({}, state, { deviceDimension: action.newDeviceDimension });
118 | }
119 |
120 | case constants.UPDATE_SLIDES_DIMENSION: {
121 | return Object.assign({}, state, { slidesDimension: action.slidesDimension })
122 | }
123 |
124 | default: {
125 | return state;
126 | }
127 | }
128 | };
129 |
130 | const ignoreActions:any = [constants.TOGGLE_GUIDELINES];
131 | const undoableAppReducer = undoable(appReducer, {
132 | filter: function filterActions(action: any, currentState: any, previousHistory: any) {
133 | if (action.type === 'SET_ACTIVE_PLUGIN' || action.type === 'GO_TO_SLIDE') return true;
134 | return false;
135 | }
136 | });
137 |
138 | export { undoableAppReducer };
139 |
--------------------------------------------------------------------------------
/app/reducers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './app.reducer';
2 | export * from './slides.reducer';
3 |
4 |
--------------------------------------------------------------------------------
/app/reducers/slides.reducer.ts:
--------------------------------------------------------------------------------
1 | import * as constants from '../constants/slides.constants';
2 |
3 | const cloneDeep = require('lodash.clonedeep');
4 | const undoable = require('redux-undo').default;
5 |
6 | interface Slide {
7 | plugins: any[];
8 | state: {
9 | backgroundColor: {
10 | r: number;
11 | g: number;
12 | b: number;
13 | a: number;
14 | };
15 | transition: {
16 | right: string;
17 | left: string;
18 | };
19 | };
20 | }
21 |
22 | const initialSlideState: Slide = {
23 | plugins: [],
24 | state: {
25 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
26 | transition: {
27 | right: 'rotate-push-left-move-from-right',
28 | left: 'rotate-push-right-move-from-left',
29 | }
30 | }
31 | };
32 |
33 | const initialSlidesState: Slide[] = [ initialSlideState ];
34 |
35 | export const slidesReducer = (state: any = initialSlidesState, action: any) => {
36 | const slides = cloneDeep(state);
37 |
38 | switch (action.type) {
39 | case constants.ADD_PLUGIN_TO_CURRENT_SLIDE: {
40 | const { plugin, slideNumber } = action;
41 | slides[slideNumber].plugins.push(plugin);
42 | return slides;
43 | }
44 |
45 | case constants.ADD_SLIDE: {
46 | const newSlide = cloneDeep(initialSlideState);
47 | slides.splice(action.currentSlide + 1, 0, newSlide);
48 | return slides;
49 | }
50 |
51 | case constants.DELETE_CURRENT_PLUGIN: {
52 | const { pluginNumber, slideNumber } = action;
53 | slides[slideNumber].plugins[pluginNumber] = null;
54 | return slides;
55 | }
56 |
57 | case constants.DELETE_SLIDE: {
58 | slides.splice(action.slideToDelete, 1);
59 | return slides;
60 | }
61 |
62 | case constants.DUPLICATE_SLIDE: {
63 | const { slideToDuplicate } = action;
64 | const dupedSlide = cloneDeep(slides[slideToDuplicate]);
65 | slides.splice(slideToDuplicate + 1, 0, dupedSlide);
66 | return slides;
67 | }
68 |
69 | case constants.MOVE_SLIDE_DOWN: {
70 | const { slideNumber } = action;
71 |
72 | if (slideNumber === 0) return slides;
73 |
74 | const currentSlide = slides[slideNumber];
75 | const previousSlide = slides[slideNumber - 1];
76 | slides[slideNumber] = previousSlide;
77 | slides[slideNumber - 1] = currentSlide;
78 |
79 | return slides;
80 | }
81 |
82 | case constants.MOVE_SLIDE_UP: {
83 | const { slideNumber } = action;
84 |
85 | if (slideNumber >= state.length - 1) return slides;
86 |
87 | const currentSlide = slides[slideNumber];
88 | const nextSlide = slides[slideNumber + 1];
89 | slides[slideNumber] = nextSlide;
90 | slides[slideNumber + 1] = currentSlide;
91 |
92 | return slides;
93 | }
94 |
95 | case constants.OPEN_FILE: {
96 | const { buffer_data } = action;
97 | const slidesFromFile = JSON.parse(buffer_data.toString());
98 | return slidesFromFile;
99 | }
100 |
101 | case constants.OPEN_NEW_DECK: {
102 | const slides = initialSlidesState;
103 | return slides;
104 | }
105 |
106 | case constants.UPDATE_CURRENT_PLUGIN: {
107 | const { changes, pluginNumber, slideNumber } = action;
108 | const plugin = slides[slideNumber].plugins[pluginNumber];
109 |
110 | for (const change in changes) {
111 | plugin.state[change] = changes[change];
112 | }
113 |
114 | return slides;
115 | }
116 |
117 | case constants.UPDATE_CURRENT_SLIDE: {
118 | const { changes, slideNumber } = action;
119 | const slide = slides[slideNumber];
120 |
121 | for (const change in changes) {
122 | slide.state[change] = changes[change];
123 | }
124 |
125 | return slides;
126 | }
127 |
128 | default: {
129 | return state;
130 | }
131 | }
132 | }
133 |
134 | const undoableSlidesReducer = undoable(slidesReducer, {
135 | filter: function filterActions(action: any, currentState: any, previousHistory: any) {
136 | return true;
137 | }
138 | });
139 |
140 | export { undoableSlidesReducer };
141 |
--------------------------------------------------------------------------------
/app/store/configureStore.development.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux';
2 | import createLogger from 'redux-logger';
3 | import rootReducer from './reducers';
4 |
5 | const logger = createLogger({
6 | level: 'info',
7 | collapsed: true
8 | });
9 |
10 | // If Redux DevTools Extension is installed use it, otherwise use Redux compose
11 | /* eslint-disable no-underscore-dangle */
12 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
13 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
14 | // Options: http://zalmoxisus.github.io/redux-devtools-extension/API/Arguments.html
15 | }) :
16 | compose;
17 | /* eslint-enable no-underscore-dangle */
18 | const enhancer = composeEnhancers(
19 | applyMiddleware(logger)
20 | );
21 |
22 | export default function configureStore(initialState) {
23 | const store = createStore(rootReducer, initialState, enhancer);
24 |
25 | if (module.hot) {
26 | module.hot.accept('./reducers', () =>
27 | store.replaceReducer(require('./reducers')) // eslint-disable-line global-require
28 | );
29 | }
30 |
31 | return store;
32 | }
33 |
--------------------------------------------------------------------------------
/app/store/configureStore.js:
--------------------------------------------------------------------------------
1 | if (process.env.NODE_ENV === 'production') {
2 | module.exports = require('./configureStore.production'); // eslint-disable-line global-require
3 | } else {
4 | module.exports = require('./configureStore.development'); // eslint-disable-line global-require
5 | }
6 |
--------------------------------------------------------------------------------
/app/store/configureStore.production.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import rootReducer from './reducers';
3 |
4 | const enhancer = applyMiddleware();
5 |
6 | export default function configureStore(initialState) {
7 | return createStore(rootReducer, initialState, enhancer);
8 | }
9 |
--------------------------------------------------------------------------------
/app/store/reducers.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import {
4 | undoableAppReducer as app,
5 | undoableSlidesReducer as slides,
6 | } from '../reducers/index.ts';
7 |
8 | const rootReducer = combineReducers({
9 | app,
10 | slides,
11 | });
12 |
13 | export default rootReducer;
14 |
--------------------------------------------------------------------------------
/app/theme/_global.scss:
--------------------------------------------------------------------------------
1 | // Enforces the scrollbar view
2 | ::-webkit-scrollbar {
3 | -webkit-appearance: none;
4 | width: 10px;
5 | }
6 | ::-webkit-scrollbar-thumb {
7 | border-radius: 4px;
8 | background-color: rgba(0,0,0,.5);
9 | box-shadow: 0 0 1px rgba(255,255,255,.5);
10 | }
11 |
12 | * {
13 | background: transparent;
14 | box-sizing: border-box;
15 | margin: 0;
16 | padding: 0;
17 | }
18 |
19 | html, body {
20 | width: 100%;
21 | height: 100%;
22 | }
23 |
24 | a:link visited {
25 | border-color: black;
26 | }
27 |
28 | a:hover {
29 | border-width: 4px;
30 | }
--------------------------------------------------------------------------------
/app/theme/_mixins.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/app/theme/_mixins.scss
--------------------------------------------------------------------------------
/app/theme/_reset.scss:
--------------------------------------------------------------------------------
1 | // blueprintjs
2 | ul, ol {
3 | margin: 0 !important;
4 | padding: 0 !important;
5 | }
6 |
7 | .pt-menu {
8 | min-width: 0 !important;
9 | padding: 0 !important;
10 | background: none !important;
11 | }
12 |
13 | .pt-large .pt-menu-item::before {
14 | margin: 0 5px !important;
15 | }
16 |
17 | .pt-transition-container {
18 | z-index: 100 !important;
19 | }
20 |
21 | .pt-popover-content {
22 | padding: 5px !important;
23 | }
24 |
25 | .pt-menu-item::before, .pt-menu-item::after {
26 | color: #ffe39f !important;
27 | }
28 |
--------------------------------------------------------------------------------
/app/theme/_utils.scss:
--------------------------------------------------------------------------------
1 | .flex {
2 | display: flex;
3 | }
4 |
5 | .flex-grow {
6 | flex-grow: 1;
7 | }
8 |
9 | .vertical {
10 | display: flex;
11 | flex-direction: column;
12 | }
13 |
14 | .v-center {
15 | align-items: center;
16 | }
17 |
18 | .middle {
19 | align-self: center;
20 | }
21 |
22 | .center {
23 | margin: 0 auto;
24 | }
25 |
26 | // margins and paddings
27 | $directions: top, right, bottom, left;
28 | $sizes: 's', 'm', 'l';
29 | $baseSize: 8px;
30 |
31 | @each $size in $sizes {
32 | @each $direction in $directions {
33 | .#{$size}-m-#{$direction} {
34 | margin-#{$direction}: $baseSize;
35 | }
36 | }
37 | $baseSize: $baseSize + 4px;
38 | }
39 |
40 | @each $size in $sizes {
41 | @each $direction in $directions {
42 | .#{$size}-p-#{$direction} {
43 | padding-#{$direction}: $baseSize;
44 | }
45 | }
46 | $baseSize: $baseSize + 4px;
47 | }
48 |
--------------------------------------------------------------------------------
/app/theme/_variables.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/app/theme/_variables.scss
--------------------------------------------------------------------------------
/app/theme/mixins/_toolbar.scss:
--------------------------------------------------------------------------------
1 | @mixin toolbar-border {
2 | border: 1px #000 solid;
3 | border-radius: 0px;
4 | }
--------------------------------------------------------------------------------
/app/theme/toolbar.scss:
--------------------------------------------------------------------------------
1 | @import './mixins/_toolbar';
--------------------------------------------------------------------------------
/app/utils/requireContext.ts:
--------------------------------------------------------------------------------
1 | // Pre-load all of our plugins so we can dynamically require
2 | // them later. Use req instead of require when trying to
3 | // access those modules.
4 |
5 | // Fix:
6 | // ERROR in ./app/utils/requireContext.ts
7 | // (4,21): error TS2339: Property 'context' does not exist on type 'NodeRequire'.
8 | declare const require: any;
9 |
10 | const req = require.context('../plugins/node_modules', true);
11 |
12 | export default function(moduleName: string) {
13 | return req(`./${moduleName}/index`).default;
14 | }
15 |
--------------------------------------------------------------------------------
/docs/API.md:
--------------------------------------------------------------------------------
1 | # API Reference
2 |
3 | * [Component](#component)
4 | * [Options Menu](#optionsmenu)
5 |
6 | ## \
7 | This is what gets render onto the slide.
8 |
9 | ### Props
10 |
11 | **```isFullscreen: boolean```**
12 |
13 | Whether the app is currently in fullscreen mode or not.
14 |
15 | **```pluginState: Object```**
16 |
17 | The state of your plugin.
18 |
19 | ```typescript
20 | {
21 | // Default properties
22 | width: number, // 300
23 | height: number, // 300
24 | lockAspectRatio: boolean, // false
25 | isResizable: {
26 | top: boolean, // false
27 | right: boolean, // true
28 | bottom: boolean, // true
29 | left: boolean, // false
30 | topRight: boolean, // false
31 | bottomRight: boolean, // true
32 | bottomLeft: boolean, // false
33 | topLeft: boolean // false
34 | },
35 |
36 | // Your properties get added after default properties so you can
37 | // overwrite our default properties or add on more properties
38 | ...
39 | }
40 | ```
41 |
42 | **```updateCurrentPlugin(changes: Object)```**
43 |
44 | The changes you want to make to `pluginState`.
45 |
46 | For example:
47 |
48 | ```javascript
49 | updateCurrentPlugin({ value: 'foo' });
50 | ```
51 |
52 | will change the `pluginState` to
53 |
54 | ```javascript
55 | {
56 | value: 'foo',
57 | ...
58 | }
59 | ```
60 |
61 | You can also pass in multiple changes at once:
62 |
63 | ```javascript
64 | updateCurrentPlugin({ value: 'foo', color: 'blue' });
65 | ```
66 |
67 | ## \
68 | The options you want to provide with your \.
69 | Keep in mind that the container is 295px wide.
70 |
71 |
72 | ### Props
73 |
74 | **```addThemeColor(color: string)```**
75 |
76 | Add a new color to your themes.
77 |
78 | ```javascript
79 | addThemeColor('#ffeb3b');
80 | ```
81 |
82 |
83 | **```pluginState: Object```**
84 |
85 | The state of your plugin. Same as `pluginState` from [\](#component).
86 |
87 | **```theme: Object```**
88 |
89 | ```typescript
90 | {
91 | colors: string[] // ['#ffeb3b', '#0062A3']
92 | }
93 | ```
94 |
95 | **```updateCurrentPlugin(changes: Object)```**
96 |
97 | The changes you want to make to `pluginState`. Same as `updateCurrentPlugin` from
98 | [\](#component).
--------------------------------------------------------------------------------
/docs/Create_Plugin.md:
--------------------------------------------------------------------------------
1 | # Creating a Plugin
2 |
3 | Your plugin is required like any other node module. We expect an object
4 | with the following properties.
5 |
6 | ```typescript
7 | {
8 | component: , // React component constructor
9 | icon: string, // icon name taken from blueprintjs
10 | moduleName: string, // name registered with npm
11 | optionsMenuComponent: , // React component constructor
12 | state: Object, // state of your plugin (set default properties here)
13 | tooltip: string, // text that appears on hovering over icon
14 | }
15 | ```
16 |
17 | Notes:
18 |
19 | - The `icon` value must be one of those from [blueprintjs](http://blueprintjs.com/docs/#icons.ui)
20 | without the 'pt-icon-'.
21 |
22 | - Create the `component` and the `optionsMenuComponent` by following the [API Guide](API.md).
--------------------------------------------------------------------------------
/docs/Example.md:
--------------------------------------------------------------------------------
1 | # Example: Creating an Image Plugin
2 | We recommend using something like babel to make your development easier!
3 |
4 | ### index.js
5 |
6 | ```javascript
7 | import Image from './Image';
8 | import ImageOptions from './ImageOptions';
9 |
10 | export default {
11 | component: Image,
12 | icon: 'media',
13 | moduleName: 'devdecks-image',
14 | tooltip: 'Image',
15 | optionsMenuComponent: ImageOptions,
16 | state: {
17 | height: 200,
18 | imageBufferString: '',
19 | lockAspectRatio: true,
20 | },
21 | };
22 | ```
23 |
24 | ### Image.jsx
25 |
26 | ```javascript
27 | import React from 'react';
28 |
29 | // Utilize Node's APIs too!
30 | import fs from 'fs';
31 |
32 | // Freely use any of Electron's API with your plugin!
33 | import { remote } from 'electron';
34 |
35 | // Options for Electron's dialog API
36 | const options = {
37 | filters: [
38 | {
39 | name: 'Images',
40 | extensions: [ 'jpeg', 'jpg', 'gif', 'png' ]
41 | }
42 | ]
43 | };
44 |
45 | const Image = ({ pluginState, updateCurrentPlugin }) => {
46 | // Prompt user to select an image from their file system
47 | const selectImageFile = () => {
48 | remote.dialog.showOpenDialog(options, filePaths => {
49 | if (!filePaths) return;
50 | fs.readFile(filePaths[0], (err, data) => {
51 | if (err) return;
52 | const imageBufferString = new Buffer(data).toString('base64');
53 | updateCurrentPlugin({ imageBufferString });
54 | });
55 | });
56 | };
57 |
58 | const { width, imageBufferString } = pluginState;
59 |
60 | return (
61 | {
62 | imageBufferString
63 | ?
68 | :
72 | }
73 | );
74 | }
75 |
76 | export default Image;
77 | ```
78 |
79 | ### ImageOptions.jsx
80 |
81 | ```javascript
82 | import React from 'react';
83 | import fs from 'fs';
84 | import { remote } from 'electron';
85 |
86 | // Feel free to use any libraries you want!
87 | // We're basing our UI from blueprintjs
88 | import { Button } from '@blueprintjs/core';
89 |
90 | // Got styles? Import your stylesheets
91 | import './devdecks-image.scss';
92 |
93 | const options = {
94 | filters: [
95 | {
96 | name: 'Images',
97 | extensions: [ 'jpeg', 'jpg', 'gif', 'png' ]
98 | }
99 | ]
100 | };
101 |
102 | const ImageOptions = ({ updateCurrentPlugin }) => {
103 | // Prompt user to select an image from their file system
104 | const selectImageFile = () => {
105 | remote.dialog.showOpenDialog(options, filePaths => {
106 | if (!filePaths) return;
107 | fs.readFile(filePaths[0], (err, data) => {
108 | if (err) return;
109 | const imageBufferString = new Buffer(data).toString('base64');
110 | updateCurrentPlugin({ imageBufferString });
111 | });
112 | });
113 | };
114 |
115 | return (
116 |
117 |
Image Options
118 |
119 |
122 | Upload Image
123 |
124 |
125 | );
126 | };
127 |
128 | export default ImageOptions;
129 | ```
130 |
131 | ### devdecks-image.scss
132 | ```scss
133 | #devdecks-image-options-container {
134 | #add-image-button {
135 | width: 100%;
136 | }
137 | }
138 | ```
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Table of Contents
2 |
3 | * [Creating a plugin](Create_Plugin.md)
4 | * [API](API.md)
5 | * [Example](Example.md)
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "devdecks",
3 | "author": {
4 | "name": "CHAD",
5 | "email": "chad.devdecks@gmail.com"
6 | },
7 | "productName": "DevDecks",
8 | "version": "0.1.0",
9 | "description": "An open-source and extensible, standalone presentation app for developers",
10 | "main": "main.js",
11 | "scripts": {
12 | "test": "cross-env NODE_ENV=test NODE_PATH=./app BABEL_DISABLE_CACHE=1 mocha --retries 2 --compilers ts:ts-node/register --recursive --require ignore-styles ./test/setup.ts test/**/*.spec.ts test/**/*.spec.tsx",
13 | "test-watch": "npm test -- --watch",
14 | "test-e2e": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 mocha --retries 2 --compilers js:babel-register --require ./test/setup.js ./test/e2e.spec.js",
15 | "lint": "tslint --ignore-path .gitignore app/**/*.tsx",
16 | "hot-server": "cross-env NODE_ENV=development node -r babel-register server.js",
17 | "build-main": "cross-env NODE_ENV=production node -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.electron.js --progress --profile --colors",
18 | "build-renderer": "cross-env NODE_ENV=production node -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.production.js --progress --profile --colors",
19 | "build": "npm run build-main && npm run build-renderer",
20 | "start": "cross-env NODE_ENV=production electron ./app/",
21 | "start-hot": "cross-env HOT=1 NODE_ENV=development electron -r babel-register -r babel-polyfill ./app/main.development",
22 | "postinstall": "concurrently \"install-app-deps\" \"node node_modules/fbjs-scripts/node/check-dev-engines.js package.json\"",
23 | "dev": "npm run hot-server -- --start-hot",
24 | "package": "npm run build && build --publish never",
25 | "package-win": "npm run build && build --win --x64",
26 | "package-linux": "npm run build && build --linux",
27 | "package-all": "npm run build && build -mwl"
28 | },
29 | "pre-commit": [
30 | "test"
31 | ],
32 | "build": {
33 | "appId": "org.develar.ElectronReact",
34 | "category": "public.app-category.tools",
35 | "dmg": {
36 | "contents": [
37 | {
38 | "x": 410,
39 | "y": 150,
40 | "type": "link",
41 | "path": "/Applications"
42 | },
43 | {
44 | "x": 130,
45 | "y": 150,
46 | "type": "file"
47 | }
48 | ]
49 | },
50 | "files": [
51 | "dist/",
52 | "node_modules/",
53 | "app.html",
54 | "main.js",
55 | "main.js.map",
56 | "package.json"
57 | ],
58 | "win": {
59 | "target": "nsis"
60 | },
61 | "linux": {
62 | "target": [
63 | "deb",
64 | "AppImage"
65 | ]
66 | }
67 | },
68 | "directories": {
69 | "buildResources": "resources",
70 | "output": "release"
71 | },
72 | "bin": {
73 | "electron": "./node_modules/.bin/electron"
74 | },
75 | "repository": {
76 | "type": "git",
77 | "url": "git+https://github.com/Team-CHAD/DevDecks.git"
78 | },
79 | "license": "MIT",
80 | "bugs": {
81 | "url": "https://github.com/Team-CHAD/DevDecks/issues"
82 | },
83 | "keywords": [
84 | "electron",
85 | "slides",
86 | "presentation",
87 | "extensible",
88 | "revolutionary",
89 | "CHAD",
90 | "DevDecks"
91 | ],
92 | "homepage": "https://github.com/Team-CHAD/DevDecks#readme",
93 | "devDependencies": {
94 | "@types/chai": "^3.4.34",
95 | "@types/classnames": "0.0.32",
96 | "@types/dom4": "^1.5.20",
97 | "@types/electron": "^1.4.29",
98 | "@types/enzyme": "^2.7.1",
99 | "@types/jsdom": "^2.0.29",
100 | "@types/mocha": "^2.2.38",
101 | "@types/node": "^6.0.60",
102 | "@types/pure-render-decorator": "^0.2.27",
103 | "@types/react": "^0.14.50",
104 | "@types/react-addons-css-transition-group": "^0.14.18",
105 | "@types/react-color": "^2.3.0",
106 | "@types/react-dom": "^0.14.19",
107 | "@types/react-redux": "^4.4.35",
108 | "@types/react-select": "^1.0.37",
109 | "@types/redux": "^3.6.31",
110 | "@types/redux-logger": "^2.6.32",
111 | "@types/sinon": "^1.16.34",
112 | "@types/tether": "^1.4.0",
113 | "asar": "^0.12.3",
114 | "babel-core": "^6.18.0",
115 | "babel-eslint": "^7.1.0",
116 | "babel-loader": "^6.2.7",
117 | "babel-plugin-add-module-exports": "^0.2.1",
118 | "babel-plugin-dev-expression": "^0.2.1",
119 | "babel-plugin-react-transform": "^2.0.2",
120 | "babel-plugin-webpack-loaders": "^0.8.0",
121 | "babel-polyfill": "^6.16.0",
122 | "babel-preset-es2015": "^6.18.0",
123 | "babel-preset-react": "^6.16.0",
124 | "babel-preset-react-optimize": "^1.0.1",
125 | "babel-preset-stage-0": "^6.16.0",
126 | "babel-register": "^6.18.0",
127 | "chai": "^3.5.0",
128 | "concurrently": "^3.1.0",
129 | "cross-env": "^3.1.3",
130 | "css-loader": "^0.26.0",
131 | "devtron": "^1.4.0",
132 | "electron": "^1.4.4",
133 | "electron-builder": "^8.3.0",
134 | "electron-devtools-installer": "^2.0.1",
135 | "enzyme": "^2.5.1",
136 | "eslint": "^3.9.1",
137 | "eslint-config-airbnb": "^13.0.0",
138 | "eslint-formatter-pretty": "^1.1.0",
139 | "eslint-import-resolver-webpack": "^0.7.0",
140 | "eslint-loader": "^1.6.0",
141 | "eslint-plugin-flowtype-errors": "^1.5.0",
142 | "eslint-plugin-import": "^2.1.0",
143 | "eslint-plugin-jsx-a11y": "^2.2.3",
144 | "eslint-plugin-mocha": "^4.7.0",
145 | "eslint-plugin-promise": "^3.3.0",
146 | "eslint-plugin-react": "^6.7.1",
147 | "express": "^4.14.0",
148 | "extract-text-webpack-plugin": "^1.0.1",
149 | "fbjs-scripts": "^0.7.1",
150 | "file-loader": "^0.9.0",
151 | "html-webpack-plugin": "^2.24.0",
152 | "ignore-styles": "^5.0.1",
153 | "jsdom": "^9.8.3",
154 | "json-loader": "^0.5.4",
155 | "minimist": "^1.2.0",
156 | "mocha": "^3.1.2",
157 | "node-sass": "^3.13.0",
158 | "pre-commit": "^1.2.2",
159 | "react-addons-test-utils": "^15.3.2",
160 | "react-transform-hmr": "^1.0.4",
161 | "redux-ignore": "^1.2.4",
162 | "redux-logger": "^2.7.4",
163 | "sass-loader": "^4.0.2",
164 | "sinon": "^1.17.6",
165 | "spectron": "^3.4.0",
166 | "style-loader": "^0.13.1",
167 | "tcomb": "^3.2.15",
168 | "ts-loader": "^1.3.2",
169 | "ts-node": "^2.0.0",
170 | "tslint": "^4.3.1",
171 | "tslint-react": "^2.3.0",
172 | "typescript": "^2.2.0-dev.20161129",
173 | "url-loader": "^0.5.7",
174 | "webpack": "^1.13.3",
175 | "webpack-dev-middleware": "^1.8.4",
176 | "webpack-hot-middleware": "^2.13.2",
177 | "webpack-merge": "^0.17.0",
178 | "webpack-validator": "^2.2.9"
179 | },
180 | "dependencies": {
181 | "@blueprintjs/core": "^1.0.1",
182 | "brace": "^0.9.0",
183 | "classnames": "^2.2.5",
184 | "electron-debug": "^1.0.1",
185 | "lodash.clonedeep": "^4.5.0",
186 | "lodash.throttle": "^4.1.1",
187 | "react": "^15.3.2",
188 | "react-ace": "^4.1.0",
189 | "react-color": "^2.10.0",
190 | "react-contenteditable": "^2.0.1",
191 | "react-dom": "^15.3.2",
192 | "react-highlight": "^0.9.0",
193 | "react-redux": "^4.4.5",
194 | "react-rnd": "^4.2.0",
195 | "react-select": "^1.0.0-rc.2",
196 | "react-sortable-hoc": "^0.4.5",
197 | "react-transitions": "0.0.2",
198 | "redux": "^3.6.0",
199 | "redux-undo": "^1.0.0-beta9-2",
200 | "source-map-support": "^0.4.6"
201 | },
202 | "devEngines": {
203 | "node": ">=6.x",
204 | "npm": ">=3.x"
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/resources/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/resources/icon.icns
--------------------------------------------------------------------------------
/resources/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/resources/icon.ico
--------------------------------------------------------------------------------
/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/resources/icon.png
--------------------------------------------------------------------------------
/resources/icons/1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/resources/icons/1024x1024.png
--------------------------------------------------------------------------------
/resources/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/resources/icons/128x128.png
--------------------------------------------------------------------------------
/resources/icons/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/resources/icons/16x16.png
--------------------------------------------------------------------------------
/resources/icons/24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/resources/icons/24x24.png
--------------------------------------------------------------------------------
/resources/icons/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/resources/icons/256x256.png
--------------------------------------------------------------------------------
/resources/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/resources/icons/32x32.png
--------------------------------------------------------------------------------
/resources/icons/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/resources/icons/48x48.png
--------------------------------------------------------------------------------
/resources/icons/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/resources/icons/512x512.png
--------------------------------------------------------------------------------
/resources/icons/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/resources/icons/64x64.png
--------------------------------------------------------------------------------
/resources/icons/96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/resources/icons/96x96.png
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | /**
3 | * Setup and run the development server for Hot-Module-Replacement
4 | * https://webpack.github.io/docs/hot-module-replacement-with-webpack.html
5 | */
6 |
7 | import express from 'express';
8 | import webpack from 'webpack';
9 | import webpackDevMiddleware from 'webpack-dev-middleware';
10 | import webpackHotMiddleware from 'webpack-hot-middleware';
11 | import { spawn } from 'child_process';
12 |
13 | import config from './webpack.config.development';
14 |
15 | const argv = require('minimist')(process.argv.slice(2));
16 |
17 | const app = express();
18 | const compiler = webpack(config);
19 | const PORT = process.env.PORT || 3000;
20 |
21 | const wdm = webpackDevMiddleware(compiler, {
22 | publicPath: config.output.publicPath,
23 | noInfo: true,
24 | hot: true,
25 | stats: {
26 | colors: true
27 | }
28 | });
29 |
30 | app.use(wdm);
31 |
32 | app.use(webpackHotMiddleware(compiler));
33 |
34 | const server = app.listen(PORT, 'localhost', serverError => {
35 | if (serverError) {
36 | return console.error(serverError);
37 | }
38 |
39 | if (argv['start-hot']) {
40 | spawn('npm', ['run', 'start-hot'], { shell: true, env: process.env, stdio: 'inherit' })
41 | .on('close', code => process.exit(code))
42 | .on('error', spawnError => console.error(spawnError));
43 | }
44 |
45 | console.log(`Listening at http://localhost:${PORT}`);
46 | });
47 |
48 | process.on('SIGTERM', () => {
49 | console.log('Stopping dev server');
50 | wdm.close();
51 | server.close(() => {
52 | process.exit(0);
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "plugins": [
6 | "mocha"
7 | ],
8 | "rules": {
9 | "mocha/no-exclusive-tests": 2,
10 | "mocha/no-skipped-tests": 0,
11 | "mocha/no-pending-tests": 2,
12 | "mocha/handle-done-callback": 2,
13 | "mocha/no-synchronous-tests": 0,
14 | "mocha/no-global-tests": 2,
15 | "mocha/valid-test-description": 2,
16 | "mocha/valid-suite-description": 2,
17 | "mocha/no-sibling-hooks": 0,
18 | "mocha/no-mocha-arrows": 0,
19 | "mocha/no-hooks": 0,
20 | "mocha/no-top-level-hooks": 0,
21 | "no-unused-expressions": 0
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test/components/ControlPanel.spec.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as sinon from 'sinon';
3 | import { expect } from 'chai';
4 | import { shallow } from 'enzyme';
5 | import { ControlPanelComponent } from '../../app/modules/ControlPanel/ControlPanel';
6 |
7 | function setup() {
8 | const props = {
9 | currentSlide: 0,
10 | numberOfSlides: 3,
11 | addSlide: sinon.spy(() => {}),
12 | goToSlide: sinon.spy(() => {}),
13 | saveLastSlideDimensions: sinon.spy(() => {}),
14 | toggleFullScreen: sinon.spy(() => {}),
15 | };
16 |
17 | const wrapper = shallow( );
18 |
19 | return { props, wrapper };
20 | }
21 |
22 | describe(' ', () => {
23 | const CLICKS = 3;
24 |
25 | describe('Self', () => {
26 | const { wrapper } = setup();
27 |
28 | it('should render two components', () => {
29 | expect(wrapper.find('Button')).to.have.length(2);
30 | });
31 |
32 | it('should render one Add Button', () => {
33 | expect(wrapper.find({ iconName: 'add' })).to.have.length(1);
34 | });
35 |
36 | it('should render one FullScreen Button', () => {
37 | expect(wrapper.find({ iconName: 'fullscreen' })).to.have.length(1);
38 | });
39 | })
40 |
41 | describe('Add Button', () => {
42 | const { props: { addSlide, goToSlide }, wrapper } = setup();
43 |
44 | const addButton = wrapper.find({ iconName: 'add' });
45 |
46 | afterEach(() => {
47 | addSlide.reset();
48 | goToSlide.reset();
49 | });
50 |
51 | it('should call addSlide when clicked', () => {
52 | for (let i = 0; i < CLICKS; i++) {
53 | addButton.simulate('click');
54 | expect(addSlide.callCount).to.equal(i + 1);
55 | }
56 | });
57 |
58 | it('should call goToSlide when clicked', () => {
59 | for (let i = 0; i < CLICKS; i++) {
60 | addButton.simulate('click');
61 | expect(goToSlide.callCount).to.equal(i + 1);
62 | }
63 | });
64 | });
65 |
66 | describe('FullScreen Button', () => {
67 | const { props: { saveLastSlideDimensions, toggleFullScreen }, wrapper } = setup();
68 | const fullScreenButton = wrapper.find({ iconName: 'fullscreen' });
69 |
70 | afterEach(() => {
71 | saveLastSlideDimensions.reset();
72 | toggleFullScreen.reset();
73 | });
74 |
75 | it('should call saveLastSlideDimensions when clicked', () => {
76 | for (let i = 0; i < CLICKS; i++) {
77 | fullScreenButton.simulate('click');
78 | expect(saveLastSlideDimensions.callCount).to.equal(i + 1);
79 | }
80 | });
81 |
82 | it('should call toggleFullScreen when clicked', () => {
83 | for(let i = 0; i < CLICKS; i++) {
84 | fullScreenButton.simulate('click');
85 | expect(toggleFullScreen.callCount).to.equal(i + 1);
86 | }
87 | });
88 | });
89 | });
--------------------------------------------------------------------------------
/test/components/SmartSlide-spec.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as sinon from 'sinon';
3 | import { expect } from 'chai';
4 | import { shallow } from 'enzyme';
5 | import { SmartSlideComponent } from '../../app/modules/SmartSlide/SmartSlide';
6 |
7 | function setup() {
8 | const deviceDimension = {
9 | width: 1280,
10 | height: 800
11 | };
12 | const props = {
13 | currentSelectedPlugin: {
14 | moduleName: '',
15 | pluginNumber: 1,
16 | slideNumber: 2
17 | },
18 | goToSlide: sinon.spy(() => { }),
19 | isInPresenterMode: false,
20 | setActivePlugin: sinon.spy(() => { }),
21 | slide: {
22 | plugins: [{ state: { text: 'plugin1' } }, { state: { text: 'plugin2' } }, { state: { text: 'plugin3' } }, { state: { text: 'plugin4' } }],
23 | state: {
24 | backgroundColor: {
25 | r: 100,
26 | g: 100,
27 | b: 100,
28 | a: 1
29 | },
30 | transition: {
31 | right: 'rotate-push-left-move-from-right',
32 | left: 'rotate-push-right-move-from-left',
33 | }
34 | }
35 | },
36 | slidesDimension: {
37 | width: deviceDimension.width * .75,
38 | height: deviceDimension.height * .75,
39 | },
40 | scale: Math.min( this.slidesDimension.width / deviceDimension.width, this.slidesDimension.height / deviceDimension.height),
41 | slideNumber: 2,
42 | toggleGuidelines: sinon.spy(() => { }),
43 | updateCurrentPlugin: sinon.spy(() => { })
44 | }
45 |
46 | const wrapper = shallow( );
47 |
48 | return { props, wrapper };
49 | }
50 |
51 | describe(' ', () => {
52 |
53 | describe('Self', () => {
54 | const { wrapper } = setup();
55 |
56 | it('should render four components to the slide', () => {
57 | expect(wrapper.find('Plugin')).to.have.length(4);
58 | })
59 | })
60 |
61 | describe('Plugin', () => {
62 | const { props: { currentSelectedPlugin, setActivePlugin, updateCurrentPlugin }, wrapper } = setup();
63 |
64 | const plugins = wrapper.find('Plugin');
65 |
66 | afterEach(() => {
67 | setActivePlugin.reset();
68 | updateCurrentPlugin.reset();
69 | });
70 |
71 | it('should call setActivePlugin on plugin 2 when plugin 2 is clicked and plugin 1 is current plugin', () => {
72 | plugins[2].simulate('click');
73 | expect(setActivePlugin.callCount).to.equal(1);
74 | expect(currentSelectedPlugin.pluginNumber).to.equal(2);
75 | })
76 |
77 | it('should update plugin 1 state[text] when plugin 1 is current plugin and state changes', () => {
78 | expect(currentSelectedPlugin.pluginNumber).to.equal(1);
79 | plugins[1].simulate('keyDown', { keycode: 65 });
80 | plugins[1].simulate('keyDown', { keycode: 78 });
81 | expect(updateCurrentPlugin.callCount).to.equal(2);
82 | expect(plugins[1].state.text).to.equal('plugin1an')
83 | })
84 | })
85 |
86 |
87 | })
88 |
89 |
90 |
--------------------------------------------------------------------------------
/test/e2e.spec.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-CHAD/DevDecks/a7b823a832fba7752b79bb28d92f40e06d4af1f2/test/e2e.spec.js
--------------------------------------------------------------------------------
/test/reducers/slides/actions/addPluginToCurrentSlide-spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { ADD_PLUGIN_TO_CURRENT_SLIDE } from '../../../../app/constants/slides.constants';
3 |
4 | export default function(initialState: any, reducer: any, slide: any) {
5 | const plugin1 = 'plugin1';
6 |
7 | describe('ADD_PLUGIN_TO_CURRENT_SLIDE', () => {
8 | it('should add a new plugin to selected slide', () => {
9 | expect(
10 | reducer(initialState, {
11 | type: ADD_PLUGIN_TO_CURRENT_SLIDE,
12 | plugin: plugin1,
13 | slideNumber: 0
14 | })
15 | ).to.deep.equal([{
16 | plugins: ['plugin1'],
17 | state: {
18 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
19 | transition: {
20 | right: 'rotate-push-left-move-from-right',
21 | left: 'rotate-push-right-move-from-left',
22 | }
23 | }
24 | }]);
25 | });
26 |
27 | it('should add new plugins to the end of the plugin list', () => {
28 | const _initialState = [{
29 | plugins: ['plugin5', 'plugin4'],
30 | state: {
31 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
32 | transition: {
33 | right: 'rotate-push-left-move-from-right',
34 | left: 'rotate-push-right-move-from-left',
35 | }
36 | }
37 | }];
38 |
39 | expect(reducer(_initialState, {
40 | type: ADD_PLUGIN_TO_CURRENT_SLIDE,
41 | plugin: plugin1,
42 | slideNumber: 0
43 | })).to.deep.equal([{
44 | plugins: ['plugin5', 'plugin4', plugin1],
45 | state: {
46 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
47 | transition: {
48 | right: 'rotate-push-left-move-from-right',
49 | left: 'rotate-push-right-move-from-left',
50 | }
51 | }
52 | }]);
53 | });
54 | });
55 | }
56 |
--------------------------------------------------------------------------------
/test/reducers/slides/actions/addSlide-spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { ADD_SLIDE } from '../../../../app/constants/slides.constants';
3 |
4 | export default function(initialState: any, reducer: any, slide: any) {
5 | describe('ADD_SLIDE', () => {
6 | it('should add a new slide at position 1 when current slide is 0 and there is only one slide', () => {
7 | const _initialState: any = [{
8 | plugins: ['plugin1'],
9 | state: {
10 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
11 | transition: {
12 | right: 'rotate-push-left-move-from-right',
13 | left: 'rotate-push-right-move-from-left',
14 | }
15 | }
16 | }];
17 |
18 | expect(
19 | reducer(_initialState, {
20 | type: ADD_SLIDE,
21 | currentSlide: 0
22 | })
23 | ).to.deep.equal([
24 | {
25 | plugins: ['plugin1'],
26 | state: {
27 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
28 | transition: {
29 | right: 'rotate-push-left-move-from-right',
30 | left: 'rotate-push-right-move-from-left',
31 | }
32 | }
33 | },
34 | slide
35 | ])
36 | .and.to.have.lengthOf(2);
37 | });
38 |
39 | it('should add a new slide at position 2 when current slide is 1 and there are three slides', () => {
40 | const _initialState: any = [{
41 | plugins: ['plugin1'],
42 | state: {
43 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
44 | transition: {
45 | right: 'rotate-push-left-move-from-right',
46 | left: 'rotate-push-right-move-from-left',
47 | }
48 | }
49 | }, {
50 | plugins: ['plugin1', 'plugin2'],
51 | state: {
52 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
53 | transition: {
54 | right: 'rotate-push-left-move-from-right',
55 | left: 'rotate-push-right-move-from-left',
56 | }
57 | }
58 | }, {
59 | plugins: ['plugin1', 'plugin2', 'plugin5'],
60 | state: {
61 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
62 | transition: {
63 | right: 'rotate-push-left-move-from-right',
64 | left: 'rotate-push-right-move-from-left',
65 | }
66 | }
67 | }];
68 |
69 | expect(
70 | reducer(_initialState, {
71 | type: ADD_SLIDE,
72 | currentSlide: 1
73 | })
74 | )
75 | .to.deep.equal([
76 | {
77 | plugins: ['plugin1'],
78 | state: {
79 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
80 | transition: {
81 | right: 'rotate-push-left-move-from-right',
82 | left: 'rotate-push-right-move-from-left',
83 | }
84 | }
85 | }, {
86 | plugins: ['plugin1', 'plugin2'],
87 | state: {
88 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
89 | transition: {
90 | right: 'rotate-push-left-move-from-right',
91 | left: 'rotate-push-right-move-from-left',
92 | }
93 | }
94 | }, slide, {
95 | plugins: ['plugin1', 'plugin2', 'plugin5'],
96 | state: {
97 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
98 | transition: {
99 | right: 'rotate-push-left-move-from-right',
100 | left: 'rotate-push-right-move-from-left',
101 | }
102 | }
103 | }
104 | ])
105 | .and.to.have.lengthOf(4);
106 | });
107 |
108 | it('should add a new slide at the end when last slide is selected', () => {
109 | const _initialState: any = [{
110 | plugins: ['plugin1', 'plugin2'],
111 | state: {
112 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
113 | transition: {
114 | right: 'rotate-push-left-move-from-right',
115 | left: 'rotate-push-right-move-from-left',
116 | }
117 | }
118 | }, {
119 | plugins: ['plugin1', 'plugin2'],
120 | state: {
121 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
122 | transition: {
123 | right: 'rotate-push-left-move-from-right',
124 | left: 'rotate-push-right-move-from-left',
125 | }
126 | }
127 | }];
128 |
129 | expect(
130 | reducer(_initialState, {
131 | type: ADD_SLIDE,
132 | currentSlide: 1
133 | })
134 | )
135 | .to.deep.equal([
136 | {
137 | plugins: ['plugin1', 'plugin2'],
138 | state: {
139 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
140 | transition: {
141 | right: 'rotate-push-left-move-from-right',
142 | left: 'rotate-push-right-move-from-left',
143 | }
144 | }
145 | },
146 | {
147 | plugins: ['plugin1', 'plugin2'],
148 | state: {
149 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
150 | transition: {
151 | right: 'rotate-push-left-move-from-right',
152 | left: 'rotate-push-right-move-from-left',
153 | }
154 | }
155 | },
156 | slide
157 | ])
158 | .and.to.have.lengthOf(3)
159 | });
160 | });
161 | }
162 |
--------------------------------------------------------------------------------
/test/reducers/slides/actions/deleteCurrentPlugin-spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { DELETE_CURRENT_PLUGIN } from '../../../../app/constants/slides.constants';
3 |
4 | export default function(initialState: any, reducer: any, slide: any) {
5 | describe('DELETE_CURRENT_PLUGIN', () => {
6 | it('should the first plugin from the first slide', () => {
7 | const _initialState: any = [{
8 | plugins: ['plugin1'],
9 | state: {
10 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
11 | transition: {
12 | right: 'rotate-push-left-move-from-right',
13 | left: 'rotate-push-right-move-from-left',
14 | }
15 | }
16 | }];
17 | expect(
18 | reducer(_initialState, {
19 | type: DELETE_CURRENT_PLUGIN,
20 | pluginNumber: 0,
21 | slideNumber: 0
22 | })
23 | ).to.deep.equal([
24 | {
25 | plugins: [null],
26 | state: {
27 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
28 | transition: {
29 | right: 'rotate-push-left-move-from-right',
30 | left: 'rotate-push-right-move-from-left',
31 | }
32 | }
33 | }
34 | ]);
35 | });
36 |
37 | it('should delete the third plugin only from the second slide', () => {
38 | const _initialState: any = [
39 | slide,
40 | {
41 | plugins: ['plugin1', 'plugin2', 'plugin3'],
42 | state: {
43 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
44 | transition: {
45 | right: 'rotate-push-left-move-from-right',
46 | left: 'rotate-push-right-move-from-left',
47 | }
48 | }
49 | }
50 | ];
51 | expect(
52 | reducer(_initialState, {
53 | type: DELETE_CURRENT_PLUGIN,
54 | pluginNumber: 2,
55 | slideNumber: 1
56 | })
57 | ).to.deep.equal([
58 | slide,
59 | {
60 | plugins: ['plugin1', 'plugin2', null],
61 | state: {
62 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
63 | transition: {
64 | right: 'rotate-push-left-move-from-right',
65 | left: 'rotate-push-right-move-from-left',
66 | }
67 | }
68 | }
69 | ]);
70 | });
71 | });
72 | }
73 |
--------------------------------------------------------------------------------
/test/reducers/slides/actions/deleteSlide-spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { DELETE_SLIDE } from '../../../../app/constants/slides.constants';
3 |
4 | export default function(initialState: any, reducer: any, slide: any) {
5 | const dummySlide1 = {
6 | plugins: ['plugin1'],
7 | state: {
8 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
9 | transition: {
10 | right: 'rotate-push-left-move-from-right',
11 | left: 'rotate-push-right-move-from-left',
12 | }
13 | }
14 | };
15 |
16 | describe('DELETE_SLIDE', () => {
17 | const _initialState: any = [slide, dummySlide1];
18 | it('should delete Slide 0 when the first slide is selected', () => {
19 | expect(
20 | reducer(_initialState, {
21 | type: DELETE_SLIDE,
22 | slideToDelete: 0
23 | })
24 | )
25 | .to.deep.equal([dummySlide1])
26 | .and.to.have.lengthOf(1)
27 | });
28 |
29 | it('should delete Slide 1 when the second slide is selected', () => {
30 | expect(
31 | reducer(_initialState, {
32 | type: DELETE_SLIDE,
33 | slideToDelete: 1
34 | })
35 | )
36 | .to.deep.equal([slide])
37 | .and.to.have.lengthOf(1)
38 | });
39 |
40 | it('should delete only one slide when there are multiple slides', () => {
41 | const _initialState = [slide, slide, slide];
42 | expect(
43 | reducer(_initialState, {
44 | type: DELETE_SLIDE,
45 | slideToDelete: 1
46 | })
47 | )
48 | .to.deep.equal([slide, slide])
49 | .and.to.have.lengthOf(2)
50 | });
51 | });
52 | }
53 |
--------------------------------------------------------------------------------
/test/reducers/slides/actions/duplicateSlide-spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { DUPLICATE_SLIDE } from '../../../../app/constants/slides.constants';
3 |
4 | export default function(initialState: any, reducer: any, slide: any) {
5 | const dummySlide1 = {
6 | plugins: ['plugin1'],
7 | state: {
8 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
9 | transition: {
10 | right: 'rotate-push-left-move-from-right',
11 | left: 'rotate-push-right-move-from-left',
12 | }
13 | }
14 | };
15 |
16 | describe('DUPLICATE_SLIDE', () => {
17 | it('should duplicate slide 0 when slide 0 is selected', () => {
18 | const _initialState: any = [dummySlide1];
19 | expect(
20 | reducer(_initialState, {
21 | type: DUPLICATE_SLIDE,
22 | slideToDuplicate: 0
23 | })
24 | ).to.deep.equal([dummySlide1, dummySlide1]);
25 | });
26 |
27 | it('should add the duplicated slide after the selected slide', () => {
28 | const _initialState: any = [dummySlide1, slide]
29 | expect(
30 | reducer(_initialState, {
31 | type: DUPLICATE_SLIDE,
32 | slideToDuplicate: 0
33 | })
34 | ).to.deep.equal([dummySlide1, dummySlide1, slide]);
35 | });
36 | });
37 | }
38 |
--------------------------------------------------------------------------------
/test/reducers/slides/actions/moveSlideDown-spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { MOVE_SLIDE_DOWN } from '../../../../app/constants/slides.constants';
3 |
4 | export default function(initialState: any, reducer: any, slide: any) {
5 | const dummySlide1 = {
6 | plugins: ['plugin1'],
7 | state: {
8 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
9 | transition: {
10 | right: 'rotate-push-left-move-from-right',
11 | left: 'rotate-push-right-move-from-left',
12 | }
13 | }
14 | };
15 |
16 | describe('MOVE_SLIDE_DOWN', () => {
17 | const _initialState = [slide, dummySlide1];
18 | it('should not move the slide when the first slide is selected', () => {
19 | expect(
20 | reducer(_initialState, {
21 | type: MOVE_SLIDE_DOWN,
22 | slideNumber: 0
23 | })
24 | ).to.deep.equal(_initialState);
25 | });
26 |
27 | it('should swap slide 1 and slide 0 when slide 1 is active', () => {
28 | expect(
29 | reducer(_initialState, {
30 | type: MOVE_SLIDE_DOWN,
31 | slideNumber: 1
32 | })
33 | ).to.deep.equal([dummySlide1, slide]);
34 | });
35 | });
36 | }
37 |
--------------------------------------------------------------------------------
/test/reducers/slides/actions/moveSlideUp-spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { MOVE_SLIDE_UP } from '../../../../app/constants/slides.constants';
3 |
4 | export default function(initialState: any, reducer: any, slide: any) {
5 | const dummySlide1 = {
6 | plugins: ['plugin1'],
7 | state: {
8 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
9 | transition: {
10 | right: 'rotate-push-left-move-from-right',
11 | left: 'rotate-push-right-move-from-left',
12 | }
13 | }
14 | };
15 |
16 | describe('MOVE_SLIDE_UP', () => {
17 | const _initialState = [slide, dummySlide1];
18 | it('should not move the slide when the last slide is selected', () => {
19 | expect(
20 | reducer(_initialState, {
21 | type: MOVE_SLIDE_UP,
22 | slideNumber: 1
23 | })
24 | ).to.deep.equal(_initialState);
25 | });
26 |
27 | it('should swap slide 0 and slide 1 when slide 0 is active', () => {
28 | expect(
29 | reducer(_initialState, {
30 | type: MOVE_SLIDE_UP,
31 | slideNumber: 0
32 | })
33 | ).to.deep.equal([dummySlide1, slide]);
34 | });
35 | });
36 | }
37 |
--------------------------------------------------------------------------------
/test/reducers/slides/actions/openFile-spec.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import * as path from 'path';
3 | import { expect } from 'chai';
4 | import { OPEN_FILE } from '../../../../app/constants/slides.constants';
5 |
6 | export default function(initialState: any, reducer: any, slide: any) {
7 | const file = fs.readFileSync(path.join(__dirname, '../test.dd'));
8 | describe('OPEN_FILE', () => {
9 | it('should set slide state to the new file', () => {
10 | const slides = JSON.parse(file.toString());
11 | expect(reducer(initialState, {
12 | type: OPEN_FILE,
13 | buffer_data: file
14 | })).to.deep.equal(slides);
15 | });
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/test/reducers/slides/actions/openNewDeck-spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { OPEN_NEW_DECK } from '../../../../app/constants/slides.constants';
3 |
4 | export default function(initialState: any, reducer: any, slide: any) {
5 | const dummySlide1 = {
6 | plugins: ['plugin1'],
7 | state: {
8 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
9 | transition: {
10 | right: 'rotate-push-left-move-from-right',
11 | left: 'rotate-push-right-move-from-left',
12 | }
13 | }
14 | };
15 |
16 | describe('OPEN_NEW_DECK', () => {
17 | const _initialState = [slide, dummySlide1];
18 | it('should reset slides state to a single slide with no plugins', () => {
19 | expect(reducer(_initialState, { type: OPEN_NEW_DECK })).to.deep.equal(initialState);
20 | });
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/test/reducers/slides/actions/updateCurrentPlugin-spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { UPDATE_CURRENT_PLUGIN } from '../../../../app/constants/slides.constants';
3 |
4 | export default function(initialState: any, reducer: any, slide: any) {
5 | const _initialState = [
6 | {
7 | plugins: [
8 | { state: {} }
9 | ]
10 | }
11 | ];
12 |
13 | describe('UPDATE_CURRENT_PLUGIN', () => {
14 | it('should update selected plugin with new change', () => {
15 | expect(
16 | reducer(_initialState, {
17 | type: UPDATE_CURRENT_PLUGIN,
18 | pluginNumber: 0,
19 | slideNumber: 0,
20 | changes: {
21 | hello: 'world'
22 | }
23 | })
24 | ).to.deep.equal([
25 | {
26 | plugins: [
27 | {
28 | state: {
29 | hello: 'world'
30 | }
31 | }
32 | ],
33 | }
34 | ]);
35 | });
36 |
37 | it('should update selected plugin with multiple changes at once', () => {
38 | expect(
39 | reducer(_initialState, {
40 | type: UPDATE_CURRENT_PLUGIN,
41 | pluginNumber: 0,
42 | slideNumber: 0,
43 | changes: {
44 | string: 'world',
45 | boolean: true,
46 | number: 1,
47 | undefined: undefined,
48 | null: null,
49 | array: [],
50 | object: {}
51 | }
52 | })
53 | ).to.deep.equal([
54 | {
55 | plugins: [
56 | {
57 | state: {
58 | string: 'world',
59 | boolean: true,
60 | number: 1,
61 | undefined: undefined,
62 | null: null,
63 | array: [],
64 | object: {}
65 | }
66 | }
67 | ]
68 | }
69 | ]);
70 | });
71 |
72 | it('should be able to accept functions in its state', () => {
73 | const nextState: any = reducer(_initialState, {
74 | type: UPDATE_CURRENT_PLUGIN,
75 | pluginNumber: 0,
76 | slideNumber: 0,
77 | changes: {
78 | function: () => {}
79 | }
80 | });
81 |
82 | expect(nextState[0].plugins[0].state)
83 | .to.have.property('function')
84 | .that.is.a('function');
85 | })
86 | });
87 | }
88 |
--------------------------------------------------------------------------------
/test/reducers/slides/slidesReducer.spec.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import * as path from 'path';
3 | import { expect } from 'chai';
4 | import { slidesReducer } from '../../../app/reducers';
5 |
6 | const files = fs.readdirSync(path.join(__dirname, './actions'));
7 |
8 | const slide: any = {
9 | plugins: [],
10 | state: {
11 | backgroundColor: { r: 255, g: 255, b: 255, a: 100 },
12 | transition: {
13 | right: 'rotate-push-left-move-from-right',
14 | left: 'rotate-push-right-move-from-left',
15 | }
16 | }
17 | };
18 |
19 | const initialState: any = [slide];
20 |
21 | describe('Slides Reducer', () => {
22 | it('should return the initial state', () => {
23 | expect(slidesReducer(undefined, {})).to.deep.equal(initialState);
24 | });
25 |
26 | files.forEach(test => {
27 | require(`./actions/${test}`).default(initialState, slidesReducer, slide);
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/test/setup.ts:
--------------------------------------------------------------------------------
1 | // Issue with Windows 10 pathing so types could not be set in tsconfig.json
2 | // so we need to specify absolute paths
3 | ///
4 | ///
5 | ///
6 |
7 | import { jsdom } from 'jsdom';
8 |
9 | declare var global: any;
10 |
11 | global.document = jsdom('');
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "baseUrl": "app",
5 | "experimentalDecorators": true,
6 | "jsx": "react",
7 | "module": "commonjs",
8 | "moduleResolution": "node",
9 | "noImplicitAny": true,
10 | "outDir": "./build/",
11 | "preserveConstEnums": true,
12 | "removeComments": true,
13 | "target": "ES6"
14 | },
15 | "include": [
16 | "app/**/*"
17 | ],
18 | "exclude": [
19 | "node_modules",
20 | "app/plugins/**/*"
21 | ]
22 | }
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["tslint:latest", "tslint-react"],
3 | "rules": {
4 | "curly": true,
5 | "jsx-no-lambda": true,
6 | "no-var-requires": false
7 | },
8 | "jsRules": {
9 | "curly": true
10 | }
11 | }
--------------------------------------------------------------------------------
/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Base webpack config used across other specific configs
3 | */
4 |
5 | import path from 'path';
6 | import validate from 'webpack-validator';
7 | import {
8 | dependencies as externals
9 | } from './app/package.json';
10 |
11 | export default validate({
12 | module: {
13 | loaders: [
14 | {
15 | test: /\.jsx?$/,
16 | loader: 'babel-loader',
17 | exclude: /node_modules/
18 | },
19 | {
20 | test: /\.json$/,
21 | loader: 'json-loader'
22 | },
23 | {
24 | test: /\.css$/,
25 | loaders: [
26 | 'style-loader',
27 | 'css-loader'
28 | ]
29 | },
30 |
31 | { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/font-woff' },
32 | { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/font-woff' },
33 | { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/octet-stream' },
34 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file' },
35 | { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=image/svg+xml' },
36 |
37 | { test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/, loader: 'url-loader' },
38 | ]
39 | },
40 |
41 | output: {
42 | path: path.join(__dirname, '../app'),
43 | filename: 'bundle.js',
44 |
45 | // https://github.com/webpack/webpack/issues/1114
46 | libraryTarget: 'commonjs2'
47 | },
48 |
49 | // https://webpack.github.io/docs/configuration.html#resolve
50 | resolve: {
51 | extensions: ['', '.ts', '.tsx', '.js', '.jsx', '.json', '.scss'],
52 | packageMains: ['webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main'],
53 | modulesDirectories: ['app', 'node_modules']
54 | },
55 |
56 | plugins: [],
57 |
58 | externals: Object.keys(externals || {})
59 | });
60 |
--------------------------------------------------------------------------------
/webpack.config.development.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | /**
3 | * Build config for development process that uses Hot-Module-Replacement
4 | * https://webpack.github.io/docs/hot-module-replacement-with-webpack.html
5 | */
6 |
7 | import webpack from 'webpack';
8 | import validate from 'webpack-validator';
9 | import merge from 'webpack-merge';
10 | import formatter from 'eslint-formatter-pretty';
11 | import baseConfig from './webpack.config.base';
12 |
13 | const port = process.env.PORT || 3000;
14 |
15 | export default validate(merge(baseConfig, {
16 | debug: true,
17 |
18 | devtool: 'inline-source-map',
19 |
20 | entry: [
21 | `webpack-hot-middleware/client?path=http://localhost:${port}/__webpack_hmr`,
22 | './app/index'
23 | ],
24 |
25 | output: {
26 | publicPath: `http://localhost:${port}/dist/`
27 | },
28 |
29 | module: {
30 | loaders: [
31 | {
32 | test: /\.tsx?$/,
33 | loaders: ['babel', 'ts'],
34 | include: /app/
35 | },
36 | {
37 | test: /\.global\.scss$/,
38 | loaders: [
39 | 'style-loader',
40 | 'css-loader?sourceMap',
41 | 'sass-loader?sourceMap'
42 | ],
43 | include: /app/
44 | },
45 |
46 | {
47 | test: /^((?!\.global).)*\.scss$/,
48 | loaders: [
49 | 'style-loader',
50 | 'css-loader?sourceMap',
51 | 'sass-loader?sourceMap'
52 | ],
53 | include: /app/
54 | },
55 | ]
56 | },
57 |
58 | eslint: {
59 | formatter
60 | },
61 |
62 | plugins: [
63 | // https://webpack.github.io/docs/hot-module-replacement-with-webpack.html
64 | new webpack.HotModuleReplacementPlugin(),
65 |
66 | // “If you are using the CLI, the webpack process will not exit with an error code by enabling this plugin.”
67 | // https://github.com/webpack/docs/wiki/list-of-plugins#noerrorsplugin
68 | new webpack.NoErrorsPlugin(),
69 |
70 | // NODE_ENV should be production so that modules do not perform certain development checks
71 | new webpack.DefinePlugin({
72 | 'process.env.NODE_ENV': JSON.stringify('development')
73 | }),
74 | ],
75 |
76 | // https://github.com/chentsulin/webpack-target-electron-renderer#how-this-module-works
77 | target: 'electron-renderer'
78 | }));
79 |
--------------------------------------------------------------------------------
/webpack.config.electron.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Build config for electron 'Main Process' file
3 | */
4 |
5 | import webpack from 'webpack';
6 | import validate from 'webpack-validator';
7 | import merge from 'webpack-merge';
8 | import baseConfig from './webpack.config.base';
9 |
10 | export default validate(merge(baseConfig, {
11 | devtool: 'source-map',
12 |
13 | entry: ['babel-polyfill', './app/main.development'],
14 |
15 | // 'main.js' in root
16 | output: {
17 | path: __dirname,
18 | filename: './app/main.js'
19 | },
20 |
21 | plugins: [
22 | // Minify the output
23 | new webpack.optimize.UglifyJsPlugin({
24 | compressor: {
25 | warnings: false
26 | }
27 | }),
28 | // Add source map support for stack traces in node
29 | // https://github.com/evanw/node-source-map-support
30 | // new webpack.BannerPlugin(
31 | // 'require("source-map-support").install();',
32 | // { raw: true, entryOnly: false }
33 | // ),
34 | new webpack.DefinePlugin({
35 | 'process.env': {
36 | NODE_ENV: JSON.stringify('production')
37 | }
38 | })
39 | ],
40 |
41 | /**
42 | * Set targed to Electron speciffic node.js env.
43 | * https://github.com/chentsulin/webpack-target-electron-renderer#how-this-module-works
44 | */
45 | target: 'electron-main',
46 |
47 | /**
48 | * Disables webpack processing of __dirname and __filename.
49 | * If you run the bundle in node.js it falls back to these values of node.js.
50 | * https://github.com/webpack/webpack/issues/2010
51 | */
52 | node: {
53 | __dirname: false,
54 | __filename: false
55 | },
56 | }));
57 |
--------------------------------------------------------------------------------
/webpack.config.eslint.js:
--------------------------------------------------------------------------------
1 | require('babel-register');
2 |
3 | module.exports = require('./webpack.config.development');
4 |
--------------------------------------------------------------------------------
/webpack.config.production.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Build config for electron 'Renderer Process' file
3 | */
4 |
5 | import path from 'path';
6 | import webpack from 'webpack';
7 | import validate from 'webpack-validator';
8 | import merge from 'webpack-merge';
9 | import HtmlWebpackPlugin from 'html-webpack-plugin';
10 | import baseConfig from './webpack.config.base';
11 |
12 | const config = validate(merge(baseConfig, {
13 | devtool: 'cheap-module-source-map',
14 |
15 | entry: [
16 | 'babel-polyfill',
17 | './app/index'
18 | ],
19 |
20 | output: {
21 | path: path.join(__dirname, '/app/dist'),
22 | publicPath: './dist/'
23 | },
24 |
25 | module: {
26 | loaders: [
27 | {
28 | test: /\.tsx?$/,
29 | loader: 'ts',
30 | include: /app/
31 | },
32 | {
33 | test: /\.scss$/,
34 | loaders: [
35 | 'style-loader',
36 | 'css-loader',
37 | 'sass-loader'
38 | ],
39 | include: /app/
40 | },
41 | ]
42 | },
43 |
44 | plugins: [
45 | // https://webpack.github.io/docs/list-of-plugins.html#occurrenceorderplugin
46 | // https://github.com/webpack/webpack/issues/864
47 | new webpack.optimize.OccurrenceOrderPlugin(),
48 |
49 | // NODE_ENV should be production so that modules do not perform certain development checks
50 | new webpack.DefinePlugin({
51 | 'process.env.NODE_ENV': JSON.stringify('production')
52 | }),
53 |
54 | // Minify without warning messages and IE8 support
55 | new webpack.optimize.UglifyJsPlugin({
56 | compressor: {
57 | screw_ie8: true,
58 | warnings: false
59 | }
60 | }),
61 | new HtmlWebpackPlugin({
62 | filename: '../app.html',
63 | template: 'app/app.html',
64 | inject: false
65 | })
66 | ],
67 |
68 | // https://github.com/chentsulin/webpack-target-electron-renderer#how-this-module-works
69 | target: 'electron-renderer'
70 | }));
71 |
72 | export default config;
73 |
--------------------------------------------------------------------------------
/webpack.config.test.js:
--------------------------------------------------------------------------------
1 | /** Used in .babelrc for 'test' environment */
2 |
3 | // for babel-plugin-webpack-loaders
4 | require('babel-register');
5 | const validate = require('webpack-validator');
6 | const devConfig = require('./webpack.config.development');
7 |
8 | module.exports = validate({
9 | output: {
10 | libraryTarget: 'commonjs2'
11 | },
12 | module: {
13 | // Use base + development loaders, but exclude 'babel-loader'
14 | loaders: devConfig.module.loaders.slice(1)
15 | }
16 | });
17 |
--------------------------------------------------------------------------------