mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-05-14 00:52:04 +00:00
Introducing Orca Cloud: https://cloud.orcaslicer.com (#13414)
* 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:
@@ -111,6 +111,7 @@ var LangText = {
|
||||
orca3: "Stealth Mode",
|
||||
orca4: "This stops the transmission of data to Bambu's cloud services. Users who don't use BBL machines or use LAN mode only can safely turn on this function.",
|
||||
orca5: "Enable Stealth Mode.",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
ca_ES: {
|
||||
t1: "Benvingut a Orca Slicer",
|
||||
@@ -220,6 +221,7 @@ var LangText = {
|
||||
t113: "Pots canviar la teva elecció en les preferències en qualsevol moment.",
|
||||
orca1: "Editar Informació del Projecte",
|
||||
orca2: "no hi ha informació del model",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
es_ES: {
|
||||
t1: "Bienvenido a Orca Slicer",
|
||||
@@ -333,6 +335,7 @@ var LangText = {
|
||||
orca3: "Modo sigiloso",
|
||||
orca4: "Esta función detiene la transmisión de datos a los servicios en la nube de Bambu. Los usuarios que no utilicen máquinas BBL o que solo usen el modo LAN pueden activar esta función con seguridad.",
|
||||
orca5: "Activar modo sigiloso.",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
it_IT: {
|
||||
t1: "Benvenuti in OrcaSlicer",
|
||||
@@ -445,6 +448,7 @@ var LangText = {
|
||||
orca3: "Modalità invisibile",
|
||||
orca4: "Con questa modalità, la trasmissione dei dati ai servizi cloud di Bambu sarà interrotta. Gli utenti che non utilizzano macchine BBL o che usano solo la modalità LAN possono attivare questa funzione in modo sicuro.",
|
||||
orca5: "Abilita la modalità invisibile.",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
de_DE: {
|
||||
t1: "Willkommen im Orca Slicer",
|
||||
@@ -548,6 +552,7 @@ var LangText = {
|
||||
t126: "Laden……",
|
||||
orca1: "Edit Project Info",
|
||||
orca2: "no model information",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
cs_CZ: {
|
||||
t1: "Vítejte v Orca Slicer",
|
||||
@@ -651,6 +656,7 @@ var LangText = {
|
||||
t126: "Načtení probíhá……",
|
||||
orca1: "Edit Project Info",
|
||||
orca2: "no model information",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
fr_FR: {
|
||||
t1: "Bienvenue sur Orca Slicer",
|
||||
@@ -773,6 +779,7 @@ var LangText = {
|
||||
wk14: "Par rapport au format STL, le format STEP apporte des informations plus efficaces. Grâce à la grande précision de ce format, de nombreuses trajectoires d'extrusion peuvent être générées sous forme d'arcs. Il inclut également la relation d'assemblage de chaque pièce d'un modèle, qui peut être utilisée pour restaurer la vue d'assemblage après la coupe d'un modèle.",
|
||||
wk15: "Texte 3D",
|
||||
wk16: "Avec l'outil Texte 3D, les utilisateurs peuvent facilement créer diverses formes de texte 3D dans le projet, ce qui rend le modèle plus personnalisé. Orca Slicer fournit des dizaines de polices et prend en charge les styles gras et italique pour donner au texte une plus grande flexibilité.",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
zh_CN: {
|
||||
t1: "欢迎使用Orca Slicer",
|
||||
@@ -899,6 +906,7 @@ var LangText = {
|
||||
wk16: "使用3D文本工具,用户可以轻松地在项目中创建各种3D文本形状,使模型更加个性化。Orca Slicer提供了数十种字体,并支持粗体和斜体样式,使文本具有更大的灵活性。",
|
||||
orca1: "编辑项目信息",
|
||||
orca2: "该模型没有相关信息",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
zh_TW: {
|
||||
t1: "歡迎使用 Orca Slicer",
|
||||
@@ -1004,6 +1012,7 @@ var LangText = {
|
||||
wk16: "使用3D文字工具,使用者可以輕鬆地在項目中建立各種3D文字形狀,使模型更加個性化。Orca Slicer 提供了數十種字體,並支援粗體和斜體樣式,使文字具有更大的靈活性。",
|
||||
orca1: "編輯專案資訊",
|
||||
orca2: "沒有模型相關資訊",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
ru_RU: {
|
||||
t1: "Приветствуем в Orca Slicer!",
|
||||
@@ -1116,7 +1125,8 @@ var LangText = {
|
||||
orca2: "Информация отсутствует",
|
||||
orca3: "Режим конфиденциальности",
|
||||
orca4: "Это остановит передачу данных в облачные сервисы Bambu. Помешает только владельцам Bambu Lab, не использующим режим «Только LAN».",
|
||||
orca5: "Включить режим конфиденциальности"
|
||||
orca5: "Включить режим конфиденциальности",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
ko_KR: {
|
||||
t1: "Orca Slicer에 오신 것을 환영합니다",
|
||||
@@ -1209,6 +1219,7 @@ var LangText = {
|
||||
t126: "로딩 중……",
|
||||
orca1: "Edit Project Info",
|
||||
orca2: "no model information",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
tr_TR: {
|
||||
t1: "Orca Slicer'a hoş geldiniz",
|
||||
@@ -1322,6 +1333,7 @@ var LangText = {
|
||||
orca3: "Gizli Mod",
|
||||
orca4: "Bu, Bambu'nun bulut hizmetlerine veri iletimini durdurur. BBL makinelerini kullanmayan veya yalnızca LAN modunu kullanan kullanıcılar bu işlevi güvenle açabilir.",
|
||||
orca5: "Gizli Modu etkinleştirin.",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
pl_PL: {
|
||||
t1: "Witamy w Orca Slicer",
|
||||
@@ -1435,6 +1447,7 @@ var LangText = {
|
||||
orca3: "Tryb «Niewidzialny»",
|
||||
orca4: "To wyłączy przesyłanie danych do usług chmurowych Bambu. Użytkownicy, którzy nie korzystają z maszyn BBL lub używają tylko trybu LAN, mogą bez obaw włączyć tę opcję.",
|
||||
orca5: "Włącz tryb «Niewidzialny»",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
pt_BR: {
|
||||
t1: "Bem-vindo ao Orca Slicer",
|
||||
@@ -1548,6 +1561,7 @@ var LangText = {
|
||||
orca3: "Modo Furtivo",
|
||||
orca4: "Isso interrompe a transmissão de dados para os serviços de nuvem da Bambu. Usuários que não usam máquinas BBL ou usam somente o modo LAN podem ativar essa função com segurança.",
|
||||
orca5: "Habilita Modo Furtivo.",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
lt_LT: {
|
||||
t1: "Pasisveikinkite su Orca Slicer",
|
||||
@@ -1660,6 +1674,7 @@ var LangText = {
|
||||
orca3: "Slaptas režimas",
|
||||
orca4: "Tai sustabdo duomenų perdavimą į Bambu debesijos paslaugas. Vartotojai, kurie nenaudoja BBL mašinų arba naudoja tik LAN režimą, gali drąsiai įjungti šią funkciją.",
|
||||
orca5: "Įjungti slaptą režimą.",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
75
resources/web/dialog/ExportPresetDialog/index.html
Normal file
75
resources/web/dialog/ExportPresetDialog/index.html
Normal 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>
|
||||
374
resources/web/dialog/ExportPresetDialog/index.js
Normal file
374
resources/web/dialog/ExportPresetDialog/index.js
Normal 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, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/\"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function EscapeAttr(str)
|
||||
{
|
||||
return EscapeHtml(str);
|
||||
}
|
||||
191
resources/web/dialog/ExportPresetDialog/styles.css
Normal file
191
resources/web/dialog/ExportPresetDialog/styles.css
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
71
resources/web/dialog/PresetBundleDialog/index.html
Normal file
71
resources/web/dialog/PresetBundleDialog/index.html
Normal 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>
|
||||
375
resources/web/dialog/PresetBundleDialog/index.js
Normal file
375
resources/web/dialog/PresetBundleDialog/index.js
Normal 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)}">↑</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("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """)
|
||||
.replaceAll("'", "'");
|
||||
}
|
||||
|
||||
function escapeAttr(str) {
|
||||
// minimal attribute escaping
|
||||
return escapeHtml(str);
|
||||
}
|
||||
|
||||
function cssEscape(str) {
|
||||
// basic css escape for attribute selectors
|
||||
return String(str).replaceAll('"', '\\"');
|
||||
}
|
||||
394
resources/web/dialog/PresetBundleDialog/styles.css
Normal file
394
resources/web/dialog/PresetBundleDialog/styles.css
Normal 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;
|
||||
}
|
||||
173
resources/web/dialog/css/common.css
Normal file
173
resources/web/dialog/css/common.css
Normal 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-------------------*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
100
resources/web/dialog/css/dark.css
Normal file
100
resources/web/dialog/css/dark.css
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
35
resources/web/dialog/css/home.css
Normal file
35
resources/web/dialog/css/home.css
Normal 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%;
|
||||
}
|
||||
58
resources/web/dialog/css/test.css
Normal file
58
resources/web/dialog/css/test.css
Normal 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;
|
||||
}
|
||||
21
resources/web/dialog/js/common.js
Normal file
21
resources/web/dialog/js/common.js
Normal 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 });
|
||||
@@ -277,4 +277,63 @@ function SendWXMessage( 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" );
|
||||
12
resources/web/dialog/js/home.js
Normal file
12
resources/web/dialog/js/home.js
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
|
||||
function NextSlide()
|
||||
{
|
||||
$('.swiper-button-next').click();
|
||||
}
|
||||
|
||||
function PreSlide()
|
||||
{
|
||||
$('.swiper-button-prev').click();
|
||||
}
|
||||
4
resources/web/dialog/js/jquery-2.1.1.min.js
vendored
Normal file
4
resources/web/dialog/js/jquery-2.1.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -127,4 +127,9 @@ body
|
||||
{
|
||||
background: rgba(54, 54, 60, 0.88);
|
||||
border: 1px solid rgba(129, 129, 131, 0.64);
|
||||
}
|
||||
}
|
||||
|
||||
/*--- Bambu Cloud Section ---*/
|
||||
#BambuCloudHeader { color: #818183; }
|
||||
#BambuCloudHeader:hover { color: #efeff0; }
|
||||
.bambu-chevron svg path { stroke: currentColor; }
|
||||
@@ -79,40 +79,151 @@ body
|
||||
|
||||
#LoginArea
|
||||
{
|
||||
height: 180px;
|
||||
min-height: 180px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
position: relative;
|
||||
width:262px;
|
||||
}
|
||||
|
||||
|
||||
#Login1
|
||||
#OrcaLoginSection
|
||||
{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#OrcaLogin1
|
||||
{
|
||||
height:36px;
|
||||
line-height: 36px;
|
||||
display: flex;
|
||||
flex-direction: column; /*ORCA*/
|
||||
align-items: center; /*Allow icon centered vertically*/
|
||||
justify-content: center; /*and use login button in new line*/
|
||||
user-select: none;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
#OrcaLogin2
|
||||
{
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 14px 0 16px;
|
||||
box-sizing: border-box;
|
||||
width: 262px;
|
||||
}
|
||||
|
||||
/* --- Bambu Cloud Section --- */
|
||||
#BambuCloudSection
|
||||
{
|
||||
display: none; /* shown by cloud_providers_info from backend */
|
||||
border-top: 1px solid;
|
||||
width: 262px;
|
||||
}
|
||||
|
||||
#BambuCloudHeader
|
||||
{
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: #A8A8A8;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
#BambuCloudHeader:hover
|
||||
{
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.bambu-chevron
|
||||
{
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
transition: transform 0.2s ease;
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
.bambu-chevron.expanded
|
||||
{
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.bambu-status-dot
|
||||
{
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: #A8A8A8;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.bambu-status-dot.online
|
||||
{
|
||||
background-color: #4CAF50;
|
||||
}
|
||||
|
||||
#BambuCloudBody
|
||||
{
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.25s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#BambuCloudBody.expanded
|
||||
{
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
#NoPluginTip
|
||||
{
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
z-index: 1;
|
||||
position: static;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding: 5px;
|
||||
padding: 5px 10px;
|
||||
z-index: auto;
|
||||
}
|
||||
|
||||
#BambuLogin1
|
||||
{
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
#BambuLogin2
|
||||
{
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 8px 0;
|
||||
width: 262px;
|
||||
}
|
||||
|
||||
#BambuAvatarIcon
|
||||
{
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
#BambuUserName
|
||||
{
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
width: 80%;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
|
||||
@@ -132,18 +243,14 @@ body
|
||||
height:96px; /*ORCA use bigger icon to fit logo size*/
|
||||
}
|
||||
|
||||
#Login2
|
||||
{
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
width: 262px;
|
||||
}
|
||||
|
||||
#UserAvatarIcon
|
||||
{
|
||||
height: 85px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
display: block;
|
||||
margin: 2px auto 10px;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
#UserName
|
||||
@@ -153,11 +260,13 @@ body
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
width: 80%;
|
||||
max-width: 190px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
#LogoutBtn
|
||||
{
|
||||
margin-top: 5px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/*------------------*/
|
||||
@@ -201,6 +310,38 @@ body
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.BtnShortcut
|
||||
{
|
||||
padding-right: 18px;
|
||||
}
|
||||
|
||||
.ShortcutBrandIcon
|
||||
{
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 5px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ShortcutTextRow
|
||||
{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ShortcutMark
|
||||
{
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-left: auto;
|
||||
color: currentColor;
|
||||
opacity: 0.72;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
|
||||
/*--------------------*/
|
||||
#RightBoard
|
||||
@@ -321,6 +462,7 @@ body
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
#RecentTitleBlock
|
||||
@@ -348,6 +490,7 @@ body
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
overflow-y: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.FileItem
|
||||
@@ -735,4 +878,4 @@ body
|
||||
opacity: 1!important;
|
||||
cursor: pointer!important;
|
||||
pointer-events: auto!important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,32 +20,72 @@
|
||||
<script type="text/javascript" src="js/home.js"></script>
|
||||
</head>
|
||||
<body class="ZScrol" onLoad="OnInit()">
|
||||
<div id="LeftBoard" style="display: none;">
|
||||
<div id="LeftBoard">
|
||||
<div id="LoginArea">
|
||||
<div id="Login1">
|
||||
<div id="Icon1"><img id="BBLIcon" src="../image/logo.png" /></div> <!-- ORCA use square icon for better consistency on UI -->
|
||||
<div id="LoginBtn" class="ButtonStyleRegular ButtonTypeWindow" onClick="OnLoginOrRegister()"><span class="trans" tid="t26">login</span> / <span class="trans" tid="t27">register</span></div>
|
||||
</div>
|
||||
|
||||
<div id="Login2">
|
||||
<div>
|
||||
<img id="UserAvatarIcon" src="img/c.jpg" onerror="this.onerror=null;this.src='img/c.jpg';" />
|
||||
|
||||
<!-- Orca Login Section -->
|
||||
<div id="OrcaLoginSection">
|
||||
<div id="OrcaLogin1">
|
||||
<div id="Icon1"><img id="BBLIcon" src="../image/logo.png" /></div> <!-- ORCA use square icon for better consistency on UI -->
|
||||
<div id="LoginBtn" class="ButtonStyleRegular ButtonTypeWindow" onClick="OnLoginOrRegister()"><span class="trans" tid="t26">login</span> / <span class="trans" tid="t27">register</span></div>
|
||||
</div>
|
||||
|
||||
<div id="OrcaLogin2">
|
||||
<div>
|
||||
<img id="UserAvatarIcon" src="img/c.jpg" onerror="this.onerror=null;this.src='img/c.jpg';" />
|
||||
</div>
|
||||
<div id="UserName" class="TextS1"></div>
|
||||
<div id="LogoutBtn" class="ButtonStyleAlert ButtonTypeWindow trans" tid="t50" onClick="OnLogOut()">log out</div>
|
||||
</div>
|
||||
<div id="UserName" class="TextS1"></div>
|
||||
<div id="LogoutBtn" class="ButtonStyleAlert ButtonTypeWindow trans" tid="t50" onClick="OnLogOut()">log out</div>
|
||||
</div>
|
||||
|
||||
<div id="NoPluginTip">
|
||||
<div id="NoPluginText"><a class="RedFont trans" tid="t76">Network plugin not detected. Click </a><a Class="LinkBtn trans" onClick="BeginDownloadNetworkPlugin()" tid="t77">here</a><a class="RedFont trans" tid="t78"> to install it.</a></div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Bambu Cloud Section -->
|
||||
<div id="BambuCloudSection">
|
||||
<div id="BambuCloudHeader" onClick="ToggleBambuSection()">
|
||||
<svg class="bambu-chevron" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.5 2.5L8 6L4.5 9.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<span class="trans" tid="orca6">Bambu Cloud</span>
|
||||
<span class="bambu-status-dot"></span>
|
||||
</div>
|
||||
|
||||
<div id="BambuCloudBody">
|
||||
<div id="BambuLogin1">
|
||||
<div id="BambuLoginBtn" class="ButtonStyleRegular ButtonTypeWindow" onClick="OnBambuLoginOrRegister()"><span class="trans" tid="t26">login</span></div>
|
||||
</div>
|
||||
|
||||
<div id="BambuLogin2">
|
||||
<div>
|
||||
<img id="BambuAvatarIcon" src="img/c.jpg" onerror="this.onerror=null;this.src='img/c.jpg';" />
|
||||
</div>
|
||||
<div id="BambuUserName" class="TextS1"></div>
|
||||
<div id="BambuLogoutBtn" class="ButtonStyleAlert ButtonTypeWindow trans" tid="t50" onClick="OnBambuLogOut()">log out</div>
|
||||
</div>
|
||||
|
||||
<div id="NoPluginTip">
|
||||
<div id="NoPluginText"><a class="RedFont trans" tid="t76">Network plugin not detected. Click </a><a Class="LinkBtn trans" onClick="BeginDownloadNetworkPlugin()" tid="t77">here</a><a class="RedFont trans" tid="t78"> to install it.</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="BtnArea">
|
||||
<div menu="recent" class="BtnItem BtnItemSelected" onClick="GotoMenu('recent')">
|
||||
<div class="BtnIcon "><img class="LeftIcon" src="img/i2.png" /></div>
|
||||
<div class="trans" tid="t28">recent</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="BtnItem BtnShortcut" onClick="OpenUrlInLocalBrowser('https://cloud.orcaslicer.com')">
|
||||
<div class="BtnIcon "><img class="ShortcutBrandIcon" src="../image/logo.png" /></div>
|
||||
<div class="ShortcutTextRow">
|
||||
<div>OrcaCloud</div>
|
||||
<svg class="ShortcutMark" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path d="M6 3.5H3.5C2.95 3.5 2.5 3.95 2.5 4.5V12.5C2.5 13.05 2.95 13.5 3.5 13.5H11.5C12.05 13.5 12.5 13.05 12.5 12.5V10" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.5 3.5H13.5V8.5" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.5 3.5L7 10" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//var TestData={"sequence_id":"0","command":"get_recent_projects","response":[{"path":"D:\\work\\Models\\Toy\\3d-puzzle-cube-model_files\\3d-puzzle-cube.3mf","time":"2022\/3\/24 20:33:10"},{"path":"D:\\work\\Models\\Art\\Carved Stone Vase - remeshed+drainage\\Carved Stone Vase.3mf","time":"2022\/3\/24 17:11:51"},{"path":"D:\\work\\Models\\Art\\Kity & Cat\\Cat.3mf","time":"2022\/3\/24 17:07:55"},{"path":"D:\\work\\Models\\Toy\\鐩村墤.3mf","time":"2022\/3\/24 17:06:02"},{"path":"D:\\work\\Models\\Toy\\minimalistic-dual-tone-whistle-model_files\\minimalistic-dual-tone-whistle.3mf","time":"2022\/3\/22 21:12:22"},{"path":"D:\\work\\Models\\Toy\\spiral-city-model_files\\spiral-city.3mf","time":"2022\/3\/22 18:58:37"},{"path":"D:\\work\\Models\\Toy\\impossible-dovetail-puzzle-box-model_files\\impossible-dovetail-puzzle-box.3mf","time":"2022\/3\/22 20:08:40"}]};
|
||||
|
||||
var m_HotModelList=null;
|
||||
var bambuSectionExpanded = false;
|
||||
|
||||
function OnInit()
|
||||
{
|
||||
@@ -8,6 +9,7 @@ function OnInit()
|
||||
TranslatePage();
|
||||
|
||||
SendMsg_GetLoginInfo();
|
||||
SendMsg_GetBambuLoginInfo();
|
||||
SendMsg_GetRecentFile();
|
||||
SendMsg_GetStaffPick();
|
||||
}
|
||||
@@ -77,11 +79,7 @@ function Set_RecentFile_MouseRightBtn_Event()
|
||||
|
||||
function SetLoginPanelVisibility(visible) {
|
||||
var leftBoard = document.getElementById("LeftBoard");
|
||||
if (visible) {
|
||||
leftBoard.style.display = "block";
|
||||
} else {
|
||||
leftBoard.style.display = "none";
|
||||
}
|
||||
leftBoard.style.display = "block";
|
||||
}
|
||||
|
||||
function HandleStudio( pVal )
|
||||
@@ -90,24 +88,41 @@ function HandleStudio( pVal )
|
||||
|
||||
if (strCmd == "get_recent_projects") {
|
||||
ShowRecentFileList(pVal["response"]);
|
||||
} else if (strCmd == "studio_userlogin") {
|
||||
SetLoginInfo(pVal["data"]["avatar"], pVal["data"]["name"]);
|
||||
} else if (strCmd == "studio_useroffline") {
|
||||
SetUserOffline();
|
||||
} else if (strCmd == "orca_userlogin") {
|
||||
SetOrcaLoginInfo(pVal["data"]["avatar"], pVal["data"]["name"]);
|
||||
} else if (strCmd == "orca_useroffline") {
|
||||
SetOrcaUserOffline();
|
||||
} else if (strCmd == "studio_bambu_userlogin") {
|
||||
SetBambuLoginInfo(pVal["data"]["avatar"], pVal["data"]["name"]);
|
||||
} else if (strCmd == "studio_bambu_useroffline") {
|
||||
SetBambuUserOffline();
|
||||
} else if (strCmd == "studio_set_mallurl") {
|
||||
SetMallUrl(pVal["data"]["url"]);
|
||||
} else if (strCmd == "studio_clickmenu") {
|
||||
let strName = pVal["data"]["menu"];
|
||||
|
||||
GotoMenu(strName);
|
||||
} else if (strCmd == "cloud_providers_info") {
|
||||
if (pVal["data"]["providers"] && pVal["data"]["providers"].indexOf("bbl") >= 0) {
|
||||
$("#BambuCloudSection").show();
|
||||
} else {
|
||||
$("#BambuCloudSection").hide();
|
||||
}
|
||||
} else if (strCmd == "network_plugin_installtip") {
|
||||
let nShow = pVal["show"] * 1;
|
||||
|
||||
if (nShow == 1) {
|
||||
// Auto-expand Bambu section to show the tip
|
||||
if (!bambuSectionExpanded) ToggleBambuSection();
|
||||
$("#BambuLogin1").hide();
|
||||
$("#NoPluginTip").show();
|
||||
$("#NoPluginTip").css("display", "flex");
|
||||
} else {
|
||||
$("#NoPluginTip").hide();
|
||||
// Only restore login button if not already logged in
|
||||
if ($("#BambuLogin2").is(":hidden")) {
|
||||
$("#BambuLogin1").show();
|
||||
}
|
||||
}
|
||||
} else if (strCmd == "modelmall_model_advise_get") {
|
||||
//alert('hot');
|
||||
@@ -120,8 +135,8 @@ function HandleStudio( pVal )
|
||||
|
||||
m_HotModelList = pVal["hits"];
|
||||
ShowStaffPick(m_HotModelList);
|
||||
} else if (data.cmd === "SetLoginPanelVisibility") {
|
||||
SetLoginPanelVisibility(data.visible);
|
||||
} else if (strCmd == "SetLoginPanelVisibility") {
|
||||
SetLoginPanelVisibility(pVal["data"]["visible"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,32 +161,32 @@ function GotoMenu( strMenu )
|
||||
}
|
||||
}
|
||||
|
||||
function SetLoginInfo( strAvatar, strName )
|
||||
function SetOrcaLoginInfo( strAvatar, strName )
|
||||
{
|
||||
$("#Login1").hide();
|
||||
|
||||
$("#OrcaLogin1").hide();
|
||||
|
||||
$("#UserName").text(strName);
|
||||
|
||||
|
||||
let OriginAvatar=$("#UserAvatarIcon").prop("src");
|
||||
if(strAvatar!=OriginAvatar)
|
||||
if(strAvatar != null && strAvatar.trim() !== '' && strAvatar!=OriginAvatar)
|
||||
$("#UserAvatarIcon").prop("src",strAvatar);
|
||||
else
|
||||
{
|
||||
//alert('Avatar is Same');
|
||||
}
|
||||
|
||||
$("#Login2").show();
|
||||
$("#Login2").css("display","flex");
|
||||
|
||||
$("#OrcaLogin2").show();
|
||||
$("#OrcaLogin2").css("display","flex");
|
||||
}
|
||||
|
||||
function SetUserOffline()
|
||||
function SetOrcaUserOffline()
|
||||
{
|
||||
$("#UserAvatarIcon").prop("src","img/c.jpg");
|
||||
$("#UserName").text('');
|
||||
$("#Login2").hide();
|
||||
|
||||
$("#Login1").show();
|
||||
$("#Login1").css("display","flex");
|
||||
$("#OrcaLogin2").hide();
|
||||
|
||||
$("#OrcaLogin1").show();
|
||||
$("#OrcaLogin1").css("display","flex");
|
||||
}
|
||||
|
||||
function SetMallUrl( strUrl )
|
||||
@@ -246,6 +261,17 @@ function SendMsg_GetLoginInfo()
|
||||
SendWXMessage( JSON.stringify(tSend) );
|
||||
}
|
||||
|
||||
function SendSimpleCommand(command) {
|
||||
var tSend = {};
|
||||
tSend['sequence_id'] = Math.round(new Date() / 1000);
|
||||
tSend['command'] = command;
|
||||
SendWXMessage(JSON.stringify(tSend));
|
||||
}
|
||||
|
||||
function OnOrcaLoginOrRegister() { SendSimpleCommand("homepage_orca_login_or_register"); }
|
||||
function OnOrcaLogOut() { SendSimpleCommand("homepage_orca_logout"); }
|
||||
function SendMsg_GetOrcaLoginInfo() { SendSimpleCommand("get_orca_login_info"); }
|
||||
|
||||
|
||||
function SendMsg_GetRecentFile()
|
||||
{
|
||||
@@ -373,10 +399,52 @@ function OnLogOut()
|
||||
var tSend={};
|
||||
tSend['sequence_id']=Math.round(new Date() / 1000);
|
||||
tSend['command']="homepage_logout";
|
||||
|
||||
SendWXMessage( JSON.stringify(tSend) );
|
||||
|
||||
SendWXMessage( JSON.stringify(tSend) );
|
||||
}
|
||||
|
||||
// --- Bambu Cloud Section ---
|
||||
|
||||
function ToggleBambuSection() {
|
||||
var body = document.getElementById('BambuCloudBody');
|
||||
var chevron = document.querySelector('.bambu-chevron');
|
||||
if (!body || !chevron) return;
|
||||
bambuSectionExpanded = !bambuSectionExpanded;
|
||||
if (bambuSectionExpanded) {
|
||||
body.classList.add('expanded');
|
||||
chevron.classList.add('expanded');
|
||||
} else {
|
||||
body.classList.remove('expanded');
|
||||
chevron.classList.remove('expanded');
|
||||
}
|
||||
}
|
||||
|
||||
function SetBambuLoginInfo(strAvatar, strName) {
|
||||
$("#BambuLogin1").hide();
|
||||
$("#BambuUserName").text(strName);
|
||||
if (strAvatar && strAvatar.trim() !== '') {
|
||||
$("#BambuAvatarIcon").prop("src", strAvatar);
|
||||
}
|
||||
$("#BambuLogin2").show();
|
||||
$("#BambuLogin2").css("display", "flex");
|
||||
$(".bambu-status-dot").addClass("online");
|
||||
}
|
||||
|
||||
function SetBambuUserOffline() {
|
||||
$("#BambuAvatarIcon").prop("src", "img/c.jpg");
|
||||
$("#BambuUserName").text('');
|
||||
$("#BambuLogin2").hide();
|
||||
if ($("#NoPluginTip").is(":hidden")) {
|
||||
$("#BambuLogin1").show();
|
||||
$("#BambuLogin1").css("display", "flex");
|
||||
}
|
||||
$(".bambu-status-dot").removeClass("online");
|
||||
}
|
||||
|
||||
function OnBambuLoginOrRegister() { SendSimpleCommand("homepage_bambu_login_or_register"); }
|
||||
function OnBambuLogOut() { SendSimpleCommand("homepage_bambu_logout"); }
|
||||
function SendMsg_GetBambuLoginInfo() { SendSimpleCommand("get_bambu_login_info"); }
|
||||
|
||||
function BeginDownloadNetworkPlugin()
|
||||
{
|
||||
var tSend={};
|
||||
@@ -431,19 +499,7 @@ function InitStaffPick()
|
||||
},
|
||||
slidesPerView : 'auto',
|
||||
slidesPerGroup : 3
|
||||
// autoplay: {
|
||||
// delay: 3000,
|
||||
// stopOnLastSlide: false,
|
||||
// disableOnInteraction: true,
|
||||
// disableOnInteraction: false
|
||||
// },
|
||||
// pagination: {
|
||||
// el: '.swiper-pagination',
|
||||
// },
|
||||
// scrollbar: {
|
||||
// el: '.swiper-scrollbar',
|
||||
// draggable: true
|
||||
// }
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
*
|
||||
{
|
||||
padding:0px;
|
||||
border: 0px;
|
||||
margin: 0px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
/*------------------*/
|
||||
body
|
||||
{
|
||||
display:flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
max-height: 900px;
|
||||
max-width: 1300px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
#ErrorBlock
|
||||
{
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#ErrorIcon
|
||||
{
|
||||
width:100px;
|
||||
}
|
||||
|
||||
#ErrorTip
|
||||
{
|
||||
font-size: 18px;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
#ErrorBtn
|
||||
{
|
||||
width:100px;
|
||||
display: none;
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.9 KiB |
@@ -1,35 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Cache-Control" content="max-age=7200" />
|
||||
<title>Network Error</title>
|
||||
<link rel="stylesheet" type="text/css" href="../include/global.css" /> <!-- ORCA One for all-->
|
||||
<link rel="stylesheet" type="text/css" href="css/login.css" />
|
||||
<script type="text/javascript" src="js/jquery-3.6.0.min.js"></script>
|
||||
<script type="text/javascript" src="../data/text.js"></script>
|
||||
<script type="text/javascript" src="js/json2.js"></script>
|
||||
<script type="text/javascript" src="js/globalapi.js"></script>
|
||||
<script type="text/javascript" src="js/login.js"></script>
|
||||
</head>
|
||||
<body onLoad="TranslatePage()">
|
||||
<div id="ErrorBlock">
|
||||
<img id="ErrorIcon" src="disconnect3.png" />
|
||||
<div id="ErrorTip" class="trans" title="t40" >Network disconnect, please check and try again later.</div>
|
||||
<div id="ErrorBtn" class="ButtonStyleRegular ButtonTypeChoice">Retry</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
/*var TestData={"sequence_id":"0","command":"studio_send_recentfile","data":[{"path":"D:\\work\\Models\\Toy\\3d-puzzle-cube-model_files\\3d-puzzle-cube.3mf","time":"2022\/3\/24 20:33:10"},{"path":"D:\\work\\Models\\Art\\Carved Stone Vase - remeshed+drainage\\Carved Stone Vase.3mf","time":"2022\/3\/24 17:11:51"},{"path":"D:\\work\\Models\\Art\\Kity & Cat\\Cat.3mf","time":"2022\/3\/24 17:07:55"},{"path":"D:\\work\\Models\\Toy\\鐩村墤.3mf","time":"2022\/3\/24 17:06:02"},{"path":"D:\\work\\Models\\Toy\\minimalistic-dual-tone-whistle-model_files\\minimalistic-dual-tone-whistle.3mf","time":"2022\/3\/22 21:12:22"},{"path":"D:\\work\\Models\\Toy\\spiral-city-model_files\\spiral-city.3mf","time":"2022\/3\/22 18:58:37"},{"path":"D:\\work\\Models\\Toy\\impossible-dovetail-puzzle-box-model_files\\impossible-dovetail-puzzle-box.3mf","time":"2022\/3\/22 20:08:40"}]};*/
|
||||
|
||||
|
||||
function OnInit()
|
||||
{
|
||||
TranslatePage();
|
||||
|
||||
$("#HotspotWEB").prop("src","https://www.bambulab.com");
|
||||
}
|
||||
|
||||
|
||||
function HandleStudio( pVal )
|
||||
{
|
||||
let strCmd = pVal['command'];
|
||||
//alert(strCmd);
|
||||
|
||||
if(strCmd=='studio_send_recentfile')
|
||||
{
|
||||
ShowRecentFileList(pVal['data']);
|
||||
}
|
||||
else if(strCmd=='studio_userlogin')
|
||||
{
|
||||
SetLoginInfo(pVal['data']['avatar'],pVal['data']['name']);
|
||||
}
|
||||
else if(strCmd=='studio_useroffline')
|
||||
{
|
||||
SetUserOffline();
|
||||
}
|
||||
else if( strCmd=="studio_set_mallurl" )
|
||||
{
|
||||
SetMallUrl( pVal['data']['url'] );
|
||||
}
|
||||
else if( strCmd=="studio_clickmenu" )
|
||||
{
|
||||
let strName=pVal['data']['menu'];
|
||||
|
||||
GotoMenu(strName);
|
||||
}
|
||||
}
|
||||
|
||||
function GotoMenu( strMenu )
|
||||
{
|
||||
let MenuList=$(".BtnItem");
|
||||
let nAll=MenuList.length;
|
||||
|
||||
for(let n=0;n<nAll;n++)
|
||||
{
|
||||
let OneBtn=MenuList[n];
|
||||
|
||||
if( $(OneBtn).attr("menu")==strMenu )
|
||||
{
|
||||
$(".BtnItem").removeClass("BtnItemSelected");
|
||||
|
||||
$(OneBtn).addClass("BtnItemSelected");
|
||||
|
||||
$("div[board]").hide();
|
||||
$("div[board=\'"+strMenu+"\']").show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function SetLoginInfo( strAvatar, strName )
|
||||
{
|
||||
$("#Login1").hide();
|
||||
|
||||
$("#UserAvatarIcon").prop("src","../../image/cache/"+strAvatar);
|
||||
$("#UserName").text(strName);
|
||||
$("#Login2").show();
|
||||
}
|
||||
|
||||
function SetUserOffline()
|
||||
{
|
||||
$("#UserAvatarIcon").prop("src","img/c.jpg");
|
||||
$("#UserName").text('');
|
||||
$("#Login2").hide();
|
||||
|
||||
$("#Login1").show();
|
||||
}
|
||||
|
||||
function SetMallUrl( strUrl )
|
||||
{
|
||||
$("#MallWeb").prop("src",strUrl);
|
||||
}
|
||||
|
||||
|
||||
function ShowRecentFileList( pList )
|
||||
{
|
||||
let nTotal=pList.length;
|
||||
|
||||
let strHtml='';
|
||||
for(let n=0;n<nTotal;n++)
|
||||
{
|
||||
let OneFile=pList[n];
|
||||
|
||||
let sImg=OneFile["image"];
|
||||
let sPath=OneFile['path'];
|
||||
let sTime=OneFile['time'];
|
||||
|
||||
let index=sPath.lastIndexOf('\\')>0?sPath.lastIndexOf('\\'):sPath.lastIndexOf('\/');
|
||||
let sShortName=sPath.substring(index+1,sPath.length);
|
||||
|
||||
let TmpHtml='<div class="FileItem" onClick="OnOpenRecentFile(\''+ encodeURI(sPath)+'\')" >'+
|
||||
'<a class="FileTip" title="'+sPath+'"></a>'+
|
||||
'<div class="FileImg" ><img src="..\..\img\temp\\'+sImg+'" onerror="this.onerror=null;this.src=\'img/d.png\';" alt="No Image" /></div>'+
|
||||
'<a>'+sShortName+'</a>'+
|
||||
'<div class="FileDate">'+sTime+'</div>'+
|
||||
'</div>';
|
||||
|
||||
strHtml+=TmpHtml;
|
||||
}
|
||||
|
||||
$("#FileList").html(strHtml);
|
||||
}
|
||||
|
||||
|
||||
/*-------MX Message------*/
|
||||
|
||||
function OnLoginOrRegister()
|
||||
{
|
||||
var tSend={};
|
||||
tSend['sequence_id']=Math.round(new Date() / 1000);
|
||||
tSend['command']="homepage_login_or_register";
|
||||
|
||||
SendWXMessage( JSON.stringify(tSend) );
|
||||
}
|
||||
|
||||
|
||||
function OnClickNewProject()
|
||||
{
|
||||
var tSend={};
|
||||
tSend['sequence_id']=Math.round(new Date() / 1000);
|
||||
tSend['command']="homepage_newproject";
|
||||
|
||||
SendWXMessage( JSON.stringify(tSend) );
|
||||
}
|
||||
|
||||
function OnClickOpenProject()
|
||||
{
|
||||
var tSend={};
|
||||
tSend['sequence_id']=Math.round(new Date() / 1000);
|
||||
tSend['command']="homepage_openproject";
|
||||
|
||||
SendWXMessage( JSON.stringify(tSend) );
|
||||
}
|
||||
|
||||
function OnOpenRecentFile( strPath )
|
||||
{
|
||||
var tSend={};
|
||||
tSend['sequence_id']=Math.round(new Date() / 1000);
|
||||
tSend['command']="homepage_open_recentfile";
|
||||
tSend['data']={};
|
||||
tSend['data']['path']=decodeURI(strPath);
|
||||
|
||||
SendWXMessage( JSON.stringify(tSend) );
|
||||
}
|
||||
|
||||
|
||||
@@ -1,927 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>OrcaCloud Login</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg-primary: #f5f5f7;
|
||||
--bg-card: #ffffff;
|
||||
--text-primary: #1d1d1f;
|
||||
--text-secondary: #86868b;
|
||||
--text-tertiary: #6e6e73;
|
||||
--border-color: #d2d2d7;
|
||||
--accent-color: #009688;
|
||||
--accent-hover: #00796b;
|
||||
--error-color: #d32f2f;
|
||||
--success-color: #388e3c;
|
||||
--google-color: #4285f4;
|
||||
--apple-color: #000000;
|
||||
--github-color: #24292e;
|
||||
--input-bg: #f5f5f7;
|
||||
--shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg-primary: #1d1d1f;
|
||||
--bg-card: #2d2d2f;
|
||||
--text-primary: #f5f5f7;
|
||||
--text-secondary: #a1a1a6;
|
||||
--text-tertiary: #86868b;
|
||||
--border-color: #424245;
|
||||
--input-bg: #3a3a3c;
|
||||
--shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background: var(--bg-card);
|
||||
border-radius: 16px;
|
||||
box-shadow: var(--shadow);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: 40px 32px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.header .logo {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
display: flex;
|
||||
background: var(--input-bg);
|
||||
border-radius: 10px;
|
||||
padding: 4px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
flex: 1;
|
||||
padding: 10px 16px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
background: var(--bg-card);
|
||||
color: var(--text-primary);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.tab:hover:not(.active) {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.form-container {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.input-group input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
font-size: 15px;
|
||||
background: var(--input-bg);
|
||||
color: var(--text-primary);
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.input-group input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 3px rgba(0, 150, 136, 0.1);
|
||||
}
|
||||
|
||||
.input-group input::placeholder {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.input-group.error input {
|
||||
border-color: var(--error-color);
|
||||
}
|
||||
|
||||
.input-group .error-text {
|
||||
font-size: 12px;
|
||||
color: var(--error-color);
|
||||
margin-top: 4px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.input-group.error .error-text {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.confirm-password-group {
|
||||
display: none;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.confirm-password-group.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
width: 100%;
|
||||
padding: 14px 24px;
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease, transform 0.1s ease;
|
||||
}
|
||||
|
||||
.primary-btn:hover {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
|
||||
.primary-btn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.primary-btn:disabled {
|
||||
background: var(--text-tertiary);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.forgot-link {
|
||||
display: block;
|
||||
text-align: center;
|
||||
color: var(--accent-color);
|
||||
font-size: 13px;
|
||||
text-decoration: none;
|
||||
margin-top: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.forgot-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.divider::before,
|
||||
.divider::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--border-color);
|
||||
}
|
||||
|
||||
.divider span {
|
||||
padding: 0 16px;
|
||||
font-size: 13px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.providers {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.provider-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
padding: 12px 24px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
background: var(--bg-card);
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.provider-btn:hover {
|
||||
border-color: var(--text-tertiary);
|
||||
background: var(--input-bg);
|
||||
}
|
||||
|
||||
.provider-btn svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.provider-btn.google:hover {
|
||||
border-color: var(--google-color);
|
||||
}
|
||||
|
||||
.provider-btn.apple:hover {
|
||||
border-color: var(--apple-color);
|
||||
}
|
||||
|
||||
.provider-btn.github:hover {
|
||||
border-color: var(--github-color);
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 16px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.message.error {
|
||||
display: block;
|
||||
background: rgba(211, 47, 47, 0.1);
|
||||
color: var(--error-color);
|
||||
border: 1px solid rgba(211, 47, 47, 0.2);
|
||||
}
|
||||
|
||||
.message.success {
|
||||
display: block;
|
||||
background: rgba(56, 142, 60, 0.1);
|
||||
color: var(--success-color);
|
||||
border: 1px solid rgba(56, 142, 60, 0.2);
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 16px;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.loading-overlay {
|
||||
background: rgba(45, 45, 47, 0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-overlay.visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid var(--border-color);
|
||||
border-top-color: var(--accent-color);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.back-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.back-link svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.reset-view {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.reset-view.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.auth-view {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.auth-view.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.reset-description {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
#debug {
|
||||
margin-top: 20px;
|
||||
font-size: 10px;
|
||||
color: var(--text-tertiary);
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
white-space: pre-wrap;
|
||||
display: none;
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
background: var(--input-bg);
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-card">
|
||||
<!-- Auth View (Sign In / Sign Up) -->
|
||||
<div id="auth-view" class="auth-view">
|
||||
<div class="header">
|
||||
<svg class="logo" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="64" height="64" rx="14" fill="#009688"/>
|
||||
<path d="M32 16C23.163 16 16 23.163 16 32C16 40.837 23.163 48 32 48C40.837 48 48 40.837 48 32C48 23.163 40.837 16 32 16ZM32 44C25.373 44 20 38.627 20 32C20 25.373 25.373 20 32 20C38.627 20 44 25.373 44 32C44 38.627 38.627 44 32 44Z" fill="white"/>
|
||||
<circle cx="32" cy="32" r="6" fill="white"/>
|
||||
</svg>
|
||||
<h1>OrcaCloud</h1>
|
||||
<p id="header-subtitle">Sign in to sync your settings</p>
|
||||
</div>
|
||||
|
||||
<div class="tab-container">
|
||||
<button class="tab active" data-mode="signin" onclick="setMode('signin')">Sign In</button>
|
||||
<button class="tab" data-mode="signup" onclick="setMode('signup')">Sign Up</button>
|
||||
</div>
|
||||
|
||||
<form id="auth-form" class="form-container" onsubmit="handleSubmit(event)">
|
||||
<div class="input-group" id="email-group">
|
||||
<label for="email">Email</label>
|
||||
<input type="email" id="email" placeholder="you@example.com" autocomplete="email" required>
|
||||
<span class="error-text" id="email-error"></span>
|
||||
</div>
|
||||
|
||||
<div class="input-group" id="password-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" placeholder="Enter your password" autocomplete="current-password" required>
|
||||
<span class="error-text" id="password-error"></span>
|
||||
</div>
|
||||
|
||||
<div class="input-group confirm-password-group" id="confirm-group">
|
||||
<label for="confirm-password">Confirm Password</label>
|
||||
<input type="password" id="confirm-password" placeholder="Confirm your password" autocomplete="new-password">
|
||||
<span class="error-text" id="confirm-error"></span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="primary-btn" id="submit-btn">Sign In</button>
|
||||
</form>
|
||||
|
||||
<a class="forgot-link" id="forgot-link" onclick="showResetView()">Forgot password?</a>
|
||||
|
||||
<div class="divider"><span>or continue with</span></div>
|
||||
|
||||
<div class="providers">
|
||||
<button class="provider-btn google" onclick="handleOAuthProvider('google')">
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/>
|
||||
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/>
|
||||
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/>
|
||||
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/>
|
||||
</svg>
|
||||
Continue with Google
|
||||
</button>
|
||||
|
||||
<button class="provider-btn apple" onclick="handleOAuthProvider('apple')">
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z" fill="currentColor"/>
|
||||
</svg>
|
||||
Continue with Apple
|
||||
</button>
|
||||
|
||||
<button class="provider-btn github" onclick="handleOAuthProvider('github')">
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.17 6.839 9.49.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.604-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.464-1.11-1.464-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0112 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.167 22 16.418 22 12c0-5.523-4.477-10-10-10z" fill="currentColor"/>
|
||||
</svg>
|
||||
Continue with GitHub
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reset Password View -->
|
||||
<div id="reset-view" class="reset-view">
|
||||
<a class="back-link" onclick="hideResetView()">
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 18L9 12L15 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
Back to sign in
|
||||
</a>
|
||||
|
||||
<div class="header">
|
||||
<h1>Reset Password</h1>
|
||||
</div>
|
||||
|
||||
<p class="reset-description">
|
||||
Enter your email address and we'll send you a link to reset your password.
|
||||
</p>
|
||||
|
||||
<form id="reset-form" class="form-container" onsubmit="handleResetSubmit(event)">
|
||||
<div class="input-group" id="reset-email-group">
|
||||
<label for="reset-email">Email</label>
|
||||
<input type="email" id="reset-email" placeholder="you@example.com" autocomplete="email" required>
|
||||
<span class="error-text" id="reset-email-error"></span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="primary-btn" id="reset-btn">Send Reset Link</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Message Display -->
|
||||
<div id="message" class="message"></div>
|
||||
|
||||
<!-- Loading Overlay -->
|
||||
<div id="loading" class="loading-overlay">
|
||||
<div class="spinner"></div>
|
||||
<span class="loading-text" id="loading-text">Signing in...</span>
|
||||
</div>
|
||||
|
||||
<!-- Debug Output -->
|
||||
<div id="debug"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Configuration (populated from C++ via get_login_cmd)
|
||||
let config = {
|
||||
backend_url: '',
|
||||
apikey: '',
|
||||
pkce: null
|
||||
};
|
||||
|
||||
// State
|
||||
let currentMode = 'signin'; // 'signin' | 'signup'
|
||||
|
||||
// Debug logging
|
||||
function log(msg) {
|
||||
console.log(msg);
|
||||
var d = document.getElementById('debug');
|
||||
if (d) {
|
||||
// Uncomment the next line to show debug panel
|
||||
// d.style.display = 'block';
|
||||
d.innerText += new Date().toISOString().substr(11, 8) + ' ' + msg + '\n';
|
||||
d.scrollTop = d.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Send message to C++ via JavaScript bridge
|
||||
function sendMessage(cmd, data) {
|
||||
log('Sending: ' + cmd);
|
||||
var msg = JSON.stringify({ command: cmd, data: data || {} });
|
||||
try {
|
||||
if (window.wx) {
|
||||
window.wx.postMessage(msg);
|
||||
} else if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.wx) {
|
||||
window.webkit.messageHandlers.wx.postMessage(msg);
|
||||
} else {
|
||||
log('Error: No bridge found (wx or webkit)');
|
||||
}
|
||||
} catch (e) {
|
||||
log('Send error: ' + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Handle messages from C++
|
||||
window.addEventListener('message', function(event) {
|
||||
log('Received message');
|
||||
var msg = event.data;
|
||||
if (typeof msg === 'string') {
|
||||
try {
|
||||
msg = JSON.parse(msg);
|
||||
} catch (e) {
|
||||
log('Parse error: ' + e.toString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log('Message action: ' + (msg.action || msg.command || 'unknown'));
|
||||
|
||||
// Handle login config from C++ (received once on page load)
|
||||
if (msg.action === 'login_config') {
|
||||
handleLoginConfig(msg);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Tab switching
|
||||
function setMode(mode) {
|
||||
currentMode = mode;
|
||||
clearMessage();
|
||||
clearErrors();
|
||||
|
||||
// Update tabs
|
||||
document.querySelectorAll('.tab').forEach(function(tab) {
|
||||
tab.classList.toggle('active', tab.dataset.mode === mode);
|
||||
});
|
||||
|
||||
// Update form
|
||||
var confirmGroup = document.getElementById('confirm-group');
|
||||
var submitBtn = document.getElementById('submit-btn');
|
||||
var forgotLink = document.getElementById('forgot-link');
|
||||
var subtitle = document.getElementById('header-subtitle');
|
||||
var passwordInput = document.getElementById('password');
|
||||
|
||||
if (mode === 'signup') {
|
||||
confirmGroup.classList.add('visible');
|
||||
submitBtn.textContent = 'Create Account';
|
||||
forgotLink.style.display = 'none';
|
||||
subtitle.textContent = 'Create your account';
|
||||
passwordInput.autocomplete = 'new-password';
|
||||
} else {
|
||||
confirmGroup.classList.remove('visible');
|
||||
submitBtn.textContent = 'Sign In';
|
||||
forgotLink.style.display = 'block';
|
||||
subtitle.textContent = 'Sign in to sync your settings';
|
||||
passwordInput.autocomplete = 'current-password';
|
||||
}
|
||||
}
|
||||
|
||||
// Form submission - calls Supabase directly
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
clearMessage();
|
||||
clearErrors();
|
||||
|
||||
var email = document.getElementById('email').value.trim();
|
||||
var password = document.getElementById('password').value;
|
||||
|
||||
// Validate email
|
||||
if (!isValidEmail(email)) {
|
||||
showFieldError('email', 'Please enter a valid email address');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate password
|
||||
if (password.length < 6) {
|
||||
showFieldError('password', 'Password must be at least 6 characters');
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentMode === 'signup') {
|
||||
var confirmPassword = document.getElementById('confirm-password').value;
|
||||
if (password !== confirmPassword) {
|
||||
showFieldError('confirm', 'Passwords do not match');
|
||||
return;
|
||||
}
|
||||
await handlePasswordSignup(email, password);
|
||||
} else {
|
||||
await handlePasswordLogin(email, password);
|
||||
}
|
||||
}
|
||||
|
||||
// Password login - direct Supabase call
|
||||
async function handlePasswordLogin(email, password) {
|
||||
if (!config.backend_url || !config.apikey) {
|
||||
showMessage('Configuration not loaded. Please try again.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading('Signing in...');
|
||||
|
||||
try {
|
||||
var url = config.backend_url + '/auth/v1/token?grant_type=password';
|
||||
var response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'apikey': config.apikey
|
||||
},
|
||||
body: JSON.stringify({ email: email, password: password })
|
||||
});
|
||||
|
||||
var data = await response.json();
|
||||
|
||||
if (response.ok && data.access_token) {
|
||||
// Success - send tokens to C++
|
||||
sendUserLogin(data);
|
||||
} else {
|
||||
hideLoading();
|
||||
var errorMsg = data.error_description || data.msg || data.error || 'Login failed';
|
||||
showMessage(errorMsg, 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
hideLoading();
|
||||
log('Login error: ' + err.toString());
|
||||
showMessage('Network error. Please check your connection.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Password signup - direct Supabase call
|
||||
async function handlePasswordSignup(email, password) {
|
||||
if (!config.backend_url || !config.apikey) {
|
||||
showMessage('Configuration not loaded. Please try again.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading('Creating account...');
|
||||
|
||||
try {
|
||||
var url = config.backend_url + '/auth/v1/signup';
|
||||
var response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'apikey': config.apikey
|
||||
},
|
||||
body: JSON.stringify({ email: email, password: password })
|
||||
});
|
||||
|
||||
var data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
if (data.access_token) {
|
||||
// Auto-confirmed - send tokens to C++
|
||||
sendUserLogin(data);
|
||||
} else {
|
||||
// Email verification required
|
||||
hideLoading();
|
||||
showMessage('Account created! Please check your email to verify your account.', 'success');
|
||||
}
|
||||
} else {
|
||||
hideLoading();
|
||||
var errorMsg = data.error_description || data.msg || data.error || 'Signup failed';
|
||||
showMessage(errorMsg, 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
hideLoading();
|
||||
log('Signup error: ' + err.toString());
|
||||
showMessage('Network error. Please check your connection.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Password reset - direct Supabase call
|
||||
async function handleResetSubmit(e) {
|
||||
e.preventDefault();
|
||||
clearMessage();
|
||||
|
||||
var email = document.getElementById('reset-email').value.trim();
|
||||
|
||||
if (!isValidEmail(email)) {
|
||||
document.getElementById('reset-email-group').classList.add('error');
|
||||
document.getElementById('reset-email-error').textContent = 'Please enter a valid email address';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!config.backend_url || !config.apikey) {
|
||||
showMessage('Configuration not loaded. Please try again.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading('Sending reset link...');
|
||||
|
||||
try {
|
||||
var url = config.backend_url + '/auth/v1/recover';
|
||||
var response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'apikey': config.apikey
|
||||
},
|
||||
body: JSON.stringify({ email: email })
|
||||
});
|
||||
|
||||
hideLoading();
|
||||
|
||||
// Supabase returns 200 even if email doesn't exist (for security)
|
||||
if (response.ok) {
|
||||
showMessage('Password reset email sent. Please check your inbox.', 'success');
|
||||
} else {
|
||||
showMessage('Failed to send reset email. Please try again.', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
hideLoading();
|
||||
log('Reset error: ' + err.toString());
|
||||
showMessage('Network error. Please check your connection.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Send successful login to C++ (unified for all auth methods)
|
||||
function sendUserLogin(data) {
|
||||
var user = data.user || {};
|
||||
var userMeta = user.user_metadata || {};
|
||||
|
||||
sendMessage('user_login', {
|
||||
token: data.access_token,
|
||||
refresh_token: data.refresh_token || '',
|
||||
user_id: user.id || '',
|
||||
username: userMeta.preferred_username || user.email || '',
|
||||
name: userMeta.full_name || userMeta.name || user.email || '',
|
||||
nickname: userMeta.preferred_username || userMeta.name || '',
|
||||
avatar: userMeta.avatar_url || '',
|
||||
state: config.pkce ? config.pkce.state : ''
|
||||
});
|
||||
}
|
||||
|
||||
// OAuth provider click - builds URL directly using stored config
|
||||
function handleOAuthProvider(provider) {
|
||||
log('OAuth provider: ' + provider);
|
||||
|
||||
if (!config.backend_url || !config.pkce) {
|
||||
showMessage('Configuration not loaded. Please try again.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading('Connecting to ' + provider.charAt(0).toUpperCase() + provider.slice(1) + '...');
|
||||
|
||||
var pkce = config.pkce;
|
||||
|
||||
// Construct Supabase Authorize URL
|
||||
var url = config.backend_url + '/auth/v1/authorize';
|
||||
var params = new URLSearchParams();
|
||||
|
||||
params.append('provider', provider);
|
||||
params.append('code_challenge', pkce.code_challenge);
|
||||
params.append('code_challenge_method', pkce.code_challenge_method);
|
||||
params.append('redirect_uri', pkce.redirect_uri);
|
||||
params.append('state', pkce.state);
|
||||
params.append('response_type', 'code');
|
||||
|
||||
var fullUrl = url + '?' + params.toString();
|
||||
|
||||
document.getElementById('loading-text').textContent = 'Opening browser for secure login...';
|
||||
|
||||
// Request C++ to open this URL in system browser
|
||||
sendMessage('thirdparty_login', { url: fullUrl });
|
||||
|
||||
// Show message after a delay
|
||||
setTimeout(function() {
|
||||
hideLoading();
|
||||
showMessage('Browser opened. Complete login there, then this window will close automatically.', 'success');
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Handle login config from C++ (received once on page load)
|
||||
function handleLoginConfig(msg) {
|
||||
log('Received login config');
|
||||
|
||||
// Store config for all auth methods
|
||||
config.backend_url = msg.backend_url || config.backend_url;
|
||||
config.apikey = msg.apikey || config.apikey;
|
||||
config.pkce = msg.pkce || config.pkce;
|
||||
|
||||
log('Config stored: backend_url=' + config.backend_url);
|
||||
}
|
||||
|
||||
// View switching
|
||||
function showResetView() {
|
||||
document.getElementById('auth-view').classList.add('hidden');
|
||||
document.getElementById('reset-view').classList.add('visible');
|
||||
clearMessage();
|
||||
document.getElementById('reset-email').value = document.getElementById('email').value;
|
||||
}
|
||||
|
||||
function hideResetView() {
|
||||
document.getElementById('auth-view').classList.remove('hidden');
|
||||
document.getElementById('reset-view').classList.remove('visible');
|
||||
clearMessage();
|
||||
}
|
||||
|
||||
// Loading state
|
||||
function showLoading(text) {
|
||||
document.getElementById('loading').classList.add('visible');
|
||||
document.getElementById('loading-text').textContent = text || 'Loading...';
|
||||
disableForm(true);
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
document.getElementById('loading').classList.remove('visible');
|
||||
disableForm(false);
|
||||
}
|
||||
|
||||
function disableForm(disabled) {
|
||||
document.querySelectorAll('input, button').forEach(function(el) {
|
||||
el.disabled = disabled;
|
||||
});
|
||||
}
|
||||
|
||||
// Messages
|
||||
function showMessage(text, type) {
|
||||
var msg = document.getElementById('message');
|
||||
msg.textContent = text;
|
||||
msg.className = 'message ' + type;
|
||||
}
|
||||
|
||||
function clearMessage() {
|
||||
var msg = document.getElementById('message');
|
||||
msg.textContent = '';
|
||||
msg.className = 'message';
|
||||
}
|
||||
|
||||
// Field errors
|
||||
function showFieldError(field, message) {
|
||||
var groupId = field + '-group';
|
||||
var errorId = field + '-error';
|
||||
var group = document.getElementById(groupId);
|
||||
var error = document.getElementById(errorId);
|
||||
if (group) group.classList.add('error');
|
||||
if (error) error.textContent = message;
|
||||
}
|
||||
|
||||
function clearErrors() {
|
||||
document.querySelectorAll('.input-group').forEach(function(group) {
|
||||
group.classList.remove('error');
|
||||
});
|
||||
document.querySelectorAll('.error-text').forEach(function(error) {
|
||||
error.textContent = '';
|
||||
});
|
||||
}
|
||||
|
||||
// Validation
|
||||
function isValidEmail(email) {
|
||||
var re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return re.test(email);
|
||||
}
|
||||
|
||||
// Initialize on load
|
||||
window.onload = function() {
|
||||
log('Window loaded');
|
||||
// Request login config from C++ to get backend URL and API key
|
||||
setTimeout(function() {
|
||||
sendMessage('get_login_cmd');
|
||||
}, 300);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user