diff --git a/configs/headless-render/webpack.config.js b/configs/headless-render/webpack.config.js
new file mode 100644
index 0000000000..43796cba33
--- /dev/null
+++ b/configs/headless-render/webpack.config.js
@@ -0,0 +1,43 @@
+const path = require('path')
+const webpack = require('webpack')
+
+module.exports = {
+ entry: './src/js/windows/headless-render/window.js',
+ target: 'electron-main',
+ output: {
+ path: path.resolve(__dirname, './../../src/build'),
+ filename: 'headless-render.js'
+ },
+ module: {
+ rules: [
+ {
+ test: /\.(js|jsx)$/,
+ exclude: /(node_modules|bower_components)/,
+ use: {
+ loader: 'babel-loader',
+ options: {
+ presets: [
+ [
+ '@babel/preset-env',
+ {
+ targets: { electron: require('electron/package.json').version }
+ }
+ ],
+ '@babel/preset-react'
+ ],
+ plugins: ['@babel/plugin-proposal-class-properties']
+ }
+ }
+ }
+ ]
+ },
+ node: {
+ __dirname: false,
+ __filename: false
+ },
+ plugins: [
+ new webpack.ProvidePlugin({
+ 'THREE': 'three'
+ })
+ ]
+}
diff --git a/package.json b/package.json
index fca033e45d..db4eb0f1db 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"start:ar": "webpack --mode=development --watch --progress --config configs/ar/webpack.config.js",
"start:shot-generator": "webpack --mode=development --watch --progress --config configs/shot-generator/webpack.config.js",
"start:shot-explorer": "webpack --mode=development --watch --progress --config configs/shot-explorer/webpack.config.js",
+ "start:headless-render": "webpack --mode=development --watch --progress --config configs/headless-render/webpack.config.js",
"start:server": "cd server && npm run start",
"start:electron": "cross-env NODE_ENV=development electron .",
"start:language-preferences": "webpack --mode=development --watch --progress --config configs/language-preferences/webpack.config.js",
@@ -27,6 +28,7 @@
"build-thumbnails": "electron test/views/thumbnail-renderer/main.js",
"build:shot-generator": "webpack --mode=production --progress --config configs/shot-generator/webpack.config.js",
"build:shot-explorer": "webpack --mode=production --progress --config configs/shot-explorer/webpack.config.js",
+ "build:headless-render": "webpack --mode=production --progress --config configs/headless-render/webpack.config.js",
"build:xr": "webpack --mode=production --progress --config configs/xr/webpack.config.js",
"build:ar": "webpack --mode=production --progress --config configs/ar/webpack.config.js",
"build:server": "cd server && npm run build",
@@ -225,6 +227,7 @@
"pdfjs-dist": "2.0.550",
"pdfkit": "^0.9.0",
"peerjs": "^1.3.1",
+ "peerjs-js-binarypack": "^1.0.1",
"plist": "2.1.0",
"postfix-calculator": "0.0.2",
"promise-cancelable": "2.1.1",
diff --git a/src/aspect-settings.html b/src/aspect-settings.html
new file mode 100644
index 0000000000..daf27cbe56
--- /dev/null
+++ b/src/aspect-settings.html
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+ aspect settings
+
+
+
+
+
+
+
What Aspect Ratio?
+
+
+
+
+
Doublewide
+
2.00:1
+
+
+
+
+
+
+
+
+
+
+
+
+ The aspect ratio defines the size of your boards.
+ 2.39:1 is the widest, like what you would watch in a movie.
+ 16:9 is what you would watch on a modern TV.
+ 4:3 is what your grandpops watched back when screens flickered and programming was wholesome.
+ Please don't select vertical HD. We added it as a joke.
+
+
+
+
+
+
+
+
diff --git a/src/css/preferences.css b/src/css/preferences.css
index 3b49ba4673..8e387eb58d 100644
--- a/src/css/preferences.css
+++ b/src/css/preferences.css
@@ -168,8 +168,13 @@ a.button:hover {
position: relative;
display: relative;
}
+.open-aspect-settings {
+ position: relative;
+ display: relative;
+}
-.open-language-editor button {
+.open-language-editor button,
+.open-aspect-settings button {
background-color: #333333;
color: white;
padding: 12px;
diff --git a/src/css/shot-explorer.css b/src/css/shot-explorer.css
index 9a19b74119..30498c21bc 100644
--- a/src/css/shot-explorer.css
+++ b/src/css/shot-explorer.css
@@ -112,4 +112,26 @@ img, a{ -webkit-user-select: none; /* Safari 3.1+ */ -moz-user-select: none; /*
height: 70px;
font-size: 18px;
color: #9d9d9d;
+}
+
+.camera-view {
+ height: 100%;
+ flex: 1;
+ position: relative;
+ overflow: hidden;
+ background-color: #111114;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+.camera-view-view {
+ position: relative;
+ overflow: hidden;
+ background-color: #111114;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
}
\ No newline at end of file
diff --git a/src/headless-render.html b/src/headless-render.html
new file mode 100644
index 0000000000..9bd337c4f6
--- /dev/null
+++ b/src/headless-render.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ Headless Render
+
+
+
+
+
+
diff --git a/src/js/headless-render/hooks/useSaveImage.js b/src/js/headless-render/hooks/useSaveImage.js
new file mode 100644
index 0000000000..eff6a94e9e
--- /dev/null
+++ b/src/js/headless-render/hooks/useSaveImage.js
@@ -0,0 +1,54 @@
+import * as THREE from 'three'
+import { useThree } from 'react-three-fiber'
+
+import { ipcRenderer } from 'electron'
+import {
+ getSerializedState,
+ } from '../../shared/reducers/shot-generator'
+const setCameraAspectFromRendererSize = (renderer, camera) => {
+ let size = renderer.getSize(new THREE.Vector2())
+ camera.aspect = size.width / size.height
+}
+
+const renderShot = (renderer, scene, originalCamera, shotSize) => {
+ let camera = originalCamera.clone()
+
+ //camera.layers.set(SHOT_LAYERS)
+
+ setCameraAspectFromRendererSize(renderer, camera)
+ camera.updateProjectionMatrix()
+ renderer.setSize(shotSize.width, shotSize.height)
+ renderer.render(scene, camera)
+ let shotImageDataUrl = renderer.domElement.toDataURL()
+ return { shotImageDataUrl}
+}
+
+const useSaveImage = (renderer, dispatch) => {
+ const { camera, scene } = useThree()
+
+ const saveImage = () => (dispatch, getState) => {
+ let state = getState()
+ let aspectRatio = state.aspectRatio
+ let shotSize = new THREE.Vector2(Math.ceil(aspectRatio * 900), 900)
+
+ let {shotImageDataUrl} = renderShot(renderer, scene, camera, shotSize)
+ let data = getSerializedState(state)
+ let currentBoard = state.board
+ let uid = currentBoard.uid
+
+ ipcRenderer.send('saveShot', {
+ uid,
+ data,
+ images: {
+ camera: shotImageDataUrl
+ }
+ })
+
+ }
+
+ const saveCurrentShotCb = () => dispatch( saveImage())
+ return {saveImage:saveCurrentShotCb}
+
+}
+
+export default useSaveImage;
\ No newline at end of file
diff --git a/src/js/headless-render/index.js b/src/js/headless-render/index.js
new file mode 100644
index 0000000000..8a177a8b89
--- /dev/null
+++ b/src/js/headless-render/index.js
@@ -0,0 +1,154 @@
+import React, { useState, useEffect, useRef, useMemo } from 'react'
+import { Provider, connect, useSelector } from 'react-redux'
+import { Canvas } from 'react-three-fiber'
+import { useThree, useFrame } from 'react-three-fiber'
+import ShotExplorerSceneManager from '../shot-explorer/ShotExplorerSceneManager'
+import FatalErrorBoundary from '../shot-generator/components/FatalErrorBoundary'
+import {OutlineEffect} from '../vendor/OutlineEffect'
+import {cache} from '../shot-generator/hooks/use-assets-manager'
+import TWEEN from '@tweenjs/tween.js'
+import electron from 'electron'
+import useSaveImage from './hooks/useSaveImage'
+const { ipcRenderer } = electron
+import { useDispatch } from 'react-redux'
+import FilepathsContext from '../shot-generator/contexts/filepaths'
+const {
+ createUserPresetPathResolver,
+ createAssetPathResolver
+} = require('../shot-generator/services/filepaths')
+const getUserPresetPath = createUserPresetPathResolver(electron.remote.app.getPath('userData'))
+const defaultSize = 900
+const Effect = ({ shouldRender, dispatch, aspectRatio}) => {
+ const {gl, size} = useThree()
+ const [newAssetsLoaded, setLoadedAssets] = useState()
+ const outlineEffect = new OutlineEffect(gl, { defaultThickness: 0.015 })
+ useEffect(() => void outlineEffect.setSize(size.width, size.height), [size])
+ const updateAssets = () => {setLoadedAssets({})}
+
+ useEffect(() => {
+ let assets = Object.values(cache.get())
+ for(let i = 0; i < assets.length; i++) {
+ let asset = assets[i]
+ if(asset.status !== 'SUCCESS') return
+ }
+ setTimeout(() => {
+ ipcRenderer.send("headless-render:loaded")
+ }, 0)
+ }, [newAssetsLoaded, size])
+
+
+ useEffect(() => {
+ cache.subscribe(updateAssets)
+ return () => {
+ cache.unsubscribe(updateAssets)
+ }
+ }, [])
+
+ const {saveImage} = useSaveImage(outlineEffect, dispatch)
+ useEffect(() => {
+ ipcRenderer.addListener('headless-render:save-shot', saveImage)
+ return () => {
+ ipcRenderer.removeListener('headless-render:save-shot', saveImage)
+ }
+ }, [saveImage])
+
+ useFrame(({ gl, scene, camera }, time) => {
+ if(!shouldRender) return
+ TWEEN.update()
+ outlineEffect.render(scene, camera)
+ }, 1)
+
+ return null
+}
+
+const HeadlessRender = React.memo(({
+ aspectRatio,
+ store,
+ board,
+}) => {
+
+ const [sceneInfo, setSceneInfo] = useState(null)
+ const dispatch = useDispatch()
+ const storyboarderFilePath = useSelector(state => state.meta.storyboarderFilePath)
+ const largeCanvasSize = {width: defaultSize, height: defaultSize}
+ const [largeCanvasInfo, setLargeCanvasInfo] = useState({width: 0, height: 0})
+ const setLargeCanvasData = (camera, scene, gl) => {
+ setSceneInfo({camera, scene, gl})
+ }
+
+ useMemo(() => {
+ if(!aspectRatio) return
+ let width = Math.ceil(largeCanvasSize.width)
+ // assign a target height, based on scene aspect ratio
+ let height = Math.ceil(width / aspectRatio)
+
+ if (height > largeCanvasSize.height) {
+ height = Math.ceil(largeCanvasSize.height)
+ width = Math.ceil(height * aspectRatio)
+ }
+ setLargeCanvasInfo({width, height})
+ }, [largeCanvasSize.width, largeCanvasSize.height, aspectRatio])
+
+
+ // FIXME
+ // apparently, storyboarderFilePath is not immediately available,
+ // so we wait to setup the resolvers until it has a value
+ // and we also don't render anything until it is
+ const filepathsState = useMemo(
+ () => {
+ if (storyboarderFilePath) {
+ return {
+ getAssetPath: createAssetPathResolver(window.__dirname, storyboarderFilePath),
+ getUserPresetPath
+ }
+ }
+ },
+ [window.__dirname, storyboarderFilePath]
+ )
+
+ // padding for right side of canvas
+ return storyboarderFilePath && (
+
+
+
+ )
+})
+
+const withState = (fn) => (dispatch, getState) => fn(dispatch, getState())
+export default connect(
+(state) => ({
+ mainViewCamera: state.mainViewCamera,
+ aspectRatio: state.aspectRatio,
+ board: state.board,
+}),
+{
+ withState,
+})
+(HeadlessRender)
\ No newline at end of file
diff --git a/src/js/locales/en-US.json b/src/js/locales/en-US.json
index ca80057a13..d337bf17db 100644
--- a/src/js/locales/en-US.json
+++ b/src/js/locales/en-US.json
@@ -688,6 +688,9 @@
"languages": "Languages",
"languages-hint": "Select a language, or create your own.",
"open-language-editor": "Open Language Editor",
+ "aspect": "Aspect",
+ "aspect-hint": "Change board aspect ratio.",
+ "open-aspect-settings": "Open Aspect Settings",
"sign-out": "Sign Out",
"sign-out-hint": "You are Signed In to your Storyboarders.com account",
"thanks-for-support": "Thanks for supporting Storyboarder!",
diff --git a/src/js/locales/ru-RU.json b/src/js/locales/ru-RU.json
index 17b8d750fc..c48d61c09d 100644
--- a/src/js/locales/ru-RU.json
+++ b/src/js/locales/ru-RU.json
@@ -689,7 +689,10 @@
"high-quality-drawing-engine-hint": "Если этот параметр включен, используется более качественный механизм рисования с более продвинутыми кистями. Когда этот параметр отключен, используется (более быстрый) механизм рисования режима эффективности.",
"languages": "Языки",
"languages-hint": "Выберите язык или добавьте свой собственный",
- "open-language-editor": "Открыть редактор языков",
+ "open-language-editor": "Открыть редактор языков",
+ "aspect": "Аспект",
+ "aspect-hint": "Измените аспект своей доски.",
+ "open-aspect-settings": "Открыть настройки аспектa",
"sign-out": "Разлогиниться",
"sign-out-hint": "Вы зашли в свой Storyboarders.com аккаунт",
"thanks-for-support": "Спасибо за поддержку Storyboarder!",
diff --git a/src/js/main.js b/src/js/main.js
index 5439c2979d..297cccb1d6 100644
--- a/src/js/main.js
+++ b/src/js/main.js
@@ -40,6 +40,7 @@ const util = require('./utils/index')
const {settings:languageSettings} = require('./services/language.config')
const autoUpdater = require('./auto-updater')
const LanguagePreferencesWindow = require('./windows/language-preferences/main')
+const AspectSettingsWindow = require('./windows/aspect-settings/main')
//https://github.com/luiseduardobrito/sample-chat-electron
@@ -1208,6 +1209,15 @@ ipcMain.on('newBoard', (e, arg)=> {
mainWindow.webContents.send('newBoard', arg)
})
+ipcMain.on('changeAspectRatio', (e, arg)=> {
+ mainWindow.webContents.send('changeAspectRatio', arg)
+})
+
+ipcMain.on('aspectRatioChanged', (e, aspectRatio)=> {
+ let win = shotGeneratorWindow.getWindow()
+ win && win.webContents.send('aspectRatioChanged', aspectRatio)
+})
+
ipcMain.on('deleteBoards', (e, arg)=> {
mainWindow.webContents.send('deleteBoards', arg)
})
@@ -1547,6 +1557,14 @@ ipcMain.on('openLanguagePreferences', (event) => {
//ipcRenderer.send('analyticsEvent', 'Board', 'exportPDF')
})
+ipcMain.on('openAspectSettings', (event) => {
+ let win = AspectSettingsWindow.getWindow()
+ if (win) {
+ AspectSettingsWindow.reveal()
+ } else {
+ AspectSettingsWindow.createWindow(() => {AspectSettingsWindow.reveal()})
+ }
+})
ipcMain.on('exportPrintablePdf', (event, sourcePath, fileName) => {
mainWindow.webContents.send('exportPrintablePdf', sourcePath, fileName)
@@ -1570,7 +1588,9 @@ ipcMain.on('scale-ui-by',
(event, value) => mainWindow.webContents.send('scale-ui-by', value))
ipcMain.on('scale-ui-reset',
(event, value) => mainWindow.webContents.send('scale-ui-reset', value))
-
+ ipcMain.on('headless-render:loaded', (event) => {
+ mainWindow.webContents.send('headless-render:loaded')
+ })
ipcMain.on('saveShot',
(event, data) => mainWindow.webContents.send('saveShot', data))
ipcMain.on('insertShot',
diff --git a/src/js/shot-generator/CameraUpdate.js b/src/js/shot-generator/CameraUpdate.js
index d9dff3240f..792859a4e3 100644
--- a/src/js/shot-generator/CameraUpdate.js
+++ b/src/js/shot-generator/CameraUpdate.js
@@ -9,15 +9,18 @@ import {
const CameraUpdate = connect(
state => ({
activeCamera: getSceneObjects(state)[getActiveCamera(state)],
+ aspectRatio: state.aspectRatio
}),
{
}
)( React.memo(({
- activeCamera
+ activeCamera,
+ aspectRatio
}) => {
const { camera } = useThree()
useEffect(() => {
let cameraObject = activeCamera
+ camera.aspect = aspectRatio
camera.position.x = cameraObject.x
camera.position.y = cameraObject.z
camera.position.z = cameraObject.y
@@ -29,12 +32,12 @@ const CameraUpdate = connect(
camera.userData.type = cameraObject.type
camera.userData.locked = cameraObject.locked
camera.userData.id = cameraObject.id
- }, [activeCamera])
-
+ }, [activeCamera, aspectRatio])
+
useEffect(() => {
camera.fov = activeCamera.fov
camera.updateProjectionMatrix()
- }, [activeCamera.fov])
+ }, [activeCamera.fov, aspectRatio])
return null
})
)
diff --git a/src/js/shot-generator/components/Toolbar/index.js b/src/js/shot-generator/components/Toolbar/index.js
index 140ee3ae5b..4527238188 100644
--- a/src/js/shot-generator/components/Toolbar/index.js
+++ b/src/js/shot-generator/components/Toolbar/index.js
@@ -95,7 +95,7 @@ const Toolbar = connect(
[room]
)
- const initializeImage = (id, imagePath = "") => {
+ const initializeImage = (id, imagePath = 'placeholder') => {
initCamera()
undoGroupStart()
createImage(id, camera.current, room.visible && roomObject3d, imagePath)
diff --git a/src/js/shot-generator/hooks/use-save-to-storyboarder.js b/src/js/shot-generator/hooks/use-save-to-storyboarder.js
index dbc6dcd78f..2bee527fe5 100644
--- a/src/js/shot-generator/hooks/use-save-to-storyboarder.js
+++ b/src/js/shot-generator/hooks/use-save-to-storyboarder.js
@@ -157,14 +157,14 @@ const useSaveToStoryboarder = (largeCanvasData, smallCanvasData, aspectRatio, sh
() => dispatch(
saveCurrentShot(shotRenderer, cameraPlotRenderer, largeCanvasData, smallCanvasData, shotSize, cameraPlotSize, aspectRatio)
),
- []
+ [shotSize]
)
const insertNewShotCb = useCallback(
() => dispatch(
insertNewShot(shotRenderer, cameraPlotRenderer, largeCanvasData, smallCanvasData, shotSize, cameraPlotSize, aspectRatio)
),
- []
+ [shotSize]
)
return {
diff --git a/src/js/utils/ImageService.js b/src/js/utils/ImageService.js
new file mode 100644
index 0000000000..df0f1684d5
--- /dev/null
+++ b/src/js/utils/ImageService.js
@@ -0,0 +1,83 @@
+const { ipcRenderer } = require('electron')
+const BoardState = {
+ Pending: "Pending",
+ Loaded: "Loaded",
+ Saved: "Saved",
+ Cancelled: "Cancelled"
+}
+class ImageService {
+ constructor(headlessRender) {
+ this.headlessRender = headlessRender
+ this.isImageRerendering = false
+ this.boards = {}
+ this.initialBoardIndex = 0
+ this.lastIndex = 0
+ this.iteration = 1
+ ipcRenderer.on('headless-render:loaded', (event) => {
+ let win = headlessRender.getWindow()
+ if(this.boards[this.lastIndex].state !== BoardState.Cancelled && this.boards[this.lastIndex].data.layers['shot-generator'] ) {
+ this.boards[this.lastIndex].state = BoardState.Loaded
+ win && win.webContents.send('headless-render:save-shot')
+ } else {
+ this.continueBoardUpdate()
+ }
+ })
+ }
+
+ initialize(boards, currentBoard, boardFilename) {
+ this.isImageRerendering = true
+ this.boards = boards.map(board => { return {state: BoardState.Pending, data:board} } )
+ this.initialBoardIndex = currentBoard
+ this.lastIndex = currentBoard
+ this.iteration = 1
+ this.boardFilename = boardFilename
+ }
+
+ continueBoardUpdate(images = {}) {
+ // If images have plot when action was sent from sg and not the headless-render
+ if(images.plot) return true
+ let win = this.headlessRender.getWindow()
+ if(this.lastIndex === this.initialBoardIndex - this.iteration) this.iteration++
+
+ let highestIndex = this.initialBoardIndex + this.iteration
+ let lowestIndex = this.initialBoardIndex - this.iteration
+ let newIndex
+ if(this.boards[this.lastIndex]) this.boards[this.lastIndex].state = BoardState.Saved
+ if(highestIndex !== this.lastIndex) {
+ newIndex = highestIndex
+ } else if(lowestIndex !== this.lastIndex) {
+ newIndex = lowestIndex
+ }
+ if(newIndex < this.boards.length && newIndex >= 0) {
+ let board = this.boards[newIndex]
+ this.lastIndex = newIndex
+ if(board.state === BoardState.Cancelled || !board.data.layers['shot-generator']) {
+ return this.continueBoardUpdate(images)
+ }
+ win.webContents.send('headless-render:load-board', {
+ storyboarderFilePath: this.boardFilename,
+ board: board.data
+ })
+ } else if(highestIndex >= this.boards.length - 1 && lowestIndex < 0) {
+ this.isImageRerendering = false
+ if(this.boards[this.lastIndex] && this.boards[this.lastIndex].state === BoardState.Cancelled) return false
+ } else {
+ this.lastIndex = newIndex
+ return this.continueBoardUpdate(images)
+ }
+ return true
+ }
+
+ cancelBoardUpdate() {
+ let boards = this.boards
+ for(let i = 0; i < boards.length; i ++) {
+ let board = boards[i]
+ if(board.state !== BoardState.Saved) {
+ this.boards[i].state = BoardState.Cancelled
+ }
+ }
+ }
+
+}
+
+module.exports = ImageService
\ No newline at end of file
diff --git a/src/js/utils/LocalizationService.js b/src/js/utils/LocalizationService.js
new file mode 100644
index 0000000000..8e451cea08
--- /dev/null
+++ b/src/js/utils/LocalizationService.js
@@ -0,0 +1,59 @@
+const { ipcRenderer, remote } = require('electron')
+const i18n = require('../services/i18next.config')
+const menu = require('../menu')
+class LocalizationService {
+ constructor(updateMethod) {
+ this._updateTranslation = () => updateMethod(i18n)
+ remote.getCurrentWindow().on('focus', () => this._onFocus)
+ i18n.on('loaded', () => this._onLoaded())
+ ipcRenderer.on("languageChanged", (event, lng) => this._onLanguageChanged(event, lng))
+ ipcRenderer.on("languageModified", (event, lng) => this._onLanguageModified(event, lng))
+ ipcRenderer.on("languageAdded", (event, lng) => this._onLanguageAdded(event, lng))
+ ipcRenderer.on("languageRemoved", (event, lng) => this._onLanguageRemoved(event, lng))
+ }
+
+ _onFocus () {
+ menu.setWelcomeMenu(i18n)
+ }
+
+ _onLoaded () {
+ let lng = ipcRenderer.sendSync("getCurrentLanguage")
+ this._updateTranslation()
+ i18n.changeLanguage(lng, () => {
+ i18n.on("languageChanged", this.changeLanguage)
+ this._updateTranslation()
+ })
+ i18n.off('loaded')
+ }
+
+ _onLanguageChanged (event, lng) {
+ i18n.off("languageChanged", this.changeLanguage)
+ i18n.changeLanguage(lng, () => {
+ i18n.on("languageChanged", this.changeLanguage)
+ this._updateTranslation()
+ })
+ }
+
+ _onLanguageModified (event, lng) {
+ i18n.reloadResources(lng).then( () => { this._updateTranslation() } )
+ }
+
+ _onLanguageAdded (event, lng) {
+ i18n.loadLanguages(lng).then(() => { i18n.changeLanguage(lng); })
+ }
+
+ _onLanguageRemoved (event, lng) {
+ i18n.changeLanguage(lng)
+ }
+
+ changeLanguage (lng) {
+ if(remote.getCurrentWindow().isFocused()) {
+ menu.setWelcomeMenu(i18n)
+ }
+ this._updateTranslation()
+ ipcRenderer.send("languageChanged", lng)
+ }
+}
+
+module.exports = LocalizationService
+
diff --git a/src/js/window/main-window.js b/src/js/window/main-window.js
index ddb39d991f..bf6b6a2ded 100644
--- a/src/js/window/main-window.js
+++ b/src/js/window/main-window.js
@@ -21,7 +21,7 @@ const { getInitialStateRenderer } = require('electron-redux')
const configureStore = require('../shared/store/configureStore')
const observeStore = require('../shared/helpers/observeStore')
-
+//const shotGeneratorWindow = require('../windows/shot-generator/main')
const StoryboarderSketchPane = require('./storyboarder-sketch-pane')
const { SketchPane } = require('alchemancy')
const SketchPaneUtil = require('alchemancy').util
@@ -54,6 +54,7 @@ const exporterWeb = require('../exporters/web')
const exporterPsd = require('../exporters/psd')
const importerPsd = require('../importers/psd')
+const headlessRender = require('../windows/headless-render/main')
const sceneSettingsView = require('./scene-settings-view')
@@ -65,6 +66,8 @@ const AudioFileControlView = require('./audio-file-control-view')
const LinkedFileManager = require('./linked-file-manager')
+const ImageService = require('../utils/ImageService')
+
const getIpAddress = require('../utils/getIpAddress')
const pkg = require('../../../package.json')
@@ -529,7 +532,7 @@ const load = async (event, args) => {
// TODO add a cancel button to loading view when a fatal error occurs?
}
initialize(path.join(app.getPath('userData'), 'storyboarder-settings.json'))
- electron.remote.getCurrentWindow().on('resize', resizeScale)
+ remote.getCurrentWindow().on('resize', resizeScale)
}
ipcRenderer.on('load', load)
@@ -7001,6 +7004,79 @@ ipcRenderer.on('importNotification', () => {
}
})
+//#region Board images rerender
+let imageService = new ImageService(headlessRender)
+let aspectThread
+function * aspectChange(signal, aspectRatio) {
+ imageService.cancelBoardUpdate()
+ boardData.aspectRatio = aspectRatio
+ fs.writeFileSync(boardFilename, JSON.stringify(boardData, null, 2))
+ let size = boardModel.boardFileImageSize(boardData)
+ size = [Math.round(size[0]), Math.round(size[1])]
+ storyboarderSketchPane.changePaneSize(size[0], size[1])
+
+ for (let board of boardData.boards) {
+ // all the layers, by name, for this board
+ yield CAF.delay(signal, 1)
+ let name = 'reference'
+ if(! board.layers[name]) continue
+ let filepath = path.join(boardPath, 'images', board.layers[name].url)
+ let img = yield exporterCommon.getImage(filepath + '?' + cacheKey(filepath))
+ if (img) {
+ let scaledImageData = scaleImage(img, size)
+ saveDataURLtoFile(scaledImageData, board.layers[name].url)
+ } else {
+ log.warn("could not load image for board", board.layers[layerName].url)
+ }
+ }
+
+ renderShotGeneratorPanel()
+ yield updateSketchPaneBoard()
+ yield renderScene()
+ resize()
+ updateThumbnailDisplayFromFile(currentBoard)
+ //Update reference layer
+ yield CAF.delay(signal, 1)
+ ipcRenderer.send('aspectRatioChanged', aspectRatio)
+ headlessRender.createWindow(() => {
+ let win = headlessRender.getWindow()
+ win.webContents.send('headless-render:open')
+ let selectedBoard = boardData.boards[currentBoard]
+ boards = Object.values(boardData.boards)
+ initialBoardIndex = boards.indexOf(selectedBoard)
+ imageService.initialize(boards, initialBoardIndex, boardFilename)
+ }, aspectRatio)
+}
+ipcRenderer.on('changeAspectRatio', async (event, {aspectRatio}) => {
+ // cancel any in-progress loading
+ if (cancelTokens.changeAspect && !cancelTokens.changeAspect.signal.aborted) {
+ log.info(`%ccanceling change of aspect`, 'color:red')
+ cancelTokens.changeAspect.abort('cancel')
+ cancelTokens.changeAspect = undefined
+ }
+
+ // start a new loading process
+ cancelTokens.changeAspect = new CAF.cancelToken()
+ try {
+ aspectThread = CAF(aspectChange)
+ await aspectThread(cancelTokens.changeAspect.signal, aspectRatio)
+ } catch (err) {
+ log.warn(err)
+ }
+
+})
+
+const scaleImage = (image, boardSize) => {
+ let context = createSizedContext(boardSize)
+ let canvas = context.canvas
+ let scaleX = canvas.width / image.width
+ let scaleY = canvas.height / image.height
+ let x = (canvas.width / 2) - (image.width / 2) * scaleX
+ let y = (canvas.height / 2) - (image.height / 2) * scaleY
+ context.drawImage(image, x, y, image.width * scaleX, image.height * scaleY)
+ return canvas.toDataURL()
+}
+////#endregion
ipcRenderer.on('importWorksheets', (event, args) => {
if (!importWindow) {
importWindow = new remote.BrowserWindow({
@@ -7180,14 +7256,16 @@ const saveToBoardFromShotGenerator = async ({ uid, data, images }) => {
saveDataURLtoFile(context.canvas.toDataURL(), board.layers['shot-generator'].url)
// save camera-plot (re-use context)
- let plotImage = await exporterCommon.getImage(images.plot)
- context.canvas.width = 900
- context.canvas.height = 900
- context.drawImage(plotImage, 0, 0)
- saveDataURLtoFile(
- context.canvas.toDataURL(),
- boardModel.boardFilenameForCameraPlot(board)
- )
+ if(images.plot) {
+ let plotImage = await exporterCommon.getImage(images.plot)
+ context.canvas.width = 900
+ context.canvas.height = 900
+ context.drawImage(plotImage, 0, 0)
+ saveDataURLtoFile(
+ context.canvas.toDataURL(),
+ boardModel.boardFilenameForCameraPlot(board)
+ )
+ }
// save shot-generator-thumbnail.jpg
// thumbnail size
@@ -7221,6 +7299,9 @@ const saveToBoardFromShotGenerator = async ({ uid, data, images }) => {
renderShotGeneratorPanel()
}
ipcRenderer.on('saveShot', async (event, { uid, data, images }) => {
+ let needsSaving = imageService.continueBoardUpdate(images)
+ if(!needsSaving) return
+
storeUndoStateForScene(true)
await saveToBoardFromShotGenerator({ uid, data, images })
storeUndoStateForScene()
@@ -7253,12 +7334,26 @@ ipcRenderer.on('storyboarder:get-boards', event => {
hasSg: board.sg ? true : false
}))
})
+ let win = headlessRender.getWindow()
+ win && win.webContents.send('headless-render:get-boards', {
+ boards: boardData.boards.map(board => ({
+ uid: board.uid,
+ shot: board.shot,
+ thumbnail: boardModel.boardFilenameForThumbnail(board),
+ hasSg: board.sg ? true : false
+ }))
+ })
})
ipcRenderer.on('storyboarder:get-board', (event, uid) => {
ipcRenderer.send(
'shot-generator:get-board',
boardData.boards.find(board => board.uid === uid)
)
+ let win = headlessRender.getWindow()
+ win && win.webContents.send(
+ 'headless-render:get-board',
+ boardData.boards.find(board => board.uid === uid)
+ )
})
ipcRenderer.on('storyboarder:get-storyboarder-file-data', (event, uid) => {
ipcRenderer.send(
@@ -7271,6 +7366,17 @@ ipcRenderer.on('storyboarder:get-storyboarder-file-data', (event, uid) => {
}
}
)
+ let win = headlessRender.getWindow()
+ win && win.webContents.send(
+ 'headless-render:get-storyboarder-file-data',
+ {
+ storyboarderFilePath: boardFilename,
+ boardData: {
+ version: boardData.version,
+ aspectRatio: boardData.aspectRatio
+ }
+ }
+ )
})
ipcRenderer.on('storyboarder:get-state', event => {
let board = boardData.boards[currentBoard]
@@ -7280,6 +7386,13 @@ ipcRenderer.on('storyboarder:get-state', event => {
board
}
)
+ let win = headlessRender.getWindow()
+ win && win.webContents.send(
+ 'headless-render:get-state',
+ {
+ board
+ }
+ )
})
const logToView = opt => ipcRenderer.send('log', opt)
diff --git a/src/js/window/storyboarder-sketch-pane.js b/src/js/window/storyboarder-sketch-pane.js
index 414ccb67bf..657efb1a3b 100644
--- a/src/js/window/storyboarder-sketch-pane.js
+++ b/src/js/window/storyboarder-sketch-pane.js
@@ -441,6 +441,64 @@ class StoryboarderSketchPane extends EventEmitter {
return true
}
+ changePaneSize(width, height) {
+ width = Math.ceil(width)
+ height = Math.ceil(height)
+ let sketchPane = this.sketchPane
+ this.canvasSize = [width, height]
+ sketchPane.width = width
+ sketchPane.height = height
+
+ let layers = sketchPane.layers
+ layers.width = width
+ layers.height = height
+
+ for(let i = 0; i < layers.length; i++) {
+ let layer = layers[i]
+ layer.width = width
+ layer.height = height
+ layer.container.height = height
+ layer.container.width = width
+
+ layer.sprite._texture.resize(width, height)
+ layer.sprite.width = width
+ layer.sprite.height = height
+ }
+
+ sketchPane.eraseMask.height = height
+ sketchPane.eraseMask.width = width
+ sketchPane.eraseMask.texture = PIXI.RenderTexture.create(width, height)
+ sketchPane.strokeSprite.texture = PIXI.RenderTexture.create(width, height)
+
+ let paneChildren = sketchPane.sketchPaneContainer.children
+ for(let i = 0; i < paneChildren.length; i++) {
+ let child = paneChildren[i]
+ if(child.name === "layerMask") {
+ child.clear()
+ child.beginFill(0x0, 1)
+ .drawRect(0, 0, width, height)
+ .endFill()
+ }
+ }
+
+ sketchPane.strokeSprite.width = width
+ sketchPane.strokeSprite.height = height
+
+ let layersChildren = sketchPane.layersContainer.children
+ for(let i = 0; i < layersChildren.length; i++) {
+ let child = layersChildren[i]
+ if(child.name === "background") {
+ child.clear()
+ child.beginFill(0xffffff)
+ .drawRect(0, 0, width, height)
+ .endFill()
+ } else if(child.name.includes("container")) {
+ child.width = width
+ child.height = height
+ }
+ }
+ }
+
// TODO do we need this?
replaceLayer (index, image) {
this.emit('addToUndoStack')
diff --git a/src/js/windows/aspect-settings/main.js b/src/js/windows/aspect-settings/main.js
new file mode 100644
index 0000000000..a8f5ea1caf
--- /dev/null
+++ b/src/js/windows/aspect-settings/main.js
@@ -0,0 +1,115 @@
+const {BrowserWindow} = electron = require('electron')
+const path = require('path')
+const url = require('url')
+
+process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = true
+//const { default: installExtension, REACT_DEVELOPER_TOOLS, REACT_PERF, REDUX_DEVTOOLS } = require('electron-devtools-installer')
+const installExtensions = async () => {
+ const installer = require('electron-devtools-installer');
+ const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
+ const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS'];
+
+ return Promise.all(
+ extensions.map(name => installer.default(installer[name], forceDownload))
+ ).catch(console.log);
+};
+
+// Removes any extension from the production version
+const removeExtensions = () => {
+ const installed = BrowserWindow.getDevToolsExtensions()
+
+ for (let extension of Object.keys(installed)) {
+ BrowserWindow.removeDevToolsExtension(extension)
+ }
+}
+
+let win
+let loaded = false
+
+let memento = {
+ x: undefined,
+ y: undefined,
+ width: 1505,
+ height: 1080
+}
+
+const reveal = () => {
+ win.show()
+ win.focus()
+ //onComplete(win)
+ }
+
+const createWindow = async ( onComplete) => {
+ if (process.env.NODE_ENV === 'development') {
+ await installExtensions()
+ } else {
+ removeExtensions()
+ }
+
+ if (win) {
+ //reveal(onComplete)
+ return
+ }
+ let { x, y } = memento
+ win = new BrowserWindow({
+ x,
+ y,
+ width: 640,
+ height: 640,
+ minWidth: 600,
+ minHeight: 600,
+ maxHeight: 700,
+ maxWidth: 700,
+
+ show: false,
+ center: true,
+ frame: true,
+
+ backgroundColor: '#333333',
+ titleBarStyle: 'hiddenInset',
+ title: "Aspect settings",
+ acceptFirstMouse: true,
+ simpleFullscreen: true,
+ webPreferences: {
+ nodeIntegration: true,
+ plugins: true,
+ webSecurity: false,
+ allowRunningInsecureContent: true,
+ experimentalFeatures: true,
+ backgroundThrottling: true,
+ backgroundThrottling: true
+ },
+ })
+
+ win.on('resize', () => memento = win.getBounds())
+ win.on('move', () => memento = win.getBounds())
+ win.webContents.on('will-prevent-unload', event => {
+ event.preventDefault()
+ win.hide()
+ })
+
+ win.once('closed', () => {
+ win = null
+ })
+ win.loadURL(url.format({
+ pathname: path.join(__dirname, '..', '..', '..', 'aspect-settings.html'),
+ protocol: 'file:',
+ slashes: true
+ }))
+
+ // use this to wait until the window has completely loaded
+ //ipcMain.on('shot-generator:window:loaded', () => { })
+
+ // use this to show sooner
+ win.once('ready-to-show', () => {
+ onComplete()
+ loaded = true
+ })
+ }
+
+module.exports = {
+ createWindow,
+ getWindow: () => win,
+ reveal,
+ isLoaded: () => loaded
+}
\ No newline at end of file
diff --git a/src/js/windows/aspect-settings/window.js b/src/js/windows/aspect-settings/window.js
new file mode 100644
index 0000000000..bad04efef3
--- /dev/null
+++ b/src/js/windows/aspect-settings/window.js
@@ -0,0 +1,29 @@
+const { ipcRenderer } = require('electron')
+const LocalizationService = require('../../utils/LocalizationService')
+translateHtml = (elementName, translation) => {
+ let elem = document.querySelector(elementName)
+ if(!elem) return
+ let array = translation.split("\n")
+ elem.innerHTML = array.map(text => `${text} `).join("")
+}
+
+const updateHTMLText = (i18n) => {
+ translateHtml("#aspect-title", i18n.t("new-window.aspect-title"))
+ translateHtml("#aspect-ultrawide", i18n.t("new-window.aspect-ultrawide"))
+ translateHtml("#aspect-doublewide", i18n.t("new-window.aspect-doublewide"))
+ translateHtml("#aspect-wide", i18n.t("new-window.aspect-wide"))
+ translateHtml("#aspect-hd", i18n.t("new-window.aspect-hd"))
+ translateHtml("#aspect-vertical-hd", i18n.t("new-window.aspect-vertical-hd"))
+ translateHtml("#aspect-square", i18n.t("new-window.aspect-square"))
+ translateHtml("#aspect-old", i18n.t("new-window.aspect-old"))
+ translateHtml("#aspect-description", i18n.t("new-window.aspect-description"))
+}
+
+let localizationService = new LocalizationService(updateHTMLText)
+
+document.querySelectorAll('.example').forEach(el => {
+ el.addEventListener('click', event => {
+ ipcRenderer.send('changeAspectRatio', { aspectRatio: el.dataset.aspectRatio })
+ event.preventDefault()
+ })
+})
\ No newline at end of file
diff --git a/src/js/windows/headless-render/main.js b/src/js/windows/headless-render/main.js
new file mode 100644
index 0000000000..8203f4fbba
--- /dev/null
+++ b/src/js/windows/headless-render/main.js
@@ -0,0 +1,93 @@
+const { BrowserWindow, ipcMain, app, dialog } = electron = require('electron').remote
+const isDev = require('electron-is-dev')
+
+const path = require('path')
+const url = require('url')
+
+process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = true
+
+let win
+let loaded = false
+
+let memento = {
+ x: undefined,
+ y: undefined,
+ width: 1505,
+ height: 1080
+}
+
+const reveal = () => {
+ win.show()
+ win.focus()
+ //win.webContents.openDevTools()
+ //onComplete(win)
+ }
+
+const createWindow = async ( onComplete, aspectRatio) => {
+ if (win) {
+ onComplete()
+ return
+ }
+ let { x, y, width, height } = memento
+ win = new BrowserWindow({
+ // minWidth: (500 * aspectRatio),
+ //minHeight: 800,
+
+ // maxWidth: (300 * aspectRatio),
+ x,
+ y,
+ width,
+ height,
+
+ show: false,
+ center: true,
+ frame: true,
+
+ backgroundColor: '#333333',
+ titleBarStyle: 'hiddenInset',
+ title: "Headless render",
+ acceptFirstMouse: true,
+ simpleFullscreen: true,
+ webPreferences: {
+ nodeIntegration: true,
+ plugins: true,
+ webSecurity: false,
+ allowRunningInsecureContent: true,
+ experimentalFeatures: true,
+ backgroundThrottling: true,
+ enableRemoteModule: true
+ },
+ })
+
+ win.on('resize', () => memento = win.getBounds())
+ win.on('move', () => memento = win.getBounds())
+ win.webContents.on('will-prevent-unload', event => {
+ event.preventDefault()
+ win.hide()
+ })
+
+ win.once('closed', () => {
+ win = null
+ })
+ win.loadURL(url.format({
+ pathname: path.join(window.__dirname, 'headless-render.html'),
+ protocol: 'file:',
+ slashes: true
+ }))
+
+ // use this to wait until the window has completely loaded
+ //ipcMain.on('shot-generator:window:loaded', () => { })
+
+ // use this to show sooner
+ win.once('ready-to-show', () => {
+ onComplete()
+ loaded = true
+ })
+ }
+
+module.exports = {
+ createWindow,
+ getWindow: () => win,
+ reveal,
+ isLoaded: () => loaded
+}
\ No newline at end of file
diff --git a/src/js/windows/headless-render/service.js b/src/js/windows/headless-render/service.js
new file mode 100644
index 0000000000..f0ec036971
--- /dev/null
+++ b/src/js/windows/headless-render/service.js
@@ -0,0 +1,43 @@
+const { ipcRenderer } = electron = require('electron')
+
+const service = {}
+
+service.getStoryboarderFileData = () =>
+ new Promise(resolve => {
+ ipcRenderer.once('headless-render:get-storyboarder-file-data', (event, data) => {
+ resolve(data)
+ })
+ ipcRenderer.send('storyboarder:get-storyboarder-file-data')
+ })
+
+service.getStoryboarderState = () =>
+ new Promise(resolve => {
+ ipcRenderer.once('headless-render:get-state', (event, data) => {
+ resolve(data)
+ })
+ ipcRenderer.send('storyboarder:get-state')
+ })
+
+service.getBoards = () =>
+ new Promise(resolve => {
+ ipcRenderer.once('headless-render:get-boards', (event, { boards }) => {
+ resolve(boards)
+ })
+ ipcRenderer.send('storyboarder:get-boards')
+ })
+
+service.getBoard = uid =>
+ new Promise(resolve => {
+ ipcRenderer.once('headless-render:get-board', (event, board) => {
+ resolve(board)
+ })
+ ipcRenderer.send('storyboarder:get-board', uid)
+ })
+
+service.loadBoardByUid = async uid => {
+ // ask main > Shot Generator > to call loadBoardByUid
+ ipcRenderer.send('headless-render:loadBoardByUid', uid)
+}
+
+
+module.exports = service
diff --git a/src/js/windows/headless-render/window.js b/src/js/windows/headless-render/window.js
new file mode 100644
index 0000000000..cb06877dca
--- /dev/null
+++ b/src/js/windows/headless-render/window.js
@@ -0,0 +1,160 @@
+const ReactDOM = require('react-dom')
+const React = require('react')
+const { ipcRenderer, shell } = electron = require('electron')
+const { Provider, batch } = require('react-redux')
+const { createStore, applyMiddleware, compose } = require('redux')
+const thunkMiddleware = require('redux-thunk').default
+const { reducer } = require('../../shared/reducers/shot-generator')
+const presetsStorage = require('../../shared/store/presetsStorage')
+const { initialState } = require('../../shared/reducers/shot-generator')
+const poses = require('../../shared/reducers/shot-generator-presets/poses.json')
+const HeadlessRender = require('../../headless-render').default
+const service = require('./service')
+const {loadAsset, cleanUpCache} = require("../../shot-generator/hooks/use-assets-manager")
+const ModelLoader = require("./../../services/model-loader")
+const {getFilePathForImages} = require("./../../shot-generator/helpers/get-filepath-for-images")
+const {
+ setBoard,
+ loadScene,
+ resetScene,
+} = require('../../shared/reducers/shot-generator')
+require("../../shared/helpers/monkeyPatchGrayscale")
+const actionSanitizer = action => (
+ action.type === 'ATTACHMENTS_SUCCESS' && action.payload ?
+ { ...action, payload: { ...action.payload, value: '<>' } } : action
+)
+const stateSanitizer = state => state.attachments ? { ...state, attachments: '<>' } : state
+const reduxDevtoolsExtensionOptions = {
+ actionSanitizer,
+ stateSanitizer,
+ trace: true,
+}
+
+const composeEnhancers = (
+ window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ &&
+ window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__(reduxDevtoolsExtensionOptions)
+ ) || compose
+
+const configureStore = function configureStore (preloadedState) {
+ const store = createStore(
+ reducer,
+ preloadedState,
+ composeEnhancers(
+ applyMiddleware(thunkMiddleware)
+ )
+ )
+ return store
+}
+
+const store = configureStore({
+ ...initialState,
+ presets: {
+ ...initialState.presets,
+ scenes: {
+ ...initialState.presets.scenes,
+ ...presetsStorage.loadScenePresets().scenes
+ },
+ characters: {
+ ...initialState.presets.characters,
+ ...presetsStorage.loadCharacterPresets().characters
+ },
+ poses: {
+ ...initialState.presets.poses,
+ ...poses,
+ ...presetsStorage.loadPosePresets().poses
+ },
+ handPoses: {
+ ...initialState.presets.handPoses,
+ ...presetsStorage.loadHandPosePresets().handPoses
+ }
+ },
+})
+
+ipcRenderer.on("headless-render:open", async (event) => {
+ const { storyboarderFilePath, boardData } = await service.getStoryboarderFileData()
+ const { board } = await service.getStoryboarderState()
+ let aspectRatio = parseFloat(boardData.aspectRatio)
+
+ let action = {
+ type: 'SET_META_STORYBOARDER_FILE_PATH',
+ payload: storyboarderFilePath
+ }
+ store.dispatch(action)
+ action = {
+ type: 'SET_ASPECT_RATIO',
+ payload: aspectRatio
+ }
+ store.dispatch(action)
+ await loadBoard(board, storyboarderFilePath)
+ renderDom()
+})
+
+ipcRenderer.on("headless-render:load-board", async (event, boardData) => {
+ const { storyboarderFilePath, board } = boardData
+ await loadBoard(board, storyboarderFilePath)
+})
+
+const loadBoard = async (board, storyboarderFilePath) => {
+ let shot = board.sg
+ let action = setBoard(board)
+ store.dispatch(action)
+
+ if (shot) {
+ action = loadScene(shot.data)
+ store.dispatch(action)
+ } else {
+ action = resetScene()
+ store.dispatch(action)
+ }
+
+
+ if (!board.sg) {
+ return false
+ }
+
+ const {sceneObjects, world} = board.sg.data
+
+ await Object.values(sceneObjects)
+ // has a value for model
+ .filter(o => o.model != null)
+ // is not a box
+ .filter(o => !(o.type === 'object' && o.model === 'box'))
+ // what's the filepath?
+ .map((object) => ModelLoader.getFilepathForModel(object, { storyboarderFilePath }))
+ // request the file
+ .map(loadAsset)
+
+ if (world.environment.file) {
+ await loadAsset(
+ ModelLoader.getFilepathForModel({
+ model: world.environment.file,
+ type: 'environment'
+ }, { storyboarderFilePath })
+ )
+ }
+
+ const paths = Object.values(sceneObjects)
+ .filter(o => o.volumeImageAttachmentIds && o.volumeImageAttachmentIds.length > 0)
+ .map((object) => getFilePathForImages(object, storyboarderFilePath))
+
+ for(let i = 0; i < paths.length; i++) {
+ if(!Array.isArray(paths[i])) {
+ await loadAsset(paths[i])
+ } else {
+ for(let j = 0; j < paths[i].length; j++) {
+ await loadAsset(paths[i][j])
+ }
+ }
+ }
+}
+
+const renderDom = () => {
+ ReactDOM.render(
+ (store &&
+
+ ),
+ document.getElementById('main')
+ )
+}
+renderDom()
+
diff --git a/src/js/windows/language-preferences/main.js b/src/js/windows/language-preferences/main.js
index 9bab375812..4228eabed6 100644
--- a/src/js/windows/language-preferences/main.js
+++ b/src/js/windows/language-preferences/main.js
@@ -18,7 +18,6 @@ let memento = {
const reveal = () => {
win.show()
win.focus()
- win.webContents.openDevTools()
//onComplete(win)
}
diff --git a/src/js/windows/preferences/editor.js b/src/js/windows/preferences/editor.js
index 4117327be9..26bc159a56 100644
--- a/src/js/windows/preferences/editor.js
+++ b/src/js/windows/preferences/editor.js
@@ -103,6 +103,9 @@ const updateHTML = () => {
translateHtml("#languages", "preferences.languages")
translateHtml("#languages-hint", "preferences.languages-hint")
translateHtml("#open-language-editor", "preferences.open-language-editor")
+ translateHtml("#aspect", "preferences.aspect")
+ translateHtml("#aspect-hint", "preferences.aspect-hint")
+ translateHtml("#open-aspect-settings", "preferences.open-aspect-settings")
translateHtml("#sign-out", "preferences.sign-out")
translateHtml("#sign-out-hint", "preferences.sign-out-hint")
translateHtml("#thanks-for-support", "preferences.thanks-for-support")
@@ -305,6 +308,10 @@ const openLanguageEditor = () => {
ipcRenderer.send('openLanguagePreferences')
}
+const openAspectSettings = () => {
+ ipcRenderer.send('openAspectSettings')
+}
+
let selectedOption
const selectLanguage = (language) => {
@@ -365,7 +372,8 @@ const init = () => {
initializeLanguageList()
let languageEditor = document.getElementsByClassName('open-language-editor')[0].children[0]
languageEditor.onclick = openLanguageEditor
-
+ let aspectSettings = document.getElementById('open-aspect-settings')
+ aspectSettings.onclick = openAspectSettings
// bind
for (let el of inputs) {
el.addEventListener('change', onChange.bind(this, el.name))
diff --git a/src/js/windows/shot-explorer/window.js b/src/js/windows/shot-explorer/window.js
index bd2ed23a3a..23b06b5473 100644
--- a/src/js/windows/shot-explorer/window.js
+++ b/src/js/windows/shot-explorer/window.js
@@ -107,19 +107,27 @@ ipcRenderer.on('shot-explorer:show', (event) => {
showShotExplorer()
})
-ipcRenderer.on("shot-generator:open:shot-explorer", async (event) => {
- const { storyboarderFilePath, boardData } = await service.getStoryboarderFileData()
- const { board } = await service.getStoryboarderState()
- let aspectRatio = parseFloat(boardData.aspectRatio)
-
- canvasHeight = defaultHeight * 0.45
+const updateWindow = (aspectRatio) => {
let scaledWidth = Math.ceil(canvasHeight * aspectRatio)
scaledWidth = minimumWidth > scaledWidth ? minimumWidth : scaledWidth
let win = electron.remote.getCurrentWindow()
- win.setSize(scaledWidth, defaultHeight)
win.setMinimumSize(scaledWidth, defaultHeight)
win.setMaximumSize(scaledWidth, 100000)
+ win.setSize(scaledWidth, defaultHeight)
win.center()
+}
+
+ipcRenderer.on("shot-explorer:change-aspect", (event, aspectRatio) => {
+ updateWindow(aspectRatio)
+})
+
+ipcRenderer.on("shot-generator:open:shot-explorer", async (event) => {
+ const { storyboarderFilePath, boardData } = await service.getStoryboarderFileData()
+ const { board } = await service.getStoryboarderState()
+ let aspectRatio = parseFloat(boardData.aspectRatio)
+
+ canvasHeight = defaultHeight * 0.45
+ updateWindow(aspectRatio)
let action = {
type: 'SET_META_STORYBOARDER_FILE_PATH',
payload: storyboarderFilePath
diff --git a/src/js/windows/shot-generator/window.js b/src/js/windows/shot-generator/window.js
index babaef4aef..320fdb0828 100644
--- a/src/js/windows/shot-generator/window.js
+++ b/src/js/windows/shot-generator/window.js
@@ -259,6 +259,14 @@ ipcRenderer.on('shot-generator:edit:redo', () => {
store.dispatch( ActionCreators.redo() )
})
+ipcRenderer.on('aspectRatioChanged', (event, aspectRatio) => {
+ store.dispatch({
+ type: 'SET_ASPECT_RATIO',
+ payload: aspectRatio
+ })
+ shotExplorer.getWindow().webContents.send('shot-explorer:change-aspect', aspectRatio)
+})
+
ipcRenderer.on('shot-generator:show:shot-explorer', () => {
if(!shotExplorer.isLoaded()) {
diff --git a/src/preferences.html b/src/preferences.html
index 20b37c315b..e79ff32b8a 100644
--- a/src/preferences.html
+++ b/src/preferences.html
@@ -327,6 +327,18 @@ Languages
+
+
Aspect
+
+ Change board aspect ratio
+
+
+
+ Open aspect settings
+
+
+
+