(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(/' +
'' +
'' +
'';
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 = '';
})();