Files
2025-12-20 17:45:44 +08:00

743 lines
22 KiB
JavaScript

var projectName = "";
var projectPictures = [
// {
// "filepath": "",
// "filename": "",
// "size": 0
// }
];
var projectEditorData;
var bomAccessories = []; //like projectPictures structure
var assemblyAccessories = []; //like projectPictures structure
var otherAccessories = []; //like projectPictures structure
var profileName = "";
var profilePictures = [];
var profileEditorData;
var bakRequestData;
function deepCloneList(list) {
return JSON.parse(JSON.stringify(list || []));
}
function safeDecode(value) {
if (!value) return '';
try {
return decodeURIComponent(value);
} catch (err) {
return value;
}
}
function getBase64ImageFormat(base64Str) {
if (!isBase64String(base64Str)) return null;
const trimmed = base64Str.trim();
const dataUrlMatch = trimmed.match(/^data:(.*?);base64,/i);
let payload = trimmed;
if (dataUrlMatch) {
const mime = (dataUrlMatch[1] || '').toLowerCase();
payload = trimmed.slice(dataUrlMatch[0].length);
if (mime.indexOf('image/png') >= 0) return 'png';
if (mime.indexOf('image/jpeg') >= 0 || mime.indexOf('image/jpg') >= 0) return 'jpg';
}
const cleanPayload = payload.replace(/\s+/g, '');
try {
const header = atob(cleanPayload.slice(0, 16));
const bytes = header.split('').map(ch => ch.charCodeAt(0));
if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4E && bytes[3] === 0x47) return 'png';
if (bytes[0] === 0xFF && bytes[1] === 0xD8 && bytes[2] === 0xFF) return 'jpg';
} catch (err) {
return null;
}
return null;
}
function normalizeCoverName(value) {
const decoded = safeDecode(value);
if (!decoded) return '';
const segments = decoded.split(/[/\\]/);
return segments[segments.length - 1];
}
function normalizeRequestPayload(raw) {
const safe = raw || {};
const model = safe.model || {};
const file = safe.file || {};
const profile = safe.profile || {};
return {
model: {
name: model.name || "",
description: model.description || "",
preview_img: deepCloneList(model.preview_img),
},
file: {
BOM: deepCloneList(file.BOM),
Assembly: deepCloneList(file.Assembly),
Other: deepCloneList(file.Other),
},
profile: {
name: profile.name || "",
description: profile.description || "",
preview_img: deepCloneList(profile.preview_img),
},
};
}
function getCurrentRequestData() {
getProjectName();
getProjectDescription();
getProfileName();
getProfilePictures();
getProfileDescription();
return {
model: {
name: encodeURIComponent(projectName || ""),
description: encodeURIComponent(projectEditorData || ""),
preview_img: deepCloneList(projectPictures),
},
file: {
BOM: deepCloneList(bomAccessories),
Assembly: deepCloneList(assemblyAccessories),
Other: deepCloneList(otherAccessories),
},
profile: {
name: encodeURIComponent(profileName || ""),
description: encodeURIComponent(profileEditorData || ""),
preview_img: deepCloneList(profilePictures),
},
};
}
const editorUploadRequests = new Map();
$(function () {
TranslatePage();
addAccessoryBtnListener();
addPictureUploadListener(
'projectImageInput',
'imageList',
projectPictures,
['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/jpg'],
4 * 1024 * 1024
);
addAccessoryUploadListener(
'bom-input',
'bom-list',
bomAccessories,
{
'.xls': 10 * 1024 * 1024,
'.xlsx': 10 * 1024 * 1024,
'.pdf': 50 * 1024 * 1024
},
10
);
addAccessoryUploadListener(
'assembly-guide-input',
'assembly-list',
assemblyAccessories,
{
'.jpg': 10 * 1024 * 1024,
'.png': 10 * 1024 * 1024,
'.pdf': 50 * 1024 * 1024
},
25
);
addAccessoryUploadListener(
'other-input',
'other-list',
otherAccessories,
{
'.txt': 10 * 1024 * 1024,
},
10
);
addPictureUploadListener(
'ProfileImageInput',
'profileImageList',
profilePictures,
['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/jpg'],
4 * 1024 * 1024
);
// TestProjectData.model.preview_img=[];
// updateInfo(TestProjectData);
RequestProjectInfo();
});
function isFormEmpty() {
getProjectName();
getProjectDescription();
getProfileName();
getProfilePictures();
getProfileDescription();
const noBasics = !projectName.trim() && !projectEditorData?.trim();
const noProjectPics = projectPictures.length === 0;
const noAttachments = bomAccessories.length === 0 &&
assemblyAccessories.length === 0 &&
otherAccessories.length === 0;
const noProfileInfo = !profileName.trim() &&
!profileEditorData?.trim() &&
profilePictures.length === 0;
return noBasics && noProjectPics && noAttachments && noProfileInfo;
}
function isChange() {
if (!bakRequestData && isFormEmpty()) {
return false;
}
const baseline = normalizeRequestPayload(bakRequestData);
const current = normalizeRequestPayload(getCurrentRequestData());
return JSON.stringify(baseline) !== JSON.stringify(current);
}
function saveInfo() {
let modelData = {};
getProjectName();
if (!projectName) {
showToast("The project name is empty.");
return;
}
modelData["name"] = encodeURIComponent(projectName);
if (projectPictures.length <= 0) {
showToast("The project pictures is empty.");
return;
}
modelData["preview_img"] = projectPictures;
getProjectDescription();
if (!projectEditorData) {
showToast("The project description is empty.");
return;
}
modelData["description"] = encodeURIComponent(projectEditorData);
let fileData = {
BOM: bomAccessories,
Assembly: assemblyAccessories,
Other: otherAccessories
};
getProfileName();
getProfilePictures();
getProfileDescription();
let profileData = {
name: encodeURIComponent(profileName),
preview_img: profilePictures,
description: encodeURIComponent(profileEditorData)
};
let updateData = {
model: modelData,
file: fileData,
profile: profileData
};
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="update_3mf_info";
tSend['model'] = updateData;
SendWXMessage( JSON.stringify(tSend) );
}
function updateInfo(p3MF) {
projectName = DOMPurify.sanitize(decodeURIComponent(p3MF.model.name));
projectPictures.length = 0;
Array.prototype.push.apply(projectPictures, p3MF.model.preview_img || []);
let projectCover = normalizeCoverName(p3MF.model.cover_img || "");
for (let i = 0; i < projectPictures.length; i++) {
const pictureName = normalizeCoverName(projectPictures[i].filename);
if (projectCover && pictureName === projectCover){
const [item] = projectPictures.splice(i, 1);
projectPictures.unshift(item);
break;
}
}
projectEditorData = decodeURIComponent(p3MF.model.description) || '';
bomAccessories.length = 0;
Array.prototype.push.apply(bomAccessories, p3MF.file.BOM || []);
assemblyAccessories.length = 0;
Array.prototype.push.apply(assemblyAccessories, p3MF.file.Assembly || []);
otherAccessories.length = 0;
Array.prototype.push.apply(otherAccessories, p3MF.file.Other || []);
profileName = DOMPurify.sanitize(decodeURIComponent(p3MF.profile.name));
profilePictures.length = 0;
Array.prototype.push.apply(profilePictures, p3MF.profile.preview_img || []);
let profileCover = normalizeCoverName(p3MF.profile.cover_img || "");
for (let i = 0; i < profilePictures.length; i++) {
const pictureName = normalizeCoverName(profilePictures[i].filename);
if (profileCover && pictureName === profileCover){
const [item] = profilePictures.splice(i, 1);
profilePictures.unshift(item);
break;
}
}
profileEditorData = decodeURIComponent(p3MF.profile.description) || '';
setProjectName();
setProjectPictrues();
setProjectDescription();
setAccessor();
setProfileName();
setProfilePictures();
setProfileDescription();
}
function setProjectName() {
$("#projectNameInput").val(projectName);
}
function getProjectName() {
projectName = $("#projectNameInput").val();
}
function setProjectPictrues() {
setPictures('imageList', projectPictures);
}
function getProjectPictures() {
return projectPictures;
}
function setProjectDescription() {
if (window.projectEditor) {
projectEditor.setData(projectEditorData || '');
}
}
function getProjectDescription() {
if (window.projectEditor) {
projectEditorData = projectEditor.getData();
}
}
function setAccessor() {
setAccessories('bom-list', bomAccessories);
setAccessories('assembly-list', assemblyAccessories);
setAccessories('other-list', otherAccessories);
}
function setProfileName() {
$("#ProfileNameInput").val(profileName);
}
function getProfileName() {
profileName = $("#ProfileNameInput").val();
}
function setProfilePictures() {
setPictures('profileImageList', profilePictures);
}
function getProfilePictures() {
return profilePictures;
}
function setProfileDescription() {
if (window.profileEditor) {
profileEditor.setData(profileEditorData || '');
}
}
function getProfileDescription() {
if (window.profileEditor) {
profileEditorData = profileEditor.getData();
}
}
//Pictures tool
function setPictures(id, picturesList) {
let updateHtml = "";
for (let i = 0; i < picturesList.length; i++) {
let pic_filepath = picturesList[i].filepath;
if(picturesList[i].base64) {
pic_filepath = picturesList[i].base64;
}
if (i == 0) {
let html = `<div class="imagePreview" data-index="${i}" style="background-image: url('${pic_filepath}')">
<img src="img/img_del.svg" />
<div class="modelCover">Model cover</div>
</div>`;
updateHtml = html + updateHtml;
}else {
let html = `<div class="imagePreview" data-index="${i}" style="background-image: url('${pic_filepath}')">
<img src="img/img_del.svg" />
<div class="setModelCover">Set as cover</div>
</div>`;
updateHtml += html;
}
}
$(`#${id}`).html(updateHtml);
$(`#${id}`).off('click', 'img');
$(`#${id}`).on('click', 'img', function (event) {
let index = parseInt($(this).parent().data('index'));
removePictureAt(index, picturesList);
setPictures(id, picturesList);
});
$(`#${id}`).off('click', '.setModelCover');
$(`#${id}`).on('click', '.setModelCover', function (event) {
let index = parseInt($(this).parent().data('index'));
const [item] = picturesList.splice(index, 1);
picturesList.unshift(item);
setPictures(id, picturesList);
});
}
function removePictureAt(index, picturesList) {
if (index < 0 || index >= picturesList.length) {
return;
}
picturesList.splice(index, 1);
}
function addPictureUploadListener(inputId, showPictrueId, picturesList, allowTypes, maxSize, maxCount) {
const input = document.getElementById(inputId);
// input.removeEventListener('click');
input.addEventListener('click', () => {
input.value = '';
});
// input.removeEventListener('change');
input.addEventListener('change', function (event) {
const [file] = event.target.files;
if (!file) return;
// Validate file type
if (allowTypes && !allowTypes.includes(file.type)) {
showToast(GetCurrentTextByKey("t144"));
return;
}
// Validate file size
if (maxSize && file.size > maxSize) {
showToast(GetCurrentTextByKey("t145"));
return;
}
if (picturesList.length >= maxCount) {
showToast(GetCurrentTextByKey("t146"));
return;
}
uploadFileToCpp(file).then(result => {
if (result && result.path) {
fileToThumbnailBase64(file).then((base64) => {
picturesList.push({
"filepath": result.path,
"filename": file.name,
"size": file.size,
"base64": base64
});
setPictures(showPictrueId, picturesList);
// showToast('add file1'+projectPictures.length);
});
}
}).catch(error => {
showToast(GetCurrentTextByKey("t147"));
});
});
}
//accessories tool
function addAccessoryBtnListener() {
$("#accessories-btn").on("click", function (e) {
e.stopPropagation();
$(".accessory-rule-wrapper").toggle();
});
$(document).on('click', function (e) {
if (!$(e.target).closest('#accessories-btn, .accessory-rule-wrapper').length) {
$('.accessory-rule-wrapper').hide();
}
});1
}
function setAccessories(id, accessoriesList) {
let updateHtml = "";
if (accessoriesList.length > 0) {
$(`#${id}`).prev().show();
}else {
$(`#${id}`).prev().hide();
}
for (let i = 0; i < accessoriesList.length; i++) {
let acc_filepath = accessoriesList[i].filepath;
let type = "";
if (accessoriesList[i].type) {
type = getFileTail(accessoriesList[i].type);
}else {
type = getFileType(acc_filepath);
}
let iconPath = `img/icon_${type}.svg`;
let html = `<div class="attachment" data-index="${i}"><img class="attachment-icon" src="${iconPath}">${decodeURIComponent(accessoriesList[i].filename)}<img class="attachment-delete" src="img/del.svg"></div>`;
updateHtml += html;
}
$(`#${id}`).html(updateHtml);
$(`#${id}`).children('.attachment').each(function(idx) {
$(this).data('path', accessoriesList[idx]?.filepath || '');
});
$(`#${id}`).prev().children('label').text(accessoriesList.length);
$(`#${id}`).off('click', '.attachment-delete');
$(`#${id}`).on('click', '.attachment-delete', function (event) {
event.stopPropagation();
let index = parseInt($(this).parent().data('index'));
removeAccessoryAt(index, accessoriesList);
setAccessories(id, accessoriesList);
});
$(`#${id}`).off('click', '.attachment');
$(`#${id}`).on('click', '.attachment', function (event) {
if ($(event.target).closest('.attachment-delete').length) return;
const path = $(this).data('path');
OnClickOpenFile(event, path);
});
}
function removeAccessoryAt(index, accessoriesList) {
if (index < 0 || index >= accessoriesList.length) {
return;
}
accessoriesList.splice(index, 1);
}
function getFileTail(filetype) {
switch(filetype) {
case 'application/pdf':
return 'pdf';
case 'text/plain':
return 'txt';
case 'image/jpeg':
return 'jpg';
case 'image/png':
return 'png';
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
return 'xcl';
case 'application/vnd.ms-excel':
return 'xcl';
default:
return '';
}
}
function getFileType(filepath) {
let parts = filepath.split('.');
if (parts.length > 1) {
switch (parts[parts.length - 1].toLowerCase()) {
case 'pdf':
return 'pdf';
case 'txt':
return 'txt';
case 'jpg':
return 'jpg';
case 'jpet':
return 'jpg';
case 'png':
return 'png';
case 'xlsx':
case 'xls':
return 'xcl';
}
}
return getBase64ImageFormat(filepath);
}
function addAccessoryUploadListener(inputId, showAccessoryId, accessoriesList, allowTypes, maxCount) {
const input = document.getElementById(inputId);
input.addEventListener('click', () => {
input.value = '';
});
input.addEventListener('change', function (event) {
const [file] = event.target.files;
if (!file) return;
const lowerName = file.name.toLowerCase();
const matchedExt = Object.keys(allowTypes).find(ext => lowerName.endsWith(ext));
if (!matchedExt) {
showToast(GetCurrentTextByKey("t144"));
return;
}
const maxSize = allowTypes[matchedExt];
if (maxSize && file.size > maxSize) {
showToast(GetCurrentTextByKey("t145"));
return;
}
if (accessoriesList.length >= maxCount) {
showToast(GetCurrentTextByKey("t146"));
return;
}
uploadFileToCpp(file).then(result => {
if (result && result.path) {
accessoriesList.push({
"filepath": result.path,
"filename": file.name,
"size": file.size,
"type": file.type
});
setAccessories(showAccessoryId, accessoriesList);
}
}).catch(error => {
showToast(GetCurrentTextByKey("t147"));
});
});
}
//common tool
function generateSequenceId() {
return `${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
}
function uploadFileToCpp(file) {
return new Promise((resolve, reject) => {
if (!(file instanceof File)) {
reject(new Error('invalid file object'));
return;
}
const reader = new FileReader();
const sequenceId = generateSequenceId();
reader.onload = () => {
let result = reader.result;
let base64Payload = '';
if (typeof result === 'string') {
const commaIndex = result.indexOf(',');
base64Payload = commaIndex >= 0 ? result.substring(commaIndex + 1) : result;
} else if (result instanceof ArrayBuffer) {
const bytes = new Uint8Array(result);
let binary = '';
const chunk = 0x8000;
for (let i = 0; i < bytes.length; i += chunk) {
binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk));
}
base64Payload = btoa(binary);
} else {
reject(new Error('unable to read file content'));
return;
}
const message = {
sequence_id: sequenceId,
command: 'editor_upload_file',
data: {
filename: file.name || '',
size: file.size || 0,
type: file.type || '',
base64: base64Payload
}
};
editorUploadRequests.set(sequenceId, { resolve, reject });
const canCallHost =
typeof window !== 'undefined' &&
typeof window.wx === 'object' &&
typeof window.wx.postMessage === 'function';
if (typeof SendWXMessage === 'function' && canCallHost) {
try {
SendWXMessage(JSON.stringify(message));
} catch (error) {
editorUploadRequests.delete(sequenceId);
reject(error);
}
} else {
editorUploadRequests.delete(sequenceId);
const fallbackPath = URL.createObjectURL(file);
resolve({
path: fallbackPath,
isMock: true
});
}
};
reader.onerror = () => {
reject(reader.error || new Error('failed to read file'));
};
reader.readAsDataURL(file);
});
}
function fileToThumbnailBase64(file, maxWidth = 200, quality = 0.85) {
return new Promise((resolve, reject) => {
if (!file || !file.type.startsWith('image/')) {
reject(new Error('select a valid image file'));
return;
}
const reader = new FileReader();
reader.onload = e => {
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = e.target.result;
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const scale = Math.min(1, maxWidth / img.width);
const width = img.width * scale;
const height = img.height * scale;
canvas.width = width;
canvas.height = height;
if (file.type === 'image/png' || file.type === 'image/webp') {
ctx.clearRect(0, 0, width, height);
} else {
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, width, height);
}
ctx.drawImage(img, 0, 0, width, height);
const outputType = ['image/png', 'image/jpeg', 'image/webp'].includes(file.type)
? file.type
: 'image/png';
const base64 = canvas.toDataURL(outputType, quality);
resolve(base64);
};
img.onerror = () => reject(new Error('invalid image file'));
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
function handleEditorMessage(rawMessage) {
let payload = rawMessage;
if (typeof rawMessage === 'string') {
try {
payload = JSON.parse(rawMessage);
} catch (error) {
return;
}
}
if (!payload || typeof payload !== 'object') {
return;
}
const command = payload.command;
if (command === 'editor_upload_file_result') {
const sequenceId = payload.sequence_id;
if (!sequenceId || !editorUploadRequests.has(sequenceId)) {
return;
}
const { resolve, reject } = editorUploadRequests.get(sequenceId);
editorUploadRequests.delete(sequenceId);
if (payload.error) {
reject(new Error(payload.error));
return;
}
resolve(payload.data || {});
return;
}
if (command === 'save_project') {
saveInfo();
return;
}
if (command === 'discard_project') {
window.location.href = 'index.html';
return;
}
if (command === 'update_3mf_info_result') {
if (payload.error) {
showToast(payload.error);
return;
}
bakRequestData = JSON.parse(JSON.stringify(getCurrentRequestData()));
showToast(payload.message || 'Saved successfully.');
window.location.href = "index.html";
}
}
function OnClickOpenFile( evt, strFullPath ) {
if (!strFullPath) return;
if (evt && $(evt.target).closest('.attachment-delete').length) {
return;
}
if(isBase64String(strFullPath)) {
showBase64ImageLayer(strFullPath);
return;
}
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="open_3mf_accessory";
tSend['accessory_path']=strFullPath;
SendWXMessage( JSON.stringify(tSend) );
SendWXDebugInfo('----open accessory: '+strFullPath);
}
function returnBtn() {
if (isChange()) {
confirmSave();
}else {
if (bakRequestData) {
window.location.href = "index.html";
}else {
window.location.href = "black.html";
}
}
}
function confirmSave() {
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="request_confirm_save_project";
SendWXMessage( JSON.stringify(tSend) );
}
function RequestProjectInfo()
{
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="request_3mf_info";
SendWXMessage( JSON.stringify(tSend) );
}
function HandleStudio(pVal)
{
let strCmd=pVal['command'];
if(strCmd=='show_3mf_info')
{
updateInfo( pVal['model'] );
bakRequestData = JSON.parse(JSON.stringify(getCurrentRequestData()));
}
}
window.HandleEditor = handleEditorMessage;