diff --git a/.gitignore b/.gitignore index ad857d3..8a7633f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,13 @@ -# ide -.idea/* -.vscode/* - -#node modules -node_modules/* - -#temp files -.DS_Store -/index.js -/index.js.map -/package-lock.json -/src/electron/package-lock.json -/src/electron/test -/dist/web -/electron/www +.idea/* +.vscode/* + +node_modules/* + +package-lock.json + +.DS_Store +/index.js +/index.js.map +/dist/web +/dist/app +/electron/www diff --git a/electron/main.js b/electron/main.js index bc3810e..e82f3a9 100644 --- a/electron/main.js +++ b/electron/main.js @@ -1,315 +1,315 @@ -const path = require('path'); -const tinify = require('tinify'); -const argv = require('optimist').argv; -const windowStateKeeper = require('electron-window-state'); -const {app, BrowserWindow, ipcMain, Menu, shell} = require('electron'); -const {autoUpdater} = require("electron-updater"); - -let mainWindow; -let RECENT_PROJECTS = []; -let CURRENT_LOCALE = ""; -let LOCALE_STRINGS = {}; -let APP_INFO = {}; -let CURRENT_PROJECT = ""; -let CURRENT_PROJECT_M0DIFIED = false; - -function createWindow() { - let w = 1220; - let h = 680; - - if(process.platform === "win32") { - w = 1240; - h = 720; - } - - let mainWindowState = windowStateKeeper({ - defaultWidth: w, - defaultHeight: h - }); - - mainWindow = new BrowserWindow({ - x: mainWindowState.x, - y: mainWindowState.y, - width: mainWindowState.width, - height: mainWindowState.height, - minWidth: w, - minHeight: h, - title: "", - icon: path.resolve(__dirname, 'www/static/images/icon.png') - }); - - mainWindowState.manage(mainWindow); - - mainWindow.on('page-title-updated', function(e) { - e.preventDefault(); - }); - - if (argv.env === 'development') { - mainWindow.loadURL('http://localhost:4000/'); - } - else { - mainWindow.loadFile('./www/index.html'); - } - - Menu.setApplicationMenu(null); - - mainWindow.on('close', function(e) { - if(CURRENT_PROJECT_M0DIFIED) { - sendMessage({actionName: 'quit'}); - e.preventDefault(); - } - }); - - mainWindow.on('closed', function() { - mainWindow = null; - }); - - mainWindow.webContents.on('will-navigate', handleRedirect); - mainWindow.webContents.on('new-window', handleRedirect); - - mainWindow.webContents.on('did-finish-load', function() { - CURRENT_PROJECT = ""; - CURRENT_PROJECT_M0DIFIED = false; - updateWindowTitle(); - - if(argv.env !== 'development' && process.argv.length > 1) { - sendMessage({actionName: 'project-load', custom: process.argv[1]}); - } - - autoUpdater.checkForUpdates(); - }); - - startAutoUpdater(); - - onProjectUpdated(); -} - -function startAutoUpdater() { - autoUpdater.autoDownload = false; - autoUpdater.autoInstallOnAppQuit = false; - - autoUpdater.on('checking-for-update', () => { - }); - - autoUpdater.on('update-available', (info) => { - mainWindow.send('update-available', info); - }); - - autoUpdater.on('update-not-available', (info) => { - }); - - autoUpdater.on('error', (err) => { - }); - - autoUpdater.on('download-progress', (progressObj) => { - mainWindow.send('download-progress', progressObj.percent); - }); - - autoUpdater.on('update-downloaded', (info) => { - autoUpdater.quitAndInstall(true, true); - }); - - ipcMain.on('install-update', (e, data) => { - autoUpdater.downloadUpdate(); - }); -} - -function handleRedirect(e, url) { - if(url !== mainWindow.getURL()) { - e.preventDefault(); - shell.openExternal(url); - } -} - -function buildMenu() { - let template = []; - - let recentProjects = []; - - if(RECENT_PROJECTS.length) { - for(let path of RECENT_PROJECTS) { - let name = path.split("/").pop(); - recentProjects.push({label: name, actionName: 'project-load', click: sendMessage, custom: path}); - } - } - else { - recentProjects.push({label: "...", enabled: false}); - } - - let quitAcc = "CmdOrCtrl+F4"; - if(process.platform === "darwin") quitAcc = "CmdOrCtrl+Q"; - - template.push({ - label: LOCALE_STRINGS.MENU_FILE, - submenu: [ - {label: LOCALE_STRINGS.MENU_FILE_PROJECT_NEW, actionName: 'project-new', click: sendMessage, accelerator: 'CmdOrCtrl+N'}, - {label: LOCALE_STRINGS.MENU_FILE_PROJECT_LOAD, actionName: 'project-load', click: sendMessage, accelerator: 'CmdOrCtrl+O'}, - {label: LOCALE_STRINGS.MENU_FILE_PROJECT_LOAD_RECENT, submenu: recentProjects}, - {type: 'separator'}, - {label: LOCALE_STRINGS.MENU_FILE_PROJECT_SAVE, actionName: 'project-save', click: sendMessage, accelerator: 'CmdOrCtrl+S'}, - {label: LOCALE_STRINGS.MENU_FILE_PROJECT_SAVE_AS, actionName: 'project-save-as', click: sendMessage, accelerator: 'CmdOrCtrl+Shift+N'}, - {type: 'separator'}, - {label: LOCALE_STRINGS.MENU_FILE_PREFERENCES_SAVE, actionName: 'preferences-save', click: sendMessage}, - {type: 'separator'}, - {label: LOCALE_STRINGS.MENU_FILE_INSTALL_CLI, click: installCLI}, - {type: 'separator'}, - {label: LOCALE_STRINGS.MENU_FILE_EXIT, actionName: 'quit', click: sendMessage, accelerator: quitAcc} - ] - }); - - template.push({ - label: LOCALE_STRINGS.MENU_ACTIONS, - submenu: [ - {label: LOCALE_STRINGS.MENU_ACTIONS_ADD_IMAGES, actionName: 'action-add-images', click: sendMessage, accelerator: 'Shift+A'}, - {label: LOCALE_STRINGS.MENU_ACTIONS_ADD_FOLDER, actionName: 'action-add-folder', click: sendMessage, accelerator: 'Shift+F'}, - {type: 'separator'}, - {label: LOCALE_STRINGS.MENU_ACTIONS_SELECT_ALL, actionName: 'action-select-all', click: sendMessage, accelerator: 'CmdOrCtrl+A'}, - {label: LOCALE_STRINGS.MENU_ACTIONS_DELETE, actionName: 'action-delete', click: sendMessage, accelerator: 'Delete'}, - {label: LOCALE_STRINGS.MENU_ACTIONS_CLEAR, actionName: 'action-clear', click: sendMessage, accelerator: 'CmdOrCtrl+Shift+C'}, - {type: 'separator'}, - {label: LOCALE_STRINGS.MENU_ACTIONS_EXPORT, actionName: 'action-export', click: sendMessage, accelerator: 'CmdOrCtrl+E'} - ] - }); - - let langs = []; - if(APP_INFO.localizations) { - for (let lang of APP_INFO.localizations) { - langs.push({ - label: LOCALE_STRINGS['LANGUAGE_' + lang], - custom: lang, - checked: CURRENT_LOCALE === lang, - type: 'checkbox', - actionName: 'change-locale', - click: sendMessage - }); - } - } - - template.push({ - label: LOCALE_STRINGS.MENU_LANGUAGE, - submenu: langs - }); - - template.push({ - label: LOCALE_STRINGS.MENU_HELP, - submenu: [ - {label: LOCALE_STRINGS.MENU_HELP_ABOUT, actionName: 'show-about', click: sendMessage, accelerator: 'F1'} - ] - }); - - if(argv.env === 'development') { - template.push({label: 'Dev', submenu: [ - {label: 'Console', click: () => mainWindow.webContents.openDevTools()}, - {label: 'Reload', click: () => mainWindow.webContents.reload()} - ]}); - } - - let menu = Menu.buildFromTemplate(template); - Menu.setApplicationMenu(menu); -} - -function installCLI() { - shell.openExternal('https://github.com/odrick/free-tex-packer-cli'); -} - -function quit() { - CURRENT_PROJECT_M0DIFIED = false; - app.quit(); -} - -function sendMessage(e) { - let payload = null; - if(e.custom) { - payload = {data: e.custom}; - } - - mainWindow.send(e.actionName, payload); -} - -function onProjectUpdated(data=null) { - CURRENT_PROJECT = data ? data.path : ""; - CURRENT_PROJECT_M0DIFIED = false; - updateWindowTitle(); -} - -function onProjectModified(data=null) { - CURRENT_PROJECT_M0DIFIED = data ? data.val : false; - updateWindowTitle(); -} - -function updateWindowTitle() { - if(!APP_INFO.displayName) { - mainWindow.setTitle(""); - return; - } - - let name; - - if(!CURRENT_PROJECT) name = "untitled.ftpp"; - else name = CURRENT_PROJECT.split('/').pop(); - - mainWindow.setTitle((CURRENT_PROJECT_M0DIFIED ? "* " : "") + name + ' - ' + APP_INFO.displayName); -} - -app.on('ready', createWindow); - -app.on('window-all-closed', function () { - if (process.platform !== 'darwin') { - app.quit(); - } -}); - -app.on('activate', function () { - if (mainWindow === null) { - createWindow(); - } -}); - -ipcMain.on('tinify', (e, data) => { - tinify.key = data.key; - tinify.fromBuffer(Buffer.from(data.imageData, 'base64')).toBuffer((err, res) => { - if (err) { - e.sender.send('tinify-complete', { - success: false, - uid: data.uid, - error: err.message - }); - return; - } - - e.sender.send('tinify-complete', { - success: true, - uid: data.uid, - data: res.toString('base64') - }); - }); -}); - -ipcMain.on('update-app-info', (e, data) => { - APP_INFO = data; - buildMenu(); - updateWindowTitle(); -}); - -ipcMain.on('update-locale', (e, data) => { - CURRENT_LOCALE = data.currentLocale; - LOCALE_STRINGS = data.strings; - buildMenu(); -}); - -ipcMain.on('project-recent-update', (e, data) => { - RECENT_PROJECTS = data.projects; - buildMenu(); -}); - -ipcMain.on('project-update', (e, data) => { - onProjectUpdated(data); -}); - -ipcMain.on('project-modified', (e, data) => { - onProjectModified(data); -}); - -ipcMain.on('quit', (e, data) => { - quit(); +const path = require('path'); +const tinify = require('tinify'); +const argv = require('optimist').argv; +const windowStateKeeper = require('electron-window-state'); +const {app, BrowserWindow, ipcMain, Menu, shell} = require('electron'); +const {autoUpdater} = require("electron-updater"); + +let mainWindow; +let RECENT_PROJECTS = []; +let CURRENT_LOCALE = ""; +let LOCALE_STRINGS = {}; +let APP_INFO = {}; +let CURRENT_PROJECT = ""; +let CURRENT_PROJECT_M0DIFIED = false; + +function createWindow() { + let w = 1300; + let h = 680; + + if(process.platform === "win32") { + w = 1320; + h = 720; + } + + let mainWindowState = windowStateKeeper({ + defaultWidth: w, + defaultHeight: h + }); + + mainWindow = new BrowserWindow({ + x: mainWindowState.x, + y: mainWindowState.y, + width: mainWindowState.width, + height: mainWindowState.height, + minWidth: w, + minHeight: h, + title: "", + icon: path.resolve(__dirname, 'www/static/images/icon.png') + }); + + mainWindowState.manage(mainWindow); + + mainWindow.on('page-title-updated', function(e) { + e.preventDefault(); + }); + + if (argv.env === 'development') { + mainWindow.loadURL('http://localhost:4000/'); + } + else { + mainWindow.loadFile('./www/index.html'); + } + + Menu.setApplicationMenu(null); + + mainWindow.on('close', function(e) { + if(CURRENT_PROJECT_M0DIFIED) { + sendMessage({actionName: 'quit'}); + e.preventDefault(); + } + }); + + mainWindow.on('closed', function() { + mainWindow = null; + }); + + mainWindow.webContents.on('will-navigate', handleRedirect); + mainWindow.webContents.on('new-window', handleRedirect); + + mainWindow.webContents.on('did-finish-load', function() { + CURRENT_PROJECT = ""; + CURRENT_PROJECT_M0DIFIED = false; + updateWindowTitle(); + + if(argv.env !== 'development' && process.argv.length > 1) { + sendMessage({actionName: 'project-load', custom: process.argv[1]}); + } + + autoUpdater.checkForUpdates(); + }); + + startAutoUpdater(); + + onProjectUpdated(); +} + +function startAutoUpdater() { + autoUpdater.autoDownload = false; + autoUpdater.autoInstallOnAppQuit = false; + + autoUpdater.on('checking-for-update', () => { + }); + + autoUpdater.on('update-available', (info) => { + mainWindow.send('update-available', info); + }); + + autoUpdater.on('update-not-available', (info) => { + }); + + autoUpdater.on('error', (err) => { + }); + + autoUpdater.on('download-progress', (progressObj) => { + mainWindow.send('download-progress', progressObj.percent); + }); + + autoUpdater.on('update-downloaded', (info) => { + autoUpdater.quitAndInstall(true, true); + }); + + ipcMain.on('install-update', (e, data) => { + autoUpdater.downloadUpdate(); + }); +} + +function handleRedirect(e, url) { + if(url !== mainWindow.getURL()) { + e.preventDefault(); + shell.openExternal(url); + } +} + +function buildMenu() { + let template = []; + + let recentProjects = []; + + if(RECENT_PROJECTS.length) { + for(let path of RECENT_PROJECTS) { + let name = path.split("/").pop(); + recentProjects.push({label: name, actionName: 'project-load', click: sendMessage, custom: path}); + } + } + else { + recentProjects.push({label: "...", enabled: false}); + } + + let quitAcc = "CmdOrCtrl+F4"; + if(process.platform === "darwin") quitAcc = "CmdOrCtrl+Q"; + + template.push({ + label: LOCALE_STRINGS.MENU_FILE, + submenu: [ + {label: LOCALE_STRINGS.MENU_FILE_PROJECT_NEW, actionName: 'project-new', click: sendMessage, accelerator: 'CmdOrCtrl+N'}, + {label: LOCALE_STRINGS.MENU_FILE_PROJECT_LOAD, actionName: 'project-load', click: sendMessage, accelerator: 'CmdOrCtrl+O'}, + {label: LOCALE_STRINGS.MENU_FILE_PROJECT_LOAD_RECENT, submenu: recentProjects}, + {type: 'separator'}, + {label: LOCALE_STRINGS.MENU_FILE_PROJECT_SAVE, actionName: 'project-save', click: sendMessage, accelerator: 'CmdOrCtrl+S'}, + {label: LOCALE_STRINGS.MENU_FILE_PROJECT_SAVE_AS, actionName: 'project-save-as', click: sendMessage, accelerator: 'CmdOrCtrl+Shift+N'}, + {type: 'separator'}, + {label: LOCALE_STRINGS.MENU_FILE_PREFERENCES_SAVE, actionName: 'preferences-save', click: sendMessage}, + {type: 'separator'}, + {label: LOCALE_STRINGS.MENU_FILE_INSTALL_CLI, click: installCLI}, + {type: 'separator'}, + {label: LOCALE_STRINGS.MENU_FILE_EXIT, actionName: 'quit', click: sendMessage, accelerator: quitAcc} + ] + }); + + template.push({ + label: LOCALE_STRINGS.MENU_ACTIONS, + submenu: [ + {label: LOCALE_STRINGS.MENU_ACTIONS_ADD_IMAGES, actionName: 'action-add-images', click: sendMessage, accelerator: 'Shift+A'}, + {label: LOCALE_STRINGS.MENU_ACTIONS_ADD_FOLDER, actionName: 'action-add-folder', click: sendMessage, accelerator: 'Shift+F'}, + {type: 'separator'}, + {label: LOCALE_STRINGS.MENU_ACTIONS_SELECT_ALL, actionName: 'action-select-all', click: sendMessage, accelerator: 'CmdOrCtrl+A'}, + {label: LOCALE_STRINGS.MENU_ACTIONS_DELETE, actionName: 'action-delete', click: sendMessage, accelerator: 'Delete'}, + {label: LOCALE_STRINGS.MENU_ACTIONS_CLEAR, actionName: 'action-clear', click: sendMessage, accelerator: 'CmdOrCtrl+Shift+C'}, + {type: 'separator'}, + {label: LOCALE_STRINGS.MENU_ACTIONS_EXPORT, actionName: 'action-export', click: sendMessage, accelerator: 'CmdOrCtrl+E'} + ] + }); + + let langs = []; + if(APP_INFO.localizations) { + for (let lang of APP_INFO.localizations) { + langs.push({ + label: LOCALE_STRINGS['LANGUAGE_' + lang], + custom: lang, + checked: CURRENT_LOCALE === lang, + type: 'checkbox', + actionName: 'change-locale', + click: sendMessage + }); + } + } + + template.push({ + label: LOCALE_STRINGS.MENU_LANGUAGE, + submenu: langs + }); + + template.push({ + label: LOCALE_STRINGS.MENU_HELP, + submenu: [ + {label: LOCALE_STRINGS.MENU_HELP_ABOUT, actionName: 'show-about', click: sendMessage, accelerator: 'F1'} + ] + }); + + if(argv.env === 'development') { + template.push({label: 'Dev', submenu: [ + {label: 'Console', click: () => mainWindow.webContents.openDevTools()}, + {label: 'Reload', click: () => mainWindow.webContents.reload()} + ]}); + } + + let menu = Menu.buildFromTemplate(template); + Menu.setApplicationMenu(menu); +} + +function installCLI() { + shell.openExternal('https://github.com/odrick/free-tex-packer-cli'); +} + +function quit() { + CURRENT_PROJECT_M0DIFIED = false; + app.quit(); +} + +function sendMessage(e) { + let payload = null; + if(e.custom) { + payload = {data: e.custom}; + } + + mainWindow.send(e.actionName, payload); +} + +function onProjectUpdated(data=null) { + CURRENT_PROJECT = data ? data.path : ""; + CURRENT_PROJECT_M0DIFIED = false; + updateWindowTitle(); +} + +function onProjectModified(data=null) { + CURRENT_PROJECT_M0DIFIED = data ? data.val : false; + updateWindowTitle(); +} + +function updateWindowTitle() { + if(!APP_INFO.displayName) { + mainWindow.setTitle(""); + return; + } + + let name; + + if(!CURRENT_PROJECT) name = "untitled.ftpp"; + else name = CURRENT_PROJECT.split('/').pop(); + + mainWindow.setTitle((CURRENT_PROJECT_M0DIFIED ? "* " : "") + name + ' - ' + APP_INFO.displayName); +} + +app.on('ready', createWindow); + +app.on('window-all-closed', function () { + if (process.platform !== 'darwin') { + app.quit(); + } +}); + +app.on('activate', function () { + if (mainWindow === null) { + createWindow(); + } +}); + +ipcMain.on('tinify', (e, data) => { + tinify.key = data.key; + tinify.fromBuffer(Buffer.from(data.imageData, 'base64')).toBuffer((err, res) => { + if (err) { + e.sender.send('tinify-complete', { + success: false, + uid: data.uid, + error: err.message + }); + return; + } + + e.sender.send('tinify-complete', { + success: true, + uid: data.uid, + data: res.toString('base64') + }); + }); +}); + +ipcMain.on('update-app-info', (e, data) => { + APP_INFO = data; + buildMenu(); + updateWindowTitle(); +}); + +ipcMain.on('update-locale', (e, data) => { + CURRENT_LOCALE = data.currentLocale; + LOCALE_STRINGS = data.strings; + buildMenu(); +}); + +ipcMain.on('project-recent-update', (e, data) => { + RECENT_PROJECTS = data.projects; + buildMenu(); +}); + +ipcMain.on('project-update', (e, data) => { + onProjectUpdated(data); +}); + +ipcMain.on('project-modified', (e, data) => { + onProjectModified(data); +}); + +ipcMain.on('quit', (e, data) => { + quit(); }); \ No newline at end of file diff --git a/electron/package.json b/electron/package.json index b54bedf..aa56094 100644 --- a/electron/package.json +++ b/electron/package.json @@ -1,73 +1,73 @@ -{ - "name": "free-tex-packer", - "version": "0.4.9", - "description": "Free texture packer", - "homepage": "https://github.com/odrick/free-tex-packer", - "author": "Alexander Norinchak ", - "main": "main.js", - "scripts": { - "start": "electron . --env development", - "start-prod": "electron .", - "build": "electron-builder --ia32 --x64", - "build-publish": "electron-builder --ia32 --x64 --publish always" - }, - "build": { - "appId": "com.free.tex.packer", - "productName": "Free texture packer", - "artifactName": "FreeTexturePacker-${arch}.${ext}", - "win": { - "target": [ - "nsis" - ], - "icon": "build/icons/256x256.png" - }, - "linux": { - "target": [ - "deb" - ], - "category": "Graphics", - "executableName": "FreeTexturePacker", - "icon": "build/icons/icons.icns" - }, - "mac": { - "category": "public.app-category.graphics-design", - "target": "dmg", - "icon": "build/icons/icons.icns" - }, - "nsis": { - "oneClick": false, - "allowToChangeInstallationDirectory": true - }, - "directories": { - "output": "dist/app" - }, - "fileAssociations": { - "ext": "ftpp", - "name": "Free texture packer project" - }, - "publish": [ - { - "provider": "github", - "owner": "odrick", - "repo": "free-tex-packer" - } - ] - }, - "repository": { - "type": "git", - "url": "git+https://github.com/odrick/free-tex-packer.git" - }, - "keywords": [], - "license": "ISC", - "devDependencies": { - "electron": "^4.0.4", - "electron-builder": "^20.38.5" - }, - "dependencies": { - "electron-log": "^2.2.17", - "electron-updater": "^4.0.6", - "electron-window-state": "^5.0.3", - "optimist": "^0.6.1", - "tinify": "^1.3.0" - } -} +{ + "name": "free-tex-packer", + "version": "0.5.0", + "description": "Free texture packer", + "homepage": "https://github.com/odrick/free-tex-packer", + "author": "Alexander Norinchak ", + "main": "main.js", + "scripts": { + "start": "electron . --env development", + "start-prod": "electron .", + "build": "electron-builder --ia32 --x64", + "build-publish": "electron-builder --ia32 --x64 --publish always" + }, + "build": { + "appId": "com.free.tex.packer", + "productName": "Free texture packer", + "artifactName": "FreeTexturePacker-${arch}.${ext}", + "win": { + "target": [ + "nsis" + ], + "icon": "build/icons/256x256.png" + }, + "linux": { + "target": [ + "deb" + ], + "category": "Graphics", + "executableName": "FreeTexturePacker", + "icon": "build/icons/icons.icns" + }, + "mac": { + "category": "public.app-category.graphics-design", + "target": "dmg", + "icon": "build/icons/icons.icns" + }, + "nsis": { + "oneClick": false, + "allowToChangeInstallationDirectory": true + }, + "directories": { + "output": "dist/app" + }, + "fileAssociations": { + "ext": "ftpp", + "name": "Free texture packer project" + }, + "publish": [ + { + "provider": "github", + "owner": "odrick", + "repo": "free-tex-packer" + } + ] + }, + "repository": { + "type": "git", + "url": "git+https://github.com/odrick/free-tex-packer.git" + }, + "keywords": [], + "license": "ISC", + "devDependencies": { + "electron": "^4.0.4", + "electron-builder": "^20.38.5" + }, + "dependencies": { + "electron-log": "^2.2.17", + "electron-updater": "^4.0.6", + "electron-window-state": "^5.0.3", + "optimist": "^0.6.1", + "tinify": "^1.3.0" + } +} diff --git a/package.json b/package.json index 51eaac4..dc1c327 100644 --- a/package.json +++ b/package.json @@ -1,70 +1,70 @@ -{ - "name": "free-tex-packer", - "displayName": "Free texture packer", - "version": "0.4.9", - "description": "Free online texture packer", - "url": "http://free-tex-packer.com", - "download": "http://free-tex-packer.com/download", - "webApp": "http://free-tex-packer.com/app", - "main": "src/index.js", - "tinifyUrl": "http://free-tex-packer.com/server/tinify.php", - "localizations": [ - "en", - "ru" - ], - "scripts": { - "start": "webpack-dev-server --host 127.0.0.1 --port 4000", - "start-electron": "webpack-dev-server --host 0.0.0.0 --port 4000 --platform electron", - "build-web": "webpack --build --platform web", - "build-electron": "webpack --build --platform electron" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/odrick/free-tex-packer.git" - }, - "keywords": [ - "texture", - "packer", - "texturepacker", - "texture-packer", - "sprites", - "spritesheet", - "export", - "sprite", - "2d" - ], - "author": "Alexander Norinchak", - "authorSite": "https://github.com/odrick/", - "authorEmail": "norinchak@gmail.com", - "license": "ISC", - "bugs": { - "url": "https://github.com/odrick/free-tex-packer/issues" - }, - "homepage": "https://github.com/odrick/free-tex-packer", - "devDependencies": { - "@babel/core": "^7.2.2", - "@babel/preset-env": "^7.3.1", - "@babel/preset-react": "^7.0.0", - "babel-loader": "^8.0.5", - "babel-plugin-transform-runtime": "^6.23.0", - "babel-polyfill": "^6.23.0", - "babel-preset-es2015": "^6.24.0", - "babel-preset-stage-0": "^6.22.0", - "babel-runtime": "^6.23.0", - "chokidar": "^2.0.4", - "copy-webpack-plugin": "^4.6.0", - "electron": "^4.0.4", - "eventemitter3": "^3.0.1", - "file-saver": "^2.0.0", - "glob": "^7.1.1", - "json-loader": "^0.5.4", - "jszip": "^3.1.3", - "mustache": "^3.0.1", - "optimist": "^0.6.1", - "react": "^16.8.1", - "react-dom": "^16.8.1", - "webpack": "^4.29.3", - "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.1.14" - } -} +{ + "name": "free-tex-packer", + "displayName": "Free texture packer", + "version": "0.5.0", + "description": "Free online texture packer", + "url": "http://free-tex-packer.com", + "download": "http://free-tex-packer.com/download", + "webApp": "http://free-tex-packer.com/app", + "main": "src/index.js", + "tinifyUrl": "http://free-tex-packer.com/server/tinify.php", + "localizations": [ + "en", + "ru" + ], + "scripts": { + "start": "webpack-dev-server --host 127.0.0.1 --port 4000", + "start-electron": "webpack-dev-server --host 0.0.0.0 --port 4000 --platform electron", + "build-web": "webpack --build --platform web", + "build-electron": "webpack --build --platform electron" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/odrick/free-tex-packer.git" + }, + "keywords": [ + "texture", + "packer", + "texturepacker", + "texture-packer", + "sprites", + "spritesheet", + "export", + "sprite", + "2d" + ], + "author": "Alexander Norinchak", + "authorSite": "https://github.com/odrick/", + "authorEmail": "norinchak@gmail.com", + "license": "ISC", + "bugs": { + "url": "https://github.com/odrick/free-tex-packer/issues" + }, + "homepage": "https://github.com/odrick/free-tex-packer", + "devDependencies": { + "@babel/core": "^7.2.2", + "@babel/preset-env": "^7.3.1", + "@babel/preset-react": "^7.0.0", + "babel-loader": "^8.0.5", + "babel-plugin-transform-runtime": "^6.23.0", + "babel-polyfill": "^6.23.0", + "babel-preset-es2015": "^6.24.0", + "babel-preset-stage-0": "^6.22.0", + "babel-runtime": "^6.23.0", + "chokidar": "^2.0.4", + "copy-webpack-plugin": "^4.6.0", + "electron": "^4.0.4", + "eventemitter3": "^3.0.1", + "file-saver": "^2.0.0", + "glob": "^7.1.1", + "json-loader": "^0.5.4", + "jszip": "^3.1.3", + "mustache": "^3.0.1", + "optimist": "^0.6.1", + "react": "^16.8.1", + "react-dom": "^16.8.1", + "webpack": "^4.29.3", + "webpack-cli": "^3.2.3", + "webpack-dev-server": "^3.1.14" + } +} diff --git a/src/client/resources/static/css/index.css b/src/client/resources/static/css/index.css index cb5c186..41623f0 100644 --- a/src/client/resources/static/css/index.css +++ b/src/client/resources/static/css/index.css @@ -30,7 +30,7 @@ html, body { .main-wrapper { height: 100%; - min-width: 1220px; + min-width: 1300px; min-height: 440px; } @@ -121,7 +121,8 @@ html, body { } .images-list { - display: block; + display: block; + width: 380px; height: 100%; float: left; text-align: left; @@ -134,7 +135,7 @@ html, body { .results-view { display: block; height: 100%; - min-width: 600px; + min-width: 640px; } .props-list { @@ -381,7 +382,7 @@ html, body { width: -o-calc(100% - 640px); width: calc(100% - 640px); margin: auto; - box-shadow: 1px 1px 1px 1px rgba(0,0,0,.2); + box-shadow: 1px 0px 0px 0px rgba(0,0,0,.2); } .results-view-container { @@ -395,11 +396,13 @@ html, body { .results-view-footer { display: block; height: 50px; + margin-left: 80px; box-sizing: border-box; padding: 8px; vertical-align: bottom; text-align: center; font-weight: bold; + box-shadow: 0px 1px 0px 0px rgba(0,0,0,.2); } .results-view-footer hr { diff --git a/src/client/resources/static/localization/en.csv b/src/client/resources/static/localization/en.csv index f205142..36f5bd5 100644 --- a/src/client/resources/static/localization/en.csv +++ b/src/client/resources/static/localization/en.csv @@ -17,9 +17,7 @@ IMAGE_DROP_HELP;or drag'n'drop images here CLEAR;Clear CLEAR_TITLE;Clear all images DELETE;Delete -DELETE_ALL;Delete All DELETE_TITLE;Delete selected images -DELETE_ALL_TITLE;Delete all files DISPLAY_OUTLINES;Outlines: SCALE;Scale: SHOW_SPRITES;Show sprites diff --git a/src/client/resources/static/localization/ru.csv b/src/client/resources/static/localization/ru.csv index 41454d3..ff0ee7a 100644 --- a/src/client/resources/static/localization/ru.csv +++ b/src/client/resources/static/localization/ru.csv @@ -17,9 +17,7 @@ IMAGE_DROP_HELP;или перетяните изображения сюда CLEAR;Очистить CLEAR_TITLE;Очистить все изображения DELETE;Удалить -DELETE_ALL;удалить все DELETE_TITLE;Удалить выбранные изображения -DELETE_ALL_TITLE;удалить все изображения DISPLAY_OUTLINES;Контуры: SCALE;Масштаб: SHOW_SPRITES;Показать спрайты diff --git a/src/client/ui/ImagesList.jsx b/src/client/ui/ImagesList.jsx index e3c5762..11d644f 100644 --- a/src/client/ui/ImagesList.jsx +++ b/src/client/ui/ImagesList.jsx @@ -1,563 +1,547 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; - -import LocalImagesLoader from '../utils/LocalImagesLoader'; -import ZipLoader from '../utils/ZipLoader'; -import I18 from '../utils/I18'; - -import {Observer, GLOBAL_EVENT} from '../Observer'; -import ImagesTree from "./ImagesTree.jsx"; - -import FileSystem from 'platform/FileSystem'; - -let INSTANCE = null; - -class ImagesList extends React.Component { - constructor(props) { - super(props); - - INSTANCE = this; - - this.addImages = this.addImages.bind(this); - this.addZip = this.addZip.bind(this); - this.addImagesFs = this.addImagesFs.bind(this); - this.addFolderFs = this.addFolderFs.bind(this); - this.loadImagesComplete = this.loadImagesComplete.bind(this); - this.clear = this.clear.bind(this); - this.deleteSelectedImages = this.deleteSelectedImages.bind(this); - this.deleteAllImages = this.deleteAllImages.bind(this); - this.doClear = this.doClear.bind(this); - this.onFilesDrop = this.onFilesDrop.bind(this); - this.handleImageItemSelected = this.handleImageItemSelected.bind(this); - this.handleImageClearSelection = this.handleImageClearSelection.bind(this); - - Observer.on(GLOBAL_EVENT.IMAGE_ITEM_SELECTED, this.handleImageItemSelected, this); - Observer.on(GLOBAL_EVENT.IMAGE_CLEAR_SELECTION, this.handleImageClearSelection, this); - Observer.on(GLOBAL_EVENT.FS_CHANGES, this.handleFsChanges, this); - - this.handleKeys = this.handleKeys.bind(this); - - window.addEventListener("keydown", this.handleKeys, false); - - this.state = {images: {}}; - } - - static get i() { - return INSTANCE; - } - - componentWillUnmount() { - Observer.off(GLOBAL_EVENT.IMAGE_ITEM_SELECTED, this.handleImageItemSelected, this); - Observer.off(GLOBAL_EVENT.IMAGE_CLEAR_SELECTION, this.handleImageClearSelection, this); - Observer.off(GLOBAL_EVENT.FS_CHANGES, this.handleFsChanges, this); - - window.removeEventListener("keydown", this.handleKeys, false); - } - - handleKeys(e) { - if(e) { - let key = e.keyCode || e.which; - if(key === 65 && e.ctrlKey) this.selectAllImages(); - } - } - - componentDidMount() { - let dropZone = ReactDOM.findDOMNode(this.refs.imagesTree); - if(dropZone) { - dropZone.ondrop = this.onFilesDrop; - - dropZone.ondragover = () => { - let help = ReactDOM.findDOMNode(this.refs.dropHelp); - if(help) help.className = "image-drop-help selected"; - return false; - }; - - dropZone.ondragleave = () => { - let help = ReactDOM.findDOMNode(this.refs.dropHelp); - if(help) help.className = "image-drop-help"; - return false; - }; - } - } - - setImages(images) { - this.setState({images: images}); - Observer.emit(GLOBAL_EVENT.IMAGES_LIST_CHANGED, images); - } - - onFilesDrop(e) { - e.preventDefault(); - - if(e.dataTransfer.files.length) { - let loader = new LocalImagesLoader(); - loader.load(e.dataTransfer.files, null, data => this.loadImagesComplete(data)); - } - - return false; - } - - addImages(e) { - if(e.target.files.length) { - Observer.emit(GLOBAL_EVENT.SHOW_SHADER); - - let loader = new LocalImagesLoader(); - loader.load(e.target.files, null, data => this.loadImagesComplete(data)); - } - } - - addZip(e) { - let file = e.target.files[0]; - if(file) { - Observer.emit(GLOBAL_EVENT.SHOW_SHADER); - - let loader = new ZipLoader(); - loader.load(file, null, data => this.loadImagesComplete(data)); - } - } - - addImagesFs() { - Observer.emit(GLOBAL_EVENT.SHOW_SHADER); - FileSystem.addImages(this.loadImagesComplete); - } - - addFolderFs() { - Observer.emit(GLOBAL_EVENT.SHOW_SHADER); - FileSystem.addFolder(this.loadImagesComplete); - } - - handleFsChanges(data) { - let image = null; - let images = this.state.images; - let imageKey = ""; - - let keys = Object.keys(images); - for(let key of keys) { - let item = images[key]; - if(item.fsPath.path === data.path) { - image = item; - imageKey = key; - break; - } - } - - if(data.event === "unlink" && image) { - delete images[imageKey]; - this.setState({images: images}); - Observer.emit(GLOBAL_EVENT.IMAGES_LIST_CHANGED, images); - } - - if(data.event === "add" || data.event === "change") { - let folder = ""; - let addPath = ""; - - for(let key of keys) { - let item = images[key]; - - if(item.fsPath.folder && data.path.substr(0, item.fsPath.folder.length) === item.fsPath.folder) { - folder = item.fsPath.folder; - addPath = folder.split("/").pop(); - } - } - - let name = ""; - if(folder) { - name = addPath + data.path.substr(folder.length); - } - else { - name = data.path.split("/").pop(); - } - - FileSystem.loadImages([{name: name, path: data.path, folder: folder}], this.loadImagesComplete); - } - } - - loadImagesComplete(data=[]) { - - Observer.emit(GLOBAL_EVENT.HIDE_SHADER); - - if(PLATFORM === "web") { - ReactDOM.findDOMNode(this.refs.addImagesInput).value = ""; - ReactDOM.findDOMNode(this.refs.addZipInput).value = ""; - } - - let names = Object.keys(data); - - if(names.length) { - let images = this.state.images; - - for (let name of names) { - images[name] = data[name]; - } - - images = this.sortImages(images); - - this.setState({images: images}); - Observer.emit(GLOBAL_EVENT.IMAGES_LIST_CHANGED, images); - } - } - - sortImages(images) { - let names = Object.keys(images); - names.sort(); - - let sorted = {}; - - for(let name of names) { - sorted[name] = images[name]; - } - - return sorted; - } - - clear() { - let keys = Object.keys(this.state.images); - if(keys.length) { - let buttons = { - "yes": {caption: I18.f("YES"), callback: this.doClear}, - "no": {caption: I18.f("NO")} - }; - - Observer.emit(GLOBAL_EVENT.SHOW_MESSAGE, I18.f("CLEAR_WARNING"), buttons); - } - } - - doClear() { - Observer.emit(GLOBAL_EVENT.IMAGES_LIST_CHANGED, {}); - Observer.emit(GLOBAL_EVENT.IMAGES_LIST_SELECTED_CHANGED, []); - this.setState({images: {}}); - } - - selectAllImages() { - let images = this.state.images; - for(let key in images) { - images[key].selected = true; - } - - this.setState({images: this.state.images}); - this.emitSelectedChanges(); - } - - removeImagesSelect() { - let images = this.state.images; - for(let key in images) { - images[key].selected = false; - } - } - - getCurrentImage() { - let images = this.state.images; - for(let key in images) { - if(images[key].current) return images[key]; - } - - return null; - } - - getImageIx(image) { - let ix = 0; - - let images = this.state.images; - for(let key in images) { - if(images[key] === image) return ix; - ix++; - } - - return -1; - } - - bulkSelectImages(to) { - let current = this.getCurrentImage(); - if(!current) { - to.selected = true; - return; - } - - let fromIx = this.getImageIx(current); - let toIx = this.getImageIx(to); - - let images = this.state.images; - let ix = 0; - for(let key in images) { - if(fromIx < toIx && ix >= fromIx && ix <= toIx) images[key].selected = true; - if(fromIx > toIx && ix <= fromIx && ix >= toIx) images[key].selected = true; - ix++; - } - } - - selectImagesFolder(path, selected) { - let images = this.state.images; - - let first = false; - for(let key in images) { - if(key.substr(0, path.length + 1) === path + "/") { - if(!first) { - first = true; - this.clearCurrentImage(); - images[key].current = true; - } - images[key].selected = selected; - } - } - } - - clearCurrentImage() { - let images = this.state.images; - for(let key in images) { - images[key].current = false; - } - } - - getFirstImageInFolder(path) { - let images = this.state.images; - - for(let key in images) { - if (key.substr(0, path.length + 1) === path + "/") return images[key]; - } - - return null; - } - - getLastImageInFolder(path) { - let images = this.state.images; - - let ret = null; - for(let key in images) { - if (key.substr(0, path.length + 1) === path + "/") ret = images[key]; - } - - return ret; - } - - handleImageItemSelected(e) { - let path = e.path; - let images = this.state.images; - - if(e.isFolder) { - if(e.ctrlKey) { - this.selectImagesFolder(path, true); - } - else if(e.shiftKey) { - let to = this.getLastImageInFolder(path); - if(to) this.bulkSelectImages(to); - - to = this.getFirstImageInFolder(path); - if(to) { - this.bulkSelectImages(to); - this.clearCurrentImage(); - to.current = true; - } - } - else { - this.removeImagesSelect(); - this.selectImagesFolder(path, true); - } - } - else { - let image = images[path]; - if(image) { - if(e.ctrlKey) { - image.selected = !image.selected; - } - else if(e.shiftKey) { - this.bulkSelectImages(image); - } - else { - this.removeImagesSelect(); - image.selected = true; - } - - this.clearCurrentImage(); - image.current = true; - } - } - - this.setState({images: images}); - - this.emitSelectedChanges(); - } - - handleImageClearSelection() { - this.removeImagesSelect(); - this.clearCurrentImage(); - this.setState({images: this.state.images}); - this.emitSelectedChanges(); - } - - emitSelectedChanges() { - let selected = []; - - let images = this.state.images; - - for(let key in images) { - if(images[key].selected) selected.push(key); - } - - Observer.emit(GLOBAL_EVENT.IMAGES_LIST_SELECTED_CHANGED, selected); - } - - createImagesFolder(name="", path="") { - return { - isFolder: true, - selected: false, - name: name, - path: path, - items: [] - }; - } - - getImageSubFolder(root, parts) { - parts = parts.slice(); - - let folder = null; - - while(parts.length) { - let name = parts.shift(); - - folder = null; - - for (let item of root.items) { - if (item.isFolder && item.name === name) { - folder = item; - break; - } - } - - if (!folder) { - let p = []; - if(root.path) p.unshift(root.path); - p.push(name); - - folder = this.createImagesFolder(name, p.join("/")); - root.items.push(folder); - } - - root = folder; - } - - return folder || root; - } - - getImagesTree() { - let res = this.createImagesFolder(); - - let keys = Object.keys(this.state.images); - - for(let key of keys) { - let parts = key.split("/"); - let name = parts.pop(); - let folder = this.getImageSubFolder(res, parts); - - folder.items.push({ - img: this.state.images[key], - path: key, - name: name - }); - - if(this.state.images[key].selected) folder.selected = true; - } - - return res; - } - - deleteSelectedImages() { - let images = this.state.images; - - let deletedCount = 0; - - let keys = Object.keys(images); - for(let key of keys) { - if(images[key].selected) { - deletedCount++; - delete images[key]; - } - } - - if(deletedCount > 0) { - images = this.sortImages(images); - - this.setState({images: images}); - Observer.emit(GLOBAL_EVENT.IMAGES_LIST_CHANGED, images); - } - } - - deleteAllImages() - { - let images = this.state.images; - - let keys = Object.keys(images); - for(let key of keys) - { - delete images[key]; - } - - this.state = {images: {}}; - this.setState({images: images}); - Observer.emit(GLOBAL_EVENT.IMAGES_LIST_CHANGED, images); - } - - renderWebButtons() { - return ( - -
- {I18.f("ADD_IMAGES")} - -
- -
- {I18.f("ADD_ZIP")} - -
-
- ); - } - - renderElectronButtons() { - return ( - -
- {I18.f("ADD_IMAGES")} -
- -
- {I18.f("ADD_FOLDER")} -
-
- ); - } - - render() { - let data = this.getImagesTree(this.state.images); - - let dropHelp = Object.keys(this.state.images).length > 0 ? null : (
{I18.f("IMAGE_DROP_HELP")}
); - - return ( -
- -
- - { - PLATFORM === "web" ? (this.renderWebButtons()) : (this.renderElectronButtons()) - } - -
- {I18.f("DELETE")} -
-
- {I18.f("DELETE_ALL")} -
- -
- -
- -
- - {dropHelp} -
- -
- ); - } -} - +import React from 'react'; +import ReactDOM from 'react-dom'; + +import LocalImagesLoader from '../utils/LocalImagesLoader'; +import ZipLoader from '../utils/ZipLoader'; +import I18 from '../utils/I18'; + +import {Observer, GLOBAL_EVENT} from '../Observer'; +import ImagesTree from "./ImagesTree.jsx"; + +import FileSystem from 'platform/FileSystem'; + +let INSTANCE = null; + +class ImagesList extends React.Component { + constructor(props) { + super(props); + + INSTANCE = this; + + this.addImages = this.addImages.bind(this); + this.addZip = this.addZip.bind(this); + this.addImagesFs = this.addImagesFs.bind(this); + this.addFolderFs = this.addFolderFs.bind(this); + this.loadImagesComplete = this.loadImagesComplete.bind(this); + this.clear = this.clear.bind(this); + this.deleteSelectedImages = this.deleteSelectedImages.bind(this); + this.doClear = this.doClear.bind(this); + this.onFilesDrop = this.onFilesDrop.bind(this); + this.handleImageItemSelected = this.handleImageItemSelected.bind(this); + this.handleImageClearSelection = this.handleImageClearSelection.bind(this); + + Observer.on(GLOBAL_EVENT.IMAGE_ITEM_SELECTED, this.handleImageItemSelected, this); + Observer.on(GLOBAL_EVENT.IMAGE_CLEAR_SELECTION, this.handleImageClearSelection, this); + Observer.on(GLOBAL_EVENT.FS_CHANGES, this.handleFsChanges, this); + + this.handleKeys = this.handleKeys.bind(this); + + window.addEventListener("keydown", this.handleKeys, false); + + this.state = {images: {}}; + } + + static get i() { + return INSTANCE; + } + + componentWillUnmount() { + Observer.off(GLOBAL_EVENT.IMAGE_ITEM_SELECTED, this.handleImageItemSelected, this); + Observer.off(GLOBAL_EVENT.IMAGE_CLEAR_SELECTION, this.handleImageClearSelection, this); + Observer.off(GLOBAL_EVENT.FS_CHANGES, this.handleFsChanges, this); + + window.removeEventListener("keydown", this.handleKeys, false); + } + + handleKeys(e) { + if(e) { + let key = e.keyCode || e.which; + if(key === 65 && e.ctrlKey) this.selectAllImages(); + } + } + + componentDidMount() { + let dropZone = ReactDOM.findDOMNode(this.refs.imagesTree); + if(dropZone) { + dropZone.ondrop = this.onFilesDrop; + + dropZone.ondragover = () => { + let help = ReactDOM.findDOMNode(this.refs.dropHelp); + if(help) help.className = "image-drop-help selected"; + return false; + }; + + dropZone.ondragleave = () => { + let help = ReactDOM.findDOMNode(this.refs.dropHelp); + if(help) help.className = "image-drop-help"; + return false; + }; + } + } + + setImages(images) { + this.setState({images: images}); + Observer.emit(GLOBAL_EVENT.IMAGES_LIST_CHANGED, images); + } + + onFilesDrop(e) { + e.preventDefault(); + + if(e.dataTransfer.files.length) { + let loader = new LocalImagesLoader(); + loader.load(e.dataTransfer.files, null, data => this.loadImagesComplete(data)); + } + + return false; + } + + addImages(e) { + if(e.target.files.length) { + Observer.emit(GLOBAL_EVENT.SHOW_SHADER); + + let loader = new LocalImagesLoader(); + loader.load(e.target.files, null, data => this.loadImagesComplete(data)); + } + } + + addZip(e) { + let file = e.target.files[0]; + if(file) { + Observer.emit(GLOBAL_EVENT.SHOW_SHADER); + + let loader = new ZipLoader(); + loader.load(file, null, data => this.loadImagesComplete(data)); + } + } + + addImagesFs() { + Observer.emit(GLOBAL_EVENT.SHOW_SHADER); + FileSystem.addImages(this.loadImagesComplete); + } + + addFolderFs() { + Observer.emit(GLOBAL_EVENT.SHOW_SHADER); + FileSystem.addFolder(this.loadImagesComplete); + } + + handleFsChanges(data) { + let image = null; + let images = this.state.images; + let imageKey = ""; + + let keys = Object.keys(images); + for(let key of keys) { + let item = images[key]; + if(item.fsPath.path === data.path) { + image = item; + imageKey = key; + break; + } + } + + if(data.event === "unlink" && image) { + delete images[imageKey]; + this.setState({images: images}); + Observer.emit(GLOBAL_EVENT.IMAGES_LIST_CHANGED, images); + } + + if(data.event === "add" || data.event === "change") { + let folder = ""; + let addPath = ""; + + for(let key of keys) { + let item = images[key]; + + if(item.fsPath.folder && data.path.substr(0, item.fsPath.folder.length) === item.fsPath.folder) { + folder = item.fsPath.folder; + addPath = folder.split("/").pop(); + } + } + + let name = ""; + if(folder) { + name = addPath + data.path.substr(folder.length); + } + else { + name = data.path.split("/").pop(); + } + + FileSystem.loadImages([{name: name, path: data.path, folder: folder}], this.loadImagesComplete); + } + } + + loadImagesComplete(data=[]) { + + Observer.emit(GLOBAL_EVENT.HIDE_SHADER); + + if(PLATFORM === "web") { + ReactDOM.findDOMNode(this.refs.addImagesInput).value = ""; + ReactDOM.findDOMNode(this.refs.addZipInput).value = ""; + } + + let names = Object.keys(data); + + if(names.length) { + let images = this.state.images; + + for (let name of names) { + images[name] = data[name]; + } + + images = this.sortImages(images); + + this.setState({images: images}); + Observer.emit(GLOBAL_EVENT.IMAGES_LIST_CHANGED, images); + } + } + + sortImages(images) { + let names = Object.keys(images); + names.sort(); + + let sorted = {}; + + for(let name of names) { + sorted[name] = images[name]; + } + + return sorted; + } + + clear() { + let keys = Object.keys(this.state.images); + if(keys.length) { + let buttons = { + "yes": {caption: I18.f("YES"), callback: this.doClear}, + "no": {caption: I18.f("NO")} + }; + + Observer.emit(GLOBAL_EVENT.SHOW_MESSAGE, I18.f("CLEAR_WARNING"), buttons); + } + } + + doClear() { + Observer.emit(GLOBAL_EVENT.IMAGES_LIST_CHANGED, {}); + Observer.emit(GLOBAL_EVENT.IMAGES_LIST_SELECTED_CHANGED, []); + this.setState({images: {}}); + } + + selectAllImages() { + let images = this.state.images; + for(let key in images) { + images[key].selected = true; + } + + this.setState({images: this.state.images}); + this.emitSelectedChanges(); + } + + removeImagesSelect() { + let images = this.state.images; + for(let key in images) { + images[key].selected = false; + } + } + + getCurrentImage() { + let images = this.state.images; + for(let key in images) { + if(images[key].current) return images[key]; + } + + return null; + } + + getImageIx(image) { + let ix = 0; + + let images = this.state.images; + for(let key in images) { + if(images[key] === image) return ix; + ix++; + } + + return -1; + } + + bulkSelectImages(to) { + let current = this.getCurrentImage(); + if(!current) { + to.selected = true; + return; + } + + let fromIx = this.getImageIx(current); + let toIx = this.getImageIx(to); + + let images = this.state.images; + let ix = 0; + for(let key in images) { + if(fromIx < toIx && ix >= fromIx && ix <= toIx) images[key].selected = true; + if(fromIx > toIx && ix <= fromIx && ix >= toIx) images[key].selected = true; + ix++; + } + } + + selectImagesFolder(path, selected) { + let images = this.state.images; + + let first = false; + for(let key in images) { + if(key.substr(0, path.length + 1) === path + "/") { + if(!first) { + first = true; + this.clearCurrentImage(); + images[key].current = true; + } + images[key].selected = selected; + } + } + } + + clearCurrentImage() { + let images = this.state.images; + for(let key in images) { + images[key].current = false; + } + } + + getFirstImageInFolder(path) { + let images = this.state.images; + + for(let key in images) { + if (key.substr(0, path.length + 1) === path + "/") return images[key]; + } + + return null; + } + + getLastImageInFolder(path) { + let images = this.state.images; + + let ret = null; + for(let key in images) { + if (key.substr(0, path.length + 1) === path + "/") ret = images[key]; + } + + return ret; + } + + handleImageItemSelected(e) { + let path = e.path; + let images = this.state.images; + + if(e.isFolder) { + if(e.ctrlKey) { + this.selectImagesFolder(path, true); + } + else if(e.shiftKey) { + let to = this.getLastImageInFolder(path); + if(to) this.bulkSelectImages(to); + + to = this.getFirstImageInFolder(path); + if(to) { + this.bulkSelectImages(to); + this.clearCurrentImage(); + to.current = true; + } + } + else { + this.removeImagesSelect(); + this.selectImagesFolder(path, true); + } + } + else { + let image = images[path]; + if(image) { + if(e.ctrlKey) { + image.selected = !image.selected; + } + else if(e.shiftKey) { + this.bulkSelectImages(image); + } + else { + this.removeImagesSelect(); + image.selected = true; + } + + this.clearCurrentImage(); + image.current = true; + } + } + + this.setState({images: images}); + + this.emitSelectedChanges(); + } + + handleImageClearSelection() { + this.removeImagesSelect(); + this.clearCurrentImage(); + this.setState({images: this.state.images}); + this.emitSelectedChanges(); + } + + emitSelectedChanges() { + let selected = []; + + let images = this.state.images; + + for(let key in images) { + if(images[key].selected) selected.push(key); + } + + Observer.emit(GLOBAL_EVENT.IMAGES_LIST_SELECTED_CHANGED, selected); + } + + createImagesFolder(name="", path="") { + return { + isFolder: true, + selected: false, + name: name, + path: path, + items: [] + }; + } + + getImageSubFolder(root, parts) { + parts = parts.slice(); + + let folder = null; + + while(parts.length) { + let name = parts.shift(); + + folder = null; + + for (let item of root.items) { + if (item.isFolder && item.name === name) { + folder = item; + break; + } + } + + if (!folder) { + let p = []; + if(root.path) p.unshift(root.path); + p.push(name); + + folder = this.createImagesFolder(name, p.join("/")); + root.items.push(folder); + } + + root = folder; + } + + return folder || root; + } + + getImagesTree() { + let res = this.createImagesFolder(); + + let keys = Object.keys(this.state.images); + + for(let key of keys) { + let parts = key.split("/"); + let name = parts.pop(); + let folder = this.getImageSubFolder(res, parts); + + folder.items.push({ + img: this.state.images[key], + path: key, + name: name + }); + + if(this.state.images[key].selected) folder.selected = true; + } + + return res; + } + + deleteSelectedImages() { + let images = this.state.images; + + let deletedCount = 0; + + let keys = Object.keys(images); + for(let key of keys) { + if(images[key].selected) { + deletedCount++; + delete images[key]; + } + } + + if(deletedCount > 0) { + images = this.sortImages(images); + + this.setState({images: images}); + Observer.emit(GLOBAL_EVENT.IMAGES_LIST_CHANGED, images); + } + } + + renderWebButtons() { + return ( + +
+ {I18.f("ADD_IMAGES")} + +
+ +
+ {I18.f("ADD_ZIP")} + +
+
+ ); + } + + renderElectronButtons() { + return ( + +
+ {I18.f("ADD_IMAGES")} +
+ +
+ {I18.f("ADD_FOLDER")} +
+
+ ); + } + + render() { + let data = this.getImagesTree(this.state.images); + + let dropHelp = Object.keys(this.state.images).length > 0 ? null : (
{I18.f("IMAGE_DROP_HELP")}
); + + return ( +
+ +
+ + { + PLATFORM === "web" ? (this.renderWebButtons()) : (this.renderElectronButtons()) + } + +
+ {I18.f("DELETE")} +
+
+ {I18.f("CLEAR")} +
+ +
+ +
+ +
+ + {dropHelp} +
+ +
+ ); + } +} + export default ImagesList; \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 083915c..9070cf2 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,121 +1,90 @@ -const path = require('path'); -const webpack = require('webpack'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const argv = require('optimist').argv; - -let entry = [ - 'babel-polyfill', - './src/client/index' -]; - -let plugins = []; - -let devtool = 'eval-source-map'; -let output = 'static/js/index.js'; -let debug = true; - -let PLATFORM = argv.platform || 'web'; -let NODE_ENV = argv.build ? 'production': 'development'; - -let target = 'web'; -if(PLATFORM === 'electron') target = 'electron-renderer'; - -plugins.push(new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify(NODE_ENV), - 'PLATFORM': JSON.stringify(PLATFORM) -})); - -if (argv.build) -{ - let outputDir; - - if (PLATFORM === 'web') - { - outputDir = 'web/'; - } - - if (PLATFORM === 'electron') - { - outputDir = '../electron/www/'; - } - - plugins.push(new CopyWebpackPlugin([{from: 'src/client/resources', to: outputDir}])); - - devtool = false; - output = outputDir + 'static/js/index.js'; - debug = false; -} -else -{ - entry.push('webpack-dev-server/client?http://localhost:4000'); - plugins.push(new CopyWebpackPlugin([{from: 'src/client/resources', to: './'}])); -} - -let config = -{ - entry: entry, - output: - { - path: __dirname + "/dist", - filename: output - }, - devtool: devtool, - target: target, - module: - { - noParse: /.*[\/\\]bin[\/\\].+\.js/, - rules: - [ - { - test: /.jsx?$/, - include: [ path.resolve(__dirname, 'src') ], - use: - [ - { - loader: 'babel-loader', - options: - { - presets: ['@babel/preset-react', '@babel/preset-env'] - } - } - ] - }, - { - test: /\.js$/, - include: [ path.resolve(__dirname, 'src') ], - use: - [ - { - loader: 'babel-loader', - options: - { - presets: ['@babel/preset-env'] - } - } - ] - }, - { - test: /\.(html|htm)$/, - use: - [ - { - loader: 'dom' - } - ] - } - ] - }, - optimization: { - minimize: false - }, - plugins: plugins -}; - -if(target === 'electron-renderer') { - config.resolve = {alias: {'platform': path.resolve(__dirname, './src/client/platform/electron')}}; -} -else { - config.resolve = {alias: {'platform': path.resolve(__dirname, './src/client/platform/web')}}; -} - +const path = require('path'); +const webpack = require('webpack'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const argv = require('optimist').argv; + +let entry = [ + 'babel-polyfill', + './src/client/index' +]; + +let plugins = []; + +let devtool = 'eval-source-map'; +let output = 'static/js/index.js'; +let debug = true; + +let PLATFORM = argv.platform || 'web'; +let NODE_ENV = argv.build ? 'production' : 'development'; + +let target = 'web'; +if (PLATFORM === 'electron') target = 'electron-renderer'; + +plugins.push(new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify(NODE_ENV), + 'PLATFORM': JSON.stringify(PLATFORM) +})); + +if (argv.build) { + let outputDir; + + if (PLATFORM === 'web') { + outputDir = 'web/'; + } + + if (PLATFORM === 'electron') { + outputDir = '../electron/www/'; + } + + plugins.push(new CopyWebpackPlugin([{from: 'src/client/resources', to: outputDir}])); + + devtool = false; + output = outputDir + 'static/js/index.js'; + debug = false; +} +else { + entry.push('webpack-dev-server/client?http://localhost:4000'); + plugins.push(new CopyWebpackPlugin([{from: 'src/client/resources', to: './'}])); +} + +let config = { + entry: entry, + output: { + path: __dirname + "/dist", + filename: output + }, + devtool: devtool, + target: target, + mode: NODE_ENV, + module: { + noParse: /.*[\/\\]bin[\/\\].+\.js/, + rules: [ + { + test: /.jsx?$/, + include: [path.resolve(__dirname, 'src')], + use: [{loader: 'babel-loader', options: {presets: ['@babel/preset-react', '@babel/preset-env']}}] + }, + { + test: /\.js$/, + include: [path.resolve(__dirname, 'src')], + use: [{loader: 'babel-loader', options: {presets: ['@babel/preset-env']}}] + }, + { + test: /\.(html|htm)$/, + use: [{loader: 'dom'}] + } + ] + }, + optimization: { + minimize: false + }, + plugins: plugins +}; + +if (target === 'electron-renderer') { + config.resolve = {alias: {'platform': path.resolve(__dirname, './src/client/platform/electron')}}; +} else { + config.resolve = {alias: {'platform': path.resolve(__dirname, './src/client/platform/web')}}; +} + module.exports = config; \ No newline at end of file