feat(extension): automate publishing to Chrome Web Store

This commit is contained in:
Tautvydas Derzinskas committed 2026-10-01 07:58:17 +02:00
1 parent 955513c5c5
commit e37073cf53
10 files changed
+269 -128

No files matched your search

+4 -3
View File
@@ -135,6 +135,10 @@ jobs:
See `extension/README.md` for install instructions:
- **Chrome / other Chromium browsers**: install it from the
[Chrome Web Store](https://chromewebstore.google.com/detail/nmblahmglpbplmfcggghdgohohlaeiee),
which keeps it up to date. `thingport-grab-chrome.zip` here is a build of this commit,
for loading by hand (`chrome://extensions` → **Developer mode** → **Load unpacked**).
- **Firefox**: install it from
[Firefox Add-ons](https://addons.mozilla.org/firefox/addon/thingport-grab/), which keeps
it up to date. `thingport-grab-firefox.xpi` here is a Mozilla-signed build of this
@@ -143,9 +147,6 @@ jobs:
[Microsoft Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/kahfidpmojfocohinlmglnfoaimocbol),
which keeps it up to date. `thingport-grab-edge.zip` here is the same build, for
loading by hand (`edge://extensions` → **Developer mode** → **Load unpacked**).
- **Chrome / other Chromium browsers**: unzip `thingport-grab-chrome.zip`, then
`chrome://extensions` → enable **Developer mode** → **Load unpacked** → select the
unzipped folder.
files: |
dist/thingport-grab-chrome.zip
dist/thingport-grab-edge.zip
+42 -1
View File
@@ -3,7 +3,7 @@ name: Extension store release
# Versioned releases of Thingport Grab via semantic-release (extension/.releaserc.json): every
# Friday at 13:00 Polish time, or on demand. Only commits touching extension/ count; with a
# releasable one it bumps the version, writes the changelog, tags, creates a GitHub release with
# the store zips, and publishes to Edge Add-ons and addons.mozilla.org. Per-merge builds are in
# the store zips, and publishes to the Chrome Web Store, Edge Add-ons and addons.mozilla.org. Per-merge builds are in
# extension-release.yml.
on:
workflow_dispatch: {}
@@ -80,6 +80,47 @@ jobs:
path: extension/dist/zips/*.zip
if-no-files-found: error
# Its own job so it can be re-run alone. Signs in as the publisher's service account; see
# extension/CONTRIBUTING.md.
publish-chrome:
needs: release
if: needs.release.outputs.version != ''
runs-on: ubuntu-latest
defaults:
run:
working-directory: extension
env:
CHROME_EXTENSION_ID: ${{ vars.CHROME_EXTENSION_ID }}
CWS_PUBLISHER_ID: ${{ secrets.CWS_PUBLISHER_ID }}
CWS_SERVICE_ACCOUNT_KEY: ${{ secrets.CWS_SERVICE_ACCOUNT_KEY }}
steps:
- name: Credentials set up?
id: creds
run: |
if [ -z "$CWS_SERVICE_ACCOUNT_KEY" ] || [ -z "$CWS_PUBLISHER_ID" ]; then
echo "::notice::CWS_SERVICE_ACCOUNT_KEY / CWS_PUBLISHER_ID aren't set -- skipping the Chrome Web Store (see extension/CONTRIBUTING.md)."
echo "ok=false" >> "$GITHUB_OUTPUT"
fi
working-directory: .
- uses: actions/checkout@v4
if: steps.creds.outputs.ok != 'false'
- uses: actions/setup-node@v4
if: steps.creds.outputs.ok != 'false'
with:
node-version: 20
cache: npm
cache-dependency-path: extension/package-lock.json
- if: steps.creds.outputs.ok != 'false'
run: npm ci
- uses: actions/download-artifact@v4
if: steps.creds.outputs.ok != 'false'
with:
name: thingport-grab-store-zips
path: extension/dist/zips
- name: Upload and submit for review
if: steps.creds.outputs.ok != 'false'
run: npx tsx scripts/publish-chrome.ts dist/zips/thingport-grab-chrome.zip
# Its own job so it can be re-run alone: Edge takes one submission at a time and fails with
# InProgressSubmission while the previous one is in certification. Credentials: see
# extension/CONTRIBUTING.md.
+1 -1
View File
@@ -84,7 +84,7 @@ Instead of having your collection scattered across different websites and your f
Thingport ships two small companion tools, each downloadable from the in-app Download page or GitHub Releases:
- **[Thingport Bridge](bridge/README.md)** — a lightweight desktop helper that makes "Open in {Slicer}" work for slicers (Bambu Studio, PrusaSlicer, Cura, Anycubic Slicer Next) whose own URL-protocol handlers won't accept a link from a self-hosted domain.
- **[Thingport Grab](extension/README.md)** — a browser extension for Chrome, Edge and Firefox that imports MakerWorld, Thingiverse, and Printables models straight from their own pages, without leaving the site (see the screenshots above). Even without a Thingport instance, it adds a "Download normalized" button to MakerWorld model pages that converts Bambu Studio projects into 3MFs other slicers open with their colours and settings. Get it from [Firefox Add-ons](https://addons.mozilla.org/firefox/addon/thingport-grab/) or [Microsoft Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/kahfidpmojfocohinlmglnfoaimocbol).
- **[Thingport Grab](extension/README.md)** — a browser extension for Chrome, Edge and Firefox that imports MakerWorld, Thingiverse, and Printables models straight from their own pages, without leaving the site (see the screenshots above). Even without a Thingport instance, it adds a "Download normalized" button to MakerWorld model pages that converts Bambu Studio projects into 3MFs other slicers open with their colours and settings. Get it from the [Chrome Web Store](https://chromewebstore.google.com/detail/nmblahmglpbplmfcggghdgohohlaeiee), [Firefox Add-ons](https://addons.mozilla.org/firefox/addon/thingport-grab/) or [Microsoft Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/kahfidpmojfocohinlmglnfoaimocbol).
## Provider Setup
+51 -7
View File
@@ -133,16 +133,14 @@ Secrets and variables > Actions > Variables), so they don't have to be hard-code
| Listing | https://addons.mozilla.org/firefox/addon/thingport-grab/ |
| Add-on ID | `grab@thingport.app` -- `gecko.id` in `scripts/manifest.ts` |
**Chrome Web Store** (submitted; the listing URL works once review passes):
**Chrome Web Store** (live; new versions are published by CI, see
[Publishing to the Chrome Web Store automatically](#publishing-to-the-chrome-web-store-automatically)):
| | Value |
| ------------ | ------------------------------------------------------------------------- |
| Listing | https://chromewebstore.google.com/detail/nmblahmglpbplmfcggghdgohohlaeiee |
| Extension ID | `nmblahmglpbplmfcggghdgohohlaeiee` -- repo variable `CHROME_EXTENSION_ID` |
Publishing to Chrome from CI (not set up yet) needs a Google Cloud OAuth client for the
[Chrome Web Store API](https://developer.chrome.com/docs/webstore/using-api), as repo secrets:
`CHROME_CLIENT_ID`, `CHROME_CLIENT_SECRET` and `CHROME_REFRESH_TOKEN`.
| Publisher ID | repo secret `CWS_PUBLISHER_ID`, used by the publish API |
Edge also issued a public key for the listing. Don't add it to the manifest (`key`) of the store
builds -- the stores set that themselves. It's only useful for giving an unpacked development build
@@ -187,7 +185,8 @@ Two workflows, for two different jobs.
touching this folder: typecheck, lint, the Chrome/Edge build, and `web-ext lint` on the Firefox
build. On a push to `main` it also signs the Firefox build through AMO's unlisted channel and
publishes `thingport-grab-chrome.zip`, `thingport-grab-edge.zip` and `thingport-grab-firefox.xpi`
to the `extension-latest` release (the in-app Download page links there for Chrome).
to the `extension-latest` release, for installing by hand (the in-app Download page links the
store listings instead).
It never changes the version: AMO rejects a version number it has already signed, so CI signs a
copy of the built manifest with the run number appended (e.g. `1.1.2.456`).
@@ -196,7 +195,8 @@ Polish time**, or whenever you start it (**Actions > Extension store release > R
`gh workflow run extension-store-release.yml`), it releases whatever changed in the extension since
the last release, with [semantic-release](https://semantic-release.gitbook.io/) (see below): bumps
the version, writes `CHANGELOG.md`, commits both to `main`, tags it, creates a GitHub release with
the store zips, and publishes to Edge Add-ons and Firefox Add-ons. If nothing releasable changed,
the store zips, and publishes to the Chrome Web Store, Edge Add-ons and Firefox Add-ons. If nothing
releasable changed,
the run just ends.
### Versioning: how the next version is picked
@@ -231,6 +231,24 @@ To see what the next release would be, without releasing anything (needs Node 22
npm run release:dry-run
```
### Publishing to the Chrome Web Store automatically
The store release's `publish-chrome` job publishes each new version to the Chrome Web Store,
through the [Chrome Web Store API v2](https://developer.chrome.com/docs/webstore/using-api)
(`scripts/publish-chrome.ts`): it signs in as the publisher's service account, uploads the exact
`thingport-grab-chrome.zip` the release built, waits for Google to process it, and submits it for
review. The update reaches users once Google's review passes, usually within a few days.
Like the other publish jobs it can be retried on its own with **Re-run failed jobs**, and it skips
itself (with a notice in the run) until the credentials below exist. To test the script by hand:
```bash
npm run zip:chrome
CHROME_EXTENSION_ID=nmblahmglpbplmfcggghdgohohlaeiee CWS_PUBLISHER_ID=... \
CWS_SERVICE_ACCOUNT_KEY="$(cat path/to/key.json)" \
npx tsx scripts/publish-chrome.ts dist/zips/thingport-grab-chrome.zip
```
### Publishing to Edge automatically
The store release's `publish-edge` job publishes each new version to Microsoft Edge Add-ons,
@@ -277,6 +295,32 @@ npx web-ext sign --source-dir dist/firefox --channel unlisted \
--api-key "$AMO_JWT_ISSUER" --api-secret "$AMO_JWT_SECRET"
```
### One-time setup: Chrome Web Store publishing credentials
The job signs in as a Google Cloud service account that's linked to the store's publisher account:
1. In the [Google Cloud Console](https://console.cloud.google.com/), pick (or create) a project and
enable the **Chrome Web Store API** (search for it in the top bar).
2. **IAM & Admin > Service Accounts > Create service account.** It needs no roles or permissions.
3. Open the service account, go to **Keys > Add key > Create new key > JSON**. The browser
downloads the key file; Google keeps no copy, so if it's lost, create a new key and delete the
old one on the same page.
4. In the [Chrome Web Store Developer Dashboard](https://chrome.google.com/webstore/devconsole),
open **Account** and add the service account's email (`...@<project>.iam.gserviceaccount.com`,
also the key file's `client_email`). A publisher can only have one service account.
5. Add the key file and the publisher ID (shown in the dashboard's publisher settings) as repo
secrets, then delete the local key file:
```bash
gh secret set CWS_SERVICE_ACCOUNT_KEY < path/to/key.json
gh secret set CWS_PUBLISHER_ID # paste the publisher ID when prompted
```
6. `CHROME_EXTENSION_ID` is already a repo **variable** (see [Store listings](#store-listings)).
Service account keys don't expire, but an organization policy can block creating them
(`iam.disableServiceAccountKeyCreation`) -- a personal Google account isn't affected.
### One-time setup: Edge publishing credentials
The job skips itself (with a notice in the run) until these exist:
+25 -15
View File
@@ -10,9 +10,9 @@ It's useful without Thingport too: on MakerWorld model pages it adds a **Downloa
button that turns the Bambu Studio project into a 3MF that PrusaSlicer, Cura and other slicers open
with its colors and print settings intact. See [Download normalized](#download-normalized-no-thingport-needed).
**Get it from [Firefox Add-ons](https://addons.mozilla.org/firefox/addon/thingport-grab/) or
**Get it from the [Chrome Web Store](https://chromewebstore.google.com/detail/nmblahmglpbplmfcggghdgohohlaeiee),
[Firefox Add-ons](https://addons.mozilla.org/firefox/addon/thingport-grab/) or
[Microsoft Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/kahfidpmojfocohinlmglnfoaimocbol).**
The Chrome build is on the [releases page](#install) until its store listing is live.
It talks directly to your Thingport instance's API from the extension's background script -- no
separate server, no data sent anywhere else -- and converts files in your browser. See the [privacy policy](PRIVACY.md) for exactly what
@@ -45,6 +45,29 @@ it stores and sends.
## Install
### Chrome / other Chromium browsers
Install **[Thingport Grab from the Chrome Web Store](https://chromewebstore.google.com/detail/nmblahmglpbplmfcggghdgohohlaeiee)**
-- click **Add to Chrome**, then click the new Thingport icon in your toolbar (it may be under the
puzzle-piece Extensions button) and enter your instance's URL and your Thingport login. Chrome
keeps it up to date from then on. Other Chromium browsers that install from the Chrome Web Store
(Brave, Vivaldi, Opera with its Chrome extensions add-on) work the same way.
<details>
<summary>Alternative: install the Chrome build by hand</summary>
Only needed if you can't use the store, e.g. to try a build before it's been published there:
1. Download `thingport-grab-chrome.zip` from the
[`extension-latest` release](https://github.com/TautvydasDerzinskas/Thingport/releases/tag/extension-latest)
and unzip it somewhere permanent (the browser loads the extension from that folder every time it
starts, so don't delete it).
2. Open `chrome://extensions` and turn on **Developer mode** (top right).
3. Click **Load unpacked** and select the unzipped folder.
A hand-installed copy doesn't update itself -- repeat these steps for a newer version.
</details>
### Firefox
Install **[Thingport Grab from Firefox Add-ons](https://addons.mozilla.org/firefox/addon/thingport-grab/)**
@@ -90,19 +113,6 @@ Only needed if you can't use the store, e.g. to try a build before it's been pub
A hand-installed copy doesn't update itself -- repeat these steps for a newer version.
</details>
### Chrome / other Chromium browsers
As a developer-mode "unpacked" extension, until the Chrome Web Store listing is live:
1. Download `thingport-grab-chrome.zip` from the in-app Download page (or the
[`extension-latest` release](https://github.com/TautvydasDerzinskas/Thingport/releases/tag/extension-latest))
and unzip it somewhere permanent (don't delete the folder afterwards -- the browser loads the
extension from it every time it starts).
2. Open `chrome://extensions` and turn on **Developer mode** (top right).
3. Click **Load unpacked** and select the unzipped folder.
4. Click the new Thingport icon in your toolbar, enter your instance's URL and your Thingport
login, and save.
## Download normalized (no Thingport needed)
MakerWorld's files are Bambu Studio projects, which PrusaSlicer, Cura, Anycubic Slicer Next,
+125
View File
@@ -0,0 +1,125 @@
// Publishes a built Chrome zip to the existing Chrome Web Store listing, through the Chrome Web
// Store API v2 (https://developer.chrome.com/docs/webstore/using-api): sign in as the publisher's
// service account, upload the package, wait for it to be processed, and submit it for review.
// Google's review then takes anywhere from hours to days -- this only gets the update into the queue.
//
// CHROME_EXTENSION_ID=... CWS_PUBLISHER_ID=... CWS_SERVICE_ACCOUNT_KEY="$(cat key.json)" \
// tsx scripts/publish-chrome.ts dist/zips/thingport-grab-chrome.zip
//
// Run by .github/workflows/extension-store-release.yml (see extension/CONTRIBUTING.md for setting
// up the credentials). CWS_API_BASE / CWS_TOKEN_URL override the endpoints, for testing against a mock.
import { createSign } from "node:crypto";
import { readFile } from "node:fs/promises";
const API_BASE = process.env.CWS_API_BASE || "https://chromewebstore.googleapis.com";
const TOKEN_URL = process.env.CWS_TOKEN_URL || "https://oauth2.googleapis.com/token";
const SCOPE = "https://www.googleapis.com/auth/chromewebstore";
const POLL_INTERVAL_MS = 5000;
const POLL_TIMEOUT_MS = 10 * 60 * 1000;
type UploadState = "SUCCEEDED" | "IN_PROGRESS" | "FAILED" | "NOT_FOUND" | "UPLOAD_STATE_UNSPECIFIED";
function requireEnv(name: string): string {
const value = process.env[name]?.trim();
if (!value) throw new Error(`${name} isn't set`);
return value;
}
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const base64url = (data: string | Buffer) => Buffer.from(data).toString("base64url");
/** Exchanges a JWT signed with the service account's private key for an access token
* (https://developers.google.com/identity/protocols/oauth2/service-account#httprest). */
async function accessToken(keyJson: string): Promise<string> {
let key: { client_email?: string; private_key?: string };
try {
key = JSON.parse(keyJson);
} catch {
throw new Error(
"CWS_SERVICE_ACCOUNT_KEY isn't valid JSON -- paste the whole key file the Cloud Console downloaded",
);
}
if (!key.client_email || !key.private_key)
throw new Error("CWS_SERVICE_ACCOUNT_KEY has no client_email/private_key -- is it a service account key file?");
const now = Math.floor(Date.now() / 1000);
const header = base64url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
const claims = base64url(
JSON.stringify({ iss: key.client_email, scope: SCOPE, aud: TOKEN_URL, iat: now, exp: now + 3600 }),
);
const signature = createSign("RSA-SHA256").update(`${header}.${claims}`).sign(key.private_key);
const res = await fetch(TOKEN_URL, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
assertion: `${header}.${claims}.${base64url(signature)}`,
}),
});
if (!res.ok)
throw new Error(
`Signing in as ${key.client_email} failed: HTTP ${res.status} ${await res.text().catch(() => "")}\n The key may have been deleted in the Cloud Console -- create a new one.`,
);
return ((await res.json()) as { access_token: string }).access_token;
}
async function call<T>(url: string, init: RequestInit, what: string): Promise<T> {
const res = await fetch(url, init);
if (!res.ok) {
const body = await res.text().catch(() => "");
const hint =
res.status === 401 || res.status === 403
? "\n Check that the service account is added under Account in the Chrome Web Store dashboard, the Chrome Web Store API is enabled in its Cloud project, and CWS_PUBLISHER_ID / CHROME_EXTENSION_ID are right."
: "";
throw new Error(`${what} failed: HTTP ${res.status} ${body}${hint}`);
}
return (await res.json()) as T;
}
async function main(): Promise<void> {
const zipPath = process.argv[2];
if (!zipPath) throw new Error("Usage: tsx scripts/publish-chrome.ts <package.zip>");
const itemId = requireEnv("CHROME_EXTENSION_ID");
const item = `publishers/${requireEnv("CWS_PUBLISHER_ID")}/items/${itemId}`;
const auth = { Authorization: `Bearer ${await accessToken(requireEnv("CWS_SERVICE_ACCOUNT_KEY"))}` };
const zip = await readFile(zipPath);
console.log(`Uploading ${zipPath} (${(zip.length / 1024).toFixed(1)} KB) to Chrome Web Store item ${itemId}…`);
const upload = await call<{ uploadState?: UploadState; crxVersion?: string }>(
`${API_BASE}/upload/v2/${item}:upload`,
{ method: "POST", headers: { ...auth, "Content-Type": "application/zip" }, body: zip },
"Upload",
);
let state = upload.uploadState;
const deadline = Date.now() + POLL_TIMEOUT_MS;
while (state === "IN_PROGRESS") {
if (Date.now() > deadline) throw new Error(`Upload still in progress after ${POLL_TIMEOUT_MS / 60000} minutes`);
await sleep(POLL_INTERVAL_MS);
const status = await call<{ lastAsyncUploadState?: UploadState }>(
`${API_BASE}/v2/${item}:fetchStatus`,
{ headers: auth },
"Upload status check",
);
state = status.lastAsyncUploadState;
}
if (state !== "SUCCEEDED")
throw new Error(`Upload failed (${state ?? "no upload state"}): ${JSON.stringify(upload)}`);
console.log(`Upload processed${upload.crxVersion ? `: version ${upload.crxVersion}` : ""}`);
const published = await call<{ state?: string; warningInfo?: unknown }>(
`${API_BASE}/v2/${item}:publish`,
{ method: "POST", headers: { ...auth, "Content-Type": "application/json" }, body: JSON.stringify({}) },
"Submission",
);
if (published.warningInfo) console.log(`Warnings: ${JSON.stringify(published.warningInfo)}`);
console.log(`Submitted for review: ${published.state ?? "ok"}`);
}
try {
await main();
} catch (err) {
console.error(err instanceof Error ? err.message : err);
process.exit(1);
}
+7 -18
View File
@@ -1,24 +1,13 @@
// Published to the fixed "extension-latest" tag, which the release workflow moves on every build,
// rather than the repo-wide "latest" release that Bridge uses.
const EXTENSION_RELEASE_BASE = "https://github.com/TautvydasDerzinskas/Thingport/releases/download/extension-latest";
// The self-updating store versions. The zips and Firefox .xpi on the extension-latest release are
// for installing by hand and are only linked from the docs.
export type ExtensionDownload = { browser: "chrome" | "edge" | "firefox"; label: string; storeUrl: string };
export type ExtensionDownload =
| { kind: "file"; browser: "chrome"; label: string; asset: string }
| { kind: "store"; browser: "edge" | "firefox"; label: string; storeUrl: string };
export type ExtensionFileBrowser = Extract<ExtensionDownload, { kind: "file" }>["browser"];
// The Edge zip and Firefox .xpi are still on every release, but the self-updating store versions
// are the ones to recommend.
export const CHROME_WEB_STORE_URL = "https://chromewebstore.google.com/detail/nmblahmglpbplmfcggghdgohohlaeiee";
export const EDGE_ADDONS_URL = "https://microsoftedge.microsoft.com/addons/detail/kahfidpmojfocohinlmglnfoaimocbol";
export const FIREFOX_ADDONS_URL = "https://addons.mozilla.org/firefox/addon/thingport-grab/";
export const EXTENSION_DOWNLOADS: ExtensionDownload[] = [
{ kind: "store", browser: "firefox", label: "Firefox", storeUrl: FIREFOX_ADDONS_URL },
{ kind: "store", browser: "edge", label: "Edge", storeUrl: EDGE_ADDONS_URL },
{ kind: "file", browser: "chrome", label: "Chrome", asset: "thingport-grab-chrome.zip" },
{ browser: "chrome", label: "Chrome", storeUrl: CHROME_WEB_STORE_URL },
{ browser: "firefox", label: "Firefox", storeUrl: FIREFOX_ADDONS_URL },
{ browser: "edge", label: "Edge", storeUrl: EDGE_ADDONS_URL },
];
export function extensionDownloadUrl(asset: string): string {
return `${EXTENSION_RELEASE_BASE}/${asset}`;
}
+1 -10
View File
@@ -564,18 +564,9 @@
"intro": "A browser extension that imports MakerWorld, Thingiverse, and Printables models straight from their own pages.",
"name": "Thingport Grab",
"store": {
"chrome": "Get from Chrome Web Store",
"edge": "Get from Edge Add-ons",
"firefox": "Get from Firefox Add-ons"
},
"modal": {
"chrome": {
"heading": "Install Thingport Grab",
"step1": "Unzip the downloaded file somewhere permanent -- Chrome loads the extension from this folder every time it starts.",
"step2": "Open chrome://extensions in a new tab.",
"step3": "Turn on Developer mode (top right).",
"step4": "Click \"Load unpacked\" and select the unzipped folder.",
"step5": "Click the new Thingport icon in your toolbar and enter your instance URL and login."
}
}
},
"bridge": {
+1 -10
View File
@@ -566,18 +566,9 @@
"intro": "Naršyklės plėtinys, importuojantis MakerWorld, Thingiverse ir Printables modelius tiesiai iš jų puslapių.",
"name": "Thingport Grab",
"store": {
"chrome": "Gauti iš Chrome Web Store",
"edge": "Gauti iš Edge Add-ons",
"firefox": "Gauti iš Firefox Add-ons"
},
"modal": {
"chrome": {
"heading": "Įdiegti Thingport Grab",
"step1": "Išarchyvuokite atsisiųstą failą kur nors nuolatinėje vietoje -- Chrome įkelia plėtinį iš šio aplanko kaskart paleidus naršyklę.",
"step2": "Naujame skirtuke atidarykite chrome://extensions.",
"step3": "Įjunkite Developer mode (viršuje dešinėje).",
"step4": "Spustelėkite „Load unpacked“ ir pasirinkite išarchyvuotą aplanką.",
"step5": "Spustelėkite naują Thingport piktogramą įrankių juostoje ir įveskite savo aplinkos URL bei prisijungimo duomenis."
}
}
},
"bridge": {
+12 -63
View File
@@ -18,12 +18,7 @@ import TerminalIcon from "@mui/icons-material/Terminal";
import CableIcon from "@mui/icons-material/Cable";
import { usePageHeader } from "../../components/Layout/PageHeaderContext";
import { BRIDGE_DOWNLOADS, bridgeDownloadUrl, type BridgeDownload } from "../../constants/bridge";
import {
EXTENSION_DOWNLOADS,
extensionDownloadUrl,
type ExtensionDownload,
type ExtensionFileBrowser,
} from "../../constants/extension";
import { EXTENSION_DOWNLOADS, type ExtensionDownload } from "../../constants/extension";
import extensionIcon from "../../assets/logos/thingport-icon-color.svg";
// Official browser logos (github.com/alrra/browser-logos), shown only to mark which browser each
// extension download is for.
@@ -101,17 +96,6 @@ export default function DownloadPage() {
usePageHeader({ title: t("sidebar.downloads") });
const [installOs, setInstallOs] = useState<BridgeDownload["os"] | null>(null);
const [extensionInstallBrowser, setExtensionInstallBrowser] = useState<ExtensionFileBrowser | null>(null);
const extensionInstallSteps: Record<ExtensionFileBrowser, InstallStep[]> = {
chrome: [
{ text: t("download.extension.modal.chrome.step1") },
{ text: t("download.extension.modal.chrome.step2") },
{ text: t("download.extension.modal.chrome.step3") },
{ text: t("download.extension.modal.chrome.step4") },
{ text: t("download.extension.modal.chrome.step5") },
],
};
const installSteps: Record<BridgeDownload["os"], InstallStep[]> = {
windows: [{ text: t("download.modal.windows.step1") }],
@@ -168,30 +152,17 @@ export default function DownloadPage() {
<Typography variant="subtitle2" fontWeight={600}>
{t("download.extension.name")} ({item.label})
</Typography>
{item.kind === "store" ? (
<Button
component="a"
href={item.storeUrl}
target="_blank"
rel="noopener noreferrer"
variant="outlined"
size="small"
startIcon={<OpenInNewIcon fontSize="small" />}
>
{t(`download.extension.store.${item.browser}`)}
</Button>
) : (
<Button
component="a"
href={extensionDownloadUrl(item.asset)}
variant="outlined"
size="small"
startIcon={<DownloadIcon fontSize="small" />}
onClick={() => setExtensionInstallBrowser(item.browser)}
>
{t("common:download")}
</Button>
)}
<Button
component="a"
href={item.storeUrl}
target="_blank"
rel="noopener noreferrer"
variant="outlined"
size="small"
startIcon={<OpenInNewIcon fontSize="small" />}
>
{t(`download.extension.store.${item.browser}`)}
</Button>
</Stack>
</Paper>
))}
@@ -260,28 +231,6 @@ export default function DownloadPage() {
</>
)}
</Dialog>
<Dialog
open={extensionInstallBrowser !== null}
onClose={() => setExtensionInstallBrowser(null)}
maxWidth="xs"
fullWidth
>
{extensionInstallBrowser && (
<>
<DialogTitle>{t(`download.extension.modal.${extensionInstallBrowser}.heading`)}</DialogTitle>
<DialogContent>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2.5 }}>
{t("download.modal.startedNote")}
</Typography>
<InstallSteps steps={extensionInstallSteps[extensionInstallBrowser]} />
</DialogContent>
<DialogActions>
<Button onClick={() => setExtensionInstallBrowser(null)}>{t("common:close")}</Button>
</DialogActions>
</>
)}
</Dialog>
</Stack>
);
}