diff --git a/.electron-vue/webpack.main.config.js b/.electron-vue/webpack.main.config.js
index 7a657ea..bfb9755 100644
--- a/.electron-vue/webpack.main.config.js
+++ b/.electron-vue/webpack.main.config.js
@@ -78,7 +78,6 @@ else if (version.indexOf('alpha') >= 0 ) channel = 'alpha'
mainConfig.plugins.push(
new webpack.DefinePlugin({
- 'AI2HTML_HASH': `"${crypto.createHash('sha1').update(fs.readFileSync(path.join(__dirname, '../static/ai2html.js'))).digest('hex')}"`,
'AUTOUPDATE_CHANNEL': `"${channel}"`
})
)
diff --git a/src/lib/index.js b/src/lib/index.js
index bb8fc20..83e31c4 100644
--- a/src/lib/index.js
+++ b/src/lib/index.js
@@ -102,3 +102,7 @@ export function streamCopyFile(src, dest) {
)
})
}
+
+export function settingsLabel() {
+ return process.platform === 'darwin' ? 'Preferences' : 'Settings'
+}
diff --git a/src/main/actions.js b/src/main/actions.js
index 6a68f63..7d2f962 100644
--- a/src/main/actions.js
+++ b/src/main/actions.js
@@ -4,13 +4,15 @@ import path from 'path'
import rmrf from 'rimraf'
import fs from 'fs'
import { slugify } from 'underscore.string'
+import yaml from 'js-yaml'
import { dispatch, resetState } from './ipc'
import state from './index'
import { install } from './install_ai_plugin'
import { run } from './workers'
-import { error } from './dialogs'
+import { error, alert, confirm } from './dialogs'
import storage from './storage'
+import defaultData from './default_data'
import { expandHomeDir, compactHomeDir } from '../lib'
import renderEmbedCode from '../lib/embed_code'
@@ -237,7 +239,7 @@ export function editSettings() {
: `file://${__dirname}/index.html#settings`
const winWidth = 520
- const winHeight = 630
+ const winHeight = 512
state.settingsWindow = new BrowserWindow({
//parent: state.mainWindow,
@@ -291,3 +293,49 @@ export function clearState() {
})
})
}
+
+export function resetSettings() {
+ confirm({
+ parentWin: state.settingsWindow,
+ message: 'Do you wish to reset and clear your settings?',
+ confirmLabel: 'Reset settings'
+ }).then(() => {
+ state.installedAi2htmlHash = null
+ state.newAi2htmlHash = null
+ dispatch('resetSettings', defaultData.Settings)
+ })
+}
+
+const ALLOWED_KEYS = [
+ 'deployBaseUrl', 'deployType',
+ 'awsBucket', 'awsPrefix', 'awsRegion', 'awsAccessKeyId', 'awsSecretAccessKey',
+ 'siteConfigName', 'extraPreviewCss', 'extraEmbedCss', 'ai2htmlFonts'
+]
+
+export function importSettings() {
+ dialog.showOpenDialog( state.settingsWindow, {
+ message: 'Select a config file to load.',
+ filters: [{name: 'Viz Config', extensions: ['vizappconfig']}],
+ properties: [ 'openFile' ]
+ }, (filePaths) => {
+ if (!filePaths || filePaths.length === 0) return;
+
+ const configFile = filePaths[0]
+ const configContent = fs.readFileSync(configFile, 'utf8')
+ const data = yaml.safeLoad(configContent)
+ const configVersion = data.version || 1
+
+ if ( configVersion != 1 ) {
+ error({
+ parentWin: state.settingsWindow,
+ message: 'This config file is for a different version of the app.'
+ })
+ } else {
+ const newSettings = {}
+ for ( const k of ALLOWED_KEYS ) {
+ if ( k in data && data[k] ) newSettings[k] = data[k]
+ }
+ dispatch('updateSettings', newSettings)
+ }
+ })
+}
diff --git a/src/main/default_data.js b/src/main/default_data.js
index 8f3cbe7..743ea57 100644
--- a/src/main/default_data.js
+++ b/src/main/default_data.js
@@ -15,7 +15,10 @@ const data = {
"awsPrefix": null,
"awsRegion": 'us-east-1',
"awsAccessKeyId": null,
- "awsSecretAccessKey": null
+ "awsSecretAccessKey": null,
+ "extraPreviewCss": null,
+ "extraEmbedCss": null,
+ "ai2htmlFonts": null
}
}
diff --git a/src/main/install_ai_plugin.js b/src/main/install_ai_plugin.js
index 8a6a6da..32d164d 100644
--- a/src/main/install_ai_plugin.js
+++ b/src/main/install_ai_plugin.js
@@ -5,7 +5,7 @@ import state from './index'
import crypto from 'crypto'
import { dispatch } from './ipc'
import { alert, confirm, error, chooseFolder } from './dialogs'
-import { streamCopyFile } from '../lib'
+import { render } from '../lib'
const PATHS = {
'darwin': [
@@ -20,6 +20,8 @@ const PATHS = {
],
}
+const HASH_ALGO = 'sha1'
+
let DEFAULT_PROGRAMS_DIR = null
let SCRIPTS_DIR = null
if ( process.platform === 'darwin' ) {
@@ -60,18 +62,25 @@ function findScriptsPath(appPath) {
console.error("Can't find Adobe Illustrator scripts folder. Looked here: ", scriptsPath)
return Promise.reject(new Error('Adobe Illustrator Scripts folder is missing.'))
}
+
return Promise.resolve(scriptsPath)
}
+function renderAi2htmlScript() {
+ return render('ai2html.js.ejs', {settings: state.data.Settings})
+}
+
function copyScript(scriptsPath) {
- const src = path.join(state.staticPath, 'ai2html.js')
+ const output = renderAi2htmlScript()
const dest = path.join(scriptsPath, 'ai2html.js')
- return streamCopyFile(src, dest).then(() => scriptsPath)
+ fs.writeFileSync(dest, output)
+ return Promise.resolve(scriptsPath)
}
-function calcHash(filename, type='sha1') {
+function calcHash(filename) {
return new Promise((resolve, reject) => {
- const hash = crypto.createHash(type)
+ if ( !fs.existsSync(filename) ) return reject(`File not found ${filename}`)
+ const hash = crypto.createHash(HASH_ALGO)
hash.on('readable', () => {
const data = hash.read()
if ( data ) resolve(data.toString('hex'))
@@ -81,72 +90,86 @@ function calcHash(filename, type='sha1') {
})
}
-function isInstalled() {
+export function calcInstalledHash() {
const installPath = state.data.Settings.scriptInstallPath
- return Promise.resolve(installPath && fs.existsSync(installPath))
+ if ( !installPath || !fs.existsSync(installPath) ) return null
+ const scriptPath = path.join(installPath, 'ai2html.js')
+ if ( !fs.existsSync(scriptPath) ) return null
+ const hash = crypto.createHash(HASH_ALGO)
+ hash.update(fs.readFileSync(scriptPath, 'utf8'))
+ return hash.digest('hex')
}
-function isUpdated() {
- const installPath = state.data.Settings.scriptInstallPath
- if ( ! fs.existsSync(installPath) ) return Promise.resolve(null)
- return calcHash(installPath).then((installedHash) => AI2HTML_HASH === installedHash)
+export function calcNewHash() {
+ const hash = crypto.createHash(HASH_ALGO)
+ hash.update(renderAi2htmlScript())
+ return hash.digest('hex')
}
export function install({parentWin = null, forceInstall = false} = {}) {
const startupCheck = state.data.Settings.disableAi2htmlStartupCheck
const installPath = state.data.Settings.scriptInstallPath
- Promise.all([isInstalled(), isUpdated()])
- .then(([installed, updated]) => {
- let verb
- if(!installed) verb = 'Install'
- else if (installed && !updated) verb = 'Update'
- else if (forceInstall) verb = 'Reinstall'
- else return;
-
- dialog.showMessageBox(parentWin, {
- type: 'question',
- title: `${verb} ai2html`,
- message: `Would you like to ${verb.toLowerCase()} ai2html?`,
- defaultId: 1,
- buttons: ['No', `${verb} ai2html`],
- checkboxLabel: "Always check on startup",
- checkboxChecked: !startupCheck,
- }, (res, checkboxChecked) => {
- dispatch('set', {key: 'disableAi2htmlStartupCheck', val: !checkboxChecked})
-
- if ( res === 0 ) return;
-
- let prom
- if (!installed) {
- prom = guessAppPath()
- .then(findScriptsPath)
- .catch(() => chooseAppPath(parentWin).then(findScriptsPath))
- .then(copyScript)
+ // We don't recalculate hashes here because they should be accurate
+ const installedHash = state.installedAi2htmlHash
+ const newHash = state.newAi2htmlHash
+
+ let verb
+ if(!installedHash) verb = 'Install'
+ else if (installedHash != newHash) verb = 'Update'
+ else if (forceInstall) verb = 'Reinstall'
+ else return;
+
+ dialog.showMessageBox(parentWin, {
+ type: 'question',
+ title: `${verb} ai2html`,
+ message: `Would you like to ${verb.toLowerCase()} ai2html?`,
+ defaultId: 1,
+ buttons: ['No', `${verb} ai2html`],
+ checkboxLabel: "Always check on startup",
+ checkboxChecked: !startupCheck,
+ }, (res, checkboxChecked) => {
+ dispatch('updateSettings', {disableAi2htmlStartupCheck: !checkboxChecked})
+
+ if ( res === 0 ) return;
+
+ let prom
+ if (!installPath) {
+ prom = guessAppPath()
+ .then(findScriptsPath)
+ .catch(() => chooseAppPath(parentWin).then(findScriptsPath))
+ .then(copyScript)
+ } else {
+ prom = copyScript(installPath)
+ }
+
+ prom.then(
+ (path) => {
+ alert({parentWin, message: 'The ai2html script has been installed.'})
+ state.installedAi2htmlHash = newHash
+ dispatch('updateSettings', {scriptInstallPath: path})
+ },
+ (err) => {
+ if ( err.code && err.code == 'EACCES' ) {
+ error({
+ parentWin,
+ message: `The ai2html script install failed.\n\nYou do not have permission to install the plugin.\n\nPlease give yourself write access to ${path.dirname(err.path)}`,
+ details: err.toString()
+ })
} else {
- prom = copyScript(path.dirname(installPath))
+ console.error('install script failed', err)
+ error({parentWin, message: 'The ai2html script install failed.', details: err.toString()})
}
-
- prom.then(
- (path) => {
- alert({parentWin, message: 'The ai2html script has been installed.'})
- if (!installed) dispatch('set', {key: 'scriptInstallPath', val: path})
- },
- (err) => {
- if ( err.code && err.code == 'EACCES' ) {
- error({parentWin, message: `The ai2html script install failed.\n\nYou do not have permission to install the plugin.\n\nPlease give yourself write access to ${path.dirname(err.path)}`, details: err.toString()})
- } else {
- console.error('install script failed', err)
- error({parentWin, message: 'The ai2html script install failed.', details: err.toString()})
- }
- }
- )
- })
-
- })
+ }
+ )
+ })
}
export function checkOnLaunch() {
+ // Calculate and stash these hashes at launch
+ state.installedAi2htmlHash = calcInstalledHash()
+ state.newAi2htmlHash = calcNewHash()
+
if ( state.data.Settings.disableAi2htmlStartupCheck === true ) return;
- install()
+ install({parentWin: state.mainWindow})
}
diff --git a/src/main/ipc.js b/src/main/ipc.js
index 81d08ec..c9303e8 100644
--- a/src/main/ipc.js
+++ b/src/main/ipc.js
@@ -2,7 +2,8 @@ import { ipcMain, BrowserWindow } from 'electron'
import ProjectContextMenu from './menus/ProjectContextMenu'
import state from './index'
import storage from './storage'
-import { newProject, addProjects, deployProject, editSettings, installAi2html, openInIllustrator } from './actions'
+import { newProject, addProjects, deployProject, editSettings, installAi2html, openInIllustrator, importSettings, resetSettings } from './actions'
+import { calcInstalledHash, calcNewHash } from './install_ai_plugin'
// Sync messages
ipcMain.on( 'get-state', (eve) => {
@@ -13,6 +14,13 @@ ipcMain.on( 'has-focus', (eve) => {
eve.returnValue = eve.sender.isFocused()
} )
+ipcMain.on( 'get-hashes', (eve) => {
+ eve.returnValue = {
+ installedHash: state.installedAi2htmlHash,
+ newHash: state.newAi2htmlHash
+ }
+} )
+
// Async messages
ipcMain.on( 'project-context-menu', (event, arg) => {
@@ -27,8 +35,13 @@ ipcMain.on( 'store-mutate', (eve, arg) => {
return console.error('State is missing in store-mutate ipc', arg.mutation, arg.state)
// Parse and cache current state
+ const oldData = state.data
state.data = JSON.parse( arg.state )
+ // Recalculate the ai2html script hash if necessary
+ if ( state.data.Settings.ai2htmlFonts != oldData.Settings.ai2htmlFonts )
+ state.newAi2htmlHash = calcNewHash()
+
// Make sure other windows have same state
const srcWin = BrowserWindow.fromWebContents(eve.sender)
BrowserWindow.getAllWindows().forEach((win) => {
@@ -75,6 +88,19 @@ ipcMain.on( 'install-ai2html', (eve, arg) => {
installAi2html()
} )
+ipcMain.on( 'import-settings', (eve, arg) => {
+ if ( arg.from == 'settings-window' )
+ importSettings(state.settingsWindow)
+ else
+ importSettings()
+} )
+
+ipcMain.on( 'reset-settings', (eve, arg) => {
+ if ( arg.from == 'settings-window' )
+ resetSettings(state.settingsWindow)
+ else
+ resetSettings()
+} )
// Senders
export function dispatch(action, payload) {
diff --git a/src/main/menus/Menubar.js b/src/main/menus/Menubar.js
index bafe43a..c237b06 100644
--- a/src/main/menus/Menubar.js
+++ b/src/main/menus/Menubar.js
@@ -1,5 +1,5 @@
import {app, Menu, shell} from 'electron'
-import { newProject, openProject, editSettings, installAi2html, clearState } from '../actions'
+import { newProject, openProject, editSettings, installAi2html, clearState, importSettings } from '../actions'
import state from '../index'
import storage from '../storage'
@@ -10,6 +10,7 @@ const MACOSX_MENUBAR_TEMPLATE = [
{role: 'about'},
{type: 'separator'},
{label: 'Preferences', click(eve) { editSettings() }},
+ {label: 'Import preferences', click(eve) { importSettings() }},
{label: 'Install ai2html', click(eve) { installAi2html() }},
{type: 'separator'},
{role: 'services', submenu: []},
diff --git a/src/renderer/App.vue b/src/renderer/App.vue
index 417b94a..5eafe62 100644
--- a/src/renderer/App.vue
+++ b/src/renderer/App.vue
@@ -59,6 +59,7 @@
body {
font: caption;
overflow:hidden;
+ user-select:none;
}
/* CSS */
diff --git a/src/renderer/Settings.vue b/src/renderer/Settings.vue
index de2547d..ac86df3 100644
--- a/src/renderer/Settings.vue
+++ b/src/renderer/Settings.vue
@@ -8,14 +8,13 @@
diff --git a/src/renderer/store/modules/Settings.js b/src/renderer/store/modules/Settings.js
index 0a50694..c89365a 100644
--- a/src/renderer/store/modules/Settings.js
+++ b/src/renderer/store/modules/Settings.js
@@ -11,20 +11,38 @@ const state = {
// awsRegion: null,
// awsAccessKeyId: null,
// awsSecretAccessKey: null,
+ // siteConfigName: null,
// extraPreviewCss: null,
// extraEmbedCss: null,
+ // ai2htmlFonts: null,
}
const mutations = {
- SETTINGS_SET ( state, { key, val } ) {
- if ( key in state ) state[key] = val
- else Vue.set(state, key, val)
+ SETTINGS_SET ( state, newSettings ) {
+ for (const key in newSettings) {
+ if ( key in state ) state[key] = newSettings[key]
+ else Vue.set(state, key, newSettings[key])
+ }
+ },
+ SETTINGS_RESET ( state, defaults ) {
+ for ( const k in state ) {
+ if ( k in defaults ) state[k] = defaults[k]
+ else state[k] = null
+ }
},
}
const actions = {
set ({commit}, { key, val }) {
- commit('SETTINGS_SET', {key, val})
+ const args = {}
+ args[key] = val
+ commit('SETTINGS_SET', args)
+ },
+ updateSettings ({commit}, newSettings) {
+ commit('SETTINGS_SET', newSettings)
+ },
+ resetSettings ({commit}, defaults) {
+ commit('SETTINGS_RESET', defaults)
}
}
diff --git a/static/ai2html.js b/static/templates/ai2html.js.ejs
similarity index 98%
rename from static/ai2html.js
rename to static/templates/ai2html.js.ejs
index cde88ee..bf31206 100644
--- a/static/ai2html.js
+++ b/static/templates/ai2html.js.ejs
@@ -411,26 +411,8 @@ var fonts = [
{"aifont":"Georgia","family":"georgia,'times new roman',times,serif","weight":"","style":""},
{"aifont":"Georgia-Bold","family":"georgia,'times new roman',times,serif","weight":"bold","style":""},
{"aifont":"Georgia-Italic","family":"georgia,'times new roman',times,serif","weight":"","style":"italic"},
-{"aifont":"Georgia-BoldItalic","family":"georgia,'times new roman',times,serif","weight":"bold","style":"italic"},
-
-{"aifont":"Balto-Medium","family":"'Balto', helvetica, sans-serif","weight":"400","style":""},
-{"aifont":"Balto-MediumItalic","family":"'Balto', helvetica, sans-serif","weight":"400","style":"italic"},
-{"aifont":"Balto-Bold","family":"'Balto', helvetica, sans-serif","weight":"700","style":""},
-{"aifont":"Balto-BoldItalic","family":"'Balto', helvetica, sans-serif","weight":"700","style":"italic"},
-{"aifont":"Balto-Book","family":"'Balto', helvetica, sans-serif","weight":"400","style":""},
-{"aifont":"Balto-BookItalic","family":"'Balto', helvetica, sans-serif","weight":"400","style":"italic"},
-{"aifont":"Balto-Black","family":"'Balto', helvetica, sans-serif","weight":"900","style":""},
-
-{"aifont":"Harriet-Bold","family":"'Harriet', helvetica, sans-serif","weight":"700","style":""},
-{"aifont":"Harriet-Book","family":"'Harriet', helvetica, sans-serif","weight":"400","style":""},
-{"aifont":"Harriet-BookItalic","family":"'Harriet', helvetica, sans-serif","weight":"400","style":"italic"},
-
-{"aifont":"HarrietDisplay-Bold","family":"'Harriet Display', helvetica, sans-serif","weight":"700","style":""},
-{"aifont":"HarrietDisplay-BoldItalic","family":"'Harriet Display', helvetica, sans-serif","weight":"700","style":"italic"},
-{"aifont":"HarrietDisplay-Black","family":"'Harriet Display', helvetica, sans-serif","weight":"900","style":""},
-
-{"aifont":"NittiGrotesk","family":"'Nitti',helvetica, sans-serif","weight":"400","style":""},
-{"aifont":"NittiGrotesk-Bold","family":"'Nitti-Bold','Nitti',helvetica, sans-serif","weight":"700","style":""}
+{"aifont":"Georgia-BoldItalic","family":"georgia,'times new roman',times,serif","weight":"bold","style":"italic"}<%=settings.ai2htmlFonts ? ',' : ''%>
+<%=settings.ai2htmlFonts%>
];
// CSS text-transform equivalents
@@ -989,7 +971,7 @@ function testSimilarBounds(a, b, maxOffs) {
function applyTemplate(template, replacements) {
var keyExp = '([_a-zA-Z][\\w-]*)';
var mustachePattern = new RegExp("\\{\\{\\{? *" + keyExp + " *\\}\\}\\}?","g");
- var ejsPattern = new RegExp("<%=? *" + keyExp + " *%>","g");
+ var ejsPattern = new RegExp("<"+"%=? *" + keyExp + " *%"+">","g");
var replace = function(match, name) {
var lcname = name.toLowerCase();
if (name in replacements) return replacements[name];
diff --git a/static/templates/embed.html.ejs b/static/templates/embed.html.ejs
index 18ef34a..e243a7f 100644
--- a/static/templates/embed.html.ejs
+++ b/static/templates/embed.html.ejs
@@ -12,120 +12,6 @@
<%=render('meta_tags.html.ejs', {slug, deploy_url, embed_meta, config, project}) %>