+ ${slide.title} +
++ ${slide.body} +
+diff --git a/src/argo-archive-list.ts b/src/argo-archive-list.ts index 9f66c25..6fa56cf 100644 --- a/src/argo-archive-list.ts +++ b/src/argo-archive-list.ts @@ -7,7 +7,6 @@ import "@material/web/list/list-item.js"; import "@material/web/checkbox/checkbox.js"; import "@material/web/icon/icon.js"; import "@material/web/labs/card/elevated-card.js"; -// @ts-expect-error import filingDrawer from "assets/images/filing-drawer.avif"; import { getLocalOption } from "./localstorage"; diff --git a/src/argo-shared-archive-list.ts b/src/argo-shared-archive-list.ts index 7b6cf84..87e9baa 100644 --- a/src/argo-shared-archive-list.ts +++ b/src/argo-shared-archive-list.ts @@ -8,7 +8,6 @@ import "@material/web/icon/icon.js"; import "@material/web/labs/card/elevated-card.js"; import "@material/web/button/filled-button.js"; import "@material/web/button/outlined-button.js"; -// @ts-expect-error import filingDrawer from "assets/images/filing-drawer.avif"; import { getLocalOption, setSharedArchives } from "./localstorage"; diff --git a/src/assets/brand/packrat-lockup-white.svg b/src/assets/brand/packrat-lockup-white.svg new file mode 100644 index 0000000..304225a --- /dev/null +++ b/src/assets/brand/packrat-lockup-white.svg @@ -0,0 +1 @@ + diff --git a/src/assets/images/downloading.avif b/src/assets/images/downloading.avif new file mode 100644 index 0000000..096aab3 Binary files /dev/null and b/src/assets/images/downloading.avif differ diff --git a/src/assets/images/forest.avif b/src/assets/images/forest.avif new file mode 100644 index 0000000..90aa901 Binary files /dev/null and b/src/assets/images/forest.avif differ diff --git a/src/assets/images/sharing-warning.avif b/src/assets/images/sharing-warning.avif new file mode 100644 index 0000000..e7ed45b Binary files /dev/null and b/src/assets/images/sharing-warning.avif differ diff --git a/src/assets/images/sharing.avif b/src/assets/images/sharing.avif new file mode 100644 index 0000000..a7fb48e Binary files /dev/null and b/src/assets/images/sharing.avif differ diff --git a/src/ext/bg.ts b/src/ext/bg.ts index 8545d5d..f252878 100644 --- a/src/ext/bg.ts +++ b/src/ext/bg.ts @@ -95,6 +95,15 @@ function sidepanelHandler(port) { // @ts-expect-error - TS2339 - Property 'port' does not exist on type 'BrowserRecorder'. self.recorders[tabId].port = port; self.recorders[tabId].doUpdateStatus(); + } else if (isRecordingEnabled) { + // Send the current recording state even if no recorder exists for this tab + port.postMessage({ + type: "status", + recording: false, // No recorder for this tab + autorun, + // @ts-expect-error + collId: defaultCollId, + }); } port.postMessage(await listAllMsg(collLoader)); break; @@ -353,8 +362,18 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo) => { if (changeInfo.url && !isValidUrl(changeInfo.url, skipDomains)) { stopRecorder(tabId); + + // Ensure debugger is detached when navigating to a skipped domain + try { + chrome.debugger.detach({ tabId }, () => { + // Debugger detached, ignore any errors as it might already be detached + }); + } catch (e) { + // Ignore errors - debugger might already be detached + } + delete self.recorders[tabId]; - // let the side-panel know the ’canRecord’/UI state changed + // let the side-panel know the 'canRecord'/UI state changed // @ts-expect-error if (sidepanelPort) { sidepanelPort.postMessage({ type: "update" }); @@ -440,6 +459,9 @@ async function startRecorder(tabId, opts) { let err = null; // @ts-expect-error - TS7034 - Variable 'sidepanelPort' implicitly has type 'any' in some locations where its type cannot be determined. if (sidepanelPort) { + // Set the port on the recorder so it can send status updates + // @ts-expect-error + self.recorders[tabId].port = sidepanelPort; sidepanelPort.postMessage({ type: "update" }); } const { waitForTabUpdate } = opts; @@ -449,9 +471,28 @@ async function startRecorder(tabId, opts) { try { self.recorders[tabId].setCollId(opts.collId); await self.recorders[tabId].attach(); + + // Send status update after successful attach + // @ts-expect-error + if (sidepanelPort && self.recorders[tabId]) { + self.recorders[tabId].doUpdateStatus(); + } } catch (e) { console.warn(e); err = e; + + // Clean up on error + // @ts-expect-error + if (err?.message?.includes("already attached")) { + // Try to detach and delete the recorder + try { + chrome.debugger.detach({ tabId }, () => { + delete self.recorders[tabId]; + }); + } catch (detachErr) { + console.warn("Failed to detach debugger:", detachErr); + } + } } return err; } @@ -462,6 +503,19 @@ async function startRecorder(tabId, opts) { function stopRecorder(tabId) { if (self.recorders[tabId]) { self.recorders[tabId].detach(); + + // Ensure the sidepanel is notified about the stop + // @ts-expect-error - TS7034 - Variable 'sidepanelPort' implicitly has type 'any' in some locations where its type cannot be determined. + if (sidepanelPort) { + sidepanelPort.postMessage({ + type: "status", + recording: false, + autorun, + // @ts-expect-error - defaultCollId implicitly has an 'any' type. + collId: defaultCollId, + }); + } + return true; } diff --git a/src/globals.d.ts b/src/globals.d.ts index 09d3061..c00e7b5 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -1,4 +1,7 @@ declare module "*.svg"; +declare module "*.png"; +declare module "*.avif" +declare module "*.jpg"; declare module "*.html"; declare module "*.scss"; declare module "*.sass"; diff --git a/src/onboarding.ts b/src/onboarding.ts new file mode 100644 index 0000000..c00f118 --- /dev/null +++ b/src/onboarding.ts @@ -0,0 +1,327 @@ +import { LitElement, html, css, unsafeCSS, CSSResultGroup } from "lit"; +import { unsafeSVG } from "lit/directives/unsafe-svg.js"; + +import { customElement, property, state } from "lit/decorators.js"; +import { styles as typescaleStyles } from "@material/web/typography/md-typescale-styles.js"; + +// Import Material Design components +import "@material/web/button/filled-button.js"; +import "@material/web/button/outlined-button.js"; +import "@material/web/divider/divider.js"; +import "@material/web/icon/icon.js"; + +// Import assets +import forestImg from "./assets/images/forest.avif"; +import packratLogo from "./assets/brand/packrat-lockup-white.svg"; +import downloadingImg from "./assets/images/downloading.avif"; +import sharingImg from "./assets/images/sharing.avif"; +import warningImg from "./assets/images/sharing-warning.avif"; + +@customElement("wr-onboarding") +export class OnboardingView extends LitElement { + static styles: CSSResultGroup = [ + typescaleStyles as unknown as CSSResultGroup, + css` + :host { + position: relative; + display: flex; + flex-direction: column; + overflow: hidden; /* Changed from scroll to hidden */ + width: 100vw; + height: 100vh; + background: url(${unsafeCSS(forestImg)}) center/cover no-repeat; + } + + .slides-container { + flex: 1; + overflow: hidden; + position: relative; + } + + .slides { + display: flex; + align-items: center; + justify-content: flex-start; /* Changed from center */ + height: 100%; + transition: transform 500ms ease-in-out; + padding: 2rem; + gap: 2rem; + box-sizing: border-box; + } + + /* Transform classes for the slides container */ + .slides.step-0 { + transform: translateX(0); + } + + .slides.step-1 { + transform: translateX(calc(-100vw + 2rem)); + } + + .slides.step-2 { + transform: translateX(calc(-200vw + 4rem)); + } + + .slides.step-3 { + transform: translateX(calc(-300vw + 6rem)); + } + + .slide { + width: calc(100vw - 4rem); + height: 100%; + box-sizing: border-box; + padding: 2rem; + background: var(--md-sys-color-surface); + border-radius: 0.5rem; + box-shadow: var(--md-sys-elevation-level2); + display: flex; + flex-direction: column; + flex-shrink: 0; /* Prevent slides from shrinking */ + opacity: 1; + } + + .slide.hidden { + opacity: 0; + transition: opacity 2s ease-out; + } + + /* First slide - full screen, no card styling */ + .slide.first { + background: transparent; + box-shadow: none; + border-radius: 0; + padding: 0; + height: 100%; + } + + /* First slide content */ + .first-content { + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3rem; + } + + .first-content .logo { + width: 100%; + max-width: 256px; + height: auto; + } + + /* Card content */ + .card-content { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: 1rem; + max-height: 100%; + } + + .card-content-imgcontainer { + width: 100%; + flex-shrink: 1; + flex-grow: 1; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.12)) + drop-shadow(0 1px 2px rgba(0, 0, 0, 0.24)); + } + + .card-content img { + width: 100%; + height: 100%; + object-fit: contain; /* or 'cover', depending on your goal */ + display: block; + } + + .card-content md-divider { + width: 100%; + margin: 0.5rem 0; + } + + /* Dots indicator */ + .dots { + display: flex; + justify-content: center; + gap: 0.5rem; + } + + .dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--md-sys-color-outline-variant); + transition: background 200ms; + } + + .dot[active] { + background: var(--md-sys-color-primary); + } + + /* Bottom navigation panel */ + .bottom-panel { + background: var(--md-sys-color-surface); + box-shadow: var(--md-sys-elevation-level2); + } + + .bottom-panel-content { + padding: 1rem; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + } + + .bottom-panel md-outlined-button { + --md-outlined-button-container-shape: 999px; + } + + .bottom-panel md-filled-button { + --md-filled-button-container-shape: 999px; + color: white; + } + + .bottom-panel md-outlined-button[disabled] { + opacity: 0.38; + } + + /* Hide bottom panel on first slide */ + .bottom-panel[hidden] { + display: none; + } + `, + ]; + + @state() private step = 0; + + private content = [ + { + first: true, + }, + { + title: "Packrat downloads websites as you browse", + img: downloadingImg, + body: "All the pages you view with the extension enabled will be saved locally to your computer.", + alt: "Digital collage of content coalessing into a vintage computer monitor", + }, + { + title: "Share your archives with others with a link", + img: sharingImg, + body: "All data is transferred directly from your computer to their browser. Nobody else gets access to your archives.", + alt: "Digital collage of two computers with images being transferred between them in a whirlwind of scribbles", + }, + { + title: "Web archives can contain private data!", + img: warningImg, + body: "Web archives of logged-in sites can contain private messages, user data, and account credentials. Only share archives of logged-in sites with people you trust.", + alt: "Digital collage of a top secret classified document cover sheet atop maps and an envelope", + }, + ]; + + private _prev() { + if (this.step > 1) { + this.step--; + } + } + + private _next() { + if (this.step < this.content.length - 1) { + this.step++; + } else { + this.dispatchEvent(new CustomEvent("completed", { bubbles: true })); + } + } + + render() { + return html` +
+ ++ When enabled, the onboarding carousel will run the next time you open the side panel. +
+