From f00de1a3d3e599f4f21c892dbbdde46e5699d860 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Mon, 24 Aug 2020 16:18:55 +1200 Subject: [PATCH] :zap: Add dnd-processor tag that solves edge cases with drag-and-drop behavior and allows dropping any supported files on any tab --- src/js/loadProject.js | 66 +++++----- src/js/preventDnDNavigation.js | 16 +++ src/node_requires/resources/fonts/index.js | 95 ++++++++++++++ src/node_requires/resources/skeletons.js | 106 ++++++++++++++++ src/riotTags/dnd-processor.tag | 68 ++++++++++ src/riotTags/fonts-panel.tag | 135 ++------------------ src/riotTags/root-tag.tag | 15 ++- src/riotTags/texture-editor.tag | 4 +- src/riotTags/textures-panel.tag | 138 ++------------------- src/styl/inputs.styl | 22 ++-- 10 files changed, 362 insertions(+), 303 deletions(-) create mode 100644 src/js/preventDnDNavigation.js create mode 100644 src/node_requires/resources/fonts/index.js create mode 100644 src/node_requires/resources/skeletons.js create mode 100644 src/riotTags/dnd-processor.tag diff --git a/src/js/loadProject.js b/src/js/loadProject.js index ee813f4ba..681dc2a62 100644 --- a/src/js/loadProject.js +++ b/src/js/loadProject.js @@ -119,8 +119,6 @@ lastProjects.pop(); } localStorage.lastProjects = lastProjects.join(';'); - window.signals.trigger('hideProjectSelector'); - window.signals.trigger('projectLoaded'); if (global.currentProject.settings.title) { document.title = global.currentProject.settings.title + ' — ct.js'; @@ -138,6 +136,7 @@ resetTypedefs(); loadAllTypedefs(); + window.signals.trigger('projectLoaded'); setTimeout(() => { window.riot.update(); }, 0); @@ -187,7 +186,7 @@ } }; - window.loadProject = proj => { + window.loadProject = async proj => { if (!proj) { const baseMessage = 'An attempt to open a project with an empty path.'; alertify.error(baseMessage + ' See the console for the call stack.'); @@ -197,36 +196,37 @@ sessionStorage.projname = path.basename(proj); global.projdir = path.dirname(proj) + path.sep + path.basename(proj, '.ict'); - fs.stat(proj + '.recovery', (err, stat) => { - if (!err && stat.isFile()) { - var targetStat = fs.statSync(proj), - voc = window.languageJSON.intro.recovery; - window.alertify - .okBtn(voc.loadRecovery) - .cancelBtn(voc.loadTarget) - /* {0} — target file date - {1} — target file state (newer/older) - {2} — recovery file date - {3} — recovery file state (newer/older) - */ - .confirm(voc.message - .replace('{0}', targetStat.mtime.toLocaleString()) - .replace('{1}', targetStat.mtime < stat.mtime ? voc.older : voc.newer) - .replace('{2}', stat.mtime.toLocaleString()) - .replace('{3}', stat.mtime < targetStat.mtime ? voc.older : voc.newer)) - .then(e => { - if (e.buttonClicked === 'ok') { - loadProjectFile(proj + '.recovery'); - } else { - loadProjectFile(proj); - } - window.alertify - .okBtn(window.languageJSON.common.ok) - .cancelBtn(window.languageJSON.common.cancel); - }); - } else { - loadProjectFile(proj); + let recoveryStat; + try { + recoveryStat = await fs.stat(proj + '.recovery'); + } catch (err) { + // no recovery file found + void 0; + } + if (recoveryStat && recoveryStat.isFile()) { + const targetStat = await fs.stat(proj); + const voc = window.languageJSON.intro.recovery; + const userResponse = await window.alertify + .okBtn(voc.loadRecovery) + .cancelBtn(voc.loadTarget) + /* {0} — target file date + {1} — target file state (newer/older) + {2} — recovery file date + {3} — recovery file state (newer/older) + */ + .confirm(voc.message + .replace('{0}', targetStat.mtime.toLocaleString()) + .replace('{1}', targetStat.mtime < recoveryStat.mtime ? voc.older : voc.newer) + .replace('{2}', recoveryStat.mtime.toLocaleString()) + .replace('{3}', recoveryStat.mtime < targetStat.mtime ? voc.older : voc.newer)); + window.alertify + .okBtn(window.languageJSON.common.ok) + .cancelBtn(window.languageJSON.common.cancel); + if (userResponse.buttonClicked === 'ok') { + return loadProjectFile(proj + '.recovery'); } - }); + return loadProjectFile(proj); + } + return loadProjectFile(proj); }; })(this); diff --git a/src/js/preventDnDNavigation.js b/src/js/preventDnDNavigation.js new file mode 100644 index 000000000..1220e8a8c --- /dev/null +++ b/src/js/preventDnDNavigation.js @@ -0,0 +1,16 @@ +/* + * This file prevents opening an image or such when a file was dragged into + * ct.js window and it was not catched by other listeners + */ + +{ + const draghHandler = function draghHandler(e) { + if (e.target.nodeName === 'INPUT' && e.target.type === 'file') { + return; + } + e.preventDefault(); + }; + document.addEventListener('dragenter', draghHandler); + document.addEventListener('dragover', draghHandler); + document.addEventListener('drop', draghHandler); +} diff --git a/src/node_requires/resources/fonts/index.js b/src/node_requires/resources/fonts/index.js new file mode 100644 index 000000000..a44153e63 --- /dev/null +++ b/src/node_requires/resources/fonts/index.js @@ -0,0 +1,95 @@ +/** + * @param {object|string} font The font object in ct.js project, or its UID. + * @param {boolean} fs If set to `true`, returns a clean path in a file system. + * Otherwise, returns an URL. + */ +const getPathToTtf = function getPathToTtf(font, fs) { + const path = require('path'); + if (fs) { + return path.join(global.projdir, 'fonts', font.origname); + } + return `file://${global.projdir}/fonts/${font.origname}`; +}; + +/** + * @param {object|string} font The font object in ct.js project, or its UID. + * @param {boolean} fs If set to `true`, returns a clean path in a file system. + * Otherwise, returns an URL. + */ +const getFontPreview = function getFontPreview(font, fs) { + const path = require('path'); + if (fs) { + return path.join(global.projdir, 'fonts', `${font.origname}_prev.png`); + } + return `file://${global.projdir}/fonts/${font.origname}_prev.png?cache=${font.lastmod}`; +}; + +const fontGenPreview = async function fontGenPreview(font) { + const template = { + weight: font.weight, + style: font.italic ? 'italic' : 'normal' + }; + const fs = require('fs-extra'); + const face = new FontFace('CTPROJFONT' + font.typefaceName, `url(${getPathToTtf(font)})`, template); + + // Trigger font loading by creating an invisible label with this font + // const elt = document.createElement('span'); + // elt.innerHTML = 'testString'; + // elt.style.position = 'fixed'; + // elt.style.right = '200%'; + // elt.style.fontFamily = 'CTPROJFONT' + font.typefaceName; + // document.body.appendChild(elt); + + const loaded = await face.load(); + loaded.external = true; + loaded.ctId = face.ctId = font.uid; + document.fonts.add(loaded); + // document.body.removeChild(elt); + + const c = document.createElement('canvas'); + c.x = c.getContext('2d'); + c.width = c.height = 64; + c.x.clearRect(0, 0, 64, 64); + c.x.font = `${font.italic ? 'italic ' : ''}${font.weight} ${Math.floor(64 * 0.75)}px "${loaded.family}"`; + c.x.fillStyle = '#000'; + c.x.fillText('Aa', 64 * 0.05, 64 * 0.75); + + // strip off the data:image url prefix to get just the base64-encoded bytes + const dataURL = c.toDataURL(); + const previewBuffer = dataURL.replace(/^data:image\/\w+;base64,/, ''); + const buf = new Buffer(previewBuffer, 'base64'); + await fs.writeFile(getFontPreview(font, true), buf); +}; + +const importTtfToFont = async function importTtfToFont(src) { + const fs = require('fs-extra'), + path = require('path'); + if (path.extname(src) !== '.ttf') { + throw new Error(`[resources/fonts] Rejecting a file as it does not have a .ttf extension: ${src}`); + } + const generateGUID = require('./../../generateGUID'); + const uid = generateGUID(); + await fs.copy(src, path.join(global.projdir, '/fonts/f' + uid + '.ttf')); + const obj = { + typefaceName: path.basename(src).replace('.ttf', ''), + weight: 400, + italic: false, + origname: `f${uid}.ttf`, + lastmod: Number(new Date()), + pixelFont: false, + pixelFontSize: 16, + pixelFontLineHeight: 18, + charsets: ['allInFont'], + customCharset: '', + uid + }; + global.currentProject.fonts.push(obj); + await fontGenPreview(obj); + window.signals.trigger('fontCreated'); +}; +module.exports = { + importTtfToFont, + fontGenPreview, + getFontPreview, + getPathToTtf +}; diff --git a/src/node_requires/resources/skeletons.js b/src/node_requires/resources/skeletons.js new file mode 100644 index 000000000..51248a3d6 --- /dev/null +++ b/src/node_requires/resources/skeletons.js @@ -0,0 +1,106 @@ +const path = require('path'); + +const getSkeletonData = function getSkeletonData(skeleton, fs) { + if (fs) { + return path.join(global.projdir, 'img', skeleton.origname); + } + return `file://${global.projdir}/img/${skeleton.origname}`; +}; +const getSkeletonTextureData = function getSkeletonTextureData(skeleton, fs) { + const slice = skeleton.origname.replace('_ske.json', ''); + if (fs) { + return path.join(global.projdir, 'img', `${slice}_tex.json`); + } + return `file://${global.projdir}/img/${slice}_tex.json`; +}; +const getSkeletonTexture = function getSkeletonTexture(skeleton, fs) { + const slice = skeleton.origname.replace('_ske.json', ''); + if (fs) { + return path.join(global.projdir, 'img', `${slice}_tex.png`); + } + return `file://${global.projdir}/img/${slice}_tex.png`; +}; + +const getSkeletonPreview = function getSkeletonPreview(skeleton, fs) { + if (fs) { + return path.join(global.projdir, 'img', `${skeleton.origname}_prev.png`); + } + return `file://${global.projdir}/img/${skeleton.origname}_prev.png`; +}; + +/** + * Generates a square thumbnail of a given skeleton + * @param {String} skeleton The skeleton object to generate a preview for. + * @returns {Promise} Resolves after creating a thumbnail. + */ +const skeletonGenPreview = function (skeleton) { + const loader = new PIXI.loaders.Loader(), + dbf = dragonBones.PixiFactory.factory; + const fs = require('fs-extra'); + return new Promise((resolve, reject) => { + // Draw the armature on a canvas/in a Pixi.js app + const skelData = getSkeletonData(skeleton), + texData = getSkeletonTextureData(skeleton), + tex = getSkeletonTexture(skeleton); + loader.add(skelData, skelData) + .add(texData, texData) + .add(tex, tex); + loader.load(() => { + dbf.parseDragonBonesData(loader.resources[skelData].data); + dbf.parseTextureAtlasData( + loader.resources[texData].data, + loader.resources[tex].texture + ); + const skel = dbf.buildArmatureDisplay('Armature', loader.resources[skelData].data.name); + + const app = new PIXI.Application(); + + const rawSkelBase64 = app.renderer.plugins.extract.base64(skel); + const skelBase64 = rawSkelBase64.replace(/^data:image\/\w+;base64,/, ''); + const buf = new Buffer(skelBase64, 'base64'); + + fs.writeFile(getSkeletonPreview(skeleton, true), buf) + .then(() => { + // Clean memory from DragonBones' armatures + // eslint-disable-next-line no-underscore-dangle + delete dbf._dragonBonesDataMap[loader.resources[skelData].data.name]; + // eslint-disable-next-line no-underscore-dangle + delete dbf._textureAtlasDataMap[loader.resources[skelData].data.name]; + }) + .then(resolve) + .catch(reject); + }); + }); +}; + +const importSkeleton = async function importSkeleton(source) { + const generateGUID = require('./../generateGUID'); + const fs = require('fs-extra'); + + const uid = generateGUID(); + const partialDest = path.join(global.projdir + '/img/skdb' + uid); + + await Promise.all([ + fs.copy(source, partialDest + '_ske.json'), + fs.copy(source.replace('_ske.json', '_tex.json'), partialDest + '_tex.json'), + fs.copy(source.replace('_ske.json', '_tex.png'), partialDest + '_tex.png') + ]); + const skel = { + name: path.basename(source).replace('_ske.json', ''), + origname: path.basename(partialDest + '_ske.json'), + from: 'dragonbones', + uid + }; + await skeletonGenPreview(skel); + global.currentProject.skeletons.push(skel); + window.signals.trigger('skeletonImported', skel); +}; + +module.exports = { + getSkeletonData, + getSkeletonTextureData, + getSkeletonTexture, + getSkeletonPreview, + skeletonGenPreview, + importSkeleton +}; diff --git a/src/riotTags/dnd-processor.tag b/src/riotTags/dnd-processor.tag new file mode 100644 index 000000000..e1f6074a1 --- /dev/null +++ b/src/riotTags/dnd-processor.tag @@ -0,0 +1,68 @@ +dnd-processor + .aDropzone(if="{dropping}") + .middleinner + svg.feather + use(xlink:href="data/icons.svg#download") + h2 {languageJSON.common.fastimport} + input( + type="file" multiple + accept=".png,.jpg,.jpeg,.bmp,.gif,.json,.ttf" + onchange="{dndImport}" + ) + script. + this.dndImport = e => { + const files = [...e.target.files].map(file => file.path); + for (let i = 0; i < files.length; i++) { + if (/\.(jpg|gif|png|jpeg)/gi.test(files[i])) { + const {importImageToTexture} = require('./data/node_requires/resources/textures'); + importImageToTexture(files[i]); + } else if (/_ske\.json/i.test(files[i])) { + const {importSkeleton} = require('./data/node_requires/resources/skeletons'); + importSkeleton(files[i]); + } else if (/\.ttf/gi.test(files[i])) { + const {importTtfToFont} = require('./data/node_requires/resources/fonts'); + importTtfToFont(files[i]); + } else { + alertify.log(`Skipped ${files[i]} as it is not supported by drag-and-drop importer.`); + } + } + e.srcElement.value = ''; + this.dropping = false; + e.preventDefault(); + }; + + /* + * drag-n-drop handling + */ + let dragTimer; + this.onDragOver = e => { + var dt = e.dataTransfer; + if (dt.types && (dt.types.indexOf ? dt.types.indexOf('Files') !== -1 : dt.types.contains('Files'))) { + this.dropping = true; + this.update(); + window.clearTimeout(dragTimer); + } + e.preventDefault(); + e.stopPropagation(); + }; + this.onDrop = e => { + e.stopPropagation(); + }; + this.onDragLeave = e => { + dragTimer = window.setTimeout(() => { + this.dropping = false; + this.update(); + }, 25); + e.preventDefault(); + e.stopPropagation(); + }; + this.on('mount', () => { + document.addEventListener('dragover', this.onDragOver); + document.addEventListener('dragleave', this.onDragLeave); + document.addEventListener('drop', this.onDrop); + }); + this.on('unmount', () => { + document.removeEventListener('dragover', this.onDragOver); + document.removeEventListener('dragleave', this.onDragLeave); + document.removeEventListener('drop', this.onDrop); + }); \ No newline at end of file diff --git a/src/riotTags/fonts-panel.tag b/src/riotTags/fonts-panel.tag index 740ecde5a..50b377c61 100644 --- a/src/riotTags/fonts-panel.tag +++ b/src/riotTags/fonts-panel.tag @@ -20,14 +20,6 @@ fonts-panel.flexfix.tall.fifty svg.feather use(xlink:href="data/icons.svg#download") span {voc.import} - .aDropzone(if="{dropping}") - .middleinner - svg.feather - use(xlink:href="data/icons.svg#download") - h2 {languageJSON.common.fastimport} - input(type="file" multiple - accept=".ttf" - onchange="{fontImport}") context-menu(menu="{fontMenu}" ref="fontMenu") font-editor(if="{editingFont}" fontobj="{editedFont}") script. @@ -36,10 +28,8 @@ fonts-panel.flexfix.tall.fifty this.fonts = global.currentProject.fonts; this.namespace = 'fonts'; this.mixin(window.riotVoc); - const fs = require('fs-extra'), - path = require('path'); - this.thumbnails = font => `file://${window.global.projdir}/fonts/${font.origname}_prev.png?cache=${font.lastmod}`; + this.thumbnails = require('./data/node_requires/resources/fonts').getFontPreview; this.names = font => `${font.typefaceName} ${font.weight} ${font.italic ? this.voc.italic : ''}`; this.setUpPanel = () => { @@ -121,130 +111,31 @@ fonts-panel.flexfix.tall.fifty * The event of importing a font through a file manager */ this.fontImport = e => { // e.target:input[type="file"] - const generateGUID = require('./data/node_requires/generateGUID'); const files = [...e.target.files].map(file => file.path); e.target.value = ''; + const {importTtfToFont} = require('./data/node_requires/resources/fonts'); for (let i = 0; i < files.length; i++) { if (/\.ttf/gi.test(files[i])) { - const id = generateGUID(); - this.loadFont( - id, - files[i], - path.join(global.projdir, '/fonts/f' + id + '.ttf'), - true - ); - } else { - alertify.log(`Skipped ${files[i]} as it is not a .ttf file.`); - void 0; - } - } - this.dropping = false; - e.srcElement.value = ''; // clear input value that prevent to upload the same filename again - e.preventDefault(); - }; - this.loadFont = (uid, filename, dest) => { - fs.copy(filename, dest, e => { - if (e) { - throw e; - } - var obj = { - typefaceName: path.basename(filename).replace('.ttf', ''), - weight: 400, - italic: false, - origname: path.basename(dest), - lastmod: Number(new Date()), - pixelFont: false, - pixelFontSize: 16, - pixelFontLineHeight: 18, - charsets: ['allInFont'], - customCharset: '', - uid - }; - global.currentProject.fonts.push(obj); - setTimeout(() => { - this.fontGenPreview(dest, dest + '_prev.png', 64, obj) + importTtfToFont(files[i]) .then(() => { this.refs.fonts.updateList(); this.update(); }); - }, 250); - }); - }; - this.fontGenPreview = (source, destFile, size, obj) => new Promise((resolve, reject) => { - const template = { - weight: obj.weight, - style: obj.italic ? 'italic' : 'normal' - }; - // we clean the source url from the possible space and the \ to / (windows specific) - const cleanedSource = source.replace(/ /g, '%20').replace(/\\/g, '/'); - const face = new FontFace('CTPROJFONT' + obj.typefaceName, `url(file://${cleanedSource})`, template); - const elt = document.createElement('span'); - elt.innerHTML = 'testString'; - elt.style.fontFamily = obj.typefaceName; - document.body.appendChild(elt); - face.load() - .then(loaded => { - loaded.external = true; - loaded.ctId = face.ctId = obj.uid; - document.fonts.add(loaded); - const c = document.createElement('canvas'); - c.x = c.getContext('2d'); - c.width = c.height = size; - c.x.clearRect(0, 0, size, size); - c.x.font = `${obj.italic ? 'italic ' : ''}${obj.weight} ${Math.floor(size * 0.75)}px "${loaded.family}"`; - c.x.fillStyle = '#000'; - c.x.fillText('Aa', size * 0.05, size * 0.75); - // strip off the data:image url prefix to get just the base64-encoded bytes - const dataURL = c.toDataURL(); - const previewBuffer = dataURL.replace(/^data:image\/\w+;base64,/, ''); - const buf = new Buffer(previewBuffer, 'base64'); - const stream = fs.createWriteStream(destFile); - stream.on('finish', () => { - setTimeout(() => { // WHY THE HECK I EVER NEED THIS?! - resolve(destFile); - }, 100); - }); - stream.on('error', err => { - reject(err); - }); - stream.end(buf); - }) - .catch(reject); - }); - /* - * Additions for drag-n-drop - */ - var dragTimer; - this.onDragOver = e => { - var dt = e.dataTransfer; - if (dt.types && (dt.types.indexOf ? dt.types.indexOf('Files') !== -1 : dt.types.contains('Files'))) { - this.dropping = true; - this.update(); - window.clearTimeout(dragTimer); + } else { + alertify.log(`Skipped ${files[i]} as it is not a .ttf file.`); + } } + e.srcElement.value = ''; // clear input value that prevent to upload the same filename again e.preventDefault(); - e.stopPropagation(); }; - this.onDrop = e => { - e.stopPropagation(); - }; - this.onDragLeave = e => { - dragTimer = window.setTimeout(() => { - this.dropping = false; - this.update(); - }, 25); - e.preventDefault(); - e.stopPropagation(); + + const updatePanels = () => { + this.refs.fonts.updateList(); + this.update(); }; - this.on('mount', () => { - document.addEventListener('dragover', this.onDragOver); - document.addEventListener('dragleave', this.onDragLeave); - document.addEventListener('drop', this.onDrop); - }); + window.signals.on('fontCreated', updatePanels); this.on('unmount', () => { - document.removeEventListener('dragover', this.onDragOver); - document.removeEventListener('dragleave', this.onDragLeave); - document.removeEventListener('drop', this.onDrop); + window.signals.off('fontCreated', updatePanels); }); this.loadFonts = () => { diff --git a/src/riotTags/root-tag.tag b/src/riotTags/root-tag.tag index cb1cf2611..f94b461fa 100644 --- a/src/riotTags/root-tag.tag +++ b/src/riotTags/root-tag.tag @@ -1,14 +1,19 @@ root-tag - main-menu(if="{!selectorVisible}") - notepad-panel(if="{!selectorVisible}") - project-selector(if="{selectorVisible}") + main-menu(if="{projectOpened}") + notepad-panel(if="{projectOpened}") + dnd-processor(if="{projectOpened}") + project-selector(if="{!projectOpened}") script. - this.selectorVisible = true; + this.projectOpened = false; window.signals.on('resetAll', () => { global.currentProject = false; - this.selectorVisible = true; + this.projectOpened = false; riot.update(); }); + window.signals.on('projectLoaded', () => { + this.projectOpened = true; + this.update(); + }); const stylesheet = document.createElement('style'); document.head.appendChild(stylesheet); diff --git a/src/riotTags/texture-editor.tag b/src/riotTags/texture-editor.tag index ce2a37852..a607a6286 100644 --- a/src/riotTags/texture-editor.tag +++ b/src/riotTags/texture-editor.tag @@ -300,7 +300,7 @@ texture-editor.panel.view global.projdir + '/img/i' + this.texture.uid + path.extname(this.texture.source) ); }; - this.paste = () => { + this.paste = async () => { const png = nw.Clipboard.get().get('png'); if (!png) { alertify.error(this.vocGlob.couldNotLoadFromClipboard); @@ -308,7 +308,7 @@ texture-editor.panel.view } const imageBase64 = png.replace(/^data:image\/\w+;base64,/, ''); const imageBuffer = new Buffer(imageBase64, 'base64'); - this.loadImg( + await this.loadImg( imageBuffer, global.projdir + '/img/i' + this.texture.uid + '.png' ); diff --git a/src/riotTags/textures-panel.tag b/src/riotTags/textures-panel.tag index f055d16bb..a92f5872b 100644 --- a/src/riotTags/textures-panel.tag +++ b/src/riotTags/textures-panel.tag @@ -7,7 +7,7 @@ textures-panel.panel.view vocspace="texture" namespace="textures" click="{openTexture}" - thumbnails="{thumbnails}" + thumbnails="{textureThumbnails}" ref="textures" ) label.file.inlineblock @@ -31,7 +31,7 @@ textures-panel.panel.view contextmenu="{showSkeletonPopup}" vocspace="texture" namespace="skeletons" - thumbnails="{thumbnails}" + thumbnails="{skelThumbnails}" ref="skeletons" ) h2 @@ -45,31 +45,17 @@ textures-panel.panel.view svg.feather use(xlink:href="data/icons.svg#download") span {voc.import} - - .aDropzone(if="{dropping}") - .middleinner - svg.feather - use(xlink:href="data/icons.svg#download") - h2 {languageJSON.common.fastimport} - input(type="file" multiple - accept=".png,.jpg,.jpeg,.bmp,.gif,.json" - onchange="{textureImport}") - texture-editor(if="{editing}" texture="{currentTexture}") context-menu(menu="{textureMenu}" ref="textureMenu") script. - const fs = require('fs-extra'), - path = require('path'); const glob = require('./data/node_requires/glob'); - const generateGUID = require('./data/node_requires/generateGUID'); this.namespace = 'texture'; this.mixin(window.riotVoc); this.editing = false; this.dropping = false; - const {getTexturePreview} = require('./data/node_requires/resources/textures'); - // this.thumbnails = texture => `file://${global.projdir}/img/${texture.origname}_prev.png?cache=${texture.lastmod}`; - this.thumbnails = getTexturePreview; + this.textureThumbnails = require('./data/node_requires/resources/textures').getTexturePreview; + this.skelThumbnails = require('./data/node_requires/resources/skeletons').getSkeletonPreview; this.fillTextureMap = () => { glob.texturemap = {}; @@ -116,10 +102,12 @@ textures-panel.panel.view window.signals.on('projectLoaded', this.setUpPanel); window.signals.on('textureImported', this.updateTextureData); + window.signals.on('skeletonImported', this.updateTextureData); this.on('mount', this.setUpPanel); this.on('unmount', () => { window.signals.off('projectLoaded', this.setUpPanel); window.signals.off('textureImported', this.updateTextureData); + window.signals.off('skeletonImported', this.updateTextureData); }); /** @@ -133,16 +121,11 @@ textures-panel.panel.view if (/\.(jpg|gif|png|jpeg)/gi.test(files[i])) { importImageToTexture(files[i]); } else if (/_ske\.json/i.test(files[i])) { - const id = generateGUID(); - this.loadSkeleton( - id, - files[i], - global.projdir + '/img/skdb' + id + '_ske.json' - ); + const {importSkeleton} = require('./data/node_requires/resources/skeletons'); + importSkeleton(files[i]); } } e.srcElement.value = ''; - this.dropping = false; e.preventDefault(); }; @@ -159,75 +142,6 @@ textures-panel.panel.view alertify.success(this.vocGlob.pastedFromClipboard); }; - this.loadSkeleton = (uid, filename, dest) => { - fs.copy(filename, dest) - .then(() => fs.copy(filename.replace('_ske.json', '_tex.json'), dest.replace('_ske.json', '_tex.json'))) - .then(() => fs.copy(filename.replace('_ske.json', '_tex.png'), dest.replace('_ske.json', '_tex.png'))) - .then(() => { - global.currentProject.skeletons.push({ - name: path.basename(filename).replace('_ske.json', ''), - origname: path.basename(dest), - from: 'dragonbones', - uid - }); - this.skelGenPreview(dest, dest + '_prev.png', [64, 128]) - .then(() => { - this.refs.skeletons.updateList(); - this.update(); - }); - }); - }; - - /** - * Generates a square preview for a given skeleton - * @param {String} source Path to the source _ske.json file - * @param {String} destFile Path to the destinating image - * @param {Array} sizes Size of the square thumbnail, in pixels - * @returns {Promise} Resolves after creating a thumbnail. On success, - * passes data-url of the created thumbnail. - */ - this.skelGenPreview = (source, destFile, sizes) => { - // TODO: Actually generate previews of different sizes - const loader = new PIXI.loaders.Loader(), - dbf = dragonBones.PixiFactory.factory; - const slice = 'file://' + source.replace('_ske.json', ''); - return new Promise((resolve, reject) => { - loader.add(`${slice}_ske.json`, `${slice}_ske.json`) - .add(`${slice}_tex.json`, `${slice}_tex.json`) - .add(`${slice}_tex.png`, `${slice}_tex.png`); - loader.load(() => { - dbf.parseDragonBonesData(loader.resources[`${slice}_ske.json`].data); - dbf.parseTextureAtlasData(loader.resources[`${slice}_tex.json`].data, loader.resources[`${slice}_tex.png`].texture); - const skel = dbf.buildArmatureDisplay('Armature', loader.resources[`${slice}_ske.json`].data.name); - const promises = sizes.map(() => new Promise((resolve, reject) => { - const app = new PIXI.Application(); - const rawSkelBase64 = app.renderer.plugins.extract.base64(skel); - const skelBase64 = rawSkelBase64.replace(/^data:image\/\w+;base64,/, ''); - const buf = new Buffer(skelBase64, 'base64'); - const stream = fs.createWriteStream(destFile); - stream.on('finish', () => { - setTimeout(() => { // WHY THE HECK I EVER NEED THIS?! - resolve(destFile); - }, 100); - }); - stream.on('error', err => { - reject(err); - }); - stream.end(buf); - })); - Promise.all(promises) - .then(() => { - // eslint-disable-next-line no-underscore-dangle - delete dbf._dragonBonesDataMap[loader.resources[`${slice}_ske.json`].data.name]; - // eslint-disable-next-line no-underscore-dangle - delete dbf._textureAtlasDataMap[loader.resources[`${slice}_ske.json`].data.name]; - }) - .then(resolve) - .catch(reject); - }); - }); - }; - const deleteCurrentTexture = () => { for (const type of global.currentProject.types) { if (type.texture === this.currentTexture.uid) { @@ -363,39 +277,3 @@ textures-panel.panel.view this.currentTextureId = global.currentProject.textures.indexOf(texture); this.editing = true; }; - - /* - * drag-n-drop handling - */ - var dragTimer; - this.onDragOver = e => { - var dt = e.dataTransfer; - if (dt.types && (dt.types.indexOf ? dt.types.indexOf('Files') !== -1 : dt.types.contains('Files'))) { - this.dropping = true; - this.update(); - window.clearTimeout(dragTimer); - } - e.preventDefault(); - e.stopPropagation(); - }; - this.onDrop = e => { - e.stopPropagation(); - }; - this.onDragLeave = e => { - dragTimer = window.setTimeout(() => { - this.dropping = false; - this.update(); - }, 25); - e.preventDefault(); - e.stopPropagation(); - }; - this.on('mount', () => { - document.addEventListener('dragover', this.onDragOver); - document.addEventListener('dragleave', this.onDragLeave); - document.addEventListener('drop', this.onDrop); - }); - this.on('unmount', () => { - document.removeEventListener('dragover', this.onDragOver); - document.removeEventListener('dragleave', this.onDragLeave); - document.removeEventListener('drop', this.onDrop); - }); diff --git a/src/styl/inputs.styl b/src/styl/inputs.styl index da3753ada..465d43915 100644 --- a/src/styl/inputs.styl +++ b/src/styl/inputs.styl @@ -391,20 +391,14 @@ input[type="range"] top 0 bottom 0 background rgba(background, 0.65) - // @stylint off - background-image: - linear-gradient(to right, accent1 0, accent1 35%, transparent 35%, transparent 100%), - linear-gradient(to left, accent1 0, accent1 35%, transparent 35%, transparent 100%), - linear-gradient(to bottom, accent1 0, accent1 35%, transparent 35%, transparent 100%), - linear-gradient(to top, accent1 0, accent1 35%, transparent 35%, transparent 100%) - // @stylint on - background-size 5rem 0.5rem, 5rem 0.5rem, 0.5rem 5rem, 0.5rem 5rem - background-repeat repeat-x, repeat-x, repeat-y, repeat-y - background-position 0 0, 0 bottom, right 0, 0 0 + border 0.5rem solid act + animation aDropzonePulsate 1.25s ease-in-out infinite text-align center svg - font-size 5rem color accent1 + width 5rem + height 5rem + stroke-width 1px h2 font-size 2rem input @@ -416,6 +410,12 @@ input[type="range"] opacity 0 cursor -webkit-grabbing +@keyframes aDropzonePulsate + 0%, 100% + border 0.5rem solid act + 50% + border 0.5rem solid rgba(act, 0.35) + .aResizer background backgroundDeeper if (darkTheme)