UNPKG

@truenewx/tnxet

Version:

互联网技术解决方案:Electron扩展支持

387 lines (364 loc) 15.7 kB
// tnxet.js /** * 对Electron渲染进程的扩展支持 */ import tnxcore from '../../tnxcore/src/tnxcore'; import './tnxet.css'; import common from './tnxet-common.js'; export const build = tnxcore.build; function ipcRequest(eventName, args, callback, onError) { const {ipcRenderer} = require('electron'); // 放在此处以使得在浏览器中可以初始化运行 return new Promise((resolve, reject) => { let requestId = tnx.util.string.uuid32(); const listener = (event, responseId, error, ...result) => { if (requestId === responseId) { ipcRenderer.off(eventName.response, listener); if (error) { handleIpcInvokeError(error, reject, callback, onError); } else { if (typeof callback === 'function') { callback(...result); } else { resolve(result[0]); // resolve只能接受一个参数 } } } }; ipcRenderer.on(eventName.response, listener); args = args || []; ipcRenderer.send(eventName.request, requestId, ...args); }); } function handleIpcInvokeError(error, reject, callback, onError) { let args = []; if (Array.isArray(error)) { args.push(...error.slice(1)); error = error[0]; } console.error(error); if (error instanceof Error) { error = error.message || error.toString(); } if (typeof onError === 'function') { onError(error, ...args); } else if (typeof callback === 'function') { tnx.error(error); } else { reject(error); // reject只能接受一个参数 } } export default build('tnxet', () => { const tnxet = Object.assign({}, tnxcore, {}); tnx.app.rpc.addEnumType({ name: 'Boolean', items: [{ key: 'true', caption: '是', }, { key: 'false', caption: '否', }], }); Object.assign(tnxet.util.file, { separator: navigator.userAgent.contains('Win') ? '\\' : '/', joinPath(...paths) { let result = ''; if (paths.length) { for (let path of paths) { if (path.startsWith(this.separator)) { path = path.substring(this.separator.length); } if (path.endsWith(this.separator)) { result += path; } else { result += path + this.separator; } } result = result.substring(0, result.length - 1); } return result; }, /** * 获取指定路径的上级目录路径,含末尾的斜杠,以示该地址为目录而不是文件 * @param path 路径 * @returns {null|string} 上级目录路径,如果没有上级目录,则返回null */ getParentDir(path) { if ((this.separator === '\\' && !path.endsWith(':\\')) || (this.separator === '/' && path !== '/')) { let index = path.lastIndexOf(this.separator); if (index >= 0) { if (index === path.length - 1) { // 已斜杠结尾,则先去掉斜杠,以便于后续判断 path = path.substring(0, index); index = path.lastIndexOf(this.separator); } if (index >= 0) { return path.substring(0, index + 1); } } } return null; }, toRelativePath(basePath, baseIsFile, absolutePath) { // 如果基础路径就是绝对路径的开头,直接截取后面的部分 if (absolutePath.startsWith(basePath)) { let relativePath = absolutePath.substring(basePath.length); // 去掉开头的分隔符 return relativePath.startsWith(this.separator) ? relativePath.substring(1) : relativePath; } // 将两个路径都分割成数组 const absoluteSegments = absolutePath.split(this.separator); const baseSegments = basePath.split(this.separator); // 找到第一个不同的位置 let i = 0; while (i < absoluteSegments.length && i < baseSegments.length && absoluteSegments[i] === baseSegments[i]) { i++; } // 构建相对路径 let relativeSegmentLength = baseSegments.length - i; if (baseIsFile) { relativeSegmentLength--; } const relativeSegments = Array(relativeSegmentLength).fill('..') .concat(absoluteSegments.slice(i)); // 使用'/'拼接最终结果 return relativeSegments.join('/'); }, toAbsolutePath(basePath, baseIsFile, relativePath) { // 将基础路径和相对路径分割成段 const baseSegments = basePath.split(this.separator); const relativeSegments = relativePath.replace(/\\/g, '/').split('/'); // 创建结果数组,初始化为基础路径的段 let resultSegments = baseIsFile ? baseSegments.slice(0, baseSegments.length - 1) : [...baseSegments]; // 处理每个相对路径段 for (const segment of relativeSegments) { if (segment === '..') { // 如果遇到'..',移除结果中的最后一段 if (resultSegments.length > 0) { resultSegments.pop(); } } else if (segment !== '.' && segment !== '') { // 如果不是'.'或空字符串,添加到结果中 resultSegments.push(segment); } } // 使用系统分隔符拼接最终结果 return resultSegments.join(this.separator); }, getFileName(path) { let index = path.lastIndexOf(this.separator); // 斜杠不是在末尾,才有文件名 if (index < path.length - 1) { return path.substring(index + 1); } return null; }, loadSeparator() { ipcRequest(common.event.file.separator, []).then(separator => { this.separator = separator; }); }, exists(path, callback) { return ipcRequest(common.event.file.exists, [path], callback); }, read(path, withMeta, dataIfNonexistent, callback, onError) { if (typeof dataIfNonexistent === 'function') { onError = callback; callback = dataIfNonexistent; dataIfNonexistent = undefined; } if (typeof withMeta === 'function') { onError = callback; callback = withMeta; dataIfNonexistent = undefined; withMeta = false; } return ipcRequest(common.event.file.read, [path, withMeta, dataIfNonexistent], callback, onError); }, write(path, data, callback, onError) { return ipcRequest(common.event.file.write, [path, data], callback, onError); }, readAll(dir, pathRegex, callback, onError) { return ipcRequest(common.event.file.readAll, [dir, pathRegex], callback, onError); }, findPath(dir, pathRegex, callback, onError) { return ipcRequest(common.event.file.findPath, [dir, pathRegex], callback, onError); }, delete(path, deleteEmptyDir, throwIfAbsent, callback, onError) { if (typeof deleteEmptyDir === 'function') { onError = callback; callback = throwIfAbsent; throwIfAbsent = deleteEmptyDir; deleteEmptyDir = false; } if (typeof throwIfAbsent === 'function') { onError = callback; callback = throwIfAbsent; throwIfAbsent = false; } return ipcRequest(common.event.file.delete, [path, deleteEmptyDir, throwIfAbsent], callback, onError); }, copy(sourceFilePath, targetFilePath, force, callback, onError) { return ipcRequest(common.event.file.copy, [sourceFilePath, targetFilePath, force], callback, onError); }, }); tnxet.util.dialog = { open(options, callback, onError) { return ipcRequest(common.event.dialog.open, [options], callback, onError); }, save(options, callback, onError) { return ipcRequest(common.event.dialog.save, [options], callback, onError); }, }; tnxet.util.zip = { filenames(zipPath, callback, onError) { return ipcRequest(common.event.zip.filenames, [zipPath], callback, onError); }, read(zipPath, filename, callback, onError) { return ipcRequest(common.event.zip.read, [zipPath, filename], callback, onError); }, readAll(zipPath, filenamePattern, callback, onError) { if (typeof filenamePattern === 'function') { onError = callback; callback = filenamePattern; filenamePattern = undefined; } return ipcRequest(common.event.zip.readAll, [zipPath, filenamePattern], callback, onError); }, } tnxet.util.clipboard = { write(text, callback, onError) { return ipcRequest(common.event.clipboard.write, [text], callback, onError); }, } tnxet.util.xml = { containsTag(xml, tag) { return xml.indexOf('<' + tag + '>') >= 0; }, /** * 将指定xml字符串中的指定标签的第一个节点替换为指定字符串 * @param xml xml字符串 * @param tag 标签名,不含<> * @param s 新的字符串 * @returns {string} 替换后的xml字符串 */ replaceFirstTag(xml, tag, s) { let index = xml.indexOf('<' + tag + '>'); if (index >= 0) { const tagEnd = '</' + tag + '>'; let index2 = xml.indexOf(tagEnd, index) + tagEnd.length; if (s) { xml = xml.substring(0, index) + s + xml.substring(index2); } else { xml = xml.substring(0, index).trim() + xml.substring(index2); } } return xml; }, /** * 在指定xml字符串中的指定标签的第一个节点后插入指定字符串。指定标签为数组时,依次判断标签是否存在,存在时在其后插入,否则检索下一个标签 * @param xml xml字符串 * @param tags 标签名清单 * @param s 要插入的字符串 */ insertAfterTag(xml, tags, s) { if (!Array.isArray(tags)) { tags = [tags]; } let index = -1; for (let tag of tags) { let tagEnd = '</' + tag + '>'; index = xml.indexOf(tagEnd); if (index >= 0) { index += tagEnd.length; break; } } if (index >= 0) { if (s && !s.startsWith('\n')) { s = '\n' + s; } xml = xml.substring(0, index) + s + xml.substring(index); } return xml; }, setTag(xml, replaceTag, tagXml, afterTags, afterIndent = 4) { tagXml = tagXml.trim(); if (this.containsTag(xml, replaceTag)) { xml = tnx.util.xml.replaceFirstTag(xml, replaceTag, tagXml); } else if (tagXml) { xml = tnx.util.xml.insertAfterTag(xml, afterTags, ' '.repeat(afterIndent) + tagXml); } if (!xml.endsWith('\n')) { xml += '\n'; } return xml; }, toFormattedXml(object, indent, ignoreBlank, keyMapper, valueMapper) { let xml = ''; if (object && !Array.isArray(object) && typeof object === 'object') { indent = indent || 0; let indentPrefix = ' '.repeat(indent); let keys = Object.keys(object); for (let key of keys) { let value = object[key]; if (value === undefined || value === null) { continue; } // 以英文字母开头的才是有效字段,可转换为合法的xml标签 if (/^[A-Za-z]/.test(key)) { let subKeyMapper = undefined; if (typeof keyMapper === 'function') { key = keyMapper(key); if (typeof key !== 'string') { continue; } subKeyMapper = function (subKey) { return keyMapper(key + '.' + subKey); } } if (typeof valueMapper === 'function') { value = valueMapper(value, key); } let items = Array.isArray(value) ? value : [value]; for (let item of items) { if (ignoreBlank && typeof item === 'string' && !item.trim()) { continue; } xml += indentPrefix + '<' + key + '>'; if (item instanceof Date) { item = item.formatDateTime(); } if (typeof item === 'object' && !(item instanceof tnxcore.Enum)) { xml += '\n' + this.toFormattedXml(item, indent + 4, ignoreBlank, subKeyMapper, valueMapper) + indentPrefix; } else { xml += item; } xml += '</' + key + '>\n'; } } } } return xml; }, }; tnxet.util.command = { listeners: {}, subscribe(name, listener) { this.listeners[name] = listener; }, unsubscribe(name) { delete this.listeners[name]; }, } const {ipcRenderer} = require('electron'); // 放在此处以使得在浏览器中可以初始化运行 ipcRenderer.on(common.channel.command, function (event, data) { let listener = tnxet.util.command.listeners[data.name]; if (listener) { let args = data.args || []; listener(...args); } } ); return tnxet; });