@truenewx/tnxet
Version:
互联网技术解决方案:Electron扩展支持
387 lines (364 loc) • 15.7 kB
JavaScript
// 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;
});