tweb/src/scripts/icomoon/icomoon.js

292 lines
9.6 KiB
JavaScript

// Thanks to https://github.com/Yuyz0112/icomoon-cli
const fs = require('fs-extra');
const path = require('path');
const extract = require('extract-zip');
const puppeteer = require('puppeteer');
const DEFAULT_TIMEOUT = 60000;
const PAGE = {
IMPORT_CONFIG_BUTTON: '.file.unit',
IMPORT_SELECTION_INPUT: '.file.unit input[type="file"]',
OVERLAY_CONFIRM: '.overlay button.mrl',
NEW_SET_BUTTON: '.menuList1 button',
MAIN_MENU_BUTTON: '.bar-top button .icon-menu',
MENU_BUTTON: 'h1 button .icon-menu',
MENU: '.menuList2.menuList3',
ICON_INPUT: '.menuList2.menuList3 .file input[type="file"]',
FIRST_ICON_BOX: '#set0 .miBox:not(.mi-selected)',
REMOVE_SET_BUTTON: '.menuList2.menuList3 li:last-child button',
SELECT_ALL_BUTTON: 'button[ng-click="selectAllNone($index, true)"]',
GENERATE_LINK: 'a[href="#/select/font"]',
GLYPH_SET: '#glyphSet0',
GLYPH_NAME: '.glyphName',
DOWNLOAD_BUTTON: '.btn4',
PREFERENCES: '#pref',
SHOW_METRICS: '[ng-class*="showMetricsFocused"] label',
CLOSE_OVERLAY: 'button[ng-click*="visiblePanels.fontPref = false"]',
IE8_SUPPORTED: '[ng-class*="noie8Focused"] .icon-checked',
IE8_DISABLE: 'label[ng-class*="noie8Focused"]',
FONT_NAME_INPUT: '[ng-model="fontPref.metadata.fontFamily"]',
CLASS_PREFIX_INPUT: '[ng-model="fontPref.prefix"]',
CSS_VARS_LABEL: '[ng-class*="fontPref.cssVars"]',
EM_HEIGHT_INPUT: '[model="fontPref.metrics.emSize"] input',
BASELINE_HEIGHT_INPUT: '[model="fontPref.metrics.baseline"] input',
WHITESPACE_WIDTH_INPUT: '[model="fontPref.metrics.whitespace"] input',
};
const DEFAULT_OPTIONS = {
outputDir: path.join(__dirname, 'output'),
};
const logger = (...args) => {
console.log('[icomoon-cli]', ...args);
};
const sleep = time => new Promise(resolve => setTimeout(resolve, time));
const getAbsolutePath = inputPath => {
let absoluteSelectionPath = inputPath;
if (!path.isAbsolute(inputPath)) {
if (!process.env.PWD) {
process.env.PWD = process.cwd();
}
absoluteSelectionPath = path.resolve(process.env.PWD, inputPath);
}
return absoluteSelectionPath;
};
const checkDownload = dest => new Promise((resolve, reject) => {
const interval = 1000;
let downloadSize = 0;
let timeCount = 0;
const timer = setInterval(async () => {
timeCount += interval;
/* const exist = await fs.exists(dest);
if (!exist) {
return;
} */
const stats = fs.statSync(dest);
if (stats.size > 0 && stats.size === downloadSize) {
clearInterval(timer);
resolve();
} else {
downloadSize = stats.size;
}
if (timeCount > DEFAULT_TIMEOUT) {
reject('Timeout when download file, please check your network.');
}
}, interval);
});
const checkDuplicateName = ({ selectionPath, icons, names }, forceOverride) => {
const iconNames = icons.map((icon, index) => {
if (names[index]) {
return names[index];
}
return path.basename(icon).replace(path.extname(icon), '');
});
const duplicates = [];
const selection = fs.readJSONSync(selectionPath);
selection.icons.forEach((icon, index) => {
const name = icon.tags[0];
if (iconNames.includes(name)) {
duplicates.push({ name, index });
}
});
if (!duplicates.length) {
return;
}
if (forceOverride) {
selection.icons = selection.icons.filter((icon, index) => !duplicates.some(d => d.index === index));
fs.writeJSONSync(selectionPath, selection, { spaces: 2 });
} else {
throw new Error(`Found duplicate icon names: ${duplicates.map(d => d.name).join(',')}`);
}
};
async function pipeline(options = {}) {
try {
const {
icons,
names = [],
selectionPath,
forceOverride = false,
whenFinished,
visible = false
} = options;
const outputDir = options.outputDir ? getAbsolutePath(options.outputDir) : DEFAULT_OPTIONS.outputDir;
// prepare stage
logger('Preparing...');
if(!icons || !icons.length) {
if(whenFinished) {
whenFinished({ outputDir });
}
return logger('No new icons found.');
}
if(!selectionPath) {
throw new Error('Please config a valid selection file path.');
}
let absoluteSelectionPath = getAbsolutePath(selectionPath);
// checkDuplicateName({
// selectionPath: absoluteSelectionPath,
// icons,
// names,
// }, forceOverride);
await fs.remove(outputDir);
await fs.ensureDir(outputDir);
const browser = await puppeteer.launch({headless: !visible});
logger('Started a new chrome instance, going to load icomoon.io.');
const page = await (await browser).newPage();
await page._client.send('Page.setDownloadBehavior', {
behavior: 'allow',
downloadPath: outputDir
});
await page.goto('https://icomoon.io/app/#/select');
await page.waitForSelector(PAGE.IMPORT_CONFIG_BUTTON);
logger('Dashboard is visible, going to upload config file');
// remove init set
await page.click(PAGE.MENU_BUTTON);
await page.click(PAGE.REMOVE_SET_BUTTON);
const importInput = await page.waitForSelector(PAGE.IMPORT_SELECTION_INPUT);
await importInput.uploadFile(absoluteSelectionPath);
logger('Uploaded config, going to upload new icon files');
try {
await Promise.race([
sleep(1000).then(() => {
throw 0;
}),
page.waitForSelector(PAGE.OVERLAY_CONFIRM, { visible: true })
]);
await page.click(PAGE.OVERLAY_CONFIRM);
} catch(err) {
logger('Overlay is missed?');
}
const selection = fs.readJSONSync(selectionPath);
/* if (selection.icons.length === 0) {
logger('Selection icons is empty, going to create an empty set');
await page.click(PAGE.MAIN_MENU_BUTTON);
await page.waitForSelector(PAGE.NEW_SET_BUTTON, { visible: true });
await page.click(PAGE.NEW_SET_BUTTON);
} */
await page.click(PAGE.MENU_BUTTON);
const iconInput = await page.waitForSelector(PAGE.ICON_INPUT);
const iconPaths = icons.map(getAbsolutePath);
await iconInput.uploadFile(...iconPaths);
await page.waitForSelector(PAGE.FIRST_ICON_BOX);
await page.click(PAGE.SELECT_ALL_BUTTON);
logger('Uploaded and selected all new icons');
await page.click(PAGE.GENERATE_LINK);
await page.waitForSelector(PAGE.GLYPH_SET);
await page.click(PAGE.PREFERENCES);
try {
await Promise.race([
sleep(1000).then(() => {
throw 0;
}),
page.waitForSelector(PAGE.IE8_SUPPORTED)
]);
await page.click(PAGE.IE8_DISABLE);
} catch(err) {
logger('IE8 is already disabled');
}
async function fillInput(selector, value) {
if(typeof(value) !== 'string') {
value = '' + value;
}
await page.focus(selector);
for(let i = 0; i < 100; ++i) {
await page.keyboard.press('Backspace');
}
await page.keyboard.type(value);
}
await fillInput(PAGE.FONT_NAME_INPUT, selection.preferences.fontPref.metadata.fontFamily);
await fillInput(PAGE.CLASS_PREFIX_INPUT, selection.preferences.fontPref.prefix);
await page.click(PAGE.CSS_VARS_LABEL);
await page.click(PAGE.SHOW_METRICS);
await fillInput(PAGE.EM_HEIGHT_INPUT, selection.preferences.fontPref.metrics.emSize);
await fillInput(PAGE.BASELINE_HEIGHT_INPUT, selection.preferences.fontPref.metrics.baseline);
await fillInput(PAGE.WHITESPACE_WIDTH_INPUT, selection.preferences.fontPref.metrics.whitespace);
// await sleep(100000);
await page.click(PAGE.CLOSE_OVERLAY);
// (await page.waitForSelector(PAGE.FONT_NAME_INPUT)).;
// if(names.length) {
// logger('Changed names of icons');
// // sleep to ensure indexedDB is ready
// await sleep(1000);
// await page.evaluate(names => {
// const request = indexedDB.open('IDBWrapper-storage', 1);
// request.onsuccess = function() {
// const db = request.result;
// const tx = db.transaction('storage', 'readwrite');
// const store = tx.objectStore('storage');
// const keys = store.getAllKeys();
// keys.onsuccess = function() {
// let timestamp;
// keys.result.forEach(function(key) {
// if (typeof key === 'number') {
// timestamp = key;
// }
// });
// const main = store.get(timestamp);
// main.onsuccess = function() {
// const data = main.result;
// for (let i = 0; i < names.length; i++) {
// data.obj.iconSets[0].selection[i].name = names[i];
// }
// store.put(data);
// };
// };
// };
// }, names);
// }
// // sleep to ensure the code was executed
// await sleep(1000);
// // reload the page let icomoon read latest indexedDB data
// await page.reload();
await sleep(2000);
await page.waitForSelector(PAGE.DOWNLOAD_BUTTON);
await page.click(PAGE.DOWNLOAD_BUTTON);
const meta = selection.preferences.fontPref.metadata;
const zipName = meta.majorVersion
? `${meta.fontFamily}-v${meta.majorVersion}.${meta.minorVersion || 0}.zip`
: `${meta.fontFamily}.zip`;
logger(`Started to download ${zipName}`);
const zipPath = path.join(outputDir, zipName);
await checkDownload(zipPath);
logger('Successfully downloaded, going to unzip it.');
await page.close();
await browser.close();
// unzip stage
extract(zipPath, {dir: outputDir}, async(err) => {
if(err) {
throw err;
}
await fs.remove(zipPath);
logger(`Finished. The output directory is ${outputDir}.`);
if(whenFinished) {
whenFinished({outputDir});
}
});
} catch(error) {
console.error(error);
}
}
module.exports = pipeline;