mirror of
https://github.com/QIDITECH/QIDIStudio.git
synced 2026-01-31 08:58:42 +03:00
update resources\web
This commit is contained in:
174
resources/web/model_new/js/ckeditor_config.js
Normal file
174
resources/web/model_new/js/ckeditor_config.js
Normal file
@@ -0,0 +1,174 @@
|
||||
const {
|
||||
ClassicEditor,
|
||||
Autosave,
|
||||
Essentials,
|
||||
Paragraph,
|
||||
ImageUtils,
|
||||
ImageEditing,
|
||||
Link,
|
||||
List,
|
||||
Alignment,
|
||||
Bold,
|
||||
Italic,
|
||||
Underline,
|
||||
AutoLink,
|
||||
Heading
|
||||
} = window.CKEDITOR;
|
||||
|
||||
const LICENSE_KEY =
|
||||
'GPL';
|
||||
|
||||
const CKEDITOR_LANGUAGE_MAP = {
|
||||
en: 'en',
|
||||
zh_CN: 'zh-cn',
|
||||
ja_JP: 'ja',
|
||||
it_IT: 'it',
|
||||
fr_FR: 'fr',
|
||||
de_DE: 'de',
|
||||
hu_HU: 'hu',
|
||||
es_ES: 'es',
|
||||
sv_SE: 'sv',
|
||||
cs_CZ: 'cs',
|
||||
nl_NL: 'nl',
|
||||
uk_UA: 'uk',
|
||||
ru_RU: 'ru',
|
||||
tr_TR: 'tr',
|
||||
pt_BR: 'pt-br',
|
||||
ko_KR: 'ko',
|
||||
pl_PL: 'pl'
|
||||
};
|
||||
|
||||
function detectEditorLanguage() {
|
||||
let lang = typeof GetQueryString === 'function' ? GetQueryString('lang') : null;
|
||||
const langStorageKey = typeof LANG_COOKIE_NAME !== 'undefined' ? LANG_COOKIE_NAME : 'QIDIWebLang';
|
||||
try {
|
||||
if (lang && typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem(langStorageKey, lang);
|
||||
}
|
||||
} catch (err) {}
|
||||
if (!lang) {
|
||||
try {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
lang = localStorage.getItem(langStorageKey);
|
||||
}
|
||||
} catch (err) {
|
||||
lang = null;
|
||||
}
|
||||
}
|
||||
if (!lang && typeof navigator !== 'undefined') {
|
||||
lang = navigator.language || navigator.userLanguage;
|
||||
}
|
||||
if (!lang) {
|
||||
return 'en';
|
||||
}
|
||||
const normalizedUnderscore = lang.replace('-', '_');
|
||||
if (CKEDITOR_LANGUAGE_MAP[normalizedUnderscore]) {
|
||||
return CKEDITOR_LANGUAGE_MAP[normalizedUnderscore];
|
||||
}
|
||||
if (CKEDITOR_LANGUAGE_MAP[lang]) {
|
||||
return CKEDITOR_LANGUAGE_MAP[lang];
|
||||
}
|
||||
return normalizedUnderscore.replace('_', '-').toLowerCase();
|
||||
}
|
||||
|
||||
const editorLanguage = detectEditorLanguage();
|
||||
const script = document.createElement('script');
|
||||
script.src = `../include/ckeditor5/translations/${editorLanguage}.umd.js`;
|
||||
|
||||
const editorConfig = {
|
||||
toolbar: {
|
||||
items: [
|
||||
'heading',
|
||||
'|',
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
'|',
|
||||
'alignment',
|
||||
'|',
|
||||
'bulletedList',
|
||||
'numberedList',
|
||||
'|',
|
||||
'link',
|
||||
'|',
|
||||
'undo',
|
||||
'redo',
|
||||
],
|
||||
shouldNotGroupWhenFull: false
|
||||
},
|
||||
plugins: [Alignment, AutoLink, Autosave, Bold, Essentials, Heading, ImageEditing, ImageUtils, Italic, Link, List, Paragraph, Underline],
|
||||
heading: {
|
||||
options: [
|
||||
{
|
||||
model: 'paragraph',
|
||||
title: 'Paragraph',
|
||||
class: 'ck-heading_paragraph'
|
||||
},
|
||||
{
|
||||
model: 'heading1',
|
||||
view: 'h1',
|
||||
title: 'Heading 1',
|
||||
class: 'ck-heading_heading1'
|
||||
},
|
||||
{
|
||||
model: 'heading2',
|
||||
view: 'h2',
|
||||
title: 'Heading 2',
|
||||
class: 'ck-heading_heading2'
|
||||
},
|
||||
{
|
||||
model: 'heading3',
|
||||
view: 'h3',
|
||||
title: 'Heading 3',
|
||||
class: 'ck-heading_heading3'
|
||||
},
|
||||
{
|
||||
model: 'heading4',
|
||||
view: 'h4',
|
||||
title: 'Heading 4',
|
||||
class: 'ck-heading_heading4'
|
||||
},
|
||||
{
|
||||
model: 'heading5',
|
||||
view: 'h5',
|
||||
title: 'Heading 5',
|
||||
class: 'ck-heading_heading5'
|
||||
},
|
||||
{
|
||||
model: 'heading6',
|
||||
view: 'h6',
|
||||
title: 'Heading 6',
|
||||
class: 'ck-heading_heading6'
|
||||
}
|
||||
]
|
||||
},
|
||||
initialData:
|
||||
'',
|
||||
licenseKey: LICENSE_KEY,
|
||||
link: {
|
||||
addTargetToExternalLinks: true,
|
||||
defaultProtocol: 'https://',
|
||||
decorators: {
|
||||
toggleDownloadable: {
|
||||
mode: 'manual',
|
||||
label: 'Downloadable',
|
||||
attributes: {
|
||||
download: 'file'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
placeholder: '',
|
||||
language: editorLanguage
|
||||
};
|
||||
|
||||
script.onload = () => {
|
||||
ClassicEditor.create(document.querySelector('#editor'), editorConfig).then( editor => {
|
||||
window.projectEditor = editor;
|
||||
});
|
||||
ClassicEditor.create(document.querySelector('#profile-editor'), editorConfig).then( editor => {
|
||||
window.profileEditor = editor;
|
||||
});
|
||||
};
|
||||
|
||||
document.head.appendChild(script);
|
||||
742
resources/web/model_new/js/editor.js
Normal file
742
resources/web/model_new/js/editor.js
Normal file
@@ -0,0 +1,742 @@
|
||||
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;
|
||||
155
resources/web/model_new/js/gallery.js
Normal file
155
resources/web/model_new/js/gallery.js
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Simple Gallery (jQuery 2.1.1)
|
||||
* 用法:
|
||||
* $('#el').bsGallery({ images: ['a.jpg','b.jpg', ...] });
|
||||
* 可选项:
|
||||
* initialIndex, loop, thumbWidth, thumbHeight, mainHeight, counter
|
||||
*/
|
||||
(function($){
|
||||
'use strict';
|
||||
|
||||
var defaults = {
|
||||
images: [], // 必填:图片数组(字符串或 {src, thumb})
|
||||
initialIndex: 0,
|
||||
loop: true,
|
||||
thumbHeight: 100,
|
||||
mainHeight: 420,
|
||||
// 新增:主图宽度与缩略栏宽度(可用数字或带单位字符串,如 '600px'、'60%'}
|
||||
mainWidth: 560,
|
||||
thumbsWidth: 180,
|
||||
counter: true
|
||||
};
|
||||
|
||||
function buildDom($root, settings){
|
||||
$root.addClass('bs-gallery');
|
||||
|
||||
var $main = $('<div class="bs-gallery-main" />');
|
||||
var $img = $('<img class="bs-gallery-image" alt="" />');
|
||||
var $counter = $('<div class="bs-gallery-counter" />');
|
||||
var $thumbs = $('<div class="bs-gallery-thumbs" />');
|
||||
|
||||
$main.append($img);
|
||||
if (settings.counter) $main.append($counter);
|
||||
|
||||
$root.append($main).append($thumbs);
|
||||
|
||||
// 尺寸
|
||||
$main.css({ height: settings.mainHeight + 'px' });
|
||||
$thumbs.css({ maxHeight: settings.mainHeight + 'px' });
|
||||
|
||||
// 应用主图宽度与缩略栏宽度
|
||||
if (settings.mainWidth != null) {
|
||||
var mw = typeof settings.mainWidth === 'number' ? (settings.mainWidth + 'px') : settings.mainWidth;
|
||||
$main.css({ width: mw });
|
||||
// 固定主图宽度,避免被 flex 拉伸
|
||||
$main.css({ flex: '0 0 ' + mw });
|
||||
}
|
||||
if (settings.thumbsWidth != null) {
|
||||
var tw = typeof settings.thumbsWidth === 'number' ? (settings.thumbsWidth + 'px') : settings.thumbsWidth;
|
||||
$thumbs.css({ width: tw });
|
||||
}
|
||||
|
||||
// 生成缩略图
|
||||
$.each(settings.images, function(i, it){
|
||||
var src = typeof it === 'string' ? it : it.src;
|
||||
var thumb = typeof it === 'string' ? it : (it.thumb || it.src);
|
||||
var $t = $('<div class="bs-gallery-thumb" />').attr('data-index', i);
|
||||
var $ti = $('<img />').attr('src', thumb).attr('alt','');
|
||||
$t.append($ti);
|
||||
$thumbs.append($t);
|
||||
});
|
||||
|
||||
// 返回引用
|
||||
return { $main:$main, $img:$img, $counter:$counter, $thumbs:$thumbs };
|
||||
}
|
||||
|
||||
function plugin($root, options){
|
||||
// 清理旧实例:移除事件、数据与DOM,支持重复初始化
|
||||
$root.off('.bsGallery');
|
||||
$root.removeData('bsGallery');
|
||||
$root.removeClass('bs-gallery');
|
||||
$root.empty();
|
||||
|
||||
var settings = $.extend({}, defaults, options||{});
|
||||
if (!settings.images || !settings.images.length) return;
|
||||
|
||||
var ui = buildDom($root, settings);
|
||||
var count = settings.images.length;
|
||||
var index = Math.max(0, Math.min(settings.initialIndex, count - 1));
|
||||
|
||||
function srcAt(i){
|
||||
var it = settings.images[i];
|
||||
return typeof it === 'string' ? it : it.src;
|
||||
}
|
||||
|
||||
function updateCounter(){
|
||||
if (!settings.counter) return;
|
||||
ui.$counter.text((index + 1) + '/' + count);
|
||||
}
|
||||
|
||||
function setActiveThumb(){
|
||||
ui.$thumbs.children('.bs-gallery-thumb').removeClass('active');
|
||||
ui.$thumbs.children('.bs-gallery-thumb').eq(index).addClass('active');
|
||||
// 确保可见
|
||||
var $active = ui.$thumbs.children('.bs-gallery-thumb').eq(index);
|
||||
var cTop = ui.$thumbs.scrollTop();
|
||||
var cH = ui.$thumbs.height();
|
||||
var tTop = $active.position().top + cTop;
|
||||
var tH = $active.outerHeight();
|
||||
if (tTop < cTop) ui.$thumbs.scrollTop(tTop);
|
||||
else if (tTop + tH > cTop + cH) ui.$thumbs.scrollTop(tTop + tH - cH);
|
||||
}
|
||||
|
||||
function show(i){
|
||||
if (i === index) return;
|
||||
index = i;
|
||||
ui.$img.stop(true, true).fadeOut(120, function(){
|
||||
ui.$img.attr('src', srcAt(index)).fadeIn(120);
|
||||
});
|
||||
updateCounter();
|
||||
setActiveThumb();
|
||||
}
|
||||
|
||||
function next(){
|
||||
if (index < count - 1) show(index + 1);
|
||||
else if (settings.loop) show(0);
|
||||
}
|
||||
|
||||
function prev(){
|
||||
if (index > 0) show(index - 1);
|
||||
else if (settings.loop) show(count - 1);
|
||||
}
|
||||
|
||||
// 事件:缩略图点击
|
||||
ui.$thumbs.on('click', '.bs-gallery-thumb', function(){
|
||||
var i = parseInt($(this).attr('data-index'), 10);
|
||||
show(i);
|
||||
});
|
||||
|
||||
// 主图点击下一张
|
||||
ui.$main.on('click', function(){ next(); });
|
||||
|
||||
// 键盘左右切换(容器获取焦点时)
|
||||
$root.attr('tabindex', 0);
|
||||
$root.on('keydown.bsGallery', function(e){
|
||||
if (e.which === 37) { // left
|
||||
prev(); e.preventDefault();
|
||||
} else if (e.which === 39) { // right
|
||||
next(); e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化显示
|
||||
ui.$img.attr('src', srcAt(index));
|
||||
setActiveThumb();
|
||||
updateCounter();
|
||||
|
||||
// 暴露简单 API
|
||||
$root.data('bsGallery', { next: next, prev: prev, show: show, index: function(){return index;}, count: function(){return count;} });
|
||||
}
|
||||
|
||||
$.fn.bsGallery = function(options){
|
||||
return this.each(function(){ plugin($(this), options); });
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
81
resources/web/model_new/js/navigation.js
Normal file
81
resources/web/model_new/js/navigation.js
Normal file
@@ -0,0 +1,81 @@
|
||||
// 侧边导航:导轨 + 活动短竖条
|
||||
(function($){
|
||||
function positionIndicator(){
|
||||
var $nav = $('#sideNav');
|
||||
var $active = $nav.find('.nav-item.active');
|
||||
if (!$active.length) return;
|
||||
var top = $active.position().top;
|
||||
var height = $active.outerHeight();
|
||||
$nav.find('.indicator').css({ top: top + 'px', height: height + 'px' });
|
||||
}
|
||||
|
||||
function scrollToAnchor($item){
|
||||
var href = $item.find('a').attr('href');
|
||||
if (href && href.charAt(0) === '#'){
|
||||
var $target = $(href);
|
||||
if ($target.length){
|
||||
var offset = 70; // 顶部预留,避免顶到视口边缘
|
||||
$('html,body').animate({ scrollTop: Math.max(0, $target.offset().top - offset) }, 300);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(function(){
|
||||
positionIndicator();
|
||||
$('#sideNav').on('click', '.nav-item', function(e){
|
||||
e.preventDefault();
|
||||
var $it = $(this);
|
||||
$it.addClass('active').siblings('.nav-item').removeClass('active');
|
||||
positionIndicator();
|
||||
scrollToAnchor($it);
|
||||
});
|
||||
|
||||
// 滚动联动(ScrollSpy)
|
||||
var ticking = false;
|
||||
function computeActiveByScroll(){
|
||||
var scrollTop = $(window).scrollTop();
|
||||
var viewportOffset = 100; // 判定阈值,提前一点触发
|
||||
var $items = $('#sideNav .nav-item');
|
||||
var currentId = null;
|
||||
$items.each(function(){
|
||||
var href = $(this).find('a').attr('href');
|
||||
if (!href || href.charAt(0) !== '#') return;
|
||||
var $sec = $(href);
|
||||
if (!$sec.length) return;
|
||||
var top = $sec.offset().top;
|
||||
if (top <= scrollTop + viewportOffset){
|
||||
currentId = href;
|
||||
}
|
||||
});
|
||||
if (currentId){
|
||||
var $cur = $items.filter(function(){
|
||||
return $(this).find('a').attr('href') === currentId;
|
||||
}).first();
|
||||
if ($cur.length && !$cur.hasClass('active')){
|
||||
$cur.addClass('active').siblings('.nav-item').removeClass('active');
|
||||
positionIndicator();
|
||||
} else {
|
||||
// 位置可能变化,仍然校准指示条
|
||||
positionIndicator();
|
||||
}
|
||||
}
|
||||
}
|
||||
function onScroll(){
|
||||
if (!ticking){
|
||||
ticking = true;
|
||||
requestAnimationFrame(function(){
|
||||
computeActiveByScroll();
|
||||
ticking = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
$(window).on('scroll', onScroll);
|
||||
$(window).on('resize', function(){
|
||||
positionIndicator();
|
||||
computeActiveByScroll();
|
||||
});
|
||||
|
||||
// 初始根据当前滚动位置激活一次
|
||||
computeActiveByScroll();
|
||||
});
|
||||
})(jQuery);
|
||||
Reference in New Issue
Block a user