868 lines
34 KiB
JavaScript
868 lines
34 KiB
JavaScript
(function () {
|
|
'use strict';
|
|
|
|
var HISTORY_KEY = 'soondesign_history';
|
|
var VERSION = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '3.2.101';
|
|
|
|
function getAppBasePathname() {
|
|
var p = (typeof window !== 'undefined' && window.location && window.location.pathname) ? window.location.pathname : '/';
|
|
while (p.length > 1 && p.endsWith('/')) {
|
|
p = p.slice(0, -1);
|
|
}
|
|
if (/\.web\.html$/i.test(p) || /\.html$/i.test(p)) {
|
|
p = p.replace(/[^/]+$/, '');
|
|
}
|
|
if (!p.endsWith('/')) p += '/';
|
|
return p;
|
|
}
|
|
|
|
var SERVER_API_BASE = (function () {
|
|
var cfg = (typeof window !== 'undefined' && window.SOON_DEPLOY_CONFIG && window.SOON_DEPLOY_CONFIG.api_v1_base);
|
|
if (cfg) return String(cfg).replace(/\/+$/, '') + '/';
|
|
if (typeof window === 'undefined' || !window.location) return '/api/v1/';
|
|
return window.location.origin + getAppBasePathname() + 'api/v1/';
|
|
})();
|
|
|
|
function getAccessToken() {
|
|
if (typeof window.soonGetAccessToken === 'function') return window.soonGetAccessToken();
|
|
try { return localStorage.getItem('soon_access') || ''; } catch (e) { return ''; }
|
|
}
|
|
|
|
function authedFetch(path, opts) {
|
|
var url = SERVER_API_BASE + String(path).replace(/^\/+/, '');
|
|
if (typeof window.soonAuthedFetch === 'function') {
|
|
return window.soonAuthedFetch(url, opts);
|
|
}
|
|
opts = opts || {};
|
|
opts.headers = Object.assign({}, opts.headers || {});
|
|
if (!opts.headers['Authorization'] && !opts.headers['authorization']) {
|
|
var t = getAccessToken();
|
|
if (t) opts.headers['Authorization'] = 'Bearer ' + t;
|
|
}
|
|
return fetch(url, opts);
|
|
}
|
|
|
|
var JSON_HEADERS = {
|
|
'Content-Type': 'application/json; charset=utf-8',
|
|
'Accept': 'application/json'
|
|
};
|
|
|
|
var CLOUD_JSON_LIMIT = 16 * 1024 * 1024 - 65536;
|
|
var CLOUD_STRIP_KEYS = [
|
|
'frontColorPic', 'backColorPic', 'frontBlackPic', 'backBlackPic', 'backDisplayPic'
|
|
];
|
|
var CLOUD_REGEN_IMAGE_TYPES = { 2: 1, 8: 1, 9: 1 };
|
|
|
|
function slimCloudFabricSide(data, sideKey, metaKey) {
|
|
var canvas = data[sideKey];
|
|
var metaList = data[metaKey];
|
|
if (!canvas || !Array.isArray(canvas.objects)) return;
|
|
canvas.objects.forEach(function (obj, i) {
|
|
if (!obj || typeof obj.src !== 'string') return;
|
|
var meta = Array.isArray(metaList) && i > 0 ? metaList[i - 1] : null;
|
|
var t = meta && meta.type;
|
|
if (CLOUD_REGEN_IMAGE_TYPES[t]) delete obj.src;
|
|
});
|
|
}
|
|
|
|
function slimCloudPayload(data) {
|
|
CLOUD_STRIP_KEYS.forEach(function (key) { delete data[key]; });
|
|
var preview = data.frontDisplayPic;
|
|
if (typeof preview === 'string' && preview.length > 120000) delete data.frontDisplayPic;
|
|
slimCloudFabricSide(data, 'f', 'fo');
|
|
slimCloudFabricSide(data, 'b', 'bo');
|
|
return data;
|
|
}
|
|
|
|
function formatPayloadTooLargeMessage(sizeBytes) {
|
|
var mb = Math.max(0.1, Math.round(sizeBytes / 1024 / 1024 * 10) / 10);
|
|
return '设计文件约 ' + mb + 'MB,超过云端上传限制。请减少或压缩自定义图片;'
|
|
+ '若仍失败,请让管理员将 MySQL max_allowed_packet 调至 32M 以上。';
|
|
}
|
|
|
|
function prepareCloudJsonStr(jsonStr) {
|
|
if (typeof jsonStr !== 'string') jsonStr = String(jsonStr || '{}');
|
|
if (jsonStr.length <= CLOUD_JSON_LIMIT) return jsonStr;
|
|
var slim = jsonStr;
|
|
try {
|
|
var data = slimCloudPayload(JSON.parse(jsonStr));
|
|
slim = JSON.stringify(data);
|
|
if (slim.length <= CLOUD_JSON_LIMIT) return slim;
|
|
delete data.frontDisplayPic;
|
|
slim = JSON.stringify(data);
|
|
if (slim.length <= CLOUD_JSON_LIMIT) return slim;
|
|
} catch (e) { /* ignore */ }
|
|
var err = new Error(formatPayloadTooLargeMessage(slim.length));
|
|
err.code = 'payload_too_large';
|
|
err.status = 413;
|
|
throw err;
|
|
}
|
|
|
|
function parseCloudRef(p) {
|
|
return typeof window.soonParseFileKey === 'function' ? window.soonParseFileKey(p) : null;
|
|
}
|
|
|
|
function normalizeSoonName(p) {
|
|
return typeof window.soonNormalizeSoonName === 'function'
|
|
? window.soonNormalizeSoonName(p)
|
|
: String(p || 'design.soon');
|
|
}
|
|
|
|
function packCloudResult(j) {
|
|
if (!j || !j.ok || !j.data) {
|
|
return Promise.reject(new Error('cloud_empty_response'));
|
|
}
|
|
var fileKey = typeof window.soonApplyCloudMeta === 'function'
|
|
? window.soonApplyCloudMeta(j.data)
|
|
: null;
|
|
if (!fileKey) {
|
|
var d = j.data;
|
|
window._soonFileMeta = { id: d.id, version: d.version, name: d.name };
|
|
fileKey = 'soondesign_file:' + d.id + ':v' + d.version;
|
|
}
|
|
return { fileId: j.data.id, version: j.data.version, fileKey: fileKey, name: j.data.name };
|
|
}
|
|
|
|
function handleCloudResponse(response, bodyText) {
|
|
if (response.ok) {
|
|
try { return Promise.resolve(JSON.parse(bodyText)); } catch (e) { return Promise.resolve(null); }
|
|
}
|
|
var info = typeof window.soonParseApiError === 'function'
|
|
? window.soonParseApiError(response, bodyText)
|
|
: { status: response.status, message: '操作失败' };
|
|
if (typeof window.soonShowApiError === 'function') window.soonShowApiError(info);
|
|
var err = new Error(info.message || 'cloud_error');
|
|
err.status = info.status;
|
|
err.code = info.code;
|
|
err.apiNotified = true;
|
|
return Promise.reject(err);
|
|
}
|
|
|
|
function requireCloudAuth(actionLabel) {
|
|
return !!getAccessToken();
|
|
}
|
|
|
|
function navigateToPage(pathWithQuery) {
|
|
try {
|
|
location.href = new URL(pathWithQuery, window.location.href).href;
|
|
} catch (e) {
|
|
location.href = pathWithQuery;
|
|
}
|
|
}
|
|
|
|
function getLocale() {
|
|
var lang = typeof navigator !== 'undefined' ? (navigator.language || navigator.browserLanguage || '') : '';
|
|
if (lang.indexOf('zh') === 0) return lang.indexOf('TW') >= 0 ? 'ozh' : 'zh';
|
|
return 'en';
|
|
}
|
|
|
|
var WEB_FONT_FALLBACK = [
|
|
'Arial', 'Arial Black', 'Comic Sans MS', 'Courier New', 'Georgia',
|
|
'Impact', 'Microsoft YaHei', 'SimHei', 'SimSun', 'KaiTi', 'FangSong',
|
|
'Times New Roman', 'Trebuchet MS', 'Verdana', 'PingFang SC', 'Hiragino Sans GB',
|
|
'Helvetica Neue', 'Segoe UI', 'Tahoma', 'STHeiti', 'STSong', 'STKaiti', 'STFangsong'
|
|
];
|
|
|
|
function mergeFontNames() {
|
|
var set = {};
|
|
WEB_FONT_FALLBACK.forEach(function (name) { set[name] = 1; });
|
|
if (typeof document !== 'undefined' && document.fonts && document.fonts.forEach) {
|
|
document.fonts.forEach(function (f) {
|
|
var name = (f.family || '').replace(/^["']|["']$/g, '');
|
|
if (name) set[name] = 1;
|
|
});
|
|
}
|
|
return Object.keys(set).sort();
|
|
}
|
|
|
|
function getSystemFonts() {
|
|
if (typeof window !== 'undefined' && window.queryLocalFonts) {
|
|
return window.queryLocalFonts().then(function (fonts) {
|
|
var set = {};
|
|
WEB_FONT_FALLBACK.forEach(function (name) { set[name] = 1; });
|
|
(fonts || []).forEach(function (f) {
|
|
var name = (f.fullName || f.family || '').replace(/^["']|["']$/g, '');
|
|
if (name) set[name] = 1;
|
|
});
|
|
var merged = Object.keys(set).sort();
|
|
return merged.length ? merged : mergeFontNames();
|
|
}).catch(function () {
|
|
return mergeFontNames();
|
|
});
|
|
}
|
|
return Promise.resolve(mergeFontNames());
|
|
}
|
|
|
|
function listCloudFiles(pageOrLimit, sizeOrOffset) {
|
|
if (!getAccessToken()) {
|
|
var empty = { items: [], total: 0 };
|
|
if (typeof pageOrLimit === 'object' && pageOrLimit) {
|
|
empty.page = pageOrLimit.page || 1;
|
|
empty.size = pageOrLimit.size || 12;
|
|
}
|
|
return Promise.resolve(empty);
|
|
}
|
|
var url;
|
|
var fallback = { items: [], total: 0 };
|
|
if (typeof pageOrLimit === 'object' && pageOrLimit) {
|
|
var page = pageOrLimit.page || 1;
|
|
var size = pageOrLimit.size || 12;
|
|
url = 'files?page=' + page + '&size=' + size;
|
|
fallback = { items: [], total: 0, page: page, size: size };
|
|
} else {
|
|
var lim = pageOrLimit || 50;
|
|
var off = sizeOrOffset || 0;
|
|
url = 'files?limit=' + lim + '&offset=' + off;
|
|
fallback = { items: [], limit: lim, offset: off, total: 0 };
|
|
}
|
|
return authedFetch(url, { headers: { Accept: 'application/json' } })
|
|
.then(function (r) { return r.text().then(function (t) { return handleCloudResponse(r, t); }); })
|
|
.then(function (j) { return (j && j.ok && j.data) ? j.data : fallback; });
|
|
}
|
|
|
|
function createCloudFile(name, jsonStr) {
|
|
if (!requireCloudAuth('保存')) return Promise.reject(new Error('unauthorized'));
|
|
try {
|
|
jsonStr = prepareCloudJsonStr(jsonStr);
|
|
} catch (e) {
|
|
return Promise.reject(e);
|
|
}
|
|
return authedFetch('files', {
|
|
method: 'POST',
|
|
headers: JSON_HEADERS,
|
|
body: JSON.stringify({ name: normalizeSoonName(name), json: jsonStr })
|
|
})
|
|
.then(function (r) { return r.text().then(function (t) { return handleCloudResponse(r, t); }); })
|
|
.then(packCloudResult);
|
|
}
|
|
|
|
function syncCloudFileAfterConflict(id) {
|
|
var oldKey = (typeof window !== 'undefined' && window.openAs && window.openAs.name) ? window.openAs.name : '';
|
|
return authedFetch('files/' + id, { headers: { Accept: 'application/json' } })
|
|
.then(function (response) {
|
|
if (!response.ok) return null;
|
|
return response.json();
|
|
})
|
|
.then(function (payload) {
|
|
if (!payload || !payload.ok || !payload.data) return null;
|
|
var d = payload.data;
|
|
var newKey = typeof window.soonApplyCloudMeta === 'function'
|
|
? window.soonApplyCloudMeta(d)
|
|
: ('soondesign_file:' + d.id + ':v' + d.version);
|
|
if (oldKey && oldKey !== newKey && typeof window.soonLocalRemove === 'function') {
|
|
window.soonLocalRemove(oldKey);
|
|
}
|
|
if (typeof window.soonRecentOnCloudSave === 'function') {
|
|
window.soonRecentOnCloudSave({
|
|
fileKey: newKey,
|
|
fileId: d.id,
|
|
name: d.name,
|
|
version: d.version
|
|
}, oldKey, null);
|
|
}
|
|
if (typeof window !== 'undefined' && window.openAs && newKey) {
|
|
window.openAs.name = newKey;
|
|
}
|
|
if (typeof window.soonToast === 'function') {
|
|
window.soonToast('文件已在其他端更新,已同步版本,请再次保存', 'warn');
|
|
}
|
|
return newKey;
|
|
});
|
|
}
|
|
|
|
function updateCloudFile(id, name, jsonStr, version) {
|
|
if (!requireCloudAuth('保存')) return Promise.reject(new Error('unauthorized'));
|
|
try {
|
|
jsonStr = prepareCloudJsonStr(jsonStr);
|
|
} catch (e) {
|
|
return Promise.reject(e);
|
|
}
|
|
var body = { name: normalizeSoonName(name), json: jsonStr };
|
|
if (version != null) body.version = version;
|
|
return authedFetch('files/' + id, {
|
|
method: 'PUT',
|
|
headers: JSON_HEADERS,
|
|
body: JSON.stringify(body)
|
|
})
|
|
.then(function (r) {
|
|
return r.text().then(function (t) {
|
|
if (r.status === 409) {
|
|
return syncCloudFileAfterConflict(id).then(function () {
|
|
var err = new Error('version_conflict');
|
|
err.status = 409;
|
|
err.conflictSynced = true;
|
|
return Promise.reject(err);
|
|
});
|
|
}
|
|
return handleCloudResponse(r, t);
|
|
});
|
|
})
|
|
.then(packCloudResult);
|
|
}
|
|
|
|
function deleteCloudFile(id) {
|
|
if (!requireCloudAuth('删除')) return Promise.reject(new Error('unauthorized'));
|
|
return authedFetch('files/' + id, { method: 'DELETE', headers: { Accept: 'application/json' } })
|
|
.then(function (r) { return r.text().then(function (t) { return handleCloudResponse(r, t); }); });
|
|
}
|
|
|
|
function importSoonFile(name, jsonObj) {
|
|
var jsonStr = typeof jsonObj === 'string' ? jsonObj : JSON.stringify(jsonObj);
|
|
return createCloudFile(name, jsonStr);
|
|
}
|
|
|
|
function downloadCloudFile(id, fileName) {
|
|
if (!getAccessToken()) {
|
|
return Promise.reject(new Error('unauthorized'));
|
|
}
|
|
return authedFetch('files/' + id + '/download', { headers: { Accept: 'application/octet-stream' } })
|
|
.then(function (response) {
|
|
if (!response.ok) {
|
|
return response.text().then(function (t) { return handleCloudResponse(response, t); });
|
|
}
|
|
return response.blob().then(function (blob) {
|
|
var a = document.createElement('a');
|
|
a.download = normalizeSoonName(fileName || 'design.soon');
|
|
a.href = URL.createObjectURL(blob);
|
|
a.click();
|
|
URL.revokeObjectURL(a.href);
|
|
});
|
|
});
|
|
}
|
|
|
|
function isSoonSaveDialog(options) {
|
|
if (options && options.soonFile === true) return true;
|
|
if (options && options.soonFile === false) return false;
|
|
var raw = (options && options.defaultPath) ? String(options.defaultPath) : '';
|
|
if (!raw) return true;
|
|
if (typeof window.soonIsTemplateKey === 'function' && window.soonIsTemplateKey(raw)) return true;
|
|
if (raw.indexOf('soondesign_file:') === 0 || raw.indexOf('soondesign_session:') === 0) return true;
|
|
var filters = options && options.filters;
|
|
if (filters && Array.isArray(filters)) {
|
|
for (var i = 0; i < filters.length; i++) {
|
|
var exts = filters[i].extensions;
|
|
if (exts && exts.indexOf('soon') >= 0) return true;
|
|
}
|
|
return false;
|
|
}
|
|
return /\.soon$/i.test(raw.split(/[/\\]/).pop() || '');
|
|
}
|
|
|
|
function genericSaveDialogName(raw) {
|
|
var n = String(raw || 'file').trim();
|
|
return n.split(/[/\\]/).pop() || n || 'file';
|
|
}
|
|
|
|
function resolveSaveDialogName(name, options, isSoon) {
|
|
var n = String(name || '').trim();
|
|
if (!n) n = isSoon ? 'design.soon' : 'file';
|
|
if (isSoon) {
|
|
return typeof window.soonEnsureSoonExt === 'function'
|
|
? window.soonEnsureSoonExt(n)
|
|
: normalizeSoonName(n);
|
|
}
|
|
return genericSaveDialogName(n);
|
|
}
|
|
|
|
function defaultSoonSaveDialogName(raw) {
|
|
if (!raw || (typeof window.soonIsTemplateKey === 'function' && window.soonIsTemplateKey(raw))) {
|
|
return typeof window.soonDefaultNewSoonName === 'function'
|
|
? window.soonDefaultNewSoonName()
|
|
: normalizeSoonName('design.soon');
|
|
}
|
|
if (raw.indexOf('soondesign_file:') === 0 && typeof window.soonDisplayFileName === 'function') {
|
|
return window.soonDisplayFileName(raw);
|
|
}
|
|
return normalizeSoonName(raw);
|
|
}
|
|
|
|
var bridge = {
|
|
readHistory: function () {
|
|
try {
|
|
var raw = localStorage.getItem(HISTORY_KEY);
|
|
return Promise.resolve(raw ? JSON.parse(raw) : { history: [] });
|
|
} catch (e) {
|
|
return Promise.resolve({ history: [] });
|
|
}
|
|
},
|
|
writeHistory: function (data) {
|
|
try {
|
|
localStorage.setItem(HISTORY_KEY, JSON.stringify(data));
|
|
return Promise.resolve({ success: true });
|
|
} catch (e) {
|
|
return Promise.resolve({ success: false, error: e.message });
|
|
}
|
|
},
|
|
listCloudFiles: listCloudFiles,
|
|
createCloudFile: createCloudFile,
|
|
updateCloudFile: updateCloudFile,
|
|
deleteCloudFile: deleteCloudFile,
|
|
importSoonFile: importSoonFile,
|
|
downloadCloudFile: downloadCloudFile,
|
|
requireCloudAuth: requireCloudAuth,
|
|
readJsonFile: function (pathOrHandle) {
|
|
if (!pathOrHandle) return Promise.resolve(null);
|
|
if (typeof pathOrHandle === 'object' && pathOrHandle.text) {
|
|
return pathOrHandle.text().then(function (t) {
|
|
try { return JSON.parse(t); } catch (e) { return null; }
|
|
});
|
|
}
|
|
var key = typeof pathOrHandle === 'string' ? pathOrHandle : '';
|
|
var cacheKey = key;
|
|
|
|
function cacheHitThen(hit) {
|
|
if (!hit || !hit.json) return null;
|
|
if (typeof window.soonLocalIsTemplateStale === 'function' &&
|
|
window.soonLocalIsTemplateStale(cacheKey, hit.meta)) {
|
|
if (typeof window.soonLocalRemove === 'function') {
|
|
return window.soonLocalRemove(cacheKey).then(function () { return null; });
|
|
}
|
|
return Promise.resolve(null);
|
|
}
|
|
if (cacheKey.indexOf('soondesign_file:') === 0 && hit.meta && hit.meta.name &&
|
|
typeof window.soonBindFileMeta === 'function') {
|
|
window.soonBindFileMeta(cacheKey, { name: hit.meta.name });
|
|
}
|
|
// 兜底:design2(背面文件)若缓存里 j.b 缺失,主动补一个空背面结构,
|
|
// 避免 design2.canvas2.loadFromJSON(undefined) 抛错或吃掉整段加载。
|
|
if ((hit.json.soonType === 2 || hit.json.backBlackPic || (hit.json.bo && hit.json.bo.length) || (hit.json.f && hit.json.b === undefined && typeof hit.json.b === 'undefined'))) {
|
|
if (!hit.json.b || typeof hit.json.b !== 'object' || !Array.isArray(hit.json.b.objects)) {
|
|
hit.json.b = { version: '4.6.0', objects: [], hoverCursor: 'move' };
|
|
}
|
|
}
|
|
return Promise.resolve(hit.json);
|
|
}
|
|
|
|
function afterNetworkJson(json, meta) {
|
|
if (!json) return null;
|
|
if (typeof window.soonLocalCacheAndThumb === 'function') {
|
|
return window.soonLocalCacheAndThumb(cacheKey, json, meta || {}).then(function () { return json; });
|
|
}
|
|
return json;
|
|
}
|
|
|
|
if (key && typeof window.soonLocalGet === 'function') {
|
|
return window.soonLocalGet(cacheKey).then(function (hit) {
|
|
return cacheHitThen(hit).then(function (cached) {
|
|
if (cached) return cached;
|
|
return fetchFromNetwork(key, cacheKey);
|
|
});
|
|
}).catch(function () { return fetchFromNetwork(key, cacheKey); });
|
|
}
|
|
return fetchFromNetwork(key, cacheKey);
|
|
|
|
function readSessionJson(key, attempt) {
|
|
attempt = attempt || 0;
|
|
if (typeof window.soonLocalGet === 'function') {
|
|
return window.soonLocalGet(cacheKey).then(function (hit) {
|
|
if (hit && hit.json) return hit.json;
|
|
var raw = null;
|
|
try {
|
|
raw = sessionStorage.getItem(key);
|
|
if (!raw && typeof localStorage !== 'undefined') raw = localStorage.getItem(key);
|
|
if (raw && raw !== 'idb') return JSON.parse(raw);
|
|
} catch (e) { /* ignore */ }
|
|
if (raw === 'idb' && attempt < 8) {
|
|
return new Promise(function (resolve) {
|
|
setTimeout(resolve, 60);
|
|
}).then(function () { return readSessionJson(key, attempt + 1); });
|
|
}
|
|
return null;
|
|
});
|
|
}
|
|
try {
|
|
var j = sessionStorage.getItem(key);
|
|
if (!j && typeof localStorage !== 'undefined') j = localStorage.getItem(key);
|
|
if (!j || j === 'idb') return Promise.resolve(null);
|
|
return Promise.resolve(JSON.parse(j));
|
|
} catch (e) { return Promise.resolve(null); }
|
|
}
|
|
|
|
function fetchFromNetwork(key, cacheKey) {
|
|
if (key.indexOf('soondesign_session:') === 0) {
|
|
return readSessionJson(key, 0).then(function (parsed) {
|
|
if (!parsed) return null;
|
|
return afterNetworkJson(parsed, { source: 'session', name: key });
|
|
});
|
|
}
|
|
if (key && key.indexOf('soondesign_file:') === 0 && typeof fetch !== 'undefined') {
|
|
var fileMatch = key.match(/^soondesign_file:(\d+)/);
|
|
if (fileMatch && getAccessToken()) {
|
|
return authedFetch('files/' + fileMatch[1], { headers: { Accept: 'application/json' } })
|
|
.then(function (response) {
|
|
if (!response.ok) return null;
|
|
return response.json();
|
|
})
|
|
.then(function (payload) {
|
|
if (!payload || !payload.ok || !payload.data) return null;
|
|
var raw = payload.data.json;
|
|
var parsed;
|
|
if (typeof raw === 'string') {
|
|
try { parsed = JSON.parse(raw); } catch (e) { return null; }
|
|
} else {
|
|
parsed = raw && typeof raw === 'object' ? raw : null;
|
|
}
|
|
if (!parsed) return null;
|
|
if (typeof window.soonApplyCloudMeta === 'function') {
|
|
window.soonApplyCloudMeta(payload.data);
|
|
}
|
|
return afterNetworkJson(parsed, {
|
|
source: 'cloud',
|
|
name: payload.data.name || '',
|
|
type: parsed.soonType || 1,
|
|
updatedAt: payload.data.updated_at || ''
|
|
});
|
|
})
|
|
.catch(function () { return null; });
|
|
}
|
|
}
|
|
if (key && key.indexOf('soondesign_template:') === 0 && typeof fetch !== 'undefined') {
|
|
var tplMatch = key.match(/^soondesign_template:(\d+)/);
|
|
if (tplMatch) {
|
|
var apiBase = (window.SOON_DEPLOY_CONFIG && window.SOON_DEPLOY_CONFIG.api_v1_base) || '';
|
|
if (!apiBase) return Promise.resolve(null);
|
|
return fetch(apiBase + '/templates/' + tplMatch[1] + '/file', { headers: { Accept: 'application/json' } })
|
|
.then(function (response) {
|
|
if (!response.ok) return null;
|
|
return response.text();
|
|
})
|
|
.then(function (text) {
|
|
if (!text) return null;
|
|
var parsed;
|
|
try { parsed = JSON.parse(text); } catch (e) { return null; }
|
|
var tplMeta = window._soonTemplateMeta || {};
|
|
return afterNetworkJson(parsed, {
|
|
source: 'template',
|
|
name: tplMeta.name || '',
|
|
type: tplMeta.type || (parsed.soonType || 1),
|
|
updatedAt: tplMeta.updated_at || ''
|
|
});
|
|
})
|
|
.catch(function () { return null; });
|
|
}
|
|
}
|
|
return Promise.resolve(null);
|
|
}
|
|
},
|
|
showOpenDialog: function (options) {
|
|
return new Promise(function (resolve) {
|
|
var input = document.createElement('input');
|
|
input.type = 'file';
|
|
if (options && options.filters && Array.isArray(options.filters) && options.filters.length > 0) {
|
|
var acceptList = [];
|
|
options.filters.forEach(function (filter) {
|
|
if (filter.extensions && Array.isArray(filter.extensions)) {
|
|
filter.extensions.forEach(function (ext) {
|
|
var cleanExt = ext.replace(/^\./, '');
|
|
if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg'].indexOf(cleanExt.toLowerCase()) >= 0) {
|
|
var mimeType = 'image/' + (cleanExt.toLowerCase() === 'jpg' ? 'jpeg' : cleanExt.toLowerCase());
|
|
if (acceptList.indexOf(mimeType) < 0) acceptList.push(mimeType);
|
|
} else {
|
|
var extWithDot = '.' + cleanExt;
|
|
if (acceptList.indexOf(extWithDot) < 0) acceptList.push(extWithDot);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
input.accept = acceptList.length > 0 ? acceptList.join(',') : '';
|
|
} else {
|
|
input.accept = '.soon,application/json';
|
|
}
|
|
input.style.display = 'none';
|
|
input.onchange = function () {
|
|
var f = input.files && input.files[0];
|
|
document.body.removeChild(input);
|
|
if (!f) resolve({ canceled: true });
|
|
else resolve({ canceled: false, filePaths: [], files: [f], file: f });
|
|
};
|
|
document.body.appendChild(input);
|
|
input.click();
|
|
});
|
|
},
|
|
showSaveDialog: function (options) {
|
|
var raw = (options && options.defaultPath) ? options.defaultPath : '';
|
|
var isSoon = isSoonSaveDialog(options);
|
|
var defaultName = isSoon ? defaultSoonSaveDialogName(raw) : genericSaveDialogName(raw);
|
|
var title = (options && options.title) ? options.title : (isSoon ? '保存到云端' : '保存文件');
|
|
return new Promise(function (resolve) {
|
|
if (typeof layer === 'undefined' || !layer.open) {
|
|
resolve({ canceled: false, filePath: defaultName, useCloud: !!isSoon });
|
|
return;
|
|
}
|
|
var esc = function (s) {
|
|
return String(s || '').replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
|
|
};
|
|
var html = '<div class="soon-dialog-body" style="padding:16px 20px;">' +
|
|
'<label style="display:block;margin-bottom:8px;color:#b8c0c8;font-size:13px;">文件名</label>' +
|
|
'<input type="text" id="soonSaveDialogInput" class="layui-input" value="' + esc(defaultName) + '" ' +
|
|
'style="background:#2b3036;border-color:rgba(255,255,255,0.08);color:#eef1f4;">' +
|
|
'</div>';
|
|
layer.open({
|
|
type: 1,
|
|
skin: 'soon-layer',
|
|
title: title,
|
|
area: ['360px', 'auto'],
|
|
shadeClose: false,
|
|
content: html,
|
|
btn: ['确定', '取消'],
|
|
btnAlign: 'r',
|
|
yes: function (index) {
|
|
var input = document.getElementById('soonSaveDialogInput');
|
|
var name = input && input.value ? String(input.value).trim() : defaultName;
|
|
name = resolveSaveDialogName(name, options, isSoon);
|
|
layer.close(index);
|
|
if (typeof window.soonCleanupLayerMask === 'function') {
|
|
setTimeout(function () { window.soonCleanupLayerMask(); }, 0);
|
|
}
|
|
resolve({ canceled: false, filePath: name, useCloud: !!isSoon });
|
|
},
|
|
btn2: function (index) {
|
|
layer.close(index);
|
|
if (typeof window.soonCleanupLayerMask === 'function') {
|
|
setTimeout(function () { window.soonCleanupLayerMask(); }, 0);
|
|
}
|
|
resolve({ canceled: true });
|
|
},
|
|
cancel: function (index) {
|
|
layer.close(index);
|
|
if (typeof window.soonCleanupLayerMask === 'function') {
|
|
setTimeout(function () { window.soonCleanupLayerMask(); }, 0);
|
|
}
|
|
resolve({ canceled: true });
|
|
}
|
|
});
|
|
});
|
|
},
|
|
writeFile: function (pathOrHandle, content) {
|
|
var isBlob = content instanceof Blob;
|
|
var str = typeof content === 'string' ? content : (isBlob ? null : (content && content.toString ? content.toString() : ''));
|
|
|
|
if (isBlob) {
|
|
var blobName = typeof pathOrHandle === 'string' ? pathOrHandle : 'output.bin';
|
|
var a = document.createElement('a');
|
|
a.download = blobName;
|
|
a.href = URL.createObjectURL(content);
|
|
a.click();
|
|
URL.revokeObjectURL(a.href);
|
|
return Promise.resolve();
|
|
}
|
|
|
|
if (pathOrHandle && pathOrHandle.createWritable) {
|
|
pathOrHandle = typeof pathOrHandle.name === 'string' ? pathOrHandle.name : 'design.soon';
|
|
}
|
|
|
|
var name = typeof pathOrHandle === 'string' ? pathOrHandle : 'design.soon';
|
|
var cloudRef = parseCloudRef(name);
|
|
var fileName = normalizeSoonName(name);
|
|
|
|
var parsedJson;
|
|
try { parsedJson = JSON.parse(str); } catch (e) { parsedJson = null; }
|
|
|
|
function cacheWriteForKey(targetKey, meta) {
|
|
if (!parsedJson || typeof window.soonLocalCacheAndThumb !== 'function') return Promise.resolve();
|
|
return window.soonLocalCacheAndThumb(targetKey, parsedJson, meta || {});
|
|
}
|
|
|
|
function afterCloudSave(res, prevKey) {
|
|
if (!res || !res.fileKey || !parsedJson) return res;
|
|
var meta = { source: 'cloud', name: res.name || fileName, type: parsedJson.soonType || 1 };
|
|
if (prevKey && prevKey !== res.fileKey && typeof window.soonLocalRenameKey === 'function') {
|
|
return window.soonLocalRenameKey(prevKey, res.fileKey, meta).then(function () { return res; });
|
|
}
|
|
return cacheWriteForKey(res.fileKey, meta).then(function () { return res; });
|
|
}
|
|
|
|
function resolveLocalSaveKey() {
|
|
if (name.indexOf('soondesign_session:') === 0) return name;
|
|
if (name.indexOf('soondesign_file:') === 0) return name;
|
|
if (typeof window.soonMakeSessionKey === 'function') {
|
|
return window.soonMakeSessionKey(name);
|
|
}
|
|
return 'soondesign_session:' + fileName.replace(/\.soon$/i, '');
|
|
}
|
|
|
|
function localSaveMeta(localKey) {
|
|
if (localKey.indexOf('soondesign_file:') === 0) {
|
|
return { source: 'cloud', name: fileName, type: parsedJson ? parsedJson.soonType : 1 };
|
|
}
|
|
return { source: 'session', name: fileName, type: parsedJson ? parsedJson.soonType : 1 };
|
|
}
|
|
|
|
function finishLocalSave(localKey) {
|
|
if (localKey.indexOf('soondesign_session:') === 0) {
|
|
try { sessionStorage.setItem(localKey, 'idb'); } catch (e) { /* ignore */ }
|
|
}
|
|
return { fileKey: localKey, name: fileName, version: 0, localOnly: true };
|
|
}
|
|
|
|
function saveLocalFirst() {
|
|
var localKey = resolveLocalSaveKey();
|
|
var prevKey = (name.indexOf('soondesign_session:') === 0 || name.indexOf('soondesign_file:') === 0) ? name : '';
|
|
var meta = localSaveMeta(localKey);
|
|
if (prevKey && prevKey !== localKey && typeof window.soonLocalRenameKey === 'function') {
|
|
return cacheWriteForKey(localKey, meta).then(function () {
|
|
return window.soonLocalRenameKey(prevKey, localKey, meta).then(function () {
|
|
return finishLocalSave(localKey);
|
|
});
|
|
});
|
|
}
|
|
return cacheWriteForKey(localKey, meta).then(function () {
|
|
return finishLocalSave(localKey);
|
|
});
|
|
}
|
|
|
|
function syncCloudIfAuthed(localResult) {
|
|
if (!getAccessToken()) return Promise.resolve(localResult);
|
|
if (cloudRef && cloudRef.id) {
|
|
var ver = cloudRef.version;
|
|
if (ver == null && window._soonFileMeta && window._soonFileMeta.id === cloudRef.id) {
|
|
ver = window._soonFileMeta.version;
|
|
}
|
|
var cloudSaveName = fileName;
|
|
if (typeof window.soonResolveCloudFileName === 'function') {
|
|
var resolved = window.soonResolveCloudFileName(name);
|
|
if (resolved) cloudSaveName = resolved;
|
|
}
|
|
return updateCloudFile(cloudRef.id, cloudSaveName, str, ver).then(function (res) {
|
|
return afterCloudSave(res, localResult.fileKey || name);
|
|
}).catch(function () {
|
|
localResult.syncFailed = true;
|
|
return localResult;
|
|
});
|
|
}
|
|
var prevKey = localResult.fileKey || name;
|
|
return createCloudFile(fileName, str).then(function (res) {
|
|
return afterCloudSave(res, prevKey);
|
|
}).catch(function () {
|
|
localResult.syncFailed = true;
|
|
return localResult;
|
|
});
|
|
}
|
|
|
|
return saveLocalFirst().then(syncCloudIfAuthed).catch(function () {
|
|
return Promise.reject(new Error('save_failed'));
|
|
});
|
|
},
|
|
readFile: function (pathOrHandle) {
|
|
if (pathOrHandle && pathOrHandle.getFile) {
|
|
return pathOrHandle.getFile().then(function (f) {
|
|
return new Promise(function (res, rej) {
|
|
var r = new FileReader();
|
|
r.onload = function () { res(r.result); };
|
|
r.onerror = rej;
|
|
r.readAsArrayBuffer(f);
|
|
});
|
|
});
|
|
}
|
|
if (pathOrHandle && pathOrHandle.arrayBuffer) {
|
|
return pathOrHandle.arrayBuffer();
|
|
}
|
|
return Promise.reject(new Error('No file'));
|
|
},
|
|
getAppVersion: function () { return Promise.resolve(VERSION); },
|
|
getLocale: getLocale,
|
|
getUserDataPath: function () { return ''; },
|
|
getSystemFonts: getSystemFonts,
|
|
openDesignPage: function (file, type) {
|
|
var t = type || 1;
|
|
var key = file || '';
|
|
if (typeof window.soonSyncOpenNavigation === 'function') {
|
|
window.soonSyncOpenNavigation(key, t);
|
|
}
|
|
var fileParam = key ? encodeURIComponent(key) : '';
|
|
navigateToPage('design' + t + '.web.html?file=' + fileParam + '&type=' + t);
|
|
},
|
|
openFirstPage: function () {
|
|
navigateToPage('index.web.html');
|
|
},
|
|
onClose: function (callback) {
|
|
if (typeof window !== 'undefined') {
|
|
window.addEventListener('beforeunload', callback);
|
|
}
|
|
},
|
|
runClose: function () {
|
|
if (typeof window !== 'undefined' && window.close) window.close();
|
|
},
|
|
openHelp: function () {
|
|
try {
|
|
window.open(new URL('../assets/help/User Manual.pdf', window.location.href).href, '_blank');
|
|
} catch (e) {
|
|
window.open('../assets/help/User Manual.pdf', '_blank');
|
|
}
|
|
},
|
|
printPdf: function (urlOrBlob) {
|
|
var url = urlOrBlob;
|
|
if (urlOrBlob && typeof urlOrBlob === 'object' && !(urlOrBlob instanceof String)) {
|
|
url = URL.createObjectURL(urlOrBlob);
|
|
}
|
|
var w = window.open(url, '_blank');
|
|
if (w) w.onload = function () { w.print(); };
|
|
},
|
|
getScaleRate: function () {
|
|
return typeof window !== 'undefined' && window.devicePixelRatio ? window.devicePixelRatio : 1;
|
|
},
|
|
clipboard: {
|
|
readText: function () {
|
|
return navigator.clipboard && navigator.clipboard.readText ? navigator.clipboard.readText() : Promise.resolve('');
|
|
},
|
|
writeText: function (text) {
|
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
return navigator.clipboard.writeText(text);
|
|
}
|
|
return Promise.resolve();
|
|
}
|
|
}
|
|
};
|
|
|
|
window.platformBridge = bridge;
|
|
|
|
var pathStub = {
|
|
join: function () { return [].slice.call(arguments).join('/').replace(/\/+/g, '/'); }
|
|
};
|
|
|
|
window.sysAPI = {
|
|
readHistory: bridge.readHistory,
|
|
writeHistory: bridge.writeHistory,
|
|
readJsonFile: bridge.readJsonFile,
|
|
getAppVersion: bridge.getAppVersion
|
|
};
|
|
window.dialog = {
|
|
showOpenDialog: bridge.showOpenDialog,
|
|
showSaveDialog: bridge.showSaveDialog
|
|
};
|
|
window.soonShowSaveDialog = bridge.showSaveDialog;
|
|
window.path = pathStub;
|
|
window.fs = null;
|
|
window.remote = null;
|
|
var loc = getLocale();
|
|
window.ipcRenderer = {
|
|
send: function (ch, a1, a2) {
|
|
if (ch === 'get-sys-language') { window._sysLanPending = true; setTimeout(function () { if (window._sysLanCb && window._sysLanPending) { window._sysLanCb(null, loc); window._sysLanPending = false; } }, 0); }
|
|
if (ch === 'get-sys-fonts' && bridge.getSystemFonts) {
|
|
bridge.getSystemFonts().then(function (fonts) {
|
|
if (typeof window._fontListCb === 'function') window._fontListCb(null, fonts || []);
|
|
});
|
|
}
|
|
if (ch === 'open-first-page' && bridge.openFirstPage) { bridge.openFirstPage(); }
|
|
if (ch === 'open-design-page' && bridge.openDesignPage && (a1 !== undefined || a2 !== undefined)) { bridge.openDesignPage(a1 || '', a2 || 1); }
|
|
if (ch === 'open-help-file' && bridge.openHelp) { bridge.openHelp(); }
|
|
if (ch === 'run-close' && bridge.runClose) { bridge.runClose(); }
|
|
},
|
|
on: function (ch, cb) {
|
|
if (ch === 'sys-lan') { window._sysLanCb = cb; if (window._sysLanPending) setTimeout(function () { if (window._sysLanCb) { window._sysLanCb(null, loc); window._sysLanPending = false; } }, 0); }
|
|
if (ch === 'font-list') {
|
|
window._fontListCb = cb;
|
|
if (bridge.getSystemFonts) {
|
|
bridge.getSystemFonts().then(function (fonts) {
|
|
if (typeof window._fontListCb === 'function') window._fontListCb(null, fonts || []);
|
|
});
|
|
}
|
|
}
|
|
if (ch === 'close') window._closeCb = cb;
|
|
}
|
|
};
|
|
window.clipboard = bridge.clipboard;
|
|
window.exePath = '';
|
|
window.fullPath = '';
|
|
})();
|