mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-22 12:11:31 +02:00
fix(inventory): deleting a spool keeps its usage history; spool ids are never reused
On PostgreSQL, deleting a spool cascaded to its usage history, so recalculating the cost of its prints fell back to an estimate. On SQLite the history stayed with the old spool id, which the next spool could get and then show the deleted spool's prints as its own. spool_usage_history.spool_id is now nullable with ON DELETE SET NULL: the model, a PostgreSQL migration, an edit of the stored table definition on SQLite (both historical shapes), and the SQLite rule list, whose start-up sweep also clears ids left by earlier deletes. The spool table joins AUTOINCREMENT on SQLite. Readers handle usage without a spool: the usage list (was a 500), the forecast panel and the stock forecast skip it, Git restore keeps it.
This commit is contained in:
@@ -162,6 +162,7 @@ All notable changes to Bambuddy will be documented in this file.
|
||||
- **The frontend build no longer warns about `path` and `crypto` being externalized for the STEP previewer (#2976)** — `occt-import-js`, the Emscripten build behind STEP previews, requires both modules, but only inside its `ENVIRONMENT_IS_NODE` branches; in the browser it loads its `.wasm` from the URL the preview worker passes and draws randomness from `crypto.getRandomValues`. Vite still externalized both and printed two warnings on every build. `vite.config.ts` now drops exactly those two warnings for that one package through `build.rolldownOptions.onLog`, so an externalization anywhere else, or of any other module, still shows.
|
||||
|
||||
### Fixed
|
||||
- **Deleting a spool keeps its usage history, and a new spool no longer takes over a deleted one's** — On PostgreSQL, deleting a spool deleted its usage history, so **Recalculate costs** replaced the real spool cost of its prints with an estimate. On SQLite the history stayed, but the next spool created could get the deleted spool's number and then showed its prints and consumption as its own. The usage history now stays on both databases without a spool, the prints keep their spool cost, and spool numbers are never given out again on SQLite. A Git backup restores such history as well. Supplier and material statistics leave out usage of deleted spools, as there is no spool left to count it for.
|
||||
- **Archives a spool was used for can be deleted on PostgreSQL** — With PostgreSQL, deleting an archive for good (deleting it with **purge from statistics**, the automatic archive purge with that option, or deleting a printer together with its archives) failed with a server error when a spool from the inventory had been used for that print. The spool's usage record now stays and only loses its link to the deleted archive, on both databases; on SQLite, usage records still pointing at an archive deleted earlier are cleared of that link at the next start. **Recalculate costs** no longer charges usage records without an archive link to archives by their print name: that added the spool cost of a deleted print, of a print made with archiving off, or of every earlier run to each archive with the same name. Archives without linked usage get the estimate from the filament price instead, as they already did when no such record existed (usage recorded before 0.2.1, or with archiving off).
|
||||
- **An id of 0 no longer slips past the checks when queueing, batching or creating a project** — Adding to the queue with an archive id of `0` next to a library file, creating a batch with an archive or library file id of `0`, and creating a project with parent `0` skipped the check that the archive, file or project exists and saved `0` as its id: a server error on PostgreSQL, a link to nothing on SQLite. `0` now means "none" there, as it already did when editing a project. Creating a batch also checks that its archive or library file exists, and refuses an unknown one.
|
||||
- **Archiving a virtual-printer upload into a project that does not exist is refused up front** — The project chosen when archiving a pending upload was saved without checking it. On PostgreSQL an unknown project failed with a server error after the file had already been archived; on SQLite the archive was saved with a broken project link. The project is now checked first and an unknown one refused with "Project not found", before anything is archived.
|
||||
|
||||
@@ -343,7 +343,7 @@ def load_all_models():
|
||||
# Tables SQLite must never give a freed id to a new row: whatever still points at
|
||||
# a deleted row (a token, a socket, in-memory state, files, a row inserted while
|
||||
# the delete committed) would point at the new one. PostgreSQL uses sequences.
|
||||
AUTOINCREMENT_TABLES = ("users", "api_keys", "printers", "print_archives", "long_lived_tokens")
|
||||
AUTOINCREMENT_TABLES = ("users", "api_keys", "printers", "print_archives", "long_lived_tokens", "spool")
|
||||
|
||||
# Columns that keep one of their ids without a foreign key, so the seed below
|
||||
# cannot find them in the metadata: a deleted printer's print log is kept as
|
||||
@@ -5804,6 +5804,7 @@ async def _run_migrations(conn):
|
||||
await _migrate_wallet_transactions_keep_deleted_users(conn)
|
||||
await _migrate_notification_providers_go_with_their_printer(conn)
|
||||
await _migrate_spool_usage_outlives_its_archive(conn)
|
||||
await _migrate_spool_usage_outlives_its_spool(conn)
|
||||
await _migrate_pg_column_types(conn)
|
||||
await _migrate_pg_columns_to_model(conn)
|
||||
|
||||
@@ -6068,6 +6069,52 @@ async def _migrate_spool_usage_outlives_its_archive(conn) -> None:
|
||||
await _pg_set_fk_ondelete(conn, "spool_usage_history", "archive_id", "print_archives", "SET NULL")
|
||||
|
||||
|
||||
async def _migrate_spool_usage_outlives_its_spool(conn) -> None:
|
||||
"""spool_usage_history.spool_id: nullable, ON DELETE SET NULL, as the model says.
|
||||
|
||||
Deleting a spool deleted its usage on PostgreSQL (the costs its prints were
|
||||
recalculated from) and left it on SQLite pointing at an id the next spool
|
||||
could get. SQLite enforces neither rule; core/sqlite_fk applies SET NULL,
|
||||
which needs the column nullable.
|
||||
"""
|
||||
from sqlalchemy import text
|
||||
|
||||
if is_sqlite():
|
||||
select_sql = text("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'spool_usage_history'")
|
||||
sql = (await conn.execute(select_sql)).scalar()
|
||||
if not isinstance(sql, str):
|
||||
return
|
||||
new_sql = re.sub(r"(\bspool_id\s+INTEGER)\s+NOT\s+NULL", r"\1", sql)
|
||||
new_sql = re.sub(
|
||||
r"(\bspool_id\s*\)?\s+(?:INTEGER\s+)?REFERENCES\s+spool\s*\(\s*id\s*\)\s+ON\s+DELETE)\s+CASCADE",
|
||||
r"\1 SET NULL",
|
||||
new_sql,
|
||||
)
|
||||
if new_sql == sql:
|
||||
return
|
||||
try:
|
||||
schema_version = (await conn.execute(text("PRAGMA schema_version"))).scalar() or 0
|
||||
await conn.execute(text("PRAGMA writable_schema = ON"))
|
||||
await conn.execute(
|
||||
text("UPDATE sqlite_master SET sql = :sql WHERE type = 'table' AND name = 'spool_usage_history'"),
|
||||
{"sql": new_sql},
|
||||
)
|
||||
# Makes this connection reload the table definition.
|
||||
await conn.execute(text(f"PRAGMA schema_version = {schema_version + 1}"))
|
||||
await conn.execute(text("PRAGMA writable_schema = OFF"))
|
||||
except (OperationalError, ProgrammingError) as exc:
|
||||
logger.error(
|
||||
"Could not make spool_usage_history.spool_id nullable; usage of deleted spools keeps "
|
||||
"pointing at their old id on this install: %s",
|
||||
exc,
|
||||
exc_info=True,
|
||||
)
|
||||
return
|
||||
|
||||
await _safe_execute(conn, "ALTER TABLE spool_usage_history ALTER COLUMN spool_id DROP NOT NULL")
|
||||
await _pg_set_fk_ondelete(conn, "spool_usage_history", "spool_id", "spool", "SET NULL")
|
||||
|
||||
|
||||
_PG_ON_DELETE_CODES = {"CASCADE": "c", "SET NULL": "n"}
|
||||
|
||||
|
||||
|
||||
@@ -51,6 +51,8 @@ APPLIED: frozenset[tuple[str, str]] = frozenset(
|
||||
("print_queue", "archive_id"),
|
||||
("project_bom_items", "archive_id"),
|
||||
("spool_usage_history", "archive_id"),
|
||||
# spool
|
||||
("spool_usage_history", "spool_id"),
|
||||
("telegram_pending_verdicts", "archive_id"),
|
||||
("wallet_transactions", "print_archive_id"),
|
||||
# library_files. print_queue.library_file_id is CASCADE in the model, but
|
||||
|
||||
@@ -10,6 +10,8 @@ class Spool(Base):
|
||||
"""Spool inventory item for tracking filament spools and their properties."""
|
||||
|
||||
__tablename__ = "spool"
|
||||
# A freed id must not pass to a new spool: its usage history would follow.
|
||||
__table_args__ = {"sqlite_autoincrement": True}
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
material: Mapped[str] = mapped_column(String(50)) # PLA, PETG, ABS, etc.
|
||||
|
||||
@@ -12,7 +12,9 @@ class SpoolUsageHistory(Base):
|
||||
__tablename__ = "spool_usage_history"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
spool_id: Mapped[int] = mapped_column(ForeignKey("spool.id", ondelete="CASCADE"))
|
||||
# Kept when the spool is deleted, like the archive link below: the usage is
|
||||
# what the prints cost, and what recalculating their cost reads.
|
||||
spool_id: Mapped[int | None] = mapped_column(ForeignKey("spool.id", ondelete="SET NULL"), nullable=True)
|
||||
printer_id: Mapped[int | None] = mapped_column(ForeignKey("printers.id", ondelete="SET NULL"))
|
||||
print_name: Mapped[str | None] = mapped_column(String(500))
|
||||
# The spool's consumption outlives the archive: deleting the archive only
|
||||
|
||||
@@ -5,7 +5,7 @@ from pydantic import BaseModel
|
||||
|
||||
class SpoolUsageHistoryResponse(BaseModel):
|
||||
id: int
|
||||
spool_id: int
|
||||
spool_id: int | None = None # None: the spool was deleted, its usage kept
|
||||
printer_id: int | None = None
|
||||
print_name: str | None = None
|
||||
weight_used: float
|
||||
|
||||
@@ -1749,7 +1749,9 @@ class GitHubRestoreService:
|
||||
|
||||
old_spool_id = entry.get("spool_id")
|
||||
spool_id = spool_id_map.get(old_spool_id) if isinstance(old_spool_id, int) else None
|
||||
if spool_id is None:
|
||||
# Usage of a spool deleted before the backup has no spool, and is
|
||||
# restored without one; only a spool id that resolves to nothing is skipped.
|
||||
if spool_id is None and old_spool_id is not None:
|
||||
# The parent spool never made it into the map: the backup's spool
|
||||
# list didn't include it, or its entry carried no integer id. A
|
||||
# spool that was merely *skipped* (matched locally, overwrite off)
|
||||
|
||||
@@ -5492,7 +5492,9 @@ class PrintScheduler:
|
||||
).all()
|
||||
history: dict[int, list[stock_forecast.UsageRecord]] = {}
|
||||
for spool_id, created_at, weight_used in rows:
|
||||
if created_at is None:
|
||||
# A deleted spool's usage forecasts no stock. Skipped here rather than
|
||||
# filtered before the limit, so the window matches the forecast panel's.
|
||||
if created_at is None or spool_id is None:
|
||||
continue
|
||||
history.setdefault(spool_id, []).append(stock_forecast.UsageRecord(created_at, float(weight_used or 0)))
|
||||
return history
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
"""Deleting a spool keeps its usage history, and its id is not given to a new spool.
|
||||
|
||||
PostgreSQL deleted the usage with the spool, so recalculating the cost of its
|
||||
prints fell back to an estimate. SQLite kept the usage with the old spool id,
|
||||
and could give that id to the next spool, which then showed the deleted
|
||||
spool's prints as its own.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
from sqlalchemy import select
|
||||
|
||||
from backend.app.models.archive import PrintArchive
|
||||
from backend.app.models.spool import Spool
|
||||
from backend.app.models.spool_usage_history import SpoolUsageHistory
|
||||
|
||||
pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
|
||||
|
||||
|
||||
async def test_deleting_a_spool_keeps_the_usage_and_the_print_cost(
|
||||
async_client: AsyncClient, db_session, printer_factory, archive_factory
|
||||
):
|
||||
printer = await printer_factory()
|
||||
archive = await archive_factory(printer.id, filament_used_grams=20.0, cost=None)
|
||||
archive_id = archive.id
|
||||
spool = Spool(material="PLA")
|
||||
db_session.add(spool)
|
||||
await db_session.flush()
|
||||
spool_id = spool.id
|
||||
db_session.add(
|
||||
SpoolUsageHistory(spool_id=spool_id, printer_id=printer.id, archive_id=archive_id, weight_used=20, cost=0.5)
|
||||
)
|
||||
await db_session.commit()
|
||||
|
||||
deleted = await async_client.delete(f"/api/v1/inventory/spools/{spool_id}")
|
||||
recalculated = await async_client.post("/api/v1/archives/recalculate-costs")
|
||||
|
||||
assert deleted.status_code in (200, 204), deleted.text
|
||||
assert recalculated.status_code == 200, recalculated.text
|
||||
db_session.expire_all()
|
||||
usage = (await db_session.execute(select(SpoolUsageHistory.spool_id, SpoolUsageHistory.cost))).one()
|
||||
assert tuple(usage) == (None, 0.5)
|
||||
cost = (await db_session.execute(select(PrintArchive.cost).where(PrintArchive.id == archive_id))).scalar()
|
||||
assert cost == 0.5
|
||||
|
||||
|
||||
async def test_a_new_spool_does_not_get_the_id_of_a_deleted_one(async_client: AsyncClient, db_session):
|
||||
spool = Spool(material="PLA")
|
||||
db_session.add(spool)
|
||||
await db_session.commit()
|
||||
old_id = spool.id
|
||||
|
||||
deleted = await async_client.delete(f"/api/v1/inventory/spools/{old_id}")
|
||||
assert deleted.status_code in (200, 204), deleted.text
|
||||
replacement = Spool(material="PETG")
|
||||
db_session.add(replacement)
|
||||
await db_session.commit()
|
||||
|
||||
assert replacement.id != old_id
|
||||
|
||||
|
||||
async def test_the_usage_list_shows_usage_of_a_deleted_spool(async_client: AsyncClient, db_session):
|
||||
spool = Spool(material="PLA")
|
||||
db_session.add(spool)
|
||||
await db_session.flush()
|
||||
db_session.add(SpoolUsageHistory(spool_id=spool.id, weight_used=12, cost=0.3))
|
||||
await db_session.commit()
|
||||
deleted = await async_client.delete(f"/api/v1/inventory/spools/{spool.id}")
|
||||
assert deleted.status_code in (200, 204), deleted.text
|
||||
|
||||
response = await async_client.get("/api/v1/inventory/usage")
|
||||
|
||||
assert response.status_code == 200, response.text
|
||||
assert [(row["spool_id"], row["weight_used"]) for row in response.json()] == [(None, 12)]
|
||||
@@ -1073,6 +1073,21 @@ class TestRestoreSpools:
|
||||
row = (await db_session.execute(select(SpoolUsageHistory))).scalar_one()
|
||||
assert row.archive_id == archive.id
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_usage_of_a_spool_deleted_before_the_backup_is_restored_without_a_spool(self, db_session):
|
||||
"""A deleted spool's usage is kept (spool_id null); it is still the cost of its prints."""
|
||||
tally = _CategoryTally()
|
||||
usage = {
|
||||
"usage_history": [{"spool_id": None, "weight_used": 4.0, "cost": 0.2, "created_at": "2026-02-01 09:00:00"}]
|
||||
}
|
||||
|
||||
await _service()._restore_spools(db_session, {"spools": []}, usage, False, tally, {})
|
||||
await db_session.commit()
|
||||
|
||||
row = (await db_session.execute(select(SpoolUsageHistory))).scalar_one()
|
||||
assert (row.spool_id, row.weight_used, row.cost) == (None, 4.0, 0.2)
|
||||
assert tally.skipped == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_usage_row_with_unresolvable_spool_is_skipped_and_explained(self, db_session):
|
||||
tally = _CategoryTally()
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
"""spool_usage_history.spool_id becomes nullable, ON DELETE SET NULL, on existing SQLite databases.
|
||||
|
||||
Deleting a spool must keep its usage (the costs its prints are recalculated
|
||||
from), so the column has to take NULL on tables made before. (PostgreSQL is
|
||||
checked by scripts/check_postgres_upgrade.py.)
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
|
||||
from backend.app.core import database
|
||||
|
||||
# As create_all made the table from the old model ...
|
||||
CREATED_BY_THE_MODEL = """
|
||||
CREATE TABLE spool_usage_history (
|
||||
\tid INTEGER NOT NULL,
|
||||
\tspool_id INTEGER NOT NULL,
|
||||
\tprinter_id INTEGER,
|
||||
\tprint_name VARCHAR(500),
|
||||
\tarchive_id INTEGER,
|
||||
\tweight_used FLOAT NOT NULL,
|
||||
\tPRIMARY KEY (id),
|
||||
\tFOREIGN KEY(spool_id) REFERENCES spool (id) ON DELETE CASCADE,
|
||||
\tFOREIGN KEY(printer_id) REFERENCES printers (id) ON DELETE SET NULL,
|
||||
\tFOREIGN KEY(archive_id) REFERENCES print_archives (id) ON DELETE SET NULL
|
||||
)
|
||||
"""
|
||||
|
||||
# ... and as the usage-history migration's own CREATE TABLE made it.
|
||||
CREATED_BY_THE_MIGRATION = """
|
||||
CREATE TABLE spool_usage_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
spool_id INTEGER NOT NULL REFERENCES spool(id) ON DELETE CASCADE,
|
||||
printer_id INTEGER REFERENCES printers(id) ON DELETE SET NULL,
|
||||
print_name VARCHAR(500),
|
||||
weight_used REAL NOT NULL DEFAULT 0
|
||||
)
|
||||
"""
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def sqlite_dialect(monkeypatch):
|
||||
monkeypatch.setattr(database, "is_sqlite", lambda: True)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("legacy", [CREATED_BY_THE_MODEL, CREATED_BY_THE_MIGRATION], ids=["model", "migration"])
|
||||
async def test_the_spool_column_takes_null_and_says_set_null(legacy):
|
||||
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
|
||||
try:
|
||||
async with engine.begin() as conn:
|
||||
await conn.execute(text(legacy))
|
||||
await conn.execute(text("INSERT INTO spool_usage_history (id, spool_id, weight_used) VALUES (1, 7, 5)"))
|
||||
|
||||
await database._migrate_spool_usage_outlives_its_spool(conn)
|
||||
await database._migrate_spool_usage_outlives_its_spool(conn) # a restart changes nothing
|
||||
|
||||
await conn.execute(text("UPDATE spool_usage_history SET spool_id = NULL WHERE id = 1"))
|
||||
assert (await conn.execute(text("SELECT spool_id, weight_used FROM spool_usage_history"))).one() == (
|
||||
None,
|
||||
5,
|
||||
)
|
||||
sql = (
|
||||
await conn.execute(text("SELECT sql FROM sqlite_master WHERE name = 'spool_usage_history'"))
|
||||
).scalar_one()
|
||||
assert "CASCADE" not in sql
|
||||
# Only the spool column changed.
|
||||
assert "weight_used" in sql and "NOT NULL" in sql.split("weight_used", 1)[1].split(",", 1)[0]
|
||||
finally:
|
||||
await engine.dispose()
|
||||
@@ -4023,7 +4023,8 @@ export interface CsvImportResult {
|
||||
|
||||
export interface SpoolUsageRecord {
|
||||
id: number;
|
||||
spool_id: number;
|
||||
// null: the spool was deleted, its usage kept
|
||||
spool_id: number | null;
|
||||
printer_id: number | null;
|
||||
print_name: string | null;
|
||||
weight_used: number;
|
||||
|
||||
@@ -215,6 +215,8 @@ export function ForecastPanel({ spools }: { spools: InventorySpool[] }) {
|
||||
const usageBySpoolId = useMemo(() => {
|
||||
const m = new Map<number, SpoolUsageRecord[]>();
|
||||
for (const r of usageHistory) {
|
||||
// Usage of a deleted spool belongs to no spool here.
|
||||
if (r.spool_id === null) continue;
|
||||
const arr = m.get(r.spool_id) ?? [];
|
||||
arr.push(r);
|
||||
m.set(r.spool_id, arr);
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{i as e}from"./chunk-aKtaBQYM.js";import{C as t,D as n,a as r,g as i,w as a}from"./client-C3LdD0iY.js";import{t as o}from"./useQuery-BSYE6Prb.js";import{a as s,t as c}from"./CameraWall-CLj-LAfe.js";import{Bn as l,Wn as u,yt as d,zn as f}from"./index-CELnCUsn.js";var p=e(n(),1),m=t(),h=5e3,g=4,_=8,v=1,y=16,b=2,x=60;function S(e,t,n,r){let i=parseInt(e??``,10);return Number.isFinite(i)?Math.min(r,Math.max(n,i)):t}function C(e,t,n,r,i,a){return e.has(t)?S(e.get(t),r,i,a):S(localStorage.getItem(n),r,i,a)}function w(e){let t=e.get(`size`)?.toLowerCase(),n=s.indexOf(t);if(n>=0)return n+1;let r=parseInt(localStorage.getItem(`camWallTileSize`)??``,10);return r>=1&&r<=4?r:2}function T(){let{t:e}=a(),t=l(),{authEnabled:n,loading:s,user:S}=d(),[T]=u(),E=T.get(`token`),D=E!=null&&E!==``,[O,k]=(0,p.useState)(()=>C(T,`maxLive`,`camWallMaxLive`,g,v,y)),[A,j]=(0,p.useState)(()=>C(T,`interval`,`camWallSnapshotSec`,_,b,x)),[M]=(0,p.useState)(()=>w(T)),[N,P]=(0,p.useState)(()=>{let e=T.get(`status`)??localStorage.getItem(`camWallStatusMode`);return(D?[`off`,`compact`]:[`off`,`compact`,`full`]).includes(e)?e:`compact`});(0,p.useEffect)(()=>{if(D)return i(E),()=>i(null)},[D,E]);let F=o({queryKey:[`camwall-printers`,E],queryFn:()=>r.getCamWallPrinters(E??void 0),enabled:D,refetchInterval:h}),I=o({queryKey:[`printers`],queryFn:()=>r.getPrinters(),enabled:!D&&!s&&(!n||S!==null),refetchInterval:h}),L=(0,p.useMemo)(()=>{let e=new Map;for(let t of F.data??[])e.set(t.id,{connected:t.connected,state:t.state,progress:t.progress,remaining_time:t.remaining_time,layer_num:t.layer_num,total_layers:t.total_layers,hms_errors:t.hms_errors});return e},[F.data]);if(!D&&s)return(0,m.jsx)(`div`,{className:`flex min-h-screen items-center justify-center bg-bambu-dark text-bambu-gray`,children:e(`common.loading`)});if(!D&&n&&!S)return(0,m.jsx)(f,{to:`/login`,replace:!0,state:{from:t}});let R=D?F:I,z=D?F.data??[]:I.data??[];return R.isError?(0,m.jsx)(`div`,{className:`flex min-h-screen items-center justify-center bg-bambu-dark p-6 text-center`,children:(0,m.jsx)(`p`,{className:`max-w-md text-sm text-red-400`,children:e(D?`printers.camWall.page.tokenRejected`:`printers.camWall.page.loadFailed`)})}):(0,m.jsx)(`div`,{className:`min-h-screen bg-bambu-dark p-4`,children:(0,m.jsx)(c,{printers:z,maxLive:O,snapshotIntervalSec:A,statusMode:N,tileSize:M,statuses:D?L:void 0,showSettings:!D,onTileClick:D?void 0:e=>window.open(`/camera/${e}`,`camera-${e}`),onChangeMaxLive:e=>{k(e),localStorage.setItem(`camWallMaxLive`,String(e))},onChangeSnapshotIntervalSec:e=>{j(e),localStorage.setItem(`camWallSnapshotSec`,String(e))},onChangeStatusMode:e=>{P(e),localStorage.setItem(`camWallStatusMode`,e)}})})}export{T as CamWallPage,T as default};
|
||||
import{i as e}from"./chunk-aKtaBQYM.js";import{C as t,D as n,a as r,g as i,w as a}from"./client-C3LdD0iY.js";import{t as o}from"./useQuery-BSYE6Prb.js";import{a as s,t as c}from"./CameraWall-CSAwKLLj.js";import{Bn as l,Wn as u,yt as d,zn as f}from"./index-5pmk7Nlq.js";var p=e(n(),1),m=t(),h=5e3,g=4,_=8,v=1,y=16,b=2,x=60;function S(e,t,n,r){let i=parseInt(e??``,10);return Number.isFinite(i)?Math.min(r,Math.max(n,i)):t}function C(e,t,n,r,i,a){return e.has(t)?S(e.get(t),r,i,a):S(localStorage.getItem(n),r,i,a)}function w(e){let t=e.get(`size`)?.toLowerCase(),n=s.indexOf(t);if(n>=0)return n+1;let r=parseInt(localStorage.getItem(`camWallTileSize`)??``,10);return r>=1&&r<=4?r:2}function T(){let{t:e}=a(),t=l(),{authEnabled:n,loading:s,user:S}=d(),[T]=u(),E=T.get(`token`),D=E!=null&&E!==``,[O,k]=(0,p.useState)(()=>C(T,`maxLive`,`camWallMaxLive`,g,v,y)),[A,j]=(0,p.useState)(()=>C(T,`interval`,`camWallSnapshotSec`,_,b,x)),[M]=(0,p.useState)(()=>w(T)),[N,P]=(0,p.useState)(()=>{let e=T.get(`status`)??localStorage.getItem(`camWallStatusMode`);return(D?[`off`,`compact`]:[`off`,`compact`,`full`]).includes(e)?e:`compact`});(0,p.useEffect)(()=>{if(D)return i(E),()=>i(null)},[D,E]);let F=o({queryKey:[`camwall-printers`,E],queryFn:()=>r.getCamWallPrinters(E??void 0),enabled:D,refetchInterval:h}),I=o({queryKey:[`printers`],queryFn:()=>r.getPrinters(),enabled:!D&&!s&&(!n||S!==null),refetchInterval:h}),L=(0,p.useMemo)(()=>{let e=new Map;for(let t of F.data??[])e.set(t.id,{connected:t.connected,state:t.state,progress:t.progress,remaining_time:t.remaining_time,layer_num:t.layer_num,total_layers:t.total_layers,hms_errors:t.hms_errors});return e},[F.data]);if(!D&&s)return(0,m.jsx)(`div`,{className:`flex min-h-screen items-center justify-center bg-bambu-dark text-bambu-gray`,children:e(`common.loading`)});if(!D&&n&&!S)return(0,m.jsx)(f,{to:`/login`,replace:!0,state:{from:t}});let R=D?F:I,z=D?F.data??[]:I.data??[];return R.isError?(0,m.jsx)(`div`,{className:`flex min-h-screen items-center justify-center bg-bambu-dark p-6 text-center`,children:(0,m.jsx)(`p`,{className:`max-w-md text-sm text-red-400`,children:e(D?`printers.camWall.page.tokenRejected`:`printers.camWall.page.loadFailed`)})}):(0,m.jsx)(`div`,{className:`min-h-screen bg-bambu-dark p-4`,children:(0,m.jsx)(c,{printers:z,maxLive:O,snapshotIntervalSec:A,statusMode:N,tileSize:M,statuses:D?L:void 0,showSettings:!D,onTileClick:D?void 0:e=>window.open(`/camera/${e}`,`camera-${e}`),onChangeMaxLive:e=>{k(e),localStorage.setItem(`camWallMaxLive`,String(e))},onChangeSnapshotIntervalSec:e=>{j(e),localStorage.setItem(`camWallSnapshotSec`,String(e))},onChangeStatusMode:e=>{P(e),localStorage.setItem(`camWallStatusMode`,e)}})})}export{T as CamWallPage,T as default};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{i as e}from"./chunk-aKtaBQYM.js";import{C as t,D as n,w as r}from"./client-C3LdD0iY.js";import{a as i,i as a}from"./info-jpcgNqAh.js";import{t as o}from"./eye-off-BKHC5oyQ.js";import{t as s}from"./grip-vertical-eoFY62AH.js";import{n as c,t as l}from"./Card-BvtE18YT.js";import{t as u}from"./Button-B2stjvVl.js";import{Gt as d,Sn as f}from"./index-CELnCUsn.js";var p=e(n(),1),m=t();function h({isOpen:e,onClose:t,columns:n,defaultColumns:h,onSave:g}){let{t:_}=r(),[v,y]=(0,p.useState)(n),[b,x]=(0,p.useState)(null),S=(0,p.useRef)(null);if((0,p.useEffect)(()=>{e&&y(n.map(e=>({...e})))},[e,n]),(0,p.useEffect)(()=>{if(!e)return;let n=e=>{e.key===`Escape`&&t()};return window.addEventListener(`keydown`,n),()=>window.removeEventListener(`keydown`,n)},[e,t]),!e)return null;let C=e=>{y(t=>t.map((t,n)=>n===e?{...t,visible:!t.visible}:t))},w=(e,t)=>{t<0||t>=v.length||y(n=>{let r=[...n],[i]=r.splice(e,1);return r.splice(t,0,i),r})},T=(e,t)=>{S.current=t,x(t),e.dataTransfer.effectAllowed=`move`},E=(e,t)=>{e.preventDefault(),e.dataTransfer.dropEffect=`move`;let n=S.current;n!==null&&n!==t&&(w(n,t),S.current=t,x(t))},D=e=>{e.preventDefault()},O=()=>{S.current=null,x(null)},k=()=>{y(h.map(e=>({...e})))},A=()=>{g(v),t()},j=v.filter(e=>e.visible).length;return(0,m.jsx)(`div`,{className:`fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4`,onClick:t,children:(0,m.jsx)(l,{className:`w-full max-w-md max-h-[80vh] flex flex-col`,onClick:e=>e.stopPropagation(),children:(0,m.jsxs)(c,{className:`p-6 flex flex-col min-h-0`,children:[(0,m.jsx)(`h3`,{className:`text-lg font-semibold text-white mb-2`,children:_(`inventory.configureColumns`)}),(0,m.jsxs)(`p`,{className:`text-sm text-bambu-gray mb-4`,children:[_(`inventory.configureColumnsDesc`),(0,m.jsxs)(`span`,{className:`ml-2 text-bambu-gray/60`,children:[`(`,j,` `,_(`inventory.of`),` `,v.length,` `,_(`inventory.visible`),`)`]})]}),(0,m.jsx)(`div`,{className:`space-y-1 overflow-y-auto flex-1 min-h-0 pr-1`,children:v.map((e,t)=>(0,m.jsxs)(`div`,{className:`flex items-center gap-2 p-2 rounded-lg border transition-colors ${b===t?`border-bambu-green bg-bambu-green/10`:`border-bambu-dark-tertiary bg-bambu-dark-tertiary/50`} ${e.visible?``:`opacity-50`}`,draggable:!0,onDragStart:e=>T(e,t),onDragOver:e=>E(e,t),onDrop:D,onDragEnd:O,children:[(0,m.jsx)(`div`,{className:`cursor-grab text-bambu-gray/50 hover:text-bambu-gray`,children:(0,m.jsx)(s,{className:`w-4 h-4`})}),(0,m.jsx)(`span`,{className:`flex-1 font-medium text-sm text-white`,children:e.label}),(0,m.jsxs)(`div`,{className:`flex items-center gap-0.5`,children:[(0,m.jsx)(`button`,{onClick:()=>w(t,t-1),disabled:t===0,className:`p-1 rounded text-bambu-gray hover:bg-bambu-dark-secondary disabled:opacity-30 disabled:cursor-not-allowed`,title:_(`inventory.moveUp`),children:(0,m.jsx)(a,{className:`w-4 h-4`})}),(0,m.jsx)(`button`,{onClick:()=>w(t,t+1),disabled:t===v.length-1,className:`p-1 rounded text-bambu-gray hover:bg-bambu-dark-secondary disabled:opacity-30 disabled:cursor-not-allowed`,title:_(`inventory.moveDown`),children:(0,m.jsx)(i,{className:`w-4 h-4`})})]}),(0,m.jsx)(`button`,{onClick:()=>C(t),className:`p-1.5 rounded transition-colors ${e.visible?`text-bambu-green hover:bg-bambu-green/10`:`text-bambu-gray/50 hover:bg-bambu-dark-secondary`}`,title:e.visible?_(`inventory.hideColumn`):_(`inventory.showColumn`),children:e.visible?(0,m.jsx)(f,{className:`w-4 h-4`}):(0,m.jsx)(o,{className:`w-4 h-4`})})]},e.id))}),(0,m.jsxs)(`div`,{className:`flex items-center gap-3 mt-4 pt-4 border-t border-bambu-dark-tertiary`,children:[(0,m.jsxs)(u,{variant:`secondary`,onClick:k,className:`mr-auto`,children:[(0,m.jsx)(d,{className:`w-4 h-4`}),_(`inventory.reset`)]}),(0,m.jsx)(u,{variant:`secondary`,onClick:t,children:_(`inventory.cancel`)}),(0,m.jsx)(u,{onClick:A,children:_(`inventory.applyChanges`)})]})]})})})}export{h as t};
|
||||
import{i as e}from"./chunk-aKtaBQYM.js";import{C as t,D as n,w as r}from"./client-C3LdD0iY.js";import{a as i,i as a}from"./info-jpcgNqAh.js";import{t as o}from"./eye-off-BKHC5oyQ.js";import{t as s}from"./grip-vertical-eoFY62AH.js";import{n as c,t as l}from"./Card-BvtE18YT.js";import{t as u}from"./Button-B2stjvVl.js";import{Gt as d,Sn as f}from"./index-5pmk7Nlq.js";var p=e(n(),1),m=t();function h({isOpen:e,onClose:t,columns:n,defaultColumns:h,onSave:g}){let{t:_}=r(),[v,y]=(0,p.useState)(n),[b,x]=(0,p.useState)(null),S=(0,p.useRef)(null);if((0,p.useEffect)(()=>{e&&y(n.map(e=>({...e})))},[e,n]),(0,p.useEffect)(()=>{if(!e)return;let n=e=>{e.key===`Escape`&&t()};return window.addEventListener(`keydown`,n),()=>window.removeEventListener(`keydown`,n)},[e,t]),!e)return null;let C=e=>{y(t=>t.map((t,n)=>n===e?{...t,visible:!t.visible}:t))},w=(e,t)=>{t<0||t>=v.length||y(n=>{let r=[...n],[i]=r.splice(e,1);return r.splice(t,0,i),r})},T=(e,t)=>{S.current=t,x(t),e.dataTransfer.effectAllowed=`move`},E=(e,t)=>{e.preventDefault(),e.dataTransfer.dropEffect=`move`;let n=S.current;n!==null&&n!==t&&(w(n,t),S.current=t,x(t))},D=e=>{e.preventDefault()},O=()=>{S.current=null,x(null)},k=()=>{y(h.map(e=>({...e})))},A=()=>{g(v),t()},j=v.filter(e=>e.visible).length;return(0,m.jsx)(`div`,{className:`fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4`,onClick:t,children:(0,m.jsx)(l,{className:`w-full max-w-md max-h-[80vh] flex flex-col`,onClick:e=>e.stopPropagation(),children:(0,m.jsxs)(c,{className:`p-6 flex flex-col min-h-0`,children:[(0,m.jsx)(`h3`,{className:`text-lg font-semibold text-white mb-2`,children:_(`inventory.configureColumns`)}),(0,m.jsxs)(`p`,{className:`text-sm text-bambu-gray mb-4`,children:[_(`inventory.configureColumnsDesc`),(0,m.jsxs)(`span`,{className:`ml-2 text-bambu-gray/60`,children:[`(`,j,` `,_(`inventory.of`),` `,v.length,` `,_(`inventory.visible`),`)`]})]}),(0,m.jsx)(`div`,{className:`space-y-1 overflow-y-auto flex-1 min-h-0 pr-1`,children:v.map((e,t)=>(0,m.jsxs)(`div`,{className:`flex items-center gap-2 p-2 rounded-lg border transition-colors ${b===t?`border-bambu-green bg-bambu-green/10`:`border-bambu-dark-tertiary bg-bambu-dark-tertiary/50`} ${e.visible?``:`opacity-50`}`,draggable:!0,onDragStart:e=>T(e,t),onDragOver:e=>E(e,t),onDrop:D,onDragEnd:O,children:[(0,m.jsx)(`div`,{className:`cursor-grab text-bambu-gray/50 hover:text-bambu-gray`,children:(0,m.jsx)(s,{className:`w-4 h-4`})}),(0,m.jsx)(`span`,{className:`flex-1 font-medium text-sm text-white`,children:e.label}),(0,m.jsxs)(`div`,{className:`flex items-center gap-0.5`,children:[(0,m.jsx)(`button`,{onClick:()=>w(t,t-1),disabled:t===0,className:`p-1 rounded text-bambu-gray hover:bg-bambu-dark-secondary disabled:opacity-30 disabled:cursor-not-allowed`,title:_(`inventory.moveUp`),children:(0,m.jsx)(a,{className:`w-4 h-4`})}),(0,m.jsx)(`button`,{onClick:()=>w(t,t+1),disabled:t===v.length-1,className:`p-1 rounded text-bambu-gray hover:bg-bambu-dark-secondary disabled:opacity-30 disabled:cursor-not-allowed`,title:_(`inventory.moveDown`),children:(0,m.jsx)(i,{className:`w-4 h-4`})})]}),(0,m.jsx)(`button`,{onClick:()=>C(t),className:`p-1.5 rounded transition-colors ${e.visible?`text-bambu-green hover:bg-bambu-green/10`:`text-bambu-gray/50 hover:bg-bambu-dark-secondary`}`,title:e.visible?_(`inventory.hideColumn`):_(`inventory.showColumn`),children:e.visible?(0,m.jsx)(f,{className:`w-4 h-4`}):(0,m.jsx)(o,{className:`w-4 h-4`})})]},e.id))}),(0,m.jsxs)(`div`,{className:`flex items-center gap-3 mt-4 pt-4 border-t border-bambu-dark-tertiary`,children:[(0,m.jsxs)(u,{variant:`secondary`,onClick:k,className:`mr-auto`,children:[(0,m.jsx)(d,{className:`w-4 h-4`}),_(`inventory.reset`)]}),(0,m.jsx)(u,{variant:`secondary`,onClick:t,children:_(`inventory.cancel`)}),(0,m.jsx)(u,{onClick:A,children:_(`inventory.applyChanges`)})]})]})})})}export{h as t};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{i as e}from"./chunk-aKtaBQYM.js";import{C as t,D as n,a as r,t as i,w as a}from"./client-C3LdD0iY.js";import{t as o}from"./link-2-BrJ-i2LF.js";import{t as s}from"./loader-circle-CbaPxZRP.js";import{t as c}from"./triangle-alert-BUlaDyuA.js";import{Wn as l,yt as u}from"./index-CELnCUsn.js";var d=e(n(),1),f=t();function p(e,t){let n=new URL(e);for(let[e,r]of Object.entries(t))r&&n.searchParams.set(e,r);return n.toString()}function m(){let{t:e}=a(),{authEnabled:t,loading:n}=u(),[m]=l(),[h,g]=(0,d.useState)({kind:`loading`}),_=(0,d.useRef)(!1),v=m.get(`client_id`)??``,y=m.get(`redirect_uri`)??``,b=m.get(`state`)??``,x=m.get(`code_challenge`)??``,S=m.get(`code_challenge_method`)??``,C=async()=>{g({kind:`redirecting`});try{let e=await r.connectAuthorize({client_id:v,redirect_uri:y,code_challenge:x,code_challenge_method:`S256`});window.location.replace(p(e.redirect_uri,{code:e.code,state:b}))}catch(t){g({kind:`error`,message:t instanceof Error?t.message:e(`connectedApps.authorize.failed`)})}};return(0,d.useEffect)(()=>{if(!(n||_.current)){if(_.current=!0,!t){g({kind:`error`,message:e(`connectedApps.authorize.authDisabled`)});return}if(!v||!y||!x||S!==`S256`){g({kind:`error`,message:e(`connectedApps.authorize.invalidRequest`)});return}r.getConnectAuthorizeInfo(v,y).then(e=>{e.already_granted?C():g({kind:`consent`,info:e})}).catch(t=>{g({kind:`error`,message:t instanceof i&&t.status===409?e(`connectedApps.authorize.authDisabled`):e(`connectedApps.authorize.invalidRequest`)})})}},[n,t]),(0,f.jsx)(`div`,{className:`min-h-screen flex items-center justify-center bg-bambu-dark p-4`,children:(0,f.jsxs)(`div`,{className:`max-w-md w-full space-y-6 p-8 bg-gradient-to-br from-bambu-card to-bambu-dark-secondary rounded-xl border border-bambu-dark-tertiary shadow-lg`,children:[(h.kind===`loading`||h.kind===`redirecting`)&&(0,f.jsxs)(`div`,{className:`flex flex-col items-center gap-3 text-bambu-gray`,role:`status`,children:[(0,f.jsx)(s,{className:`w-8 h-8 animate-spin text-bambu-green`}),(0,f.jsx)(`p`,{children:h.kind===`loading`?e(`connectedApps.authorize.checking`):e(`connectedApps.authorize.redirecting`)})]}),h.kind===`error`&&(0,f.jsxs)(`div`,{className:`text-center space-y-3`,children:[(0,f.jsx)(c,{className:`w-10 h-10 mx-auto text-yellow-600 dark:text-yellow-400`}),(0,f.jsx)(`h1`,{className:`text-xl font-semibold text-white`,children:e(`connectedApps.authorize.errorTitle`)}),(0,f.jsx)(`p`,{className:`text-sm text-bambu-gray`,children:h.message})]}),h.kind===`consent`&&(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`div`,{className:`text-center space-y-3`,children:[(0,f.jsx)(`div`,{className:`w-14 h-14 mx-auto rounded-full bg-bambu-green/20 flex items-center justify-center`,children:(0,f.jsx)(o,{className:`w-7 h-7 text-bambu-green`})}),(0,f.jsx)(`h1`,{className:`text-xl font-semibold text-white text-balance`,children:e(`connectedApps.authorize.title`,{app:h.info.app_name})}),(0,f.jsx)(`p`,{className:`text-sm text-bambu-gray`,children:e(`connectedApps.authorize.signedInAs`,{username:h.info.username})})]}),(0,f.jsx)(`p`,{className:`text-sm text-bambu-gray`,children:e(`connectedApps.authorize.sharedData`,{app:h.info.app_name})}),(0,f.jsxs)(`div`,{className:`flex gap-3`,children:[(0,f.jsx)(`button`,{type:`button`,onClick:()=>{window.location.replace(p(y,{error:`access_denied`,state:b}))},className:`flex-1 px-4 py-2 bg-bambu-dark-tertiary text-white rounded-md hover:bg-bambu-dark-tertiary/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-bambu-green`,children:e(`connectedApps.authorize.deny`)}),(0,f.jsx)(`button`,{type:`button`,onClick:()=>void C(),className:`flex-1 px-4 py-2 bg-bambu-green text-white rounded-md hover:bg-bambu-green/90 focus:outline-none focus-visible:ring-2 focus-visible:ring-white`,children:e(`connectedApps.authorize.allow`)})]})]})]})})}export{m as ConnectAuthorizePage};
|
||||
import{i as e}from"./chunk-aKtaBQYM.js";import{C as t,D as n,a as r,t as i,w as a}from"./client-C3LdD0iY.js";import{t as o}from"./link-2-BrJ-i2LF.js";import{t as s}from"./loader-circle-CbaPxZRP.js";import{t as c}from"./triangle-alert-BUlaDyuA.js";import{Wn as l,yt as u}from"./index-5pmk7Nlq.js";var d=e(n(),1),f=t();function p(e,t){let n=new URL(e);for(let[e,r]of Object.entries(t))r&&n.searchParams.set(e,r);return n.toString()}function m(){let{t:e}=a(),{authEnabled:t,loading:n}=u(),[m]=l(),[h,g]=(0,d.useState)({kind:`loading`}),_=(0,d.useRef)(!1),v=m.get(`client_id`)??``,y=m.get(`redirect_uri`)??``,b=m.get(`state`)??``,x=m.get(`code_challenge`)??``,S=m.get(`code_challenge_method`)??``,C=async()=>{g({kind:`redirecting`});try{let e=await r.connectAuthorize({client_id:v,redirect_uri:y,code_challenge:x,code_challenge_method:`S256`});window.location.replace(p(e.redirect_uri,{code:e.code,state:b}))}catch(t){g({kind:`error`,message:t instanceof Error?t.message:e(`connectedApps.authorize.failed`)})}};return(0,d.useEffect)(()=>{if(!(n||_.current)){if(_.current=!0,!t){g({kind:`error`,message:e(`connectedApps.authorize.authDisabled`)});return}if(!v||!y||!x||S!==`S256`){g({kind:`error`,message:e(`connectedApps.authorize.invalidRequest`)});return}r.getConnectAuthorizeInfo(v,y).then(e=>{e.already_granted?C():g({kind:`consent`,info:e})}).catch(t=>{g({kind:`error`,message:t instanceof i&&t.status===409?e(`connectedApps.authorize.authDisabled`):e(`connectedApps.authorize.invalidRequest`)})})}},[n,t]),(0,f.jsx)(`div`,{className:`min-h-screen flex items-center justify-center bg-bambu-dark p-4`,children:(0,f.jsxs)(`div`,{className:`max-w-md w-full space-y-6 p-8 bg-gradient-to-br from-bambu-card to-bambu-dark-secondary rounded-xl border border-bambu-dark-tertiary shadow-lg`,children:[(h.kind===`loading`||h.kind===`redirecting`)&&(0,f.jsxs)(`div`,{className:`flex flex-col items-center gap-3 text-bambu-gray`,role:`status`,children:[(0,f.jsx)(s,{className:`w-8 h-8 animate-spin text-bambu-green`}),(0,f.jsx)(`p`,{children:h.kind===`loading`?e(`connectedApps.authorize.checking`):e(`connectedApps.authorize.redirecting`)})]}),h.kind===`error`&&(0,f.jsxs)(`div`,{className:`text-center space-y-3`,children:[(0,f.jsx)(c,{className:`w-10 h-10 mx-auto text-yellow-600 dark:text-yellow-400`}),(0,f.jsx)(`h1`,{className:`text-xl font-semibold text-white`,children:e(`connectedApps.authorize.errorTitle`)}),(0,f.jsx)(`p`,{className:`text-sm text-bambu-gray`,children:h.message})]}),h.kind===`consent`&&(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`div`,{className:`text-center space-y-3`,children:[(0,f.jsx)(`div`,{className:`w-14 h-14 mx-auto rounded-full bg-bambu-green/20 flex items-center justify-center`,children:(0,f.jsx)(o,{className:`w-7 h-7 text-bambu-green`})}),(0,f.jsx)(`h1`,{className:`text-xl font-semibold text-white text-balance`,children:e(`connectedApps.authorize.title`,{app:h.info.app_name})}),(0,f.jsx)(`p`,{className:`text-sm text-bambu-gray`,children:e(`connectedApps.authorize.signedInAs`,{username:h.info.username})})]}),(0,f.jsx)(`p`,{className:`text-sm text-bambu-gray`,children:e(`connectedApps.authorize.sharedData`,{app:h.info.app_name})}),(0,f.jsxs)(`div`,{className:`flex gap-3`,children:[(0,f.jsx)(`button`,{type:`button`,onClick:()=>{window.location.replace(p(y,{error:`access_denied`,state:b}))},className:`flex-1 px-4 py-2 bg-bambu-dark-tertiary text-white rounded-md hover:bg-bambu-dark-tertiary/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-bambu-green`,children:e(`connectedApps.authorize.deny`)}),(0,f.jsx)(`button`,{type:`button`,onClick:()=>void C(),className:`flex-1 px-4 py-2 bg-bambu-green text-white rounded-md hover:bg-bambu-green/90 focus:outline-none focus-visible:ring-2 focus-visible:ring-white`,children:e(`connectedApps.authorize.allow`)})]})]})]})})}export{m as ConnectAuthorizePage};
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{i as e}from"./chunk-aKtaBQYM.js";import{C as t,D as n,a as r,w as i}from"./client-C3LdD0iY.js";import{t as a}from"./useQuery-BSYE6Prb.js";import{t as o}from"./loader-circle-CbaPxZRP.js";import{t as s}from"./triangle-alert-BUlaDyuA.js";import{Un as c,Vn as l,vt as u}from"./index-CELnCUsn.js";var d=e(n(),1),f=t();function p(){let{t:e}=i(),{id:t}=c(),{mode:n}=u(),l=(0,d.useRef)(null),p=m(l);g(l);let{data:h,isLoading:_,error:v}=a({queryKey:[`external-link`,t],queryFn:()=>r.getExternalLink(Number(t)),enabled:!!t});return _?(0,f.jsx)(`div`,{className:`flex items-center justify-center h-full`,children:(0,f.jsx)(o,{className:`w-8 h-8 text-bambu-green animate-spin`})}):v||!h?(0,f.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-4 text-bambu-gray`,children:[(0,f.jsx)(s,{className:`w-12 h-12`}),(0,f.jsx)(`p`,{children:e(`common.linkNotFound`)})]}):(0,f.jsx)(`iframe`,{ref:l,src:h.url,onLoad:p,className:`h-full w-full border-0`,style:{colorScheme:n},title:h.name,sandbox:`allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox`})}function m(e){let{resolvedMode:t,darkStyle:n,darkBackground:r,darkAccent:i,lightStyle:a,lightBackground:o,lightAccent:s}=u(),c=t===`dark`,l=c?n:a,f=c?r:o,p=c?i:s,m=(0,d.useCallback)(()=>{let n=e.current;if(!n?.contentWindow)return;let r;try{r=new URL(n.src).origin}catch{return}n.contentWindow.postMessage({type:`bambuddy:theme`,mode:t,style:l,background:f,accent:p,canNavigate:!0},r)},[e,t,l,f,p]);return(0,d.useEffect)(()=>m(),[m]),(0,d.useEffect)(()=>{let t=t=>{t.source===e.current?.contentWindow&&t.data?.type===`bambuddy:theme-request`&&m()};return window.addEventListener(`message`,t),()=>window.removeEventListener(`message`,t)},[e,m]),m}var h=512;function g(e){let t=l();(0,d.useEffect)(()=>{let n=n=>{let r=e.current;if(!r?.contentWindow||n.source!==r.contentWindow||n.data?.type!==`bambuddy:navigate`)return;let i;try{i=new URL(r.src).origin}catch{return}if(n.origin!==i)return;let a=_(n.data.path);a!==null&&t(a)};return window.addEventListener(`message`,n),()=>window.removeEventListener(`message`,n)},[e,t])}function _(e){if(typeof e!=`string`||e.length>h||!e.startsWith(`/`)||e.startsWith(`//`)||e.includes(`\\`))return null;let t;try{t=new URL(e,window.location.origin)}catch{return null}return t.origin===window.location.origin?t.pathname+t.search+t.hash:null}export{p as ExternalLinkPage};
|
||||
import{i as e}from"./chunk-aKtaBQYM.js";import{C as t,D as n,a as r,w as i}from"./client-C3LdD0iY.js";import{t as a}from"./useQuery-BSYE6Prb.js";import{t as o}from"./loader-circle-CbaPxZRP.js";import{t as s}from"./triangle-alert-BUlaDyuA.js";import{Un as c,Vn as l,vt as u}from"./index-5pmk7Nlq.js";var d=e(n(),1),f=t();function p(){let{t:e}=i(),{id:t}=c(),{mode:n}=u(),l=(0,d.useRef)(null),p=m(l);g(l);let{data:h,isLoading:_,error:v}=a({queryKey:[`external-link`,t],queryFn:()=>r.getExternalLink(Number(t)),enabled:!!t});return _?(0,f.jsx)(`div`,{className:`flex items-center justify-center h-full`,children:(0,f.jsx)(o,{className:`w-8 h-8 text-bambu-green animate-spin`})}):v||!h?(0,f.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-4 text-bambu-gray`,children:[(0,f.jsx)(s,{className:`w-12 h-12`}),(0,f.jsx)(`p`,{children:e(`common.linkNotFound`)})]}):(0,f.jsx)(`iframe`,{ref:l,src:h.url,onLoad:p,className:`h-full w-full border-0`,style:{colorScheme:n},title:h.name,sandbox:`allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox`})}function m(e){let{resolvedMode:t,darkStyle:n,darkBackground:r,darkAccent:i,lightStyle:a,lightBackground:o,lightAccent:s}=u(),c=t===`dark`,l=c?n:a,f=c?r:o,p=c?i:s,m=(0,d.useCallback)(()=>{let n=e.current;if(!n?.contentWindow)return;let r;try{r=new URL(n.src).origin}catch{return}n.contentWindow.postMessage({type:`bambuddy:theme`,mode:t,style:l,background:f,accent:p,canNavigate:!0},r)},[e,t,l,f,p]);return(0,d.useEffect)(()=>m(),[m]),(0,d.useEffect)(()=>{let t=t=>{t.source===e.current?.contentWindow&&t.data?.type===`bambuddy:theme-request`&&m()};return window.addEventListener(`message`,t),()=>window.removeEventListener(`message`,t)},[e,m]),m}var h=512;function g(e){let t=l();(0,d.useEffect)(()=>{let n=n=>{let r=e.current;if(!r?.contentWindow||n.source!==r.contentWindow||n.data?.type!==`bambuddy:navigate`)return;let i;try{i=new URL(r.src).origin}catch{return}if(n.origin!==i)return;let a=_(n.data.path);a!==null&&t(a)};return window.addEventListener(`message`,n),()=>window.removeEventListener(`message`,n)},[e,t])}function _(e){if(typeof e!=`string`||e.length>h||!e.startsWith(`/`)||e.startsWith(`//`)||e.includes(`\\`))return null;let t;try{t=new URL(e,window.location.origin)}catch{return null}return t.origin===window.location.origin?t.pathname+t.search+t.hash:null}export{p as ExternalLinkPage};
|
||||
+3
-3
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{i as e}from"./chunk-aKtaBQYM.js";import{C as t,D as n,a as r,w as i}from"./client-C3LdD0iY.js";import{t as a}from"./useQuery-BSYE6Prb.js";import{t as o}from"./arrow-left-CSvb6Y3A.js";import{t as s}from"./GcodeToolpathViewer-N4Eow1eU.js";import{Vn as c,Wn as l}from"./index-CELnCUsn.js";var u=e(n(),1),d=t();function f(){let e=c(),[t,n]=l(),{t:f}=i(),p=t.get(`archive`),m=t.get(`library_file`),h=t.get(`plate`),g=a({queryKey:[`gcode-viewer-archive-colors`,p],queryFn:()=>r.getArchiveCapabilities(Number(p)),enabled:!!p,staleTime:5*6e4,retry:!1}),_=a({queryKey:[`gcode-viewer-library-colors`,m],queryFn:()=>r.getLibraryFilePlates(Number(m)),enabled:!!m,staleTime:5*6e4,retry:!1}),v=a({queryKey:[`gcode-viewer-archive-plates`,p],queryFn:()=>r.getArchivePlates(Number(p)),enabled:!!p,staleTime:5*6e4,retry:!1}),y=(0,u.useMemo)(()=>(p?v.data?.plates:_.data?.plates)??[],[p,v.data,_.data]),b=(0,u.useMemo)(()=>h?Number(h):y.length===0?null:Math.min(...y.map(e=>e.index)),[h,y]),x=e=>{if(e===b)return;let r=new URLSearchParams(t);r.set(`plate`,String(e)),n(r,{replace:!0})},S=(0,u.useMemo)(()=>{if(p)return g.data?.filament_colors;let e=y.find(e=>e.index===b)||y[0];if(!e?.filaments?.length)return;let t=[];for(let n of e.filaments){let e=Math.max(0,(n.slot_id??1)-1);n.color&&(t[e]=n.color)}return t.length>0?t:void 0},[p,g.data,y,b]),C=(0,u.useMemo)(()=>{let e=e=>h?`${e}?plate=${encodeURIComponent(h)}`:e;return p?e(r.getArchiveGcode(Number(p))):m?e(r.getLibraryFileGcodeUrl(Number(m))):null},[p,m,h]),w=()=>{window.history.length>1?e(-1):e(p?`/archives`:`/files`)},T=p?f(`gcodeViewer.backToArchives`,`Back to Archives`):f(`gcodeViewer.backToFiles`,`Back to File Manager`);return(0,d.jsxs)(`div`,{className:`flex flex-col min-h-[calc(100vh-64px)] lg:h-screen`,children:[(0,d.jsxs)(`div`,{className:`flex-shrink-0 px-4 py-2 border-b border-bambu-dark-tertiary flex flex-wrap items-center gap-x-4 gap-y-2`,children:[(0,d.jsxs)(`button`,{type:`button`,onClick:w,className:`inline-flex items-center gap-1.5 text-sm text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors`,children:[(0,d.jsx)(o,{className:`w-4 h-4`}),T]}),y.length>1&&(0,d.jsxs)(`div`,{className:`flex flex-wrap items-center gap-1.5`,children:[(0,d.jsx)(`span`,{className:`text-xs text-bambu-gray`,children:f(`gcodeViewer.plates`,`Plates`)}),y.map(e=>(0,d.jsx)(`button`,{type:`button`,onClick:()=>x(e.index),"aria-pressed":e.index===b,title:e.name??void 0,className:`px-2 py-0.5 rounded text-xs transition-colors ${e.index===b?`bg-bambu-green text-white`:`bg-bambu-dark-tertiary text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white`}`,children:f(`gcodeViewer.plateN`,`Plate {{n}}`,{n:e.index})},e.index))]})]}),C?(0,d.jsx)(s,{gcodeUrl:C,filamentColors:S,className:`flex-1 min-h-0`}):(0,d.jsx)(`div`,{className:`flex-1 flex items-center justify-center text-sm text-bambu-gray`,children:f(`gcodeViewer.noSource`,`No file was given to preview.`)})]})}export{f as GCodeViewerPage};
|
||||
import{i as e}from"./chunk-aKtaBQYM.js";import{C as t,D as n,a as r,w as i}from"./client-C3LdD0iY.js";import{t as a}from"./useQuery-BSYE6Prb.js";import{t as o}from"./arrow-left-CSvb6Y3A.js";import{t as s}from"./GcodeToolpathViewer-N4Eow1eU.js";import{Vn as c,Wn as l}from"./index-5pmk7Nlq.js";var u=e(n(),1),d=t();function f(){let e=c(),[t,n]=l(),{t:f}=i(),p=t.get(`archive`),m=t.get(`library_file`),h=t.get(`plate`),g=a({queryKey:[`gcode-viewer-archive-colors`,p],queryFn:()=>r.getArchiveCapabilities(Number(p)),enabled:!!p,staleTime:5*6e4,retry:!1}),_=a({queryKey:[`gcode-viewer-library-colors`,m],queryFn:()=>r.getLibraryFilePlates(Number(m)),enabled:!!m,staleTime:5*6e4,retry:!1}),v=a({queryKey:[`gcode-viewer-archive-plates`,p],queryFn:()=>r.getArchivePlates(Number(p)),enabled:!!p,staleTime:5*6e4,retry:!1}),y=(0,u.useMemo)(()=>(p?v.data?.plates:_.data?.plates)??[],[p,v.data,_.data]),b=(0,u.useMemo)(()=>h?Number(h):y.length===0?null:Math.min(...y.map(e=>e.index)),[h,y]),x=e=>{if(e===b)return;let r=new URLSearchParams(t);r.set(`plate`,String(e)),n(r,{replace:!0})},S=(0,u.useMemo)(()=>{if(p)return g.data?.filament_colors;let e=y.find(e=>e.index===b)||y[0];if(!e?.filaments?.length)return;let t=[];for(let n of e.filaments){let e=Math.max(0,(n.slot_id??1)-1);n.color&&(t[e]=n.color)}return t.length>0?t:void 0},[p,g.data,y,b]),C=(0,u.useMemo)(()=>{let e=e=>h?`${e}?plate=${encodeURIComponent(h)}`:e;return p?e(r.getArchiveGcode(Number(p))):m?e(r.getLibraryFileGcodeUrl(Number(m))):null},[p,m,h]),w=()=>{window.history.length>1?e(-1):e(p?`/archives`:`/files`)},T=p?f(`gcodeViewer.backToArchives`,`Back to Archives`):f(`gcodeViewer.backToFiles`,`Back to File Manager`);return(0,d.jsxs)(`div`,{className:`flex flex-col min-h-[calc(100vh-64px)] lg:h-screen`,children:[(0,d.jsxs)(`div`,{className:`flex-shrink-0 px-4 py-2 border-b border-bambu-dark-tertiary flex flex-wrap items-center gap-x-4 gap-y-2`,children:[(0,d.jsxs)(`button`,{type:`button`,onClick:w,className:`inline-flex items-center gap-1.5 text-sm text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors`,children:[(0,d.jsx)(o,{className:`w-4 h-4`}),T]}),y.length>1&&(0,d.jsxs)(`div`,{className:`flex flex-wrap items-center gap-1.5`,children:[(0,d.jsx)(`span`,{className:`text-xs text-bambu-gray`,children:f(`gcodeViewer.plates`,`Plates`)}),y.map(e=>(0,d.jsx)(`button`,{type:`button`,onClick:()=>x(e.index),"aria-pressed":e.index===b,title:e.name??void 0,className:`px-2 py-0.5 rounded text-xs transition-colors ${e.index===b?`bg-bambu-green text-white`:`bg-bambu-dark-tertiary text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white`}`,children:f(`gcodeViewer.plateN`,`Plate {{n}}`,{n:e.index})},e.index))]})]}),C?(0,d.jsx)(s,{gcodeUrl:C,filamentColors:S,className:`flex-1 min-h-0`}):(0,d.jsx)(`div`,{className:`flex-1 flex items-center justify-center text-sm text-bambu-gray`,children:f(`gcodeViewer.noSource`,`No file was given to preview.`)})]})}export{f as GCodeViewerPage};
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{i as e}from"./chunk-aKtaBQYM.js";import{C as t,D as n,a as r,l as i,w as a}from"./client-C3LdD0iY.js";import{t as o}from"./loader-circle-CbaPxZRP.js";import{n as s,t as c}from"./zoom-out-CpxEPaFQ.js";import{t as l}from"./file-DDkFXH-b.js";import{Gt as u,un as d}from"./index-CELnCUsn.js";import{n as f,r as p,t as m}from"./usePreviewFullscreen-Bd6UWAwo.js";var h=e(n(),1),g=t(),_=.4,v=8,y=1.25,b=30,x=Math.log(y)/b,S=e=>Math.min(v,Math.max(_,e)),C={x:0,y:0};function w(e){return e.deltaMode===1?e.deltaY*16:e.deltaMode===2?e.deltaY*100:e.deltaY}function T({libraryFileId:e,filename:t,fileSize:n,onClose:_}){let{t:v}=a(),T=m(),{isFullscreen:E,toggleFullscreen:D}=T,O=(0,h.useRef)(null),k=(0,h.useRef)(null),[A,j]=(0,h.useState)(null),[M,N]=(0,h.useState)(!1),[P,F]=(0,h.useState)(null),[I,L]=(0,h.useState)(1),[R,z]=(0,h.useState)(C),[B,V]=(0,h.useState)(!1),H=(0,h.useRef)(1),U=(0,h.useRef)(C),W=(0,h.useRef)(new Map),G=(0,h.useRef)(null),K=(0,h.useCallback)((e,t)=>{let n=O.current,r=k.current;if(!n||!r)return e;let i=Math.max(0,(r.offsetWidth*t-n.clientWidth)/2),a=Math.max(0,(r.offsetHeight*t-n.clientHeight)/2);return{x:Math.min(i,Math.max(-i,e.x)),y:Math.min(a,Math.max(-a,e.y))}},[]),q=(0,h.useCallback)((e,t)=>{H.current=e,U.current=t,L(e),z(t)},[]),J=(0,h.useCallback)((e,t,n)=>{let r=H.current,i=S(r*e);if(i===r)return;let a=O.current,o=U.current,s={x:o.x*(i/r),y:o.y*(i/r)};if(a){let e=a.getBoundingClientRect(),c=(t??e.left+e.width/2)-(e.left+e.width/2),l=(n??e.top+e.height/2)-(e.top+e.height/2),u=i/r;s={x:c-(c-o.x)*u,y:l-(l-o.y)*u}}q(i,K(s,i))},[K,q]),Y=(0,h.useCallback)(()=>q(1,C),[q]);(0,h.useEffect)(()=>{let t=!1,a=null;if(j(null),N(!1),F(null),q(1,C),n>52428800){F(v(`fileManager.preview.tooLarge`,{size:l(n)}));return}let o={},s=i();return s&&(o.Authorization=`Bearer ${s}`),(async()=>{let n=await fetch(r.getLibraryFileDownloadUrl(e),{headers:o});if(!n.ok)throw Error(`HTTP ${n.status}`);let i=await n.blob();t||(a=URL.createObjectURL(i),j(a))})().catch(e=>{console.error(`[image-preview] load failed`,e),t||F(v(`fileManager.preview.error`))}),()=>{t=!0,a&&URL.revokeObjectURL(a)}},[e,n,v,q]),(0,h.useEffect)(()=>{let e=e=>{e.ctrlKey||e.metaKey||e.altKey||(e.key===`+`||e.key===`=`?(e.preventDefault(),J(y)):e.key===`-`?(e.preventDefault(),J(1/y)):e.key===`0`&&(e.preventDefault(),Y()))};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[Y,J]),(0,h.useEffect)(()=>{let e=O.current;if(!e)return;let t=e=>{e.preventDefault();let t=Math.max(-30,Math.min(b,w(e)));J(Math.exp(-t*x),e.clientX,e.clientY)};return e.addEventListener(`wheel`,t,{passive:!1}),()=>e.removeEventListener(`wheel`,t)},[J,A]);let X=e=>{W.current.set(e.pointerId,{x:e.clientX,y:e.clientY}),G.current=null,W.current.size===1&&H.current>1&&(e.currentTarget.setPointerCapture(e.pointerId),V(!0))},Z=e=>{W.current.delete(e.pointerId),G.current=null,W.current.size===0&&V(!1)},Q=e=>{let t=W.current,n=t.get(e.pointerId);if(!n)return;if(e.pointerType===`mouse`&&e.buttons===0){Z(e);return}if(t.set(e.pointerId,{x:e.clientX,y:e.clientY}),t.size===1){if(H.current<=1)return;let t={x:U.current.x+(e.clientX-n.x),y:U.current.y+(e.clientY-n.y)};q(H.current,K(t,H.current));return}if(t.size!==2)return;let[r,i]=Array.from(t.values()),a=Math.hypot(r.x-i.x,r.y-i.y),o=G.current;G.current=a,o!=null&&o>0&&a>0&&J(a/o,(r.x+i.x)/2,(r.y+i.y)/2)},$=I>1;return(0,g.jsx)(f,{title:t,fullscreen:T,onClose:_,icon:(0,g.jsx)(d,{className:`w-5 h-5 text-bambu-green flex-shrink-0`}),actions:A?(0,g.jsxs)(g.Fragment,{children:[(0,g.jsx)(`button`,{onClick:()=>J(1/y),className:p,"aria-label":v(`fileManager.preview.zoomOut`),children:(0,g.jsx)(c,{className:`w-4 h-4`})}),(0,g.jsx)(`button`,{onClick:()=>J(y),className:p,"aria-label":v(`fileManager.preview.zoomIn`),children:(0,g.jsx)(s,{className:`w-4 h-4`})}),(0,g.jsx)(`button`,{onClick:Y,className:p,"aria-label":v(`fileManager.preview.resetZoom`),children:(0,g.jsx)(u,{className:`w-4 h-4`})})]}):void 0,children:(0,g.jsx)(`div`,{ref:O,"data-testid":`image-preview-content`,className:`relative flex-1 min-h-0 overflow-hidden bg-bambu-dark flex items-center justify-center touch-none ${E?``:`rounded-b-lg`} ${$?B?`cursor-grabbing`:`cursor-grab`:``}`,onDoubleClick:D,onPointerDown:X,onPointerMove:Q,onPointerUp:Z,onPointerCancel:Z,onPointerLeave:Z,children:P?(0,g.jsx)(`p`,{className:`text-bambu-gray text-center p-6`,children:P}):(0,g.jsxs)(g.Fragment,{children:[A&&(0,g.jsx)(`img`,{ref:k,src:A,alt:t,draggable:!1,onLoad:()=>N(!0),onError:()=>F(v(`fileManager.preview.error`)),style:{transform:`translate(${R.x}px, ${R.y}px) scale(${I})`},className:`max-w-full max-h-full object-contain select-none`}),!M&&(0,g.jsx)(`div`,{className:`absolute inset-0 flex items-center justify-center pointer-events-none`,children:(0,g.jsx)(o,{className:`w-8 h-8 text-bambu-green animate-spin`})})]})})})}export{T as ImagePreviewModal};
|
||||
import{i as e}from"./chunk-aKtaBQYM.js";import{C as t,D as n,a as r,l as i,w as a}from"./client-C3LdD0iY.js";import{t as o}from"./loader-circle-CbaPxZRP.js";import{n as s,t as c}from"./zoom-out-CpxEPaFQ.js";import{t as l}from"./file-DDkFXH-b.js";import{Gt as u,un as d}from"./index-5pmk7Nlq.js";import{n as f,r as p,t as m}from"./usePreviewFullscreen-Bd6UWAwo.js";var h=e(n(),1),g=t(),_=.4,v=8,y=1.25,b=30,x=Math.log(y)/b,S=e=>Math.min(v,Math.max(_,e)),C={x:0,y:0};function w(e){return e.deltaMode===1?e.deltaY*16:e.deltaMode===2?e.deltaY*100:e.deltaY}function T({libraryFileId:e,filename:t,fileSize:n,onClose:_}){let{t:v}=a(),T=m(),{isFullscreen:E,toggleFullscreen:D}=T,O=(0,h.useRef)(null),k=(0,h.useRef)(null),[A,j]=(0,h.useState)(null),[M,N]=(0,h.useState)(!1),[P,F]=(0,h.useState)(null),[I,L]=(0,h.useState)(1),[R,z]=(0,h.useState)(C),[B,V]=(0,h.useState)(!1),H=(0,h.useRef)(1),U=(0,h.useRef)(C),W=(0,h.useRef)(new Map),G=(0,h.useRef)(null),K=(0,h.useCallback)((e,t)=>{let n=O.current,r=k.current;if(!n||!r)return e;let i=Math.max(0,(r.offsetWidth*t-n.clientWidth)/2),a=Math.max(0,(r.offsetHeight*t-n.clientHeight)/2);return{x:Math.min(i,Math.max(-i,e.x)),y:Math.min(a,Math.max(-a,e.y))}},[]),q=(0,h.useCallback)((e,t)=>{H.current=e,U.current=t,L(e),z(t)},[]),J=(0,h.useCallback)((e,t,n)=>{let r=H.current,i=S(r*e);if(i===r)return;let a=O.current,o=U.current,s={x:o.x*(i/r),y:o.y*(i/r)};if(a){let e=a.getBoundingClientRect(),c=(t??e.left+e.width/2)-(e.left+e.width/2),l=(n??e.top+e.height/2)-(e.top+e.height/2),u=i/r;s={x:c-(c-o.x)*u,y:l-(l-o.y)*u}}q(i,K(s,i))},[K,q]),Y=(0,h.useCallback)(()=>q(1,C),[q]);(0,h.useEffect)(()=>{let t=!1,a=null;if(j(null),N(!1),F(null),q(1,C),n>52428800){F(v(`fileManager.preview.tooLarge`,{size:l(n)}));return}let o={},s=i();return s&&(o.Authorization=`Bearer ${s}`),(async()=>{let n=await fetch(r.getLibraryFileDownloadUrl(e),{headers:o});if(!n.ok)throw Error(`HTTP ${n.status}`);let i=await n.blob();t||(a=URL.createObjectURL(i),j(a))})().catch(e=>{console.error(`[image-preview] load failed`,e),t||F(v(`fileManager.preview.error`))}),()=>{t=!0,a&&URL.revokeObjectURL(a)}},[e,n,v,q]),(0,h.useEffect)(()=>{let e=e=>{e.ctrlKey||e.metaKey||e.altKey||(e.key===`+`||e.key===`=`?(e.preventDefault(),J(y)):e.key===`-`?(e.preventDefault(),J(1/y)):e.key===`0`&&(e.preventDefault(),Y()))};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[Y,J]),(0,h.useEffect)(()=>{let e=O.current;if(!e)return;let t=e=>{e.preventDefault();let t=Math.max(-30,Math.min(b,w(e)));J(Math.exp(-t*x),e.clientX,e.clientY)};return e.addEventListener(`wheel`,t,{passive:!1}),()=>e.removeEventListener(`wheel`,t)},[J,A]);let X=e=>{W.current.set(e.pointerId,{x:e.clientX,y:e.clientY}),G.current=null,W.current.size===1&&H.current>1&&(e.currentTarget.setPointerCapture(e.pointerId),V(!0))},Z=e=>{W.current.delete(e.pointerId),G.current=null,W.current.size===0&&V(!1)},Q=e=>{let t=W.current,n=t.get(e.pointerId);if(!n)return;if(e.pointerType===`mouse`&&e.buttons===0){Z(e);return}if(t.set(e.pointerId,{x:e.clientX,y:e.clientY}),t.size===1){if(H.current<=1)return;let t={x:U.current.x+(e.clientX-n.x),y:U.current.y+(e.clientY-n.y)};q(H.current,K(t,H.current));return}if(t.size!==2)return;let[r,i]=Array.from(t.values()),a=Math.hypot(r.x-i.x,r.y-i.y),o=G.current;G.current=a,o!=null&&o>0&&a>0&&J(a/o,(r.x+i.x)/2,(r.y+i.y)/2)},$=I>1;return(0,g.jsx)(f,{title:t,fullscreen:T,onClose:_,icon:(0,g.jsx)(d,{className:`w-5 h-5 text-bambu-green flex-shrink-0`}),actions:A?(0,g.jsxs)(g.Fragment,{children:[(0,g.jsx)(`button`,{onClick:()=>J(1/y),className:p,"aria-label":v(`fileManager.preview.zoomOut`),children:(0,g.jsx)(c,{className:`w-4 h-4`})}),(0,g.jsx)(`button`,{onClick:()=>J(y),className:p,"aria-label":v(`fileManager.preview.zoomIn`),children:(0,g.jsx)(s,{className:`w-4 h-4`})}),(0,g.jsx)(`button`,{onClick:Y,className:p,"aria-label":v(`fileManager.preview.resetZoom`),children:(0,g.jsx)(u,{className:`w-4 h-4`})})]}):void 0,children:(0,g.jsx)(`div`,{ref:O,"data-testid":`image-preview-content`,className:`relative flex-1 min-h-0 overflow-hidden bg-bambu-dark flex items-center justify-center touch-none ${E?``:`rounded-b-lg`} ${$?B?`cursor-grabbing`:`cursor-grab`:``}`,onDoubleClick:D,onPointerDown:X,onPointerMove:Q,onPointerUp:Z,onPointerCancel:Z,onPointerLeave:Z,children:P?(0,g.jsx)(`p`,{className:`text-bambu-gray text-center p-6`,children:P}):(0,g.jsxs)(g.Fragment,{children:[A&&(0,g.jsx)(`img`,{ref:k,src:A,alt:t,draggable:!1,onLoad:()=>N(!0),onError:()=>F(v(`fileManager.preview.error`)),style:{transform:`translate(${R.x}px, ${R.y}px) scale(${I})`},className:`max-w-full max-h-full object-contain select-none`}),!M&&(0,g.jsx)(`div`,{className:`absolute inset-0 flex items-center justify-center pointer-events-none`,children:(0,g.jsx)(o,{className:`w-8 h-8 text-bambu-green animate-spin`})})]})})})}export{T as ImagePreviewModal};
|
||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{ft as e}from"./index-CELnCUsn.js";function t(t,n){let r=n.trim().toLowerCase();if(!r)return!0;let i=r.match(/^#(\d+)$/);return i?t.id===Number(i[1]):String(t.id).includes(r)||t.material.toLowerCase().includes(r)||(t.brand?.toLowerCase().includes(r)??!1)||(t.color_name?.toLowerCase().includes(r)??!1)||(e(t.color_name,t.rgba,t.color_name_is_synthesized)?.toLowerCase().includes(r)??!1)||(t.subtype?.toLowerCase().includes(r)??!1)||(t.note?.toLowerCase().includes(r)??!1)||(t.slicer_filament_name?.toLowerCase().includes(r)??!1)||(t.storage_location?.toLowerCase().includes(r)??!1)||(t.material_number?.toLowerCase().includes(r)??!1)||(t.suppliers?.some(e=>e.supplier_name.toLowerCase().includes(r)||(e.supplier_article_number?.toLowerCase().includes(r)??!1))??!1)}function n(e,n){return n?e.filter(e=>t(e,n)):e}export{n as t};
|
||||
import{ft as e}from"./index-5pmk7Nlq.js";function t(t,n){let r=n.trim().toLowerCase();if(!r)return!0;let i=r.match(/^#(\d+)$/);return i?t.id===Number(i[1]):String(t.id).includes(r)||t.material.toLowerCase().includes(r)||(t.brand?.toLowerCase().includes(r)??!1)||(t.color_name?.toLowerCase().includes(r)??!1)||(e(t.color_name,t.rgba,t.color_name_is_synthesized)?.toLowerCase().includes(r)??!1)||(t.subtype?.toLowerCase().includes(r)??!1)||(t.note?.toLowerCase().includes(r)??!1)||(t.slicer_filament_name?.toLowerCase().includes(r)??!1)||(t.storage_location?.toLowerCase().includes(r)??!1)||(t.material_number?.toLowerCase().includes(r)??!1)||(t.suppliers?.some(e=>e.supplier_name.toLowerCase().includes(r)||(e.supplier_article_number?.toLowerCase().includes(r)??!1))??!1)}function n(e,n){return n?e.filter(e=>t(e,n)):e}export{n as t};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -26,7 +26,7 @@
|
||||
|
||||
<!-- Splash screens for iOS -->
|
||||
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
|
||||
<script type="module" crossorigin src="/assets/index-CELnCUsn.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-5pmk7Nlq.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/chunk-aKtaBQYM.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/client-C3LdD0iY.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/QueryClientProvider-DzWmuIjj.js">
|
||||
|
||||
Reference in New Issue
Block a user