From d1f594dae625bfc3cdca860cf0b7a92011c4478e Mon Sep 17 00:00:00 2001 From: Banbury Date: Sat, 12 Aug 2017 20:44:41 +0200 Subject: [PATCH 1/3] Added support for OpenRaster format. Storyboards can now be edited in Krita and other programs, that support the OpenRaster format. --- package.json | 1 + src/js/main.js | 20 +- src/js/menu.js | 9 +- src/js/window/main-window.js | 233 +++++++++++++++------- src/js/window/storyboarder-sketch-pane.js | 33 +-- src/js/window/toolbar.js | 20 +- src/main-window.html | 11 +- 7 files changed, 225 insertions(+), 102 deletions(-) diff --git a/package.json b/package.json index 0b980b9e62..84336bc0b7 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "electron-mocha": "4.0.0", "mocha": "3.4.2", "mock-fs": "4.4.1", + "ora.js": "github:banbury/ora.js", "tmp": "0.0.31" }, "dependencies": { diff --git a/src/js/main.js b/src/js/main.js index a0b55cc9b6..eb11253522 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -95,7 +95,7 @@ app.on('ready', () => { console.error('Could not load', filePath) } } - + // this only works on mac. if (toBeOpenedPath) { openFile(toBeOpenedPath) @@ -116,7 +116,7 @@ let openKeyCommandWindow = ()=> { app.on('activate', ()=> { if (!mainWindow && !welcomeWindow) openWelcomeWindow() - + }) let openNewWindow = () => { @@ -261,7 +261,7 @@ let openDialogue = () => { let importImagesDialogue = () => { dialog.showOpenDialog( { - title:"Import Boards", + title:"Import Boards", filters:[ {name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'psd']}, ], @@ -296,7 +296,7 @@ let importImagesDialogue = () => { handleDirectory(filepath) } } - + mainWindow.webContents.send('insertNewBoardsWithFiles', filepathsRecursive) } } @@ -306,7 +306,7 @@ let importImagesDialogue = () => { let importWorksheetDialogue = () => { dialog.showOpenDialog( { - title:"Import Worksheet", + title:"Import Worksheet", filters:[ {name: 'Images', extensions: ['png', 'jpg', 'jpeg']}, ], @@ -341,7 +341,7 @@ let processFountainData = (data, create, update) => { break case 'scene': metadata.sceneCount++ - let id + let id if (node.scene_id) { id = node.scene_id.split('-') if (id.length>1) { @@ -424,7 +424,7 @@ let createNewGivenAspectRatio = aspectRatio => { tasks = tasks.then(() => trash(filename)).catch(err => reject(err)) } else { dialog.showMessageBox(null, { - message: "Could not overwrite file " + path.basename(filename) + ". Only folders can be overwritten." + message: "Could not overwrite file " + path.basename(filename) + ". Only folders can be overwritten." }) return reject(null) } @@ -512,7 +512,7 @@ let loadStoryboarderWindow = (filename, scriptData, locations, characters, board experimentalCanvasFeatures: true, devTools: true, plugins: true - } + } }) let projectName = path.basename(filename, path.extname(filename)) @@ -665,6 +665,10 @@ ipcMain.on('openInEditor', (e, arg)=> { mainWindow.webContents.send('openInEditor') }) +ipcMain.on('openInOraEditor', (e, arg)=> { + mainWindow.webContents.send('openInOraEditor') +}) + ipcMain.on('goPreviousBoard', (e, arg)=> { mainWindow.webContents.send('goPreviousBoard') }) diff --git a/src/js/menu.js b/src/js/menu.js index aeda2cbe91..16b62eacee 100644 --- a/src/js/menu.js +++ b/src/js/menu.js @@ -379,6 +379,13 @@ const template = [ click ( item, focusedWindow, event) { ipcRenderer.send('openInEditor') } + }, + { + label: 'Edit in Ora Editor', + accelerator: 'CmdOrCtrl+,', + click ( item, focusedWindow, event) { + ipcRenderer.send('openInOraEditor') + } } ] }, @@ -713,4 +720,4 @@ const menu = { } } -module.exports = menu \ No newline at end of file +module.exports = menu diff --git a/src/js/window/main-window.js b/src/js/window/main-window.js index 6eeb3e1862..09b30e44d2 100644 --- a/src/js/window/main-window.js +++ b/src/js/window/main-window.js @@ -36,6 +36,10 @@ const FileHelper = require('../files/file-helper.js') const readPsd = require('ag-psd').readPsd; const initializeCanvas = require('ag-psd').initializeCanvas; +const Ora = require('ora.js').ora.Ora +const oramod = require('ora.js').ora +oramod.scriptsPath = '../node_modules/ora.js/' + const StsSidebar = require('./sts-sidebar.js') const pkg = require('../../../package.json') @@ -215,7 +219,7 @@ const commentOnLineMileage = (miles) => { ] message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) break - case 1: + case 1: otherMessages = [ "Looking great!!!", "Absolutely fantastic!", @@ -231,7 +235,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.playEffect('tool-pencil') break - case 5: + case 5: message.push('5 line miles.') otherMessages = [ "You should be done with your rough drawing.", @@ -247,7 +251,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.playEffect('tool-light-pencil') break - case 8: + case 8: message.push('8 line miles.') otherMessages = [ "Let's finish this up!", @@ -263,7 +267,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.playEffect('tool-brush') break - case 10: + case 10: message.push('10 miles!') otherMessages = [ "Let's finish this up!", @@ -279,7 +283,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.positive() break - case 20: + case 20: message.push('20 miles!!!') otherMessages = [ "This is done. Let's move on.", @@ -294,7 +298,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.negative() break - case 50: + case 50: message.push('50 miles!!!') otherMessages = [ "Uhh.. I fell asleep. What did I miss?", @@ -309,7 +313,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.negative() break - case 100: + case 100: message.push('100 miles!!!') otherMessages = [ "Nope!!! I'm going to delete this board if you keep drawing. Just kidding. Or am I?", @@ -325,7 +329,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.error() break - case 200: + case 200: message.push('200 miles!!!') otherMessages = [ "Now you're just fucking with me.", @@ -336,7 +340,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.error() break - case 300: + case 300: message.push('300 miles!!!') otherMessages = [ "I quit.", @@ -347,7 +351,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.error() break - case 500: + case 500: message.push('500 miles!!!') otherMessages = [ "So close to 1000!!!", @@ -355,7 +359,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.error() break - case 1000: + case 1000: message.push('1000 miles!!!') otherMessages = [ "Great job. :/ See ya.", @@ -387,7 +391,7 @@ let loadBoardUI = ()=> { document.getElementById('storyboarder-sketch-pane'), size ) - + window.addEventListener('resize', () => { resize() setTimeout(() => storyboarderSketchPane.resize(), 500) // TODO hack, remove this #440 @@ -554,8 +558,8 @@ let loadBoardUI = ()=> { }) } - - + + // for (var item of document.querySelectorAll('.thumbnail')) { // item.classList.remove('active') // } @@ -609,7 +613,7 @@ let loadBoardUI = ()=> { if (el) { offset = el.getBoundingClientRect().width el = thumbnailFromPoint(x, y, offset/2) - } + } if (!el) { console.warn("couldn't find nearest thumbnail") @@ -726,7 +730,7 @@ let loadBoardUI = ()=> { } sfx.playEffect('metal') }) - + toolbar.on('grid', value => { guides.setState({ grid: value }) sfx.playEffect('metal') @@ -767,6 +771,9 @@ let loadBoardUI = ()=> { toolbar.on('open-in-editor', () => { openInEditor() }) + toolbar.on('open-in-ora-editor', () => { + openInOraEditor() + }) storyboarderSketchPane.toolbar = toolbar @@ -1124,7 +1131,7 @@ let insertNewBoardsWithFiles = (filepaths) => { // thumbnail const thumbnailHeight = 60 let thumbRatio = thumbnailHeight / boardSize.height - + image.width = (image.width / boardSize.width) * (thumbRatio * boardSize.width) image.height = image.height / boardSize.height * 60 canvas.width = thumbRatio * boardSize.width @@ -1191,8 +1198,8 @@ let markImageFileDirty = layerIndices => { const addToLineMileage = value => { let board = boardData.boards[currentBoard] - if (!(board.lineMileage)) { - board.lineMileage = 0 + if (!(board.lineMileage)) { + board.lineMileage = 0 } let mileageChecks = [0.01,1,5,8,10,20,50,100,200,300,1000] for (let checkAmount of mileageChecks) { @@ -1286,7 +1293,7 @@ let saveImageFile = () => { } } } - + if (shouldSaveBoardFile) { saveBoardFile() } @@ -1324,9 +1331,9 @@ let openInEditor = () => { "name": "notes" }) } - + let psdPath = path.join(boardPath, 'images', board.url.replace('.png', '.psd')) - + FileHelper.writePhotoshopFileFromPNGPathLayers(pngPaths, psdPath) .then(()=>{ shell.openItem(psdPath); @@ -1365,7 +1372,7 @@ let openInEditor = () => { storeUndoStateForImage(true, [0, 1, 3]) isCurrentBoard = true } - + psdData = FileHelper.getBase64ImageDataFromFilePath(board.psd, readerOptions) if(!psdData || !psdData.main) { return; @@ -1401,9 +1408,95 @@ let openInEditor = () => { .catch(error =>{ console.error(error) }) - + } +let openInOraEditor = () => { + let board = boardData.boards[currentBoard] + let imageFilePath = path.join(boardPath, 'images', board.url.replace('.png', '.ora')) + + let w = storyboarderSketchPane.canvasSize[0] + let h = storyboarderSketchPane.canvasSize[1] + + var whiteBG = document.createElement('canvas') + whiteBG.width = w + whiteBG.height = h + var whiteBGContext = whiteBG.getContext('2d') + whiteBGContext.fillStyle = 'white' + whiteBGContext.fillRect(0, 0, w, h) + + let children = ['reference', 'main', 'notes'].map((layerName, i) => { + return { + "id": (i+2), + "name": layerName, + "canvas": storyboarderSketchPane.getLayerCanvasByName(layerName) + } + }); + + let oraFile = new Ora(w, h) + let layer = oraFile.addLayer('background', 0) + layer.image = new Image(); + layer.image.src = whiteBG.toDataURL("image/png") + + children.forEach((el) => { + let layer = oraFile.addLayer(el.name) + layer.image = new Image(); + layer.image.src = el.canvas.toDataURL("image/png") + }) + + oraFile.save(function (blob) { + var fileReader = new FileReader() + fileReader.onload = function() { + var uint8Array = new Uint8Array(this.result) + fs.writeFileSync(imageFilePath, uint8Array) + shell.openItem(imageFilePath) + }; + fileReader.readAsArrayBuffer(blob) + + fs.watchFile(imageFilePath, (cur, prev) => { + console.log("File changed: " + imageFilePath) + + if (!fs.existsSync(imageFilePath)) { + fs.unwatchFile(imageFilePath) + return + } + + let buf = new Uint8Array(fs.readFileSync(imageFilePath)) + let blob = new Blob([buf]) + oramod.load(blob, (oraFile) => { + if (boardData.boards[currentBoard].uid === board.uid) { + storeUndoStateForImage(true, [0, 1, 3]) + oraFile.layers.forEach((layer) => { + let canvas = storyboarderSketchPane.getLayerCanvasByName(layer.name) + if (canvas) { + let ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, w, h) + ctx.drawImage(layer.image, 0, 0) + } + }) + storeUndoStateForImage(false, [0, 1, 3]) + markImageFileDirty([0, 1, 3]) // reference, main, notes layers + saveImageFile() + } else { + oraFile.layers.forEach((layer) => { + let p = path.join(boardPath, 'images') + if (layer.name === 'main') { + p = path.join(p, board.url) + } else { + p = path.join(p, board.url.replace('.png', '-' + layer.name + '.png')) + } + if (fs.existsSync(p)) { + console.log(p) + let imgdata = layer.image.src.substring( "data:image/png;base64,".length ) + fs.writeFileSync(p, imgdata, 'base64') + } + }) + } + renderThumbnailDrawer() + }) + }) + }) +} // // always currentBoard // const saveProgressFile = () => { @@ -1425,7 +1518,7 @@ let openInEditor = () => { // let imageData = canvas // .toDataURL('image/png') // .replace(/^data:image\/\w+;base64,/, '') - + // try { // fs.writeFile(imageFilePath, imageData, 'base64', () => { // resolve() @@ -1478,7 +1571,7 @@ const saveThumbnailFile = (index, options = { forceReadFromFiles: false }) => { let imageData = canvas .toDataURL('image/png') .replace(/^data:image\/\w+;base64,/, '') - + try { fs.writeFile(imageFilePath, imageData, 'base64', () => { console.log('saved thumbnail', imageFilePath) @@ -1710,11 +1803,11 @@ let gotoBoard = (boardNumber, shouldPreserveSelections = false) => { currentBoard = boardNumber currentBoard = Math.max(currentBoard, 0) currentBoard = Math.min(currentBoard, boardData.boards.length-1) - + if (!shouldPreserveSelections) selections.clear() selections = new Set([...selections.add(currentBoard)].sort(util.compareNumbers)) renderThumbnailDrawerSelections() - + for (var item of document.querySelectorAll('.thumbnail')) { item.classList.remove('active') } @@ -1863,7 +1956,7 @@ let renderMetaData = () => { // TODO how to regenerate tooltips? // if (boardData.defaultBoardTiming) { // document.querySelector('input[name="duration"]').dataset.tooltipDescription = `Enter the number of milliseconds for a board. There are 1000 milliseconds in a second. ${boardData.defaultBoardTiming} milliseconds is the default.` - // + // // let defaultFramesPerBoard = Math.round(boardData.defaultBoardTiming / 1000 * 24) // document.querySelector('input[name="frames"]').dataset.tooltipDescription = `Enter the number of frames for a board. There are 24 frames in a second. ${defaultFramesPerBoard} frames is the default.` // } @@ -1896,11 +1989,11 @@ const renderStats = () => { let stats = [] let totalNewShots = boardData.boards.reduce((a, b) => a + (b.newShot ? 1 : 0), 0) || 1 - secondaryStats.push( + secondaryStats.push( `${boardData.boards.length} ${util.pluralize(boardData.boards.length, 'board').toUpperCase()}, ` + `${totalNewShots} ${util.pluralize(totalNewShots, 'shot').toUpperCase()}` ) - + let totalLineMileage = boardData.boards.reduce((a, b) => a + (b.lineMileage || 0), 0) let avgLineMileage = totalLineMileage / boardData.boards.length secondaryStats.push( (avgLineMileage/5280).toFixed(1) + ' AVG. LINE MILEAGE' ) @@ -1918,9 +2011,9 @@ const renderStats = () => { // if (scriptData) { // let numScenes = scriptData.filter(data => data.type == 'scene').length - + // let numBoards = 'N' // TODO sum total number of boards in the script - + // document.querySelector('#right-stats .stats-primary').innerHTML = `${numScenes} SCENES ${numBoards} BOARDS` // } else { // let numBoards = boardData.boards.length @@ -1998,7 +2091,7 @@ let updateSketchPaneBoard = () => { return new Promise((resolve, reject) => { // get current board let board = boardData.boards[currentBoard] - + // always load the main layer let layersData = [ @@ -2289,7 +2382,7 @@ let renderThumbnailDrawer = ()=> { renderThumbnailDrawerSelections() } else if (currentBoard !== index) { // go to board by index - + // reset selections selections.clear() @@ -2303,7 +2396,7 @@ let renderThumbnailDrawer = ()=> { renderThumbnailButtons() renderTimeline() - + //gotoBoard(currentBoard) } @@ -2327,7 +2420,7 @@ let renderThumbnailButtons = () => {
` drawerEl.appendChild(el) - + el.addEventListener('pointerdown', event => { let eventMouseOut = document.createEvent('MouseEvents') eventMouseOut.initMouseEvent('mouseout', true, true) @@ -2553,7 +2646,7 @@ let setDragTarget = (x) => { let mouseX = x - containerRect.left let midpointX = containerRect.width / 2 - + // distance ratio -1...0...1 let distance = (mouseX - midpointX) / midpointX @@ -2563,7 +2656,7 @@ let setDragTarget = (x) => { if (distance < -0.5) { strength = -util.norm(distance, -0.5, -1) - } + } // 0.5..1 else if (distance > 0.5) { @@ -2586,7 +2679,7 @@ let updateDrag = () => { return } - + if (isEditMode && dragMode) { setDragTarget(lastPointer.x) updateThumbnailCursor(lastPointer.x, lastPointer.y) @@ -3084,6 +3177,10 @@ ipcRenderer.on('openInEditor', (event, args)=>{ openInEditor() }) +ipcRenderer.on('openInOraEditor', (event, args)=>{ + openInOraEditor() +}) + ipcRenderer.on('togglePlayback', (event, args)=>{ if (!textInputMode) { togglePlayback() @@ -3679,9 +3776,9 @@ let moveSelectedBoards = (position) => { if (position > firstSelection) { position = position - numRemoved } - - console.log('move starting at board', firstSelection, - ', moving', numRemoved, + + console.log('move starting at board', firstSelection, + ', moving', numRemoved, 'boards to index', position) boardData.boards.splice(position, 0, ...movedBoards) @@ -3802,11 +3899,11 @@ let updateThumbnailCursor = (x, y) => { if (el) { offset = el.getBoundingClientRect().width el = thumbnailFromPoint(x, y, offset/2) - } + } if (el) thumbnailCursor.el = el // only update if found if (!el) return - + // store a reference to the nearest thumbnail thumbnailCursor.el = el @@ -3818,14 +3915,14 @@ let updateThumbnailCursor = (x, y) => { el.offsetParent.offsetParent.scrollLeft let elementOffsetX = el.getBoundingClientRect().right - + // is this an end shot? if (el.classList.contains('endShot')) { elementOffsetX += 5 } let arrowOffsetX = -8 - + thumbnailCursor.x = sidebarOffsetX + scrollOffsetX + elementOffsetX + @@ -3899,9 +3996,9 @@ const welcomeMessage = () => { ] message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) notifications.notify({message: message.join(' '), timing: 10}) -} +} -const setupRandomizedNotifications = () => { +const setupRandomizedNotifications = () => { let defaultMessages = util.shuffle(NotificationData.messages) setTimeout(()=>{welcomeMessage()}, 1000) setTimeout(()=>{runRandomizedNotifications(defaultMessages)}, 3000) @@ -3930,7 +4027,7 @@ const getSceneObjectByIndex = (index) => scriptData && scriptData.find(data => data.type == 'scene' && data.scene_number == index + 1) const storeUndoStateForScene = (isBefore) => { - let scene = getSceneObjectByIndex(currentScene) + let scene = getSceneObjectByIndex(currentScene) // sceneId is allowed to be null (for a single storyboard with no script) let sceneId = scene && scene.scene_id undoStack.addSceneData(isBefore, { sceneId : sceneId, boardData: util.stringifyClone(boardData) }) @@ -4233,16 +4330,16 @@ ipcRenderer.on('printWorksheet', (event, args) => { if (!printWindow) { printWindow = new remote.BrowserWindow({ - width: 1200, - height: 800, - minWidth: 600, - minHeight: 600, + width: 1200, + height: 800, + minWidth: 600, + minHeight: 600, backgroundColor: '#333333', - show: false, - center: true, - parent: remote.getCurrentWindow(), - resizable: true, - frame: false, + show: false, + center: true, + parent: remote.getCurrentWindow(), + resizable: true, + frame: false, modal: true }) printWindow.loadURL(`file://${__dirname}/../../print-window.html`) @@ -4267,16 +4364,16 @@ ipcRenderer.on('importFromWorksheet', (event, args) => { ipcRenderer.on('importWorksheets', (event, args) => { if (!importWindow) { importWindow = new remote.BrowserWindow({ - width: 1200, - height: 800, - minWidth: 600, - minHeight: 600, + width: 1200, + height: 800, + minWidth: 600, + minHeight: 600, backgroundColor: '#333333', - show: false, - center: true, - parent: remote.getCurrentWindow(), - resizable: true, - frame: false, + show: false, + center: true, + parent: remote.getCurrentWindow(), + resizable: true, + frame: false, modal: true }) importWindow.loadURL(`file://${__dirname}/../../import-window.html`) diff --git a/src/js/window/storyboarder-sketch-pane.js b/src/js/window/storyboarder-sketch-pane.js index 1e0eae8acb..64e5bd776b 100644 --- a/src/js/window/storyboarder-sketch-pane.js +++ b/src/js/window/storyboarder-sketch-pane.js @@ -184,7 +184,7 @@ class StoryboarderSketchPane extends EventEmitter { if (!this.getIsDrawingOrStabilizing()) this.toolbar.emit('cancelTransform') } } - + if (!this.getIsDrawingOrStabilizing()) { if (!keytracker('')) { this.unsetQuickErase() @@ -340,7 +340,7 @@ class StoryboarderSketchPane extends EventEmitter { drawComposite (layerIndices, destinationContext) { for (let index of layerIndices) { let canvas = this.sketchPane.getLayerCanvas(index) - + destinationContext.save() destinationContext.globalAlpha = this.sketchPane.getLayerOpacity(index) destinationContext.drawImage(canvas, 0, 0) @@ -416,7 +416,7 @@ class StoryboarderSketchPane extends EventEmitter { */ updateContainerSize () { // this.sketchPaneDOMElement.style.display = 'none' - + let rect = this.el.getBoundingClientRect() let size = [rect.width - this.containerPadding, rect.height - this.containerPadding] @@ -466,12 +466,12 @@ class StoryboarderSketchPane extends EventEmitter { let threshold = 0xff // TODO why are we creating a new pointer every time? let brushPointerCanvas = this.sketchPane.createBrushPointer( - image, + image, Math.max(6, this.brush.getSize() * this.scaleFactor), this.brush.getAngle(), threshold, true) - + let brushPointer = document.createElement('img') brushPointer.src = brushPointerCanvas.toDataURL('image/png') brushPointer.style.width = brushPointerCanvas.width @@ -632,7 +632,12 @@ class StoryboarderSketchPane extends EventEmitter { getLayerCanvasByName (name) { // HACK hardcoded const layerIndexByName = ['reference', 'main', 'onion', 'notes', 'guides', 'composite'] - return this.sketchPane.getLayerCanvas(layerIndexByName.indexOf(name)) + let idx = layerIndexByName.indexOf(name) + if (idx > -1) { + return this.sketchPane.getLayerCanvas(idx) + } else { + return null + } } getSnapshotAsCanvas (index) { @@ -640,7 +645,7 @@ class StoryboarderSketchPane extends EventEmitter { el.id = Math.floor(Math.random()*16777215).toString(16) // for debugging return el } - + getIsDrawingOrStabilizing () { return this.sketchPane.isDrawing || this.sketchPane.isStabilizing } @@ -669,7 +674,7 @@ class StoryboarderSketchPane extends EventEmitter { this.setBrushTool(this.toolbar.getBrushOptions().kind, this.toolbar.getBrushOptions()) } } - + getCanvasImageSources () { return [ // reference @@ -729,7 +734,7 @@ class DrawingStrategy { document.removeEventListener('pointermove', this.container.canvasPointerMove) document.removeEventListener('pointerup', this.container.canvasPointerUp) } - + renderMoveEvent (moveEvent) { this.container.sketchPane.move(moveEvent.x, moveEvent.y, moveEvent.pointerType === "pen" ? moveEvent.pressure : 1) } @@ -759,7 +764,7 @@ class DrawingStrategy { context.restore() } - + dispose () { this.container.stopMultiLayerOperation() this.container.isMultiLayerOperation = false // ensure we reset the var @@ -849,7 +854,7 @@ class MovingStrategy { document.removeEventListener('pointermove', this.container.canvasPointerMove) document.removeEventListener('pointerup', this.container.canvasPointerUp) } - + renderMoveEvent (moveEvent) { let compositeContext = this.storedComposite.getContext('2d') let paintingContext = this.container.sketchPane.paintingCanvas.getContext('2d') @@ -943,7 +948,7 @@ class ScalingStrategy { this.container.startMultiLayerOperation() this.container.setCompositeLayerVisibility(true) - + // if we previously were in erase mode, undo its effects, // and ensure paintingCanvas is visible this.container.sketchPane.setPaintingKnockout(false) @@ -1023,7 +1028,7 @@ class ScalingStrategy { let h = this.container.sketchPane.size.height // store a copy - + let storedContext = this.container.createContext() storedContext.drawImage(context.canvas, 0, 0) @@ -1038,7 +1043,7 @@ class ScalingStrategy { context.scale(this.scale, this.scale) context.translate(-this.translate[0], -this.translate[1]) context.drawImage(storedContext.canvas, 0, 0) - + context.restore() } diff --git a/src/js/window/toolbar.js b/src/js/window/toolbar.js index b3bed78d1d..178a4ba97a 100644 --- a/src/js/window/toolbar.js +++ b/src/js/window/toolbar.js @@ -104,7 +104,7 @@ const initialState = { center: false, thirds: false, diagonals: false, - + onion: false } @@ -241,7 +241,7 @@ class Toolbar extends EventEmitter { for (let el of overableControls) { el.addEventListener('pointerenter', this.onButtonOver) - } + } } // TODO cleanup, remove listeners @@ -268,7 +268,7 @@ class Toolbar extends EventEmitter { palette: opt.palette.map(color => color.clone()) } } - + getBrushOptions (brushName) { brushName = brushName || this.state.brush return this.cloneOptions(this.state.brushes[brushName]) @@ -288,7 +288,7 @@ class Toolbar extends EventEmitter { case 'duplicate': this.emit('duplicate') break - + // brushes case 'light-pencil': if (this.state.transformMode) this.emit('cancelTransform') @@ -392,7 +392,9 @@ class Toolbar extends EventEmitter { case 'pomodoro-running-status': this.emit('pomodoro-running') break - + case 'open-in-ora-editor': + this.emit('open-in-ora-editor') + break default: // console.log('toolbar selection', selection) break @@ -420,7 +422,7 @@ class Toolbar extends EventEmitter { } document.addEventListener('pointerup', this.onSwatchUp) } - + onSwatchColorPicker (target) { clearTimeout(this.swatchTimer) this.swatchTimer = null @@ -480,7 +482,7 @@ class Toolbar extends EventEmitter { btnMove.classList.remove('active') break } - + let btnCaptions = this.el.querySelector('#toolbar-captions') if (this.state.captions) { btnCaptions.classList.add('active') @@ -517,7 +519,7 @@ class Toolbar extends EventEmitter { const brushSizeValue = this.getBrushOptions().size brushSizeEl.innerHTML = Math.round(brushSizeValue) } - + onBrushSizePointerDown (event) { let direction = parseInt(event.target.dataset.direction) this.changeBrushSize(direction, true) @@ -527,7 +529,7 @@ class Toolbar extends EventEmitter { this.setState({ captions: !this.state.captions }) this.emit('captions') } - + onButtonOver (event) { // console.log('onButtonOver', event) sfx.rollover() diff --git a/src/main-window.html b/src/main-window.html index c1c7e61081..8ad0fd6a9d 100644 --- a/src/main-window.html +++ b/src/main-window.html @@ -205,6 +205,13 @@ data-tooltip-position="bottom right"> +
+ +
+ data-tooltip-position="left middle">
- +
Shot Generator
Date: Sat, 12 Aug 2017 20:44:41 +0200 Subject: [PATCH 2/3] Added support for OpenRaster format. Storyboards can now be edited in Krita and other programs, that support the OpenRaster format. --- package.json | 3 +- src/js/main.js | 20 +- src/js/menu.js | 9 +- src/js/window/main-window.js | 241 +++++++++++++++------- src/js/window/storyboarder-sketch-pane.js | 33 +-- src/js/window/toolbar.js | 20 +- src/main-window.html | 9 +- 7 files changed, 229 insertions(+), 106 deletions(-) diff --git a/package.json b/package.json index fc0e753414..c640706754 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,8 @@ "electron-mocha": "4.0.0", "mocha": "3.5.0", "mock-fs": "4.4.1", - "tmp": "0.0.33" + "tmp": "0.0.33", + "ora.js": "github:banbury/ora.js" }, "dependencies": { "ag-psd": "^1.2.0", diff --git a/src/js/main.js b/src/js/main.js index 9c8793a93f..ad4e7e4a51 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -113,7 +113,7 @@ app.on('ready', () => { console.error('Could not load', filePath) } } - + // this only works on mac. if (toBeOpenedPath) { openFile(toBeOpenedPath) @@ -134,7 +134,7 @@ let openKeyCommandWindow = ()=> { app.on('activate', ()=> { if (!mainWindow && !welcomeWindow) openWelcomeWindow() - + }) let openNewWindow = () => { @@ -279,7 +279,7 @@ let openDialogue = () => { let importImagesDialogue = () => { dialog.showOpenDialog( { - title:"Import Boards", + title:"Import Boards", filters:[ {name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'psd']}, ], @@ -314,7 +314,7 @@ let importImagesDialogue = () => { handleDirectory(filepath) } } - + mainWindow.webContents.send('insertNewBoardsWithFiles', filepathsRecursive) } } @@ -324,7 +324,7 @@ let importImagesDialogue = () => { let importWorksheetDialogue = () => { dialog.showOpenDialog( { - title:"Import Worksheet", + title:"Import Worksheet", filters:[ {name: 'Images', extensions: ['png', 'jpg', 'jpeg']}, ], @@ -359,7 +359,7 @@ let processFountainData = (data, create, update) => { break case 'scene': metadata.sceneCount++ - let id + let id if (node.scene_id) { id = node.scene_id.split('-') if (id.length>1) { @@ -442,7 +442,7 @@ let createNewGivenAspectRatio = aspectRatio => { tasks = tasks.then(() => trash(filename)).catch(err => reject(err)) } else { dialog.showMessageBox(null, { - message: "Could not overwrite file " + path.basename(filename) + ". Only folders can be overwritten." + message: "Could not overwrite file " + path.basename(filename) + ". Only folders can be overwritten." }) return reject(null) } @@ -530,7 +530,7 @@ let loadStoryboarderWindow = (filename, scriptData, locations, characters, board experimentalCanvasFeatures: true, devTools: true, plugins: true - } + } }) let projectName = path.basename(filename, path.extname(filename)) @@ -696,6 +696,10 @@ ipcMain.on('openInEditor', (e, arg)=> { mainWindow.webContents.send('openInEditor') }) +ipcMain.on('openInOraEditor', (e, arg)=> { + mainWindow.webContents.send('openInOraEditor') +}) + ipcMain.on('goPreviousBoard', (e, arg)=> { mainWindow.webContents.send('goPreviousBoard') }) diff --git a/src/js/menu.js b/src/js/menu.js index 043961adcd..890cec1af5 100644 --- a/src/js/menu.js +++ b/src/js/menu.js @@ -379,6 +379,13 @@ const template = [ click ( item, focusedWindow, event) { ipcRenderer.send('openInEditor') } + }, + { + label: 'Edit in Ora Editor', + accelerator: 'CmdOrCtrl+,', + click ( item, focusedWindow, event) { + ipcRenderer.send('openInOraEditor') + } } ] }, @@ -713,4 +720,4 @@ const menu = { } } -module.exports = menu \ No newline at end of file +module.exports = menu diff --git a/src/js/window/main-window.js b/src/js/window/main-window.js index 74236ae5f8..ebfbe457d7 100644 --- a/src/js/window/main-window.js +++ b/src/js/window/main-window.js @@ -36,8 +36,12 @@ const boardModel = require('../models/board') const FileHelper = require('../files/file-helper.js') const readPsd = require('ag-psd').readPsd; const initializeCanvas = require('ag-psd').initializeCanvas; - const ShotTemplateSystem = require('../shot-template-system') + +const Ora = require('ora.js').ora.Ora +const oramod = require('ora.js').ora +oramod.scriptsPath = '../node_modules/ora.js/' + const StsSidebar = require('./sts-sidebar.js') const pkg = require('../../../package.json') @@ -219,7 +223,7 @@ const commentOnLineMileage = (miles) => { // ] // message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) // break - // case 1: + // case 1: // otherMessages = [ // "Looking great!!!", // "Absolutely fantastic!", @@ -235,7 +239,7 @@ const commentOnLineMileage = (miles) => { // message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) // sfx.playEffect('tool-pencil') // break - case 5: + case 5: message.push('5 line miles.') otherMessages = [ "You should be done with your rough drawing.", @@ -251,7 +255,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.playEffect('tool-light-pencil') break - case 8: + case 8: message.push('8 line miles.') otherMessages = [ "Let's finish this up!", @@ -267,7 +271,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.playEffect('tool-brush') break - case 10: + case 10: message.push('10 miles!') otherMessages = [ "Let's finish this up!", @@ -283,7 +287,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.positive() break - case 20: + case 20: message.push('20 miles!!!') otherMessages = [ "This is done. Let's move on.", @@ -298,7 +302,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.negative() break - case 50: + case 50: message.push('50 miles!!!') otherMessages = [ "Uhh.. I fell asleep. What did I miss?", @@ -313,7 +317,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.negative() break - case 100: + case 100: message.push('100 miles!!!') otherMessages = [ "Nope!!! I'm going to delete this board if you keep drawing. Just kidding. Or am I?", @@ -329,7 +333,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.error() break - case 200: + case 200: message.push('200 miles!!!') otherMessages = [ "Now you're just fucking with me.", @@ -340,7 +344,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.error() break - case 300: + case 300: message.push('300 miles!!!') otherMessages = [ "I quit.", @@ -351,7 +355,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.error() break - case 500: + case 500: message.push('500 miles!!!') otherMessages = [ "So close to 1000!!!", @@ -359,7 +363,7 @@ const commentOnLineMileage = (miles) => { message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) sfx.error() break - case 1000: + case 1000: message.push('1000 miles!!!') otherMessages = [ "Great job. :/ See ya.", @@ -390,7 +394,7 @@ let loadBoardUI = ()=> { document.getElementById('storyboarder-sketch-pane'), size ) - + window.addEventListener('resize', () => { resize() // this is pretty hacky. @@ -560,8 +564,8 @@ let loadBoardUI = ()=> { }) } - - + + // for (var item of document.querySelectorAll('.thumbnail')) { // item.classList.remove('active') // } @@ -615,7 +619,7 @@ let loadBoardUI = ()=> { if (el) { offset = el.getBoundingClientRect().width el = thumbnailFromPoint(x, y, offset/2) - } + } if (!el) { console.warn("couldn't find nearest thumbnail") @@ -732,7 +736,7 @@ let loadBoardUI = ()=> { } sfx.playEffect('metal') }) - + toolbar.on('grid', value => { guides.setState({ grid: value }) sfx.playEffect('metal') @@ -773,6 +777,9 @@ let loadBoardUI = ()=> { toolbar.on('open-in-editor', () => { openInEditor() }) + toolbar.on('open-in-ora-editor', () => { + openInOraEditor() + }) storyboarderSketchPane.toolbar = toolbar @@ -1145,7 +1152,7 @@ let insertNewBoardsWithFiles = (filepaths) => { // thumbnail const thumbnailHeight = 60 let thumbRatio = thumbnailHeight / boardSize.height - + image.width = (image.width / boardSize.width) * (thumbRatio * boardSize.width) image.height = image.height / boardSize.height * 60 canvas.width = thumbRatio * boardSize.width @@ -1212,8 +1219,8 @@ let markImageFileDirty = layerIndices => { const addToLineMileage = value => { let board = boardData.boards[currentBoard] - if (!(board.lineMileage)) { - board.lineMileage = 0 + if (!(board.lineMileage)) { + board.lineMileage = 0 } let mileageChecks = [5,8,10,20,50,100,200,300,1000] for (let checkAmount of mileageChecks) { @@ -1307,7 +1314,7 @@ let saveImageFile = () => { } } } - + if (shouldSaveBoardFile) { saveBoardFile() } @@ -1345,9 +1352,9 @@ let openInEditor = () => { "name": "notes" }) } - + let psdPath = path.join(boardPath, 'images', board.url.replace('.png', '.psd')) - + FileHelper.writePhotoshopFileFromPNGPathLayers(pngPaths, psdPath) .then(()=>{ shell.openItem(psdPath); @@ -1386,7 +1393,7 @@ let openInEditor = () => { storeUndoStateForImage(true, [0, 1, 3]) isCurrentBoard = true } - + psdData = FileHelper.getBase64ImageDataFromFilePath(board.psd, readerOptions) if(!psdData || !psdData.main) { return; @@ -1422,9 +1429,95 @@ let openInEditor = () => { .catch(error =>{ console.error(error) }) - + } +let openInOraEditor = () => { + let board = boardData.boards[currentBoard] + let imageFilePath = path.join(boardPath, 'images', board.url.replace('.png', '.ora')) + + let w = storyboarderSketchPane.canvasSize[0] + let h = storyboarderSketchPane.canvasSize[1] + + var whiteBG = document.createElement('canvas') + whiteBG.width = w + whiteBG.height = h + var whiteBGContext = whiteBG.getContext('2d') + whiteBGContext.fillStyle = 'white' + whiteBGContext.fillRect(0, 0, w, h) + + let children = ['reference', 'main', 'notes'].map((layerName, i) => { + return { + "id": (i+2), + "name": layerName, + "canvas": storyboarderSketchPane.getLayerCanvasByName(layerName) + } + }); + + let oraFile = new Ora(w, h) + let layer = oraFile.addLayer('background', 0) + layer.image = new Image(); + layer.image.src = whiteBG.toDataURL("image/png") + + children.forEach((el) => { + let layer = oraFile.addLayer(el.name) + layer.image = new Image(); + layer.image.src = el.canvas.toDataURL("image/png") + }) + + oraFile.save(function (blob) { + var fileReader = new FileReader() + fileReader.onload = function() { + var uint8Array = new Uint8Array(this.result) + fs.writeFileSync(imageFilePath, uint8Array) + shell.openItem(imageFilePath) + }; + fileReader.readAsArrayBuffer(blob) + + fs.watchFile(imageFilePath, (cur, prev) => { + console.log("File changed: " + imageFilePath) + + if (!fs.existsSync(imageFilePath)) { + fs.unwatchFile(imageFilePath) + return + } + + let buf = new Uint8Array(fs.readFileSync(imageFilePath)) + let blob = new Blob([buf]) + oramod.load(blob, (oraFile) => { + if (boardData.boards[currentBoard].uid === board.uid) { + storeUndoStateForImage(true, [0, 1, 3]) + oraFile.layers.forEach((layer) => { + let canvas = storyboarderSketchPane.getLayerCanvasByName(layer.name) + if (canvas) { + let ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, w, h) + ctx.drawImage(layer.image, 0, 0) + } + }) + storeUndoStateForImage(false, [0, 1, 3]) + markImageFileDirty([0, 1, 3]) // reference, main, notes layers + saveImageFile() + } else { + oraFile.layers.forEach((layer) => { + let p = path.join(boardPath, 'images') + if (layer.name === 'main') { + p = path.join(p, board.url) + } else { + p = path.join(p, board.url.replace('.png', '-' + layer.name + '.png')) + } + if (fs.existsSync(p)) { + console.log(p) + let imgdata = layer.image.src.substring( "data:image/png;base64,".length ) + fs.writeFileSync(p, imgdata, 'base64') + } + }) + } + renderThumbnailDrawer() + }) + }) + }) +} // // always currentBoard // const saveProgressFile = () => { @@ -1446,7 +1539,7 @@ let openInEditor = () => { // let imageData = canvas // .toDataURL('image/png') // .replace(/^data:image\/\w+;base64,/, '') - + // try { // fs.writeFile(imageFilePath, imageData, 'base64', () => { // resolve() @@ -1499,7 +1592,7 @@ const saveThumbnailFile = (index, options = { forceReadFromFiles: false }) => { let imageData = canvas .toDataURL('image/png') .replace(/^data:image\/\w+;base64,/, '') - + try { fs.writeFile(imageFilePath, imageData, 'base64', () => { console.log('saved thumbnail', imageFilePath) @@ -1731,11 +1824,11 @@ let gotoBoard = (boardNumber, shouldPreserveSelections = false) => { currentBoard = boardNumber currentBoard = Math.max(currentBoard, 0) currentBoard = Math.min(currentBoard, boardData.boards.length-1) - + if (!shouldPreserveSelections) selections.clear() selections = new Set([...selections.add(currentBoard)].sort(util.compareNumbers)) renderThumbnailDrawerSelections() - + for (var item of document.querySelectorAll('.thumbnail')) { item.classList.remove('active') } @@ -1892,7 +1985,7 @@ let renderMetaData = () => { // TODO how to regenerate tooltips? // if (boardData.defaultBoardTiming) { // document.querySelector('input[name="duration"]').dataset.tooltipDescription = `Enter the number of milliseconds for a board. There are 1000 milliseconds in a second. ${boardData.defaultBoardTiming} milliseconds is the default.` - // + // // let defaultFramesPerBoard = Math.round(boardData.defaultBoardTiming / 1000 * 24) // document.querySelector('input[name="frames"]').dataset.tooltipDescription = `Enter the number of frames for a board. There are 24 frames in a second. ${defaultFramesPerBoard} frames is the default.` // } @@ -1925,11 +2018,11 @@ const renderStats = () => { let stats = [] let totalNewShots = boardData.boards.reduce((a, b) => a + (b.newShot ? 1 : 0), 0) || 1 - secondaryStats.push( + secondaryStats.push( `${boardData.boards.length} ${util.pluralize(boardData.boards.length, 'board').toUpperCase()}, ` + `${totalNewShots} ${util.pluralize(totalNewShots, 'shot').toUpperCase()}` ) - + let totalLineMileage = boardData.boards.reduce((a, b) => a + (b.lineMileage || 0), 0) let avgLineMileage = totalLineMileage / boardData.boards.length secondaryStats.push( (avgLineMileage/5280).toFixed(1) + ' AVG. LINE MILEAGE' ) @@ -1949,9 +2042,9 @@ const renderStats = () => { // if (scriptData) { // let numScenes = scriptData.filter(data => data.type == 'scene').length - + // let numBoards = 'N' // TODO sum total number of boards in the script - + // document.querySelector('#right-stats .stats-primary').innerHTML = `${numScenes} SCENES ${numBoards} BOARDS` // } else { // let numBoards = boardData.boards.length @@ -2029,7 +2122,7 @@ let updateSketchPaneBoard = () => { return new Promise((resolve, reject) => { // get current board let board = boardData.boards[currentBoard] - + // always load the main layer let layersData = [ @@ -2320,7 +2413,7 @@ let renderThumbnailDrawer = ()=> { renderThumbnailDrawerSelections() } else if (currentBoard !== index) { // go to board by index - + // reset selections selections.clear() @@ -2334,7 +2427,7 @@ let renderThumbnailDrawer = ()=> { renderThumbnailButtons() renderTimeline() - + //gotoBoard(currentBoard) } @@ -2358,7 +2451,7 @@ let renderThumbnailButtons = () => {
` drawerEl.appendChild(el) - + el.addEventListener('pointerdown', event => { let eventMouseOut = document.createEvent('MouseEvents') eventMouseOut.initMouseEvent('mouseout', true, true) @@ -2584,7 +2677,7 @@ let setDragTarget = (x) => { let mouseX = x - containerRect.left let midpointX = containerRect.width / 2 - + // distance ratio -1...0...1 let distance = (mouseX - midpointX) / midpointX @@ -2594,7 +2687,7 @@ let setDragTarget = (x) => { if (distance < -0.5) { strength = -util.norm(distance, -0.5, -1) - } + } // 0.5..1 else if (distance > 0.5) { @@ -2617,7 +2710,7 @@ let updateDrag = () => { return } - + if (isEditMode && dragMode) { setDragTarget(lastPointer.x) updateThumbnailCursor(lastPointer.x, lastPointer.y) @@ -3115,6 +3208,10 @@ ipcRenderer.on('openInEditor', (event, args)=>{ openInEditor() }) +ipcRenderer.on('openInOraEditor', (event, args)=>{ + openInOraEditor() +}) + ipcRenderer.on('togglePlayback', (event, args)=>{ if (!textInputMode) { togglePlayback() @@ -3710,9 +3807,9 @@ let moveSelectedBoards = (position) => { if (position > firstSelection) { position = position - numRemoved } - - console.log('move starting at board', firstSelection, - ', moving', numRemoved, + + console.log('move starting at board', firstSelection, + ', moving', numRemoved, 'boards to index', position) boardData.boards.splice(position, 0, ...movedBoards) @@ -3833,11 +3930,11 @@ let updateThumbnailCursor = (x, y) => { if (el) { offset = el.getBoundingClientRect().width el = thumbnailFromPoint(x, y, offset/2) - } + } if (el) thumbnailCursor.el = el // only update if found if (!el) return - + // store a reference to the nearest thumbnail thumbnailCursor.el = el @@ -3849,14 +3946,14 @@ let updateThumbnailCursor = (x, y) => { el.offsetParent.offsetParent.scrollLeft let elementOffsetX = el.getBoundingClientRect().right - + // is this an end shot? if (el.classList.contains('endShot')) { elementOffsetX += 5 } let arrowOffsetX = -8 - + thumbnailCursor.x = sidebarOffsetX + scrollOffsetX + elementOffsetX + @@ -3930,9 +4027,9 @@ const welcomeMessage = () => { ] message.push(otherMessages[Math.floor(Math.random()*otherMessages.length)]) notifications.notify({message: message.join(' '), timing: 10}) -} +} -const setupRandomizedNotifications = () => { +const setupRandomizedNotifications = () => { let defaultMessages = util.shuffle(NotificationData.messages) //setTimeout(()=>{welcomeMessage()}, 1000) setTimeout(()=>{runRandomizedNotifications(defaultMessages)}, 3000) @@ -3961,7 +4058,7 @@ const getSceneObjectByIndex = (index) => scriptData && scriptData.find(data => data.type == 'scene' && data.scene_number == index + 1) const storeUndoStateForScene = (isBefore) => { - let scene = getSceneObjectByIndex(currentScene) + let scene = getSceneObjectByIndex(currentScene) // sceneId is allowed to be null (for a single storyboard with no script) let sceneId = scene && scene.scene_id undoStack.addSceneData(isBefore, { sceneId : sceneId, boardData: util.stringifyClone(boardData) }) @@ -4263,14 +4360,14 @@ ipcRenderer.on('exportWorksheetPdf', (event, sourcePath) => { exporterCommon.ensureExportsPathExists(boardFilename), 'Worksheet ' + moment().format('YYYY-MM-DD hh.mm.ss') + '.pdf' ) - + if (!fs.existsSync(outputPath)) { fs.writeFileSync(outputPath, fs.readFileSync(sourcePath)) - + notifications.notify({ message: "A Worksheet PDF has been exported.", timing: 20 }) sfx.positive() shell.showItemInFolder(outputPath) - + } else { console.error('File exists') sfx.error() @@ -4282,16 +4379,16 @@ ipcRenderer.on('printWorksheet', (event, args) => { if (!printWindow) { printWindow = new remote.BrowserWindow({ - width: 1200, - height: 800, - minWidth: 600, - minHeight: 600, + width: 1200, + height: 800, + minWidth: 600, + minHeight: 600, backgroundColor: '#333333', - show: false, - center: true, - parent: remote.getCurrentWindow(), - resizable: true, - frame: false, + show: false, + center: true, + parent: remote.getCurrentWindow(), + resizable: true, + frame: false, modal: true }) printWindow.loadURL(`file://${__dirname}/../../print-window.html`) @@ -4325,16 +4422,16 @@ ipcRenderer.on('importNotification', (event, args) => { ipcRenderer.on('importWorksheets', (event, args) => { if (!importWindow) { importWindow = new remote.BrowserWindow({ - width: 1200, - height: 800, - minWidth: 600, - minHeight: 600, + width: 1200, + height: 800, + minWidth: 600, + minHeight: 600, backgroundColor: '#333333', - show: false, - center: true, - parent: remote.getCurrentWindow(), - resizable: true, - frame: false, + show: false, + center: true, + parent: remote.getCurrentWindow(), + resizable: true, + frame: false, modal: true }) importWindow.loadURL(`file://${__dirname}/../../import-window.html`) diff --git a/src/js/window/storyboarder-sketch-pane.js b/src/js/window/storyboarder-sketch-pane.js index 82dbb97cc8..ce49aa1ddc 100644 --- a/src/js/window/storyboarder-sketch-pane.js +++ b/src/js/window/storyboarder-sketch-pane.js @@ -216,7 +216,7 @@ class StoryboarderSketchPane extends EventEmitter { if (!this.getIsDrawingOrStabilizing()) this.toolbar.emit('cancelTransform') } } - + if (!this.getIsDrawingOrStabilizing()) { if (!keytracker('') && !this.isEraseButtonActive) { this.unsetQuickErase() @@ -370,7 +370,7 @@ class StoryboarderSketchPane extends EventEmitter { drawComposite (layerIndices, destinationContext) { for (let index of layerIndices) { let canvas = this.sketchPane.getLayerCanvas(index) - + destinationContext.save() destinationContext.globalAlpha = this.sketchPane.getLayerOpacity(index) destinationContext.drawImage(canvas, 0, 0) @@ -446,7 +446,7 @@ class StoryboarderSketchPane extends EventEmitter { */ updateContainerSize () { // this.sketchPaneDOMElement.style.display = 'none' - + let rect = this.el.getBoundingClientRect() let size = [rect.width - this.containerPadding, rect.height - this.containerPadding] @@ -496,12 +496,12 @@ class StoryboarderSketchPane extends EventEmitter { let threshold = 0xff // TODO why are we creating a new pointer every time? let brushPointerCanvas = this.sketchPane.createBrushPointer( - image, + image, Math.max(6, this.brush.getSize() * this.scaleFactor), this.brush.getAngle(), threshold, true) - + let brushPointer = document.createElement('img') brushPointer.src = brushPointerCanvas.toDataURL('image/png') brushPointer.style.width = brushPointerCanvas.width @@ -662,7 +662,12 @@ class StoryboarderSketchPane extends EventEmitter { getLayerCanvasByName (name) { // HACK hardcoded const layerIndexByName = ['reference', 'main', 'onion', 'notes', 'guides', 'composite'] - return this.sketchPane.getLayerCanvas(layerIndexByName.indexOf(name)) + let idx = layerIndexByName.indexOf(name) + if (idx > -1) { + return this.sketchPane.getLayerCanvas(idx) + } else { + return null + } } getSnapshotAsCanvas (index) { @@ -670,7 +675,7 @@ class StoryboarderSketchPane extends EventEmitter { el.id = Math.floor(Math.random()*16777215).toString(16) // for debugging return el } - + getIsDrawingOrStabilizing () { return this.sketchPane.isDrawing || this.sketchPane.isStabilizing } @@ -699,7 +704,7 @@ class StoryboarderSketchPane extends EventEmitter { this.setBrushTool(this.toolbar.getBrushOptions().kind, this.toolbar.getBrushOptions()) } } - + getCanvasImageSources () { return [ // reference @@ -773,7 +778,7 @@ class DrawingStrategy { document.removeEventListener('pointermove', this.container.canvasPointerMove) document.removeEventListener('pointerup', this.container.canvasPointerUp) } - + renderMoveEvent (moveEvent) { this.container.sketchPane.move(moveEvent.x, moveEvent.y, moveEvent.pointerType === "pen" ? moveEvent.pressure : 1) } @@ -803,7 +808,7 @@ class DrawingStrategy { context.restore() } - + dispose () { this.container.stopMultiLayerOperation() this.container.isMultiLayerOperation = false // ensure we reset the var @@ -891,7 +896,7 @@ class MovingStrategy { document.removeEventListener('pointermove', this.container.canvasPointerMove) document.removeEventListener('pointerup', this.container.canvasPointerUp) } - + renderMoveEvent (moveEvent) { let compositeContext = this.storedComposite.getContext('2d') let paintingContext = this.container.sketchPane.paintingCanvas.getContext('2d') @@ -988,7 +993,7 @@ class ScalingStrategy { this.container.startMultiLayerOperation() this.container.setCompositeLayerVisibility(true) - + // if we previously were in erase mode, undo its effects, // and ensure paintingCanvas is visible this.container.sketchPane.setPaintingKnockout(false) @@ -1068,7 +1073,7 @@ class ScalingStrategy { let h = this.container.sketchPane.size.height // store a copy - + let storedContext = this.container.createContext() storedContext.drawImage(context.canvas, 0, 0) @@ -1083,7 +1088,7 @@ class ScalingStrategy { context.scale(this.scale, this.scale) context.translate(-this.translate[0], -this.translate[1]) context.drawImage(storedContext.canvas, 0, 0) - + context.restore() } diff --git a/src/js/window/toolbar.js b/src/js/window/toolbar.js index 1e35ccd987..466f28233c 100644 --- a/src/js/window/toolbar.js +++ b/src/js/window/toolbar.js @@ -104,7 +104,7 @@ const initialState = { center: false, thirds: false, perspective: false, - + onion: false } @@ -241,7 +241,7 @@ class Toolbar extends EventEmitter { for (let el of overableControls) { el.addEventListener('pointerenter', this.onButtonOver) - } + } } // TODO cleanup, remove listeners @@ -268,7 +268,7 @@ class Toolbar extends EventEmitter { palette: opt.palette.map(color => color.clone()) } } - + getBrushOptions (brushName) { brushName = brushName || this.state.brush return this.cloneOptions(this.state.brushes[brushName]) @@ -288,7 +288,7 @@ class Toolbar extends EventEmitter { case 'duplicate': this.emit('duplicate') break - + // brushes case 'light-pencil': if (this.state.transformMode) this.emit('cancelTransform') @@ -392,7 +392,9 @@ class Toolbar extends EventEmitter { case 'pomodoro-running-status': this.emit('pomodoro-running') break - + case 'open-in-ora-editor': + this.emit('open-in-ora-editor') + break default: // console.log('toolbar selection', selection) break @@ -420,7 +422,7 @@ class Toolbar extends EventEmitter { } document.addEventListener('pointerup', this.onSwatchUp) } - + onSwatchColorPicker (target) { clearTimeout(this.swatchTimer) this.swatchTimer = null @@ -480,7 +482,7 @@ class Toolbar extends EventEmitter { btnMove.classList.remove('active') break } - + let btnCaptions = this.el.querySelector('#toolbar-captions') if (this.state.captions) { btnCaptions.classList.add('active') @@ -517,7 +519,7 @@ class Toolbar extends EventEmitter { const brushSizeValue = this.getBrushOptions().size brushSizeEl.innerHTML = Math.round(brushSizeValue) } - + onBrushSizePointerDown (event) { let direction = parseInt(event.target.dataset.direction) this.changeBrushSize(direction, true) @@ -527,7 +529,7 @@ class Toolbar extends EventEmitter { this.setState({ captions: !this.state.captions }) this.emit('captions') } - + onButtonOver (event) { // console.log('onButtonOver', event) sfx.rollover() diff --git a/src/main-window.html b/src/main-window.html index cb1519acc5..b6e51b0477 100644 --- a/src/main-window.html +++ b/src/main-window.html @@ -205,6 +205,13 @@ data-tooltip-position="bottom center">
+
+ +
+ data-tooltip-position="left middle">
Date: Sat, 2 Sep 2017 11:44:05 +0200 Subject: [PATCH 3/3] Added preference for external editor. There is now an option in the preference dialog to select the external graphics editor to use. --- src/js/prefs.js | 3 ++- src/js/window/main-window.js | 13 ++++++++--- src/js/window/toolbar.js | 3 --- src/js/windows/preferences/editor.js | 8 +++++-- src/main-window.html | 11 ++-------- src/preferences.html | 33 +++++++++++++++++++++++++++- 6 files changed, 52 insertions(+), 19 deletions(-) diff --git a/src/js/prefs.js b/src/js/prefs.js index 056fdced80..17f5e33146 100644 --- a/src/js/prefs.js +++ b/src/js/prefs.js @@ -27,7 +27,8 @@ const defaultPrefs = { enableAutoSave: true, import: { offset: [0, 0] - } + }, + editor: "PSD" } // For slow computers, override the defaults here. diff --git a/src/js/window/main-window.js b/src/js/window/main-window.js index ebfbe457d7..9be6556b4a 100644 --- a/src/js/window/main-window.js +++ b/src/js/window/main-window.js @@ -777,9 +777,6 @@ let loadBoardUI = ()=> { toolbar.on('open-in-editor', () => { openInEditor() }) - toolbar.on('open-in-ora-editor', () => { - openInOraEditor() - }) storyboarderSketchPane.toolbar = toolbar @@ -1330,6 +1327,16 @@ let saveImageFile = () => { } let openInEditor = () => { + var editor = prefsModule.getPrefs('main')['editor'] + + if (editor === 'PSD') { + openInPSDEditor() + } else if (editor === 'ORA') { + openInOraEditor() + } +} + +let openInPSDEditor = () => { let imageFilePaths = [] let psdPromises = [] for(let selection of selections) { diff --git a/src/js/window/toolbar.js b/src/js/window/toolbar.js index 466f28233c..dd17a4b9ad 100644 --- a/src/js/window/toolbar.js +++ b/src/js/window/toolbar.js @@ -392,9 +392,6 @@ class Toolbar extends EventEmitter { case 'pomodoro-running-status': this.emit('pomodoro-running') break - case 'open-in-ora-editor': - this.emit('open-in-ora-editor') - break default: // console.log('toolbar selection', selection) break diff --git a/src/js/windows/preferences/editor.js b/src/js/windows/preferences/editor.js index bae5fddc6d..8f9e76daa1 100644 --- a/src/js/windows/preferences/editor.js +++ b/src/js/windows/preferences/editor.js @@ -10,6 +10,8 @@ const onChange = (name, event) => { prefsModule.set(name, el.checked) } else if (el.type == 'number') { prefsModule.set(name, el.value) + } else if (el.type == 'radio') { + prefsModule.set(name, el.value) } render() } @@ -18,6 +20,8 @@ const render = () => { for (let el of inputs) { if (el.type == 'checkbox') { el.checked = prefs[el.name] + } else if (el.type == 'radio' && prefs[el.name] === el.value) { + el.checked = "checked" } else if (el.type == 'number') { el.value = prefs[el.name] @@ -30,7 +34,7 @@ const render = () => { } } -let inputs = document.querySelectorAll('input[type="checkbox"], input[type="number"]') +let inputs = document.querySelectorAll('input[type="checkbox"], input[type="number"], input[type="radio"]') // bind for (let el of inputs) { @@ -42,4 +46,4 @@ window.ondragleave = () => { return false } window.ondragend = () => { return false } window.ondrop = () => { return false } -render() \ No newline at end of file +render() diff --git a/src/main-window.html b/src/main-window.html index b6e51b0477..3a38c93bab 100644 --- a/src/main-window.html +++ b/src/main-window.html @@ -199,19 +199,12 @@
-
- -
Performance Enhancements Enable Line Smoothing
- + +
+ +
+

External Editor

+ +
+ + + +
+ +
+ + + +
+