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/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 74236ae5f8..9be6556b4a 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') @@ -1145,7 +1149,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 +1216,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 +1311,7 @@ let saveImageFile = () => { } } } - + if (shouldSaveBoardFile) { saveBoardFile() } @@ -1323,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) { @@ -1345,9 +1359,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 +1400,7 @@ let openInEditor = () => { storeUndoStateForImage(true, [0, 1, 3]) isCurrentBoard = true } - + psdData = FileHelper.getBase64ImageDataFromFilePath(board.psd, readerOptions) if(!psdData || !psdData.main) { return; @@ -1422,9 +1436,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 +1546,7 @@ let openInEditor = () => { // let imageData = canvas // .toDataURL('image/png') // .replace(/^data:image\/\w+;base64,/, '') - + // try { // fs.writeFile(imageFilePath, imageData, 'base64', () => { // resolve() @@ -1499,7 +1599,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 +1831,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 +1992,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 +2025,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 +2049,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 +2129,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 +2420,7 @@ let renderThumbnailDrawer = ()=> { renderThumbnailDrawerSelections() } else if (currentBoard !== index) { // go to board by index - + // reset selections selections.clear() @@ -2334,7 +2434,7 @@ let renderThumbnailDrawer = ()=> { renderThumbnailButtons() renderTimeline() - + //gotoBoard(currentBoard) } @@ -2358,7 +2458,7 @@ let renderThumbnailButtons = () => {