* Add OrcaCloud sync platform and preset bundle sharing system

  Introduce OrcaCloud, a cloud sync platform for user presets, alongside
  a preset bundle system that enables sharing printer/filament/process
  profiles as local exportable bundles or subscribed cloud bundles.

  OrcaCloud platform:
  - Auth to Orca Cloud
  - Encrypted token storage (file-based or system keychain)
  - User preset sync with
  - Profile migration from default/bambu folders on first login
  - Homepage integration with entrance to cloud.orcaslicer.com

  Preset bundles:
  - Local bundle import/export with bundle_structure.json metadata
  - Subscribed cloud bundles with version-based update checking
  - Thread-safe concurrent bundle access with read-write mutex
  - Canonical bundle preset naming (_local/<id>/... and _subscribed/<id>/...)
  - Bundle presets are read-only; grouped under subheaders in combo boxes
  - PresetBundleDialog with auto-sync toggle, refresh, update notifications
  - Hyperlinked bundle names to cloud bundle pages

  Co-authored-by: Sabriel Koh <sabrielkcr@gmail.com>
  Co-authored-by: Derrick <derrick992110@gmail.com>
  Co-authored-by: Mykola Nahirnyi <mnahirnyi@amcbridge.com>
  Co-authored-by: Ian Chua <iancrb00@gmail.com>
  Co-authored-by: Draginraptor <draginraptor@gmail.com>
  Co-authored-by: ExPikaPaka <112851715+ExPikaPaka@users.noreply.github.com>
  Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
  Co-authored-by: Ocraftyone <Ocraftyone@users.noreply.github.com>
  Co-authored-by: yw4z <ywsyildiz@gmail.com>
  Co-authored-by: peterm-m <101202951+peterm-m@users.noreply.github.com>

* Fixed an issue on Windows it failed to login Orca Cloud with Google account
This commit is contained in:
SoftFever
2026-05-01 18:01:29 +08:00
committed by GitHub
parent e54e7a61c0
commit c04be9ab37
113 changed files with 8691 additions and 3467 deletions

View File

@@ -0,0 +1,75 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Cache-Control" content="max-age=0" />
<title>Export Presets</title>
<link rel="stylesheet" href="./styles.css" />
<link rel="stylesheet" type="text/css" href="../../include/global.css" /> <!-- ORCA One for all-->
<link rel="stylesheet" type="text/css" href="../css/common.css" />
<link rel="stylesheet" type="text/css" href="../css/dark.css" />
<script type="text/javascript" src="../js/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<script src="./index.js"></script>
</head>
<body onLoad="OnInit()">
<!-- ORCA column browser -->
<div class="cbr-browser-container">
<div class="cbr-column">
<div class="cbr-column-title-container">
<div class="search-icon"></div>
<input type="text" class="cbr-search-bar" placeholder=" " tabindex="1"/>
<span class="cbr-search-placeholder trans" tid="t15">printer</span>
<div class="clear-icon"></div>
</div>
<div class="cbr-content thin-scroll" id="MachineList">
<div class="CValues">
<label><input type="checkbox" mode="all" onClick="ChooseAllMachine()" /><span class="trans" tid="t11">all</span></label>
</div>
<div class="cbr-no-items">No items</div>
</div>
</div>
<div class="cbr-column">
<div class="cbr-column-title-container">
<div class="search-icon"></div>
<input type="text" class="cbr-search-bar" placeholder=" " tabindex="2"/>
<span class="cbr-search-placeholder trans" tid="t16">filament</span>
<div class="clear-icon"></div>
</div>
<div class="cbr-content thin-scroll" id="FilatypeList">
<div class="CValues">
<label><input type="checkbox" class="trans" tid="t11" onClick="ChooseAllFilament()" /><span class="trans" tid="t11">all</span></label>
</div>
<div class="cbr-no-items">No items</div>
</div>
</div>
<div class="cbr-column">
<div class="cbr-column-title-container">
<div class="search-icon"></div>
<input type="text" class="cbr-search-bar" placeholder=" " tabindex="3"/>
<span class="cbr-search-placeholder trans" tid="t17">presets</span>
<div class="clear-icon"></div>
</div>
<div class="cbr-content thin-scroll" id="PresetList">
<div class="CValues">
<label><input type="checkbox" class="trans" tid="t11" onClick="ChooseAllPreset()" /><span class="trans" tid="t11">all</span></label>
</div>
<div class="cbr-no-items">No items</div>
</div>
</div>
</div>
<div id="AcceptArea">
<div id="export_cloud_btn" class="ButtonStyleConfirm ButtonTypeChoice">Export to OrcaCloud</div>
<div id="export_local_btn" class="ButtonStyleRegular ButtonTypeChoice">Export to folder</div>
<div id="close_btn" class="ButtonStyleRegular ButtonTypeChoice">Close</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,374 @@
var g_profile = {
machines: [],
filaments: [],
presets: []
};
var g_search = {
machine: "",
filament: "",
preset: ""
};
function OnInit()
{
if (typeof TranslatePage === "function")
TranslatePage();
InstallInputSafeKeydown();
BindSearchInputs();
BindClearIcons();
BindBottomButtons();
// Always load demo data first so the page works without C++ backend.
// LoadDemoProfile();
RequestProfile();
}
function InstallInputSafeKeydown()
{
// common.js blocks all key events globally; allow typing in text inputs.
document.onkeydown = function (event) {
var e = event || window.event || arguments.callee.caller.arguments[0];
var target = e && e.target ? e.target : null;
var tag = target && target.tagName ? String(target.tagName).toUpperCase() : "";
var type = target && target.type ? String(target.type).toLowerCase() : "";
var editable =
!!(target && target.isContentEditable) ||
tag === "TEXTAREA" ||
(tag === "INPUT" && type !== "checkbox" && type !== "radio" && type !== "button" && type !== "submit");
if (editable)
return true;
if (e && e.keyCode === 27 && typeof ClosePage === "function")
ClosePage();
if (window.event) {
try { e.keyCode = 0; } catch (err) { }
e.returnValue = false;
}
if (e && typeof e.preventDefault === "function")
e.preventDefault();
return false;
};
}
function RequestProfile()
{
SendMessage("request_export_preset_profile", {});
}
function HandleStudio(pVal)
{
var payload = (typeof pVal === "string") ? SafeJsonParse(pVal) : pVal;
if (!payload || typeof payload !== "object")
return;
var cmd = String(payload.command || "");
if (cmd === "response_export_preset_profile" ) {
ApplyProfile(payload.data);
}
}
function ApplyProfile(profile)
{
g_profile.machines = BuildNameRows(profile.printers);
g_profile.filaments = BuildNameRows(profile.filaments);
g_profile.presets = BuildNameRows(profile.process);
RenderColumn("MachineList", g_profile.machines, "mode", "MachineClick");
RenderColumn("FilatypeList", g_profile.filaments, "filatype", "FilaClick");
RenderColumn("PresetList", g_profile.presets, "preset", "PresetClick");
ApplyColumnSearch("MachineList", g_search.machine);
ApplyColumnSearch("FilatypeList", g_search.filament);
ApplyColumnSearch("PresetList", g_search.preset);
}
function BuildNameRows(names)
{
var src = Array.isArray(names) ? names : [];
var out = [];
for (var n = 0; n < src.length; n++) {
var row = src[n];
if (row === undefined || row === null)
continue;
var name = String(row);
out.push({ id: name, label: name, checked: false });
}
return out;
}
function RenderColumn(listId, items, attrName, onChangeFn)
{
var root = $("#" + listId + " .CValues");
if (!root.length)
return;
root.find("label:gt(0)").remove();
var html = "";
for (var n = 0; n < items.length; n++) {
var one = items[n];
html += '<label data-dynamic="1">' +
'<input type="checkbox" data-key="' + EscapeAttr(one.id) + '" ' + attrName + '="' + EscapeAttr(one.id) + '"' +
(one.checked ? ' checked="checked"' : "") +
' onChange="' + onChangeFn + '()" />' +
'<span title="' + EscapeAttr(one.label) + '">' + EscapeHtml(one.label) + '</span>' +
'</label>';
}
root.append(html);
SyncMasterCheckbox(listId);
ToggleNoItems(listId, items.length === 0);
}
function ChooseAllMachine()
{
var checked = !!$("#MachineList .CValues input:first").prop("checked");
$("#MachineList .CValues input:gt(0)").prop("checked", checked);
SyncListFromDom("MachineList", g_profile.machines);
}
function MachineClick()
{
SyncMasterCheckbox("MachineList");
SyncListFromDom("MachineList", g_profile.machines);
}
function ChooseAllFilament()
{
var checked = !!$("#FilatypeList .CValues input:first").prop("checked");
$("#FilatypeList .CValues input:gt(0)").prop("checked", checked);
SyncListFromDom("FilatypeList", g_profile.filaments);
}
function FilaClick()
{
SyncMasterCheckbox("FilatypeList");
SyncListFromDom("FilatypeList", g_profile.filaments);
}
function ChooseAllPreset()
{
var checked = !!$("#PresetList .CValues input:first").prop("checked");
$("#PresetList .CValues input:gt(0)").prop("checked", checked);
SyncListFromDom("PresetList", g_profile.presets);
}
function PresetClick()
{
SyncMasterCheckbox("PresetList");
SyncListFromDom("PresetList", g_profile.presets);
}
function SyncMasterCheckbox(listId)
{
var all = $("#" + listId + " .CValues input:gt(0)");
var master = $("#" + listId + " .CValues input:first");
if (!all.length) {
master.prop("checked", false);
return;
}
master.prop("checked", all.length === all.filter(":checked").length);
}
function SyncListFromDom(listId, store)
{
var map = {};
for (var n = 0; n < store.length; n++)
map[store[n].id] = store[n];
$("#" + listId + " .CValues input:gt(0)").each(function () {
var id = String($(this).attr("data-key") || "");
if (map[id])
map[id].checked = !!$(this).prop("checked");
});
}
function BindSearchInputs()
{
var inputs = document.querySelectorAll(".cbr-search-bar");
if (inputs.length > 0) {
inputs[0].addEventListener("input", function () {
g_search.machine = String(this.value || "").toLowerCase();
ApplyColumnSearch("MachineList", g_search.machine);
});
}
if (inputs.length > 1) {
inputs[1].addEventListener("input", function () {
g_search.filament = String(this.value || "").toLowerCase();
ApplyColumnSearch("FilatypeList", g_search.filament);
});
}
if (inputs.length > 2) {
inputs[2].addEventListener("input", function () {
g_search.preset = String(this.value || "").toLowerCase();
ApplyColumnSearch("PresetList", g_search.preset);
});
}
}
function ApplyColumnSearch(listId, query)
{
var rows = $("#" + listId + " .CValues label:gt(0)");
var visibleCount = 0;
rows.each(function () {
var row = $(this);
var text = String(row.text() || "").toLowerCase();
var key = String(row.find("input").attr("data-key") || "").toLowerCase();
if (!query || text.indexOf(query) >= 0 || key.indexOf(query) >= 0) {
row.show();
visibleCount++;
}
else {
row.hide();
}
});
ToggleNoItems(listId, visibleCount === 0);
}
function ToggleNoItems(listId, show)
{
var node = $("#" + listId + " .cbr-no-items");
if (!node.length)
return;
if (show)
node.addClass("show");
else
node.removeClass("show");
}
function BindClearIcons()
{
var icons = document.querySelectorAll(".clear-icon");
for (var n = 0; n < icons.length; n++) {
icons[n].addEventListener("click", function () {
var parent = this.parentElement;
if (!parent)
return;
var input = parent.querySelector("input[type='text']");
if (!input)
return;
input.value = "";
input.dispatchEvent(new Event("input", { bubbles: true }));
input.focus();
});
}
}
function BindBottomButtons()
{
var backBtn = document.getElementById("back_btn");
var exportCloud = document.getElementById("export_cloud_btn")
var exportLocal = document.getElementById("export_local_btn");
var closeBtn = document.getElementById("close_btn");
backBtn?.addEventListener("click", function () {
SendMessage("navigate_back", {});
});
exportLocal?.addEventListener("click", function () {
SendMessage("export_local", BuildResultPayload());
});
closeBtn?.addEventListener("click", () => {
const tSend = {
sequence_id: Math.round(Date.now() / 1000),
command: "close_page"
};
SendWXMessage(JSON.stringify(tSend));
});
}
function BuildResultPayload()
{
return {
machines: g_profile.machines.filter(function (x) { return x.checked; }).map(function (x) { return x.id; }),
filaments: g_profile.filaments.filter(function (x) { return x.checked; }).map(function (x) { return x.id; }),
presets: g_profile.presets.filter(function (x) { return x.checked; }).map(function (x) { return x.id; })
};
}
function LoadDemoProfile()
{
ApplyProfile({
machines: [
{ id: "printer_x1c_04", name: "X1 Carbon 0.4 nozzle", selected: 1 },
{ id: "printer_p1s_04", name: "P1S 0.4 nozzle", selected: 1 },
{ id: "printer_a1_04", name: "A1 0.4 nozzle", selected: 0 },
{ id: "printer_prusa_mk4_04", name: "Prusa MK4 0.4 nozzle", selected: 1 }
],
filaments: [
{ id: "filament_generic_pla", name: "Generic PLA", selected: 1 },
{ id: "filament_generic_petg", name: "Generic PETG", selected: 1 },
{ id: "filament_bambu_abs", name: "Bambu ABS", selected: 0 },
{ id: "filament_esun_pla_plus", name: "eSUN PLA+", selected: 1 }
],
presets: [
{ id: "preset_quality_020", name: "Quality 0.20mm", selected: 1 },
{ id: "preset_quality_012", name: "Quality 0.12mm", selected: 0 },
{ id: "preset_speed_024", name: "Speed 0.24mm", selected: 1 },
{ id: "preset_draft_028", name: "Draft 0.28mm", selected: 0 }
]
});
}
function SendMessage(command, data)
{
var msg = {};
msg.sequence_id = Math.round(new Date() / 1000);
msg.command = command;
if (data && typeof data === "object")
msg.data = data;
if (typeof SendWXMessage === "function")
SendWXMessage(JSON.stringify(msg));
}
function SafeJsonParse(str)
{
try {
return JSON.parse(str);
}
catch (err) {
return null;
}
}
function EscapeHtml(str)
{
return String(str)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\"/g, "&quot;")
.replace(/'/g, "&#039;");
}
function EscapeAttr(str)
{
return EscapeHtml(str);
}

View File

@@ -0,0 +1,191 @@
:root {
--cbr-border-color: #d2d2d7;
--cbr-header-bg: #f6f7f9;
--cbr-panel-bg: #ffffff;
--cbr-input-bg: #ffffff;
--cbr-input-focus-bg: #f2f8f7;
--cbr-label-color: #7b7b84;
--cbr-icon-color: #75757f;
}
@media (prefers-color-scheme: dark) {
:root {
--cbr-border-color: #4a4a51;
--cbr-header-bg: #2f2f34;
--cbr-panel-bg: #2d2d31;
--cbr-input-bg: #2d2d31;
--cbr-input-focus-bg: #3b3b41;
--cbr-label-color: #b9b9bc;
--cbr-icon-color: #b9b9bc;
}
}
.cbr-browser-container {
flex: 1 1 auto;
min-height: 0;
margin: 10px 15px 0;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-rows: minmax(0, 1fr);
border: 1px solid var(--cbr-border-color);
background: var(--cbr-panel-bg);
box-sizing: border-box;
}
.cbr-column {
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
.cbr-column:not(:last-child) {
border-right: 1px solid var(--cbr-border-color);
}
.cbr-column-title-container {
position: relative;
display: flex;
align-items: center;
min-height: 36px;
padding: 3px 6px;
background: var(--cbr-header-bg);
border-bottom: 1px solid var(--cbr-border-color);
}
.cbr-search-bar {
width: 100%;
min-width: 0;
box-sizing: border-box;
font-size: 14px;
line-height: 22px;
padding: 2px 26px 2px 26px;
border: 1px solid transparent;
border-radius: 4px;
background: var(--cbr-input-bg);
}
.cbr-search-bar:hover,
.cbr-search-bar:focus {
border-color: var(--main-color);
outline: none;
}
.cbr-search-bar:focus {
background: var(--cbr-input-focus-bg);
}
.cbr-search-placeholder {
position: absolute;
left: 33px;
top: 50%;
transform: translateY(-50%);
font-size: 14px;
line-height: 20px;
color: var(--cbr-label-color);
pointer-events: none;
}
.cbr-search-bar:not(:placeholder-shown) + .cbr-search-placeholder {
opacity: 0;
}
.search-icon,
.clear-icon {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
color: var(--cbr-icon-color);
line-height: 1;
}
.search-icon {
left: 11px;
pointer-events: none;
}
.search-icon::before {
content: "";
width: 9px;
height: 9px;
box-sizing: border-box;
border: 1.8px solid currentColor;
border-radius: 50%;
}
.search-icon::after {
content: "";
position: absolute;
width: 5px;
height: 1.8px;
background: currentColor;
transform: rotate(45deg);
right: 0;
bottom: 2px;
}
.clear-icon {
right: 11px;
cursor: pointer;
display: none;
}
.clear-icon::before {
content: "";
position: absolute;
width: 10px;
height: 1.8px;
background: currentColor;
transform: rotate(45deg);
}
.clear-icon::after {
content: "";
position: absolute;
width: 10px;
height: 1.8px;
background: currentColor;
transform: rotate(-45deg);
}
.cbr-search-bar:not(:placeholder-shown) ~ .clear-icon {
display: flex;
}
.cbr-content {
min-height: 0;
overflow-y: auto;
padding: 4px 8px;
}
.cbr-column .CValues {
display: grid;
}
.CValues label {
margin: 0 !important;
padding: 1px 0;
}
.cbr-content .cbr-no-items {
display: none;
color: var(--cbr-label-color);
font-size: 12px;
padding-top: 6px;
}
.cbr-content .cbr-no-items.show {
display: block;
}
#AcceptArea {
border-top: 1px solid var(--cbr-border-color);
}

View File

@@ -0,0 +1,71 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Preset Bundle</title>
<link rel="stylesheet" href="./styles.css" />
<link rel="stylesheet" type="text/css" href="../../include/global.css" /> <!-- ORCA One for all-->
<link rel="stylesheet" type="text/css" href="../css/common.css" />
<!-- <link rel="stylesheet" type="text/css" href="23.css" /> -->
<link rel="stylesheet" type="text/css" href="../css/dark.css" />
<script type="text/javascript" src="../js/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<!-- <script type="text/javascript" src="./23.js"></script> -->
<script src="./index.js"></script>
</head>
<body onLoad="OnInit()">
<div class="app">
<div class="split">
<div class="top-toolbar">
<button id="refresh_btn" class="ButtonStyleConfirm ButtonTypeChoice toolbar-btn">Refresh</button>
<!-- <label class="auto-update-switch" for="auto_update_toggle">
<span class="auto-update-label">Auto update</span>
<input id="auto_update_toggle" type="checkbox" />
<span class="auto-update-slider" aria-hidden="true">
<span class="auto-update-text auto-update-text-off">OFF</span>
<span class="auto-update-text auto-update-text-on">ON</span>
</span>
</label> -->
</div>
<section class="pane top">
<div class="hdr top-cols">
<span>Name</span>
<span>Type</span>
<span>Version</span>
<span>Update</span>
</div>
<div id="topList" class="body"></div>
</section>
<section class="pane bottom">
<div class="hdr bot-cols">
<span>Name</span>
<span>Type</span>
</div>
<div id="bottomList" class="body"></div>
</section>
</div>
</div>
<!-- Footer -->
<div id="AcceptArea">
<div id="export_btn" class="ButtonStyleConfirm ButtonTypeChoice" hidden>Export Presets</div>
<div id="close_btn" class="ButtonStyleRegular ButtonTypeChoice">Close</div>
</div>
<!-- Context Menu -->
<div id="ctxMenu" class="ctx" hidden>
<button class="ctx-item" data-action="open_folder">Open folder in explorer</button>
<button id = "delete_btn" class="ctx-item-delete" data-action="delete_bundle" hidden>Delete bundle</button>
<button id = "unsubscribe_btn" class="ctx-item-subscribed" data-action="unsubscribe_bundle" hidden>Unsubscribe bundle</button>
</div>
</body>
</html>

View File

@@ -0,0 +1,375 @@
// ========= Data Stores =========
const bundlesById = new Map(); // bundleId -> bundle object
const printersByBundle = new Map(); // bundleId -> Map(index -> printerName)
const filamentsByBundle = new Map(); // bundleId -> Map(index -> filamentName)
const presetsByBundle = new Map(); // bundleId -> Map(index -> presetName)
const UPDATE_TOOLTIP = "Update available";
const UNAUTHORIZED_TOOLTIP = "Unauthorized bundle";
// ========= DOM =========
let topList = null;
let bottomList = null;
let ctxMenu = null;
let contextRow = null;
let ctxMenuSubscribed = null;
let ctxMenuDelete = null;
let selectedBundleId = null;
// ========= Init =========
function OnInit() {
topList = document.getElementById("topList");
bottomList = document.getElementById("bottomList");
ctxMenu = document.getElementById("ctxMenu");
ctxMenuSubscribed = document.getElementById("unsubscribe_btn");
ctxMenuDelete = document.getElementById("delete_btn");
const closeBtn = document.getElementById("close_btn");
const exportbtn = document.getElementById("export_btn");
const refreshBtn = document.getElementById("refresh_btn");
const autoUpdateToggle = document.getElementById("auto_update_toggle");
if (!topList || !bottomList) return;
TranslatePage();
// If wx side needs to request bundles after page load:
RequestBundles();
refreshBtn?.addEventListener("click", () => {
const tSend = {
sequence_id: Math.round(Date.now() / 1000),
command: "refresh_bundles"
};
SendWXMessage(JSON.stringify(tSend));
});
autoUpdateToggle?.addEventListener("change", () => {
const tSend = {
sequence_id: Math.round(Date.now() / 1000),
command: "set_auto_update",
enabled: !!autoUpdateToggle.checked
};
SendWXMessage(JSON.stringify(tSend));
});
// Hook selection on top list
topList.addEventListener("click", (e) => {
const cloudLink = e.target.closest(".bundle-cloud-link");
if (cloudLink) {
e.preventDefault();
e.stopPropagation();
const row = cloudLink.closest(".row");
if (!row) return;
selectTopRow(row);
selectedBundleId = String(row.dataset.id || "");
renderBottomForBundle(selectedBundleId);
sendOpenBundleOnCloud(selectedBundleId);
return;
}
const updateBtn = e.target.closest(".bundle-update-btn");
if (updateBtn) {
e.stopPropagation();
if (updateBtn.disabled) return;
const row = updateBtn.closest(".row");
if (!row) return;
selectTopRow(row);
selectedBundleId = String(row.dataset.id || "");
renderBottomForBundle(selectedBundleId);
sendUpdateBundleCommand(selectedBundleId);
return;
}
const row = e.target.closest(".row");
if (!row) return;
selectTopRow(row);
selectedBundleId = String(row.dataset.id || "");
renderBottomForBundle(selectedBundleId);
});
// for top list rows if right click open context menu
topList.addEventListener("contextmenu", (e) => {
const row = e.target.closest(".row");
if (!row) return; // top rows only
const bundleType = String(row.dataset.bundleType || "").toLowerCase();
if (bundleType !== "subscribed") return;
e.preventDefault();
selectTopRow(row);
contextRow = row;
showSubscribedMenu(e.clientX, e.clientY);
});
// for top list rows except subscribed if right click open regular context menu
topList.addEventListener("contextmenu", (e) => {
const row = e.target.closest(".row");
if (!row) return; // top rows only
const bundleType = String(row.dataset.bundleType || "").toLowerCase();
if (bundleType === "subscribed") return;
e.preventDefault();
selectTopRow(row);
contextRow = row;
showMenu(e.clientX, e.clientY);
});
ctxMenu?.addEventListener("click", (e) => {
const btn = e.target.closest("[data-action]");
if (!btn || !contextRow) return;
const tSend = {
sequence_id: Math.round(Date.now() / 1000),
command: "top_row_menu_action",
action: String(btn.dataset.action || ""),
bundle_id: String(contextRow.dataset.id || "")
};
SendWXMessage(JSON.stringify(tSend));
hideMenu();
});
closeBtn?.addEventListener("click", () => {
const tSend = {
sequence_id: Math.round(Date.now() / 1000),
command: "close_page"
};
SendWXMessage(JSON.stringify(tSend));
});
exportbtn?.addEventListener("click", () => {
const tSend = {
sequence_id: Math.round(Date.now() / 1000),
command: "export_page"
};
SendWXMessage(JSON.stringify(tSend));
});
document.addEventListener("click", (e) => {
if (!e.target.closest(".ctx")) hideMenu();
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") hideMenu();
});
}
// ========= wx bridge requests =========
function RequestBundles() {
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="request_bundles";
SendWXMessage(JSON.stringify(tSend));
}
function HandleStudio(pVal) {
const msg = (typeof pVal === "string") ? safeJsonParse(pVal) : pVal;
if (!msg || typeof msg !== "object") return;
const strCmd = msg.command;
if (strCmd === "list_bundles") {
unpackPayload(msg);
renderTop();
// auto-select first bundle if none selected
autoSelectFirstBundle();
const autoUpdateToggle = document.getElementById("auto_update_toggle");
if (autoUpdateToggle) {
autoUpdateToggle.checked = !!msg.auto_update_enabled;
}
}
}
// ========= Parse / store =========
function unpackPayload(payload) {
bundlesById.clear();
printersByBundle.clear();
filamentsByBundle.clear();
presetsByBundle.clear();
const list = payload?.data || [];
for (const bundle of list) {
const id = String(bundle.id ?? "");
if (!id) continue;
bundlesById.set(id, {
id,
name: bundle.name ?? "",
type: bundle.type ?? "",
version: bundle.version ?? "",
path: bundle.path ?? "",
update_available: Boolean(bundle.update_available) ,
unauthorized: Boolean(bundle.unauthorized)
});
printersByBundle.set(id, new Map((bundle.printers || []).map((name, i) => [i, name])));
filamentsByBundle.set(id, new Map((bundle.filaments || []).map((name, i) => [i, name])));
presetsByBundle.set(id, new Map((bundle.presets || []).map((name, i) => [i, name])));
}
}
// ========= Render: top =========
function renderTop() {
const bundles = Array.from(bundlesById.values());
topList.innerHTML = bundles.map(b => `
<div class="row" data-id="${escapeAttr(b.id)}" data-bundle-type="${escapeAttr(String(b.type || "").toLowerCase())}">
<div class="cell bundle-name-cell" title="${escapeAttr(b.name)}">
${b.unauthorized
? `<span class="bundle-status-icon bundle-status-icon-unauthorized" title="${escapeAttr(UNAUTHORIZED_TOOLTIP)}" aria-label="${escapeAttr(UNAUTHORIZED_TOOLTIP)}">!</span>`
: b.update_available
? `<span class="bundle-status-icon bundle-status-icon-update" title="${escapeAttr(UPDATE_TOOLTIP)}" aria-label="${escapeAttr(UPDATE_TOOLTIP)}">&uarr;</span>`
: `<span class="bundle-status-icon-spacer" aria-hidden="true"></span>`}
${
b.type === "Subscribed" ?
`<a href="#" class="bundle-name-text bundle-cloud-link" title="Open this bundle in your browser">${escapeHtml(b.name)}</a>`
: `<span class="bundle-name-text">${escapeHtml(b.name)}</span>`
}
</div>
<span title="${escapeAttr(b.type)}">${escapeHtml(b.type)}</span>
<span title="${escapeAttr(b.version)}">${escapeHtml(b.version)}</span>
<div class="cell bundle-update-cell">
<button
type="button"
class="bundle-update-btn ${(!b.unauthorized && b.update_available) ? "is-enabled" : "is-disabled"}"
${(!b.unauthorized && b.update_available) ? "" : "disabled"}
data-id="${escapeAttr(b.id)}"
>Update</button>
</div>
</div>
`).join("");
}
function sendOpenBundleOnCloud(bundleId) {
const bundle = bundlesById.get(String(bundleId || ""));
if (!bundle) return;
const tSend = {
sequence_id: Math.round(Date.now() / 1000),
command: "open_bundle_on_cloud",
bundle_id: String(bundle.id || "")
};
SendWXMessage(JSON.stringify(tSend));
}
function sendUpdateBundleCommand(bundleId) {
const bundle = bundlesById.get(String(bundleId || ""));
if (!bundle || bundle.unauthorized || !bundle.update_available) return;
const tSend = {
sequence_id: Math.round(Date.now() / 1000),
command: "update_bundle",
bundle_id: String(bundle.id || "")
};
SendWXMessage(JSON.stringify(tSend));
}
// ========= Render: bottom (for a selected bundle) =========
function renderBottomForBundle(bundleId) {
const key = String(bundleId || "");
const printers = printersByBundle.get(key) || new Map();
const filaments = filamentsByBundle.get(key) || new Map();
const presets = presetsByBundle.get(key) || new Map();
// Convert to a flat list of rows { typeLabel, name }
const rows = [];
for (const [, name] of printers) rows.push({ type: "Printer", name });
for (const [, name] of filaments) rows.push({ type: "Filament", name });
for (const [, name] of presets) rows.push({ type: "Preset", name });
bottomList.innerHTML = rows.map((r, idx) => `
<div class="row" data-id="${escapeAttr(bundleId)}" data-idx="${idx}">
<span>${escapeHtml(r.name)}</span>
<span title="${escapeAttr(r.type)}">${escapeHtml(r.type)}</span>
</div>
`).join("");
}
// ========= Selection helpers =========
function clearSelection() {
document.querySelectorAll(".row.selected").forEach(r => r.classList.remove("selected"));
}
function selectTopRow(rowEl) {
// only clear selection in top list, not bottom
topList.querySelectorAll(".row.selected").forEach(r => r.classList.remove("selected"));
rowEl.classList.add("selected");
}
function autoSelectFirstBundle() {
if (selectedBundleId && bundlesById.has(selectedBundleId)) {
// reselect existing
const el = topList.querySelector(`.row[data-id="${cssEscape(selectedBundleId)}"]`);
if (el) selectTopRow(el);
renderBottomForBundle(selectedBundleId);
return;
}
const first = topList.querySelector(".row");
if (!first) {
bottomList.innerHTML = "";
selectedBundleId = null;
return;
}
selectTopRow(first);
selectedBundleId = first.dataset.id;
renderBottomForBundle(selectedBundleId);
}
function showSubscribedMenu(x, y) {
if (!ctxMenu) return;
ctxMenu.style.left = `${x}px`;
ctxMenu.style.top = `${y}px`;
ctxMenu.hidden = false;
ctxMenuDelete.hidden = true;
ctxMenuSubscribed.hidden = false;
}
function showMenu(x, y) {
if (!ctxMenu) return;
ctxMenu.style.left = `${x}px`;
ctxMenu.style.top = `${y}px`;
ctxMenu.hidden = false;
ctxMenuDelete.hidden = false;
ctxMenuSubscribed.hidden = true;
}
function hideMenu() {
if (!ctxMenu) return;
ctxMenu.hidden = true;
ctxMenuSubscribed.hidden = true;
contextRow = null;
}
// ========= Utilities =========
function safeJsonParse(s) {
try { return JSON.parse(s); } catch { return null; }
}
function escapeHtml(str) {
return String(str)
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
}
function escapeAttr(str) {
// minimal attribute escaping
return escapeHtml(str);
}
function cssEscape(str) {
// basic css escape for attribute selectors
return String(str).replaceAll('"', '\\"');
}

View File

@@ -0,0 +1,394 @@
:root {
--bg: #ffffff;
--panel: #ffffff;
--border: #d8d8d8;
--border-strong: #e6e6e6;
--border-soft: #f0f0f0;
--col-sep: #e1e1e1;
--text: #1f2328;
--row-hover: #f7f9fb;
--row-selected: #eaf2ff;
--row-selected-outline: #b7d0ff;
--footer-bg: #fafafa;
--btn-bg: #ffffff;
--btn-border: #cccccc;
--btn-hover: #f0f0f0;
--ctx-bg: #ffffff;
--ctx-border: #cccccc;
--ctx-hover: #efefef;
}
html, body {
height: 100%;
margin: 0;
font-family: sans-serif;
background: var(--bg);
color: var(--text);
overflow: hidden;
}
body {
display: flex;
flex-direction: column;
}
/* App layout: split content + footer */
.app {
flex: 1 1 auto;
min-height: 0;
overflow: hidden;
}
/* Split panes */
.split {
height: 100%;
display: grid;
grid-template-rows: auto minmax(0, 1.25fr) minmax(140px, 0.75fr);
gap: 8px;
padding: 8px;
box-sizing: border-box;
min-height: 0;
overflow: hidden;
}
.top-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 0 2px;
}
.toolbar-btn {
min-width: 92px;
}
.auto-update-switch {
display: inline-flex;
align-items: center;
gap: 10px;
font-size: 13px;
font-weight: 600;
color: var(--text);
user-select: none;
cursor: pointer;
position: relative;
}
.auto-update-switch input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.auto-update-label {
white-space: nowrap;
}
.auto-update-slider {
position: relative;
width: 52px;
height: 24px;
flex: 0 0 52px;
border-radius: 999px;
background: #b9b9b9;
transition: background 0.2s ease;
box-sizing: border-box;
overflow: hidden;
}
.auto-update-slider::before {
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
border-radius: 50%;
background: #ffffff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.28);
transition: transform 0.2s ease;
z-index: 2;
}
.auto-update-text {
position: absolute;
top: 50%;
transform: translateY(-50%);
font-size: 9px;
font-weight: 700;
line-height: 1;
letter-spacing: 0.3px;
z-index: 1;
pointer-events: none;
}
.auto-update-text-off {
right: 7px;
color: #ffffff;
}
.auto-update-text-on {
left: 7px;
color: #ffffff;
opacity: 0.55;
}
.auto-update-switch input:checked + .auto-update-slider {
background: var(--main-color);
}
.auto-update-switch input:checked + .auto-update-slider::before {
transform: translateX(28px);
}
.auto-update-switch input:checked + .auto-update-slider .auto-update-text-on {
opacity: 1;
}
.auto-update-switch input:checked + .auto-update-slider .auto-update-text-off {
opacity: 0.55;
}
.auto-update-switch input:not(:checked) + .auto-update-slider .auto-update-text-on {
opacity: 0.55;
}
.auto-update-switch input:not(:checked) + .auto-update-slider .auto-update-text-off {
opacity: 1;
}
.auto-update-switch input:focus-visible + .auto-update-slider {
outline: 2px solid var(--main-color);
outline-offset: 2px;
}
.auto-update-switch input:disabled + .auto-update-slider {
opacity: 0.5;
}
.pane {
min-height: 0;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--panel);
display: grid;
grid-template-rows: auto 1fr;
}
.hdr {
font-weight: 600;
font-size: 12px;
border-bottom: 1px solid var(--border-strong);
padding: 8px 10px;
display: grid;
gap: 10px;
}
.top-cols { grid-template-columns: minmax(0, 2fr) 1fr 1fr 88px; }
.bot-cols { grid-template-columns: 2fr 1fr; }
.top .row { grid-template-columns: minmax(0, 2fr) 1fr 1fr 88px; }
.bottom .row { grid-template-columns: 2fr 1fr; }
.body {
min-height: 0;
overflow: hidden;
}
.hdr > *,
.row > * {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-right: 8px;
border-right: 1px solid var(--col-sep);
box-sizing: border-box;
}
.hdr > *:last-child,
.row > *:last-child {
border-right: none;
padding-right: 0;
}
.bundle-name-cell {
display: flex;
align-items: center;
gap: 8px;
}
.bundle-name-text {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.bundle-cloud-link {
color: inherit;
cursor: pointer;
text-decoration: none;
text-underline-offset: 2px;
}
.bundle-cloud-link:hover,
.bundle-cloud-link:focus-visible {
color: inherit;
text-decoration: underline;
}
.bundle-status-icon,
.bundle-status-icon-spacer {
flex: 0 0 14px;
width: 14px;
height: 14px;
}
.bundle-status-icon {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #f59e0b;
color: #fff;
font-size: 10px;
font-weight: 700;
line-height: 14px;
}
.bundle-status-icon-unauthorized {
background: #dc2626;
}
.bundle-status-icon-update {
background: var(--main-color);
font-size: 12px;
}
.bundle-update-cell {
display: flex;
align-items: center;
justify-content: flex-start;
}
.bundle-update-btn {
box-sizing: border-box;
min-width: 64px;
height: 20px;
line-height: 18px;
padding: 0 8px;
border-radius: 4px;
font-size: 11px;
cursor: pointer;
border: 1px solid transparent;
}
.bundle-update-btn.is-enabled {
background: var(--main-color);
color: var(--button-fg-light);
}
.bundle-update-btn.is-enabled:hover {
background: var(--main-color-hover);
}
.bundle-update-btn.is-disabled {
background: var(--button-bg-disabled);
color: var(--button-fg-disabled);
cursor: not-allowed;
}
#topList,
#bottomList {
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
}
/* Column separators + clipping */
.row {
padding: 8px 10px;
border-bottom: 1px solid var(--border-soft);
display: grid;
gap: 10px;
font-size: 13px;
white-space: nowrap;
cursor: pointer;
}
.row:hover { background: var(--row-hover); }
.row.selected {
background: var(--row-selected);
outline: 1px solid var(--row-selected-outline);
}
/* Footer styling
.footer {
border-top: 1px solid var(--border);
padding: 10px 16px;
background: var(--footer-bg);
display: flex;
justify-content: flex-end;
gap: 12px;
}
.footer-btn {
padding: 8px 16px;
border-radius: 6px;
border: 1px solid var(--btn-border);
background: var(--btn-bg);
color: var(--text);
cursor: pointer;
font-size: 13px;
}
.footer-btn:hover { background: var(--btn-hover); } */
/* Context menu */
.ctx {
position: fixed;
min-width: 180px;
background: var(--ctx-bg);
border: 1px solid var(--ctx-border);
border-radius: 6px;
box-shadow: 0 8px 20px rgba(0,0,0,.18);
padding: 4px;
z-index: 9999;
}
.ctx-item,
.ctx-item-subscribed, .ctx-item-delete {
display: block;
width: 100%;
text-align: left;
border: 0;
background: transparent;
color: var(--text);
padding: 8px 10px;
cursor: pointer;
border-radius: 4px;
}
.ctx-item:hover,
.ctx-item-subscribed:hover ,
.ctx-item-delete:hover
{ background: var(--ctx-hover); }
.ctx-item[hidden],
.ctx-item-subscribed[hidden] ,
.ctx-item-delete[hidden]{
display: none;
}
#AcceptArea {
flex: 0 0 auto;
}

View File

@@ -0,0 +1,173 @@
*
{
padding: 0;
border: 0;
margin: 0;
font-family: "system-ui", "Segoe UI", Roboto, Oxygen, Ubuntu, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-sans;
user-select: none;
}
html
{
height:100%;
background-color: #626262;
}
body
{
height:100%;
max-height: 660px;
max-width: 820px;
padding: 0;
border: 0;
margin: 0;
overflow: hidden;
background-color: #fff;
font-size: 14px;
line-height: 22px;
}
.TextPoint
{
font-size:1px;
}
.ZScrol::-webkit-scrollbar {/*滚动条整体样式*/
width: 12px; /*高宽分别对应横竖滚动条的尺寸*/
height: 12px;
padding: 2px;
}
.ZScrol::-webkit-scrollbar-thumb {/*滚动条里面小方块*/
border-radius: 6px;
-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
background-color: #AAAAAA;
}
.ZScrol::-webkit-scrollbar-track {/*滚动条里面轨道*/
-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
border-radius: 10px;
background: #EDEDED;
}
/*----Three Part----*/
body
{
display:flex;
flex-direction: column;
}
#Title
{
height: 12%;
display: flex;
text-align: center;
flex-direction:column;
justify-content: center;
}
#Title div
{
font-size:28px;
line-height: 28px;
color: #009688;
padding: 0px 10mm;
}
#Content
{
height: 76%;
padding: 20px 40px;
overflow-x: hidden;
overflow-y: auto;
text-align: left;
font-size: 14px;
line-height: 22px;
color: #464646;
position: relative;
display:flex;
flex-direction: column;
}
#Content div
{
}
#AcceptArea
{
height:var(--dialog-button-sizer-height); /*----ORCA Use fixed size to prevent position change----*/
max-height:var(--dialog-button-sizer-height);
min-height:var(--dialog-button-sizer-height);
padding: 0 var(--dialog-button-gap);
text-align: left;
display: flex;
justify-content:flex-end;
align-items: center;
}
/*---HyperLink---*/
.HyperLink
{
color: #009688;
text-decoration: underline;
font-weight: 700;
cursor: pointer;
}
/*---Checkboxes ORCA ---*/
label:has(input[type="checkbox"]){
margin:0;
padding: 0;
margin-right: 20px;
}
label:has(input[type="checkbox"])>span{
vertical-align: middle;
margin:0;
}
input[type="checkbox"] {
-webkit-appearance: none;
appearance: none;
background-color:#FFFFFF;
margin:0;
margin-right: 6px;
width: 1em;
height: 1em;
border: 0.1em solid #DBDBDB;
border-radius: 0.15em;
display: inline-flex;
place-content: center;
background-color:#FFFFFF;
}
input[type="checkbox"]::before {
content: "";
width: 0.8em;
height: 0.8em;
transform: scale(0);
box-shadow: inset 1em 1em #FFFFFF;
clip-path: polygon(7% 37%, 0 45%, 33% 78%, 100% 30%, 95% 21%, 33% 64%);
}
input[type="checkbox"]:checked {
border-color:#009688;
background-color:#009688;
}
input[type="checkbox"]:checked::before {
transform: scale(1);
}
/*----------------Light Mode-------------------*/

View File

@@ -0,0 +1,100 @@
:root {
--bg: #1b1f24;
--panel: #242a31;
--border: #3a424d;
--border-strong: #3a424d;
--border-soft: #313843;
--col-sep: #3a424d;
--text: #e6ebf0;
--row-hover: #2b3340;
--row-selected: #244945;
--row-selected-outline: #00bfa5;
--footer-bg: #20262d;
--btn-bg: #2a313a;
--btn-border: #4b5664;
--btn-hover: #333c47;
--ctx-bg: #2a313a;
--ctx-border: #4b5664;
--ctx-hover: #3a4451;
}
*
{
color: #efeff0;
border-color: #B9B9BC;
}
body
{
background-color:#2D2D31; /* ORCA match background color */
color: #efeff0;
}
.ZScrol::-webkit-scrollbar-thumb {/*滚动条里面小方块*/
background-color: #939594;
}
.ZScrol::-webkit-scrollbar-track {/*滚动条里面轨道*/
background: #161817;
}
#Title div
{
color: #009688;
}
.search>input[type=text]{
background-color:#2D2D31;
}
/*---Checkboxes ORCA---*/
input[type=checkbox]{
background-color:#2D2D31;
border-color:#4A4A51;
}
input[type=checkbox]:checked{
background-color:#009688;
}
/*-------Text------*/
.TextS1
{
}
.TextS2
{
color:#B9B9BC;
}
/*---Policy---*/
.TextArea1
{
background-color: #4A4A51;
color: #BEBEC0;
}
/*----Region---*/
.RegionItem:hover
{
background-color:#4C4C55;
}
.RegionSelected:hover
{
background-color:#009688;
color: #fff;
}
/*----Menu----*/
#Title div.TitleUnselected
{
color: #BEBEC0;
}

View File

@@ -0,0 +1,35 @@
#FullArea
{
height:100%;
}
#LoadProgress
{
position:fixed;
top: 0px;
left: 0px;
width: 100%;
height: 3mm;
display: none;
}
#PercentTip
{
width:70%;
height: 100%;
background-color: #335DFC;
}
#PageArea
{
width:100%;
height: 100%;
}
#IEPage
{
height:100%;
width: 100%;
}

View File

@@ -0,0 +1,58 @@
*
{
padding: 0;
border: 0;
margin: 0;
}
html,body
{
height:100%;
padding: 0;
border: 0;
margin: 0;
overflow: hidden;
background-color: #fff;
}
#PageArea
{
height:100%;
width: 100%;
}
.swiper {
height: 100%;
top: 0px;
left: 0px;
bottom: 35px;
}
.swiper-slide {
font-size: 18px;
height: 100%;
width: 100%;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.swiper-slide iframe
{
width:100%;
height: 100%;
overflow-y: hidden;
}
.CBtn
{
padding: 3mm 100m;
background-color: #77EFF9;
display: inline-block;
height: 30px;
width: 150px;
text-align: center;
z-index: 99;
position: absolute;
top:30px;
}

View File

@@ -0,0 +1,21 @@
function ClosePage() {
var tSend = {};
tSend['sequence_id'] = Math.round(new Date() / 1000);
tSend['command'] = "close_page";
SendWXMessage(JSON.stringify(tSend));
}
document.onkeydown = function (event) {
var e = event || window.event || arguments.callee.caller.arguments[0];
if (window.event) {
try { e.keyCode = 0; } catch (e) { }
e.returnValue = false;
}
};
window.addEventListener('wheel', function (event) {
if (event.ctrlKey === true || event.metaKey) {
event.preventDefault();
}
}, { passive: false });

View File

@@ -0,0 +1,339 @@
/*------------------ Date Function ------------------------*/
function GetFullToday( )
{
var d=new Date();
var nday=d.getDate();
var nmonth=d.getMonth()+1;
var nyear=d.getFullYear();
var strM=nmonth+'';
if( nmonth<10 )
strM='0'+nmonth;
var strD=nday+'';
if( nday<10 )
strD='0'+nday;
return nyear+'-'+strM+'-'+strD;
}
function GetFullDate()
{
var d=new Date();
var tDate={};
tDate.nyear=d.getFullYear();
tDate.nmonth=d.getMonth()+1;
tDate.nday=d.getDate();
tDate.nhour=d.getHours();
tDate.nminute=d.getMinutes();
tDate.nsecond=d.getSeconds();
tDate.nweek=d.getDay();
tDate.ndate=d.getDate();
var strM=tDate.nmonth+'';
if( tDate.nmonth<10 )
strM='0'+tDate.nmonth;
var strD=tDate.nday+'';
if( tDate.nday<10 )
strD='0'+tDate.nday;
var strH=tDate.nhour+'';
if( tDate.nhour<10 )
strH='0'+tDate.nhour;
var strMin=tDate.nminute+'';
if( tDate.nminute<10 )
strMin='0'+tDate.nminute;
var strS=tDate.nsecond+'';
if( tDate.nsecond<10 )
strS='0'+tDate.nsecond;
tDate.strdate=tDate.nyear+'-'+strM+'-'+strD;
tDate.strFulldate=tDate.strdate+' '+strH+':'+strMin+':'+strS;
return tDate;
}
function Unixtimestamp2Date( nSecond )
{
var d=new Date(nSecond*1000);
var tDate={};
tDate.nyear=d.getFullYear();
tDate.nmonth=d.getMonth()+1;
tDate.nday=d.getDate();
tDate.nhour=d.getHours();
tDate.nminute=d.getMinutes();
tDate.nsecond=d.getSeconds();
tDate.nweek=d.getDay();
tDate.ndate=d.getDate();
var strM=tDate.nmonth+'';
if( tDate.nmonth<10 )
strM='0'+tDate.nmonth;
var strD=tDate.nday+'';
if( tDate.nday<10 )
strD='0'+tDate.nday;
tDate.strdate=tDate.nyear+'-'+strM+'-'+strD;
return tDate.strdate;
}
//------------Array Function-------------
Array.prototype.in_array = function (e) {
let sArray= ',' + this.join(this.S) + ',';
let skey=','+e+',';
if(sArray.indexOf(skey)>=0)
return true;
else
return false;
}
//------------String Function------------------
/**
* Delete Left/Right Side Blank
*/
String.prototype.trim=function()
{
return this.replace(/(^\s*)|(\s*$)/g, '');
}
/**
* Delete Left Side Blank
*/
String.prototype.ltrim=function()
{
return this.replace(/(^\s*)/g,'');
}
/**
* Delete Right Side Blank
*/
String.prototype.rtrim=function()
{
return this.replace(/(\s*$)/g,'');
}
//----------------Get Param-------------
function GetQueryString(name)
{
var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);
if (r!=null)
{
return unescape(r[2]);
}
else
{
return null;
}
}
function GetGetStr()
{
let strGet="";
//获取当前URL
let url = document.location.href;
//获取?的位置
let index = url.indexOf("?")
if(index != -1) {
//截取出?后面的字符串
strGet = url.substr(index + 1);
}
return strGet;
}
/*--------------------JSON Function------------*/
/*
功能检查一个字符串是不是标准的JSON格式
参数: strJson 被检查的字符串
返回值: 如果字符串是一个标准的JSON格式则返回JSON对象
如果字符串不是标准JSON格式则返回null
*/
function IsJson( strJson )
{
var tJson=null;
try
{
tJson=JSON.parse(strJson);
}
catch(exception)
{
return null;
}
return tJson;
}
/*-----------------------Ajax Function--------------------*/
/*对JQuery的Ajax函数的封装只支持异步
参数说明:
url 目标地址
action post/get
data 字符串格式的发送内容
asyn true---异步模式;false-----同步模式;
*/
function HttpReq( url,action, data,callbackfunc)
{
var strAction=action.toLowerCase();
if( strAction=="post")
{
$.post(url,data,callbackfunc);
}
else if( strAction=="get")
{
$.get(url,callbackfunc);
}
}
/*---------------Cookie Function-------------------*/
function setCookie(name, value, time='',path='') {
if(time && path){
var strsec = time * 1000;
var exp = new Date();
exp.setTime(exp.getTime() + strsec * 1);
document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString() + ";path="+path;
}else if(time){
var strsec = time * 1000;
var exp = new Date();
exp.setTime(exp.getTime() + strsec * 1);
document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString();
}else if(path){
document.cookie = name + "=" + escape(value) + ";path="+path;
}else{
document.cookie = name + "=" + escape(value);
}
}
function getCookie(c_name)
{
if(document.cookie.length > 0) {
c_start = document.cookie.indexOf(c_name + "=");//获取字符串的起点
if(c_start != -1) {
c_start = c_start + c_name.length + 1;//获取值的起点
c_end = document.cookie.indexOf(";", c_start);//获取结尾处
if(c_end == -1) c_end = document.cookie.length;//如果是最后一个结尾就是cookie字符串的结尾
return decodeURI(document.cookie.substring(c_start, c_end));//截取字符串返回
}
}
return "";
}
function checkCookie(c_name) {
username = getCookie(c_name);
console.log(username);
if (username != null && username != "")
{ return true; }
else
{ return false; }
}
function clearCookie(name) {
setCookie(name, "", -1);
}
/*--------Studio WX Message-------*/
function IsInSlicer()
{
let bMatch=navigator.userAgent.match( RegExp('BBL-Slicer','i') );
return bMatch;
}
function SendWXMessage( strMsg )
{
let bCheck=IsInSlicer();
if(bCheck!=null)
{
window.wx.postMessage(strMsg);
}
}
/*------CSS Link Control----*/
function RemoveCssLink( LinkPath )
{
let pNow=$("head link[href='"+LinkPath+"']");
let nTotal=pNow.length;
for( let n=0;n<nTotal;n++ )
{
pNow[n].remove();
}
}
function AddCssLink( LinkPath )
{
var head = document.getElementsByTagName('head')[0];
var link = document.createElement('link');
link.href = LinkPath;
link.rel = 'stylesheet';
link.type = 'text/css';
head.appendChild(link);
}
function CheckCssLinkExist( LinkPath )
{
let pNow=$("head link[href='"+LinkPath+"']");
let nTotal=pNow.length;
return nTotal;
}
/*------Dark Mode------*/
function SwitchDarkMode( DarkCssPath )
{
ExecuteDarkMode( DarkCssPath );
setInterval("ExecuteDarkMode('"+DarkCssPath+"')",1000);
}
function ExecuteDarkMode( DarkCssPath )
{
let nMode=0;
let bDarkMode=navigator.userAgent.match( RegExp('dark','i') );
if( bDarkMode!=null )
nMode=1;
let nNow=CheckCssLinkExist(DarkCssPath);
if( nMode==0 )
{
if(nNow>0)
RemoveCssLink(DarkCssPath);
}
else
{
if(nNow==0)
AddCssLink(DarkCssPath);
}
}
SwitchDarkMode( "../css/dark.css" );

View File

@@ -0,0 +1,12 @@
function NextSlide()
{
$('.swiper-button-next').click();
}
function PreSlide()
{
$('.swiper-button-prev').click();
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,185 @@
var JSON;
if (!JSON) {
JSON = {};
}
(function () {
'use strict';
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function (key) {
return isFinite(this.valueOf())
? this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z'
: null;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
return this.valueOf();
};
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
escapable.lastIndex = 0;
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string'
? c
: '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + string + '"';
}
function str(key, holder) {
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
return String(value);
case 'object':
if (!value) {
return 'null';
}
gap += indent;
partial = [];
if (Object.prototype.toString.apply(value) === '[object Array]') {
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
v = partial.length === 0
? '[]'
: gap
? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
: '[' + partial.join(',') + ']';
gap = mind;
return v;
}
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
if (typeof rep[i] === 'string') {
k = rep[i];
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
v = partial.length === 0
? '{}'
: gap
? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
: '{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
if (typeof JSON.stringify !== 'function') {
JSON.stringify = function (value, replacer, space) {
var i;
gap = '';
indent = '';
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
} else if (typeof space === 'string') {
indent = space;
}
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
return str('', {'': value});
};
}
if (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
var j;
function walk(holder, key) {
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
text = String(text);
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
if (/^[\],:{}\s]*$/
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
j = eval('(' + text + ')');
return typeof reviver === 'function'
? walk({'': j}, '')
: j;
}
throw new SyntaxError('JSON.parse');
};
}
}());