mirror of
https://github.com/TautvydasDerzinskas/Thingport.git
synced 2026-10-21 06:09:56 -05:00
feat: FireFox browser extension build
This commit is contained in:
1 parent
063beb317e
commit
523916d2b1
7 files changed
+248
-67
No files matched your search
@@ -7,7 +7,7 @@ name: Extension release
|
||||
# ran most recently, breaking the OTHER track's /releases/latest/download/<asset> links. Instead
|
||||
# this always publishes to the same fixed tag, "extension-latest" -- deleting and recreating it
|
||||
# each run -- and the Downloads page links to the tag-scoped path
|
||||
# (.../releases/download/extension-latest/thingport-grab.zip), never the /latest/ alias.
|
||||
# (.../releases/download/extension-latest/<asset>), never the /latest/ alias.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
@@ -26,7 +26,7 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-chrome:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -41,9 +41,56 @@ jobs:
|
||||
path: dist/thingport-grab.zip
|
||||
if-no-files-found: error
|
||||
|
||||
build-firefox:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Lint (validates the manifest for both PRs and pushes, no AMO credentials needed)
|
||||
run: npx --yes web-ext@8 lint --source-dir extension
|
||||
# Signing only happens on a push to main (it needs the AMO_JWT_* secrets, which forked PRs
|
||||
# don't get, and there's nothing useful to attach to a PR run anyway).
|
||||
#
|
||||
# AMO rejects re-uploading a version number it has already seen for this add-on ID, even on
|
||||
# the unlisted channel -- and extension/manifest.json's own version isn't bumped on every
|
||||
# commit. So this signs a copy of the manifest with the run number appended as an extra
|
||||
# version segment (e.g. 1.0.1.456), guaranteeing a unique version on every run without
|
||||
# touching the checked-in version Chrome's build (or the repo) actually uses.
|
||||
- name: Patch a build-local version for signing
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
cp -r extension firefox-src
|
||||
base_version=$(jq -r .version firefox-src/manifest.json)
|
||||
jq --arg v "${base_version}.${{ github.run_number }}" '.version = $v' \
|
||||
extension/manifest.json > firefox-src/manifest.json.tmp
|
||||
mv firefox-src/manifest.json.tmp firefox-src/manifest.json
|
||||
- name: Sign (unlisted -- not published to addons.mozilla.org's public listing)
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
env:
|
||||
WEB_EXT_API_KEY: ${{ secrets.AMO_JWT_ISSUER }}
|
||||
WEB_EXT_API_SECRET: ${{ secrets.AMO_JWT_SECRET }}
|
||||
run: |
|
||||
npx --yes web-ext@8 sign \
|
||||
--source-dir firefox-src \
|
||||
--artifacts-dir dist \
|
||||
--channel unlisted
|
||||
- name: Rename to a stable filename
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
xpi=$(ls dist/*.xpi | head -n1)
|
||||
mv "$xpi" dist/thingport-grab.xpi
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
with:
|
||||
name: thingport-grab-firefox
|
||||
path: dist/thingport-grab.xpi
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
needs: [build]
|
||||
needs: [build-chrome, build-firefox]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -53,6 +100,10 @@ jobs:
|
||||
with:
|
||||
name: thingport-grab
|
||||
path: dist
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: thingport-grab-firefox
|
||||
path: dist
|
||||
- name: Move the extension-latest tag to this commit
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -66,7 +117,15 @@ jobs:
|
||||
Automated build of the Thingport Grab browser extension (see `extension/README.md`)
|
||||
from ${{ github.sha }}.
|
||||
|
||||
Not published to the Chrome Web Store -- unzip `thingport-grab.zip`, then in Chrome:
|
||||
`chrome://extensions` → enable **Developer mode** → **Load unpacked** → select the
|
||||
unzipped folder.
|
||||
files: dist/thingport-grab.zip
|
||||
Not published to either browser's extension store -- see `extension/README.md` for
|
||||
install instructions:
|
||||
|
||||
- **Chrome / Edge / other Chromium browsers**: unzip `thingport-grab.zip`, then
|
||||
`chrome://extensions` → enable **Developer mode** → **Load unpacked** → select the
|
||||
unzipped folder.
|
||||
- **Firefox**: download `thingport-grab.xpi` and open it directly (or drag it onto a
|
||||
Firefox window). It's signed by Mozilla for self-distribution, so it installs and
|
||||
updates like any other extension -- no Developer mode needed.
|
||||
files: |
|
||||
dist/thingport-grab.zip
|
||||
dist/thingport-grab.xpi
|
||||
+69
-6
@@ -9,11 +9,15 @@ same way Thingport's own "+ Add > Import" does.
|
||||
It talks directly to your Thingport instance's API from the extension's background service worker
|
||||
-- no separate server, no data sent anywhere else.
|
||||
|
||||
## Install (no Chrome Web Store listing)
|
||||
## Install (no extension store listing)
|
||||
|
||||
This isn't published to the Chrome Web Store, so it installs the same way the
|
||||
This isn't published to either browser's extension store, so it installs the same way the
|
||||
[Thingport releases](https://github.com/TautvydasDerzinskas/Thingport/releases) page's other
|
||||
downloads do -- as a developer-mode "unpacked" extension:
|
||||
downloads do.
|
||||
|
||||
### Chrome / Edge / other Chromium browsers
|
||||
|
||||
As a developer-mode "unpacked" extension:
|
||||
|
||||
1. Download `thingport-grab.zip` from the in-app Download page (or the
|
||||
[`extension-latest` release](https://github.com/TautvydasDerzinskas/Thingport/releases/tag/extension-latest))
|
||||
@@ -24,6 +28,23 @@ downloads do -- as a developer-mode "unpacked" extension:
|
||||
4. Click the new Thingport icon in your toolbar, enter your instance's URL and your Thingport
|
||||
login, and save.
|
||||
|
||||
### Firefox
|
||||
|
||||
Firefox refuses to install *any* unsigned extension outside of Developer Edition/Nightly, even for
|
||||
local/unpacked use -- so unlike Chrome, this needs an actual Mozilla-signed build, not just a zip.
|
||||
CI signs one on every push to `main` (see [Build / package](#build--package) below):
|
||||
|
||||
1. Download `thingport-grab.xpi` from the in-app Download page (or the
|
||||
[`extension-latest` release](https://github.com/TautvydasDerzinskas/Thingport/releases/tag/extension-latest)).
|
||||
2. Open it directly (double-click, or `File > Open File` in Firefox) -- or drag it onto a Firefox
|
||||
window -- and confirm the install prompt.
|
||||
3. Click the new Thingport icon in your toolbar, enter your instance's URL and your Thingport
|
||||
login, and save.
|
||||
|
||||
It's signed for **self-distribution** (the "unlisted" channel), not listed on
|
||||
addons.mozilla.org -- signing is still required for Firefox to allow the install at all, and it's
|
||||
what lets Firefox treat later versions as updates to the same install instead of a fresh add-on.
|
||||
|
||||
## Setup
|
||||
|
||||
The popup asks for your instance URL (e.g. `https://thingport.example.com`) and your Thingport
|
||||
@@ -67,15 +88,57 @@ just stops showing progress, it doesn't cancel anything.
|
||||
|
||||
## Build / package
|
||||
|
||||
There's no build step -- `extension/` is loaded directly by Chrome. To produce the distributable
|
||||
zip by hand (CI does this on every push to `main` that touches this folder -- see
|
||||
`.github/workflows/extension-release.yml`):
|
||||
There's no build step -- `extension/` is loaded directly by Chrome and Firefox alike. CI packages
|
||||
both browsers' distributables on every push to `main` that touches this folder (see
|
||||
`.github/workflows/extension-release.yml`) and attaches them to the `extension-latest` release.
|
||||
|
||||
To produce the Chrome zip by hand:
|
||||
|
||||
```bash
|
||||
cd extension
|
||||
zip -r ../thingport-grab.zip . -x '*.DS_Store'
|
||||
```
|
||||
|
||||
To produce a signed Firefox build by hand, you need a Mozilla Add-on Developer account's API
|
||||
credentials (see below):
|
||||
|
||||
```bash
|
||||
npx web-ext sign --source-dir extension --channel unlisted \
|
||||
--api-key "$AMO_JWT_ISSUER" --api-secret "$AMO_JWT_SECRET"
|
||||
```
|
||||
|
||||
Signing rejects re-uploading a version number it's already seen for this add-on ID (even on the
|
||||
unlisted channel), so CI signs a copy of the manifest with the GitHub Actions run number appended
|
||||
to the version (e.g. `1.0.1.456`) rather than requiring a manifest version bump on every commit --
|
||||
see the workflow for details. Doing this by hand, bump `version` in `manifest.json` first instead.
|
||||
|
||||
### One-time setup: AMO signing credentials
|
||||
|
||||
Firefox requires every extension -- even self-distributed, unlisted ones -- to be signed by
|
||||
Mozilla before it will install. The workflow needs two repo secrets to do this automatically:
|
||||
|
||||
1. Create a free account at [addons.mozilla.org](https://addons.mozilla.org) if you don't have one.
|
||||
2. Go to [Manage API Keys](https://addons.mozilla.org/en-US/developers/addon/api/key/) and generate
|
||||
a new API key/secret pair.
|
||||
3. In the GitHub repo, add them as **Settings > Secrets and variables > Actions** secrets named
|
||||
`AMO_JWT_ISSUER` (the API key) and `AMO_JWT_SECRET` (the API secret).
|
||||
|
||||
No manual submission through the AMO web UI is needed first -- `web-ext sign --channel unlisted`
|
||||
creates the add-on listing (hidden, unlisted) on its first run.
|
||||
|
||||
The extension's Firefox identity (`browser_specific_settings.gecko.id` in `manifest.json`, currently
|
||||
`grab@thingport.app`) is what ties every signed version together as updates to the same add-on --
|
||||
changing it later creates an unrelated add-on from Mozilla's point of view, so avoid changing it
|
||||
once builds have been signed and distributed.
|
||||
|
||||
`browser_specific_settings.gecko.data_collection_permissions` is declared as `["none"]` -- Mozilla
|
||||
requires every add-on to disclose this (as of policy effective 2025-11-03) and rejects signing
|
||||
without it. This only covers data sent *off-device to the extension's developer or a third party
|
||||
it controls* -- the credentials/cookies this extension sends to your own self-hosted Thingport
|
||||
instance don't count, since that's a destination you configure and control, not the developer. If
|
||||
that ever changes (e.g. adding telemetry to a Thingport-operated service), update this declaration
|
||||
to match.
|
||||
|
||||
## Regenerating the icons
|
||||
|
||||
The toolbar icon PNGs (`icons/thingport-icon-{color,dark}-{16,32,48,128}.png`) and the inline
|
||||
|
||||
+11
-1
@@ -19,7 +19,17 @@
|
||||
}
|
||||
},
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
"service_worker": "background.js",
|
||||
"scripts": ["background.js"]
|
||||
},
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "grab@thingport.app",
|
||||
"strict_min_version": "109.0",
|
||||
"data_collection_permissions": {
|
||||
"required": ["none"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"permissions": ["storage", "tabs", "cookies", "downloads"],
|
||||
"optional_host_permissions": ["*://*/*"],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Thingport Grab is a Chrome extension (source in /extension, built by
|
||||
// Thingport Grab is a browser extension (source in /extension, built by
|
||||
// .github/workflows/extension-release.yml) that imports MakerWorld/Thingiverse/Printables models
|
||||
// straight from their own pages -- see extension/README.md. Unlike Bridge's own release (which
|
||||
// GitHub's "latest release" points at), this one publishes to a fixed tag, "extension-latest",
|
||||
@@ -8,4 +8,15 @@
|
||||
// build on every release rather than ever making a new one.
|
||||
const EXTENSION_RELEASE_BASE = "https://github.com/TautvydasDerzinskas/Thingport/releases/download/extension-latest";
|
||||
|
||||
export const EXTENSION_DOWNLOAD_URL = `${EXTENSION_RELEASE_BASE}/thingport-grab.zip`;
|
||||
export type ExtensionDownload = { browser: "chrome" | "firefox"; label: string; asset: string };
|
||||
|
||||
// Firefox's asset is a Mozilla-signed .xpi rather than a zip -- see extension/README.md's
|
||||
// "Firefox" install section for why an unpacked zip won't do there.
|
||||
export const EXTENSION_DOWNLOADS: ExtensionDownload[] = [
|
||||
{ browser: "chrome", label: "Chrome", asset: "thingport-grab.zip" },
|
||||
{ browser: "firefox", label: "Firefox", asset: "thingport-grab.xpi" },
|
||||
];
|
||||
|
||||
export function extensionDownloadUrl(asset: string): string {
|
||||
return `${EXTENSION_RELEASE_BASE}/${asset}`;
|
||||
}
|
||||
@@ -408,15 +408,23 @@
|
||||
"intro": "Companion tools that work alongside your Thingport instance.",
|
||||
"extension": {
|
||||
"heading": "Thingport Grab",
|
||||
"intro": "A Chrome extension that imports MakerWorld, Thingiverse, and Printables models straight from their own pages.",
|
||||
"intro": "A browser extension that imports MakerWorld, Thingiverse, and Printables models straight from their own pages.",
|
||||
"name": "Thingport Grab",
|
||||
"modal": {
|
||||
"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."
|
||||
"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."
|
||||
},
|
||||
"firefox": {
|
||||
"heading": "Install Thingport Grab",
|
||||
"step1": "Open the downloaded .xpi file directly, or drag it onto a Firefox window.",
|
||||
"step2": "Click \"Add\" on Firefox's install confirmation prompt.",
|
||||
"step3": "Click the new Thingport icon in your toolbar and enter your instance URL and login."
|
||||
}
|
||||
}
|
||||
},
|
||||
"bridge": {
|
||||
|
||||
@@ -408,15 +408,23 @@
|
||||
"intro": "Papildomi įrankiai, veikiantys kartu su jūsų Thingport aplinka.",
|
||||
"extension": {
|
||||
"heading": "Thingport Grab",
|
||||
"intro": "Chrome plėtinys, importuojantis MakerWorld, Thingiverse ir Printables modelius tiesiai iš jų puslapių.",
|
||||
"intro": "Naršyklės plėtinys, importuojantis MakerWorld, Thingiverse ir Printables modelius tiesiai iš jų puslapių.",
|
||||
"name": "Thingport Grab",
|
||||
"modal": {
|
||||
"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."
|
||||
"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."
|
||||
},
|
||||
"firefox": {
|
||||
"heading": "Įdiegti Thingport Grab",
|
||||
"step1": "Atidarykite atsisiųstą .xpi failą tiesiogiai arba nutempkite jį į Firefox langą.",
|
||||
"step2": "Firefox diegimo lange spustelėkite „Add“.",
|
||||
"step3": "Spustelėkite naują Thingport piktogramą įrankių juostoje ir įveskite savo aplinkos URL bei prisijungimo duomenis."
|
||||
}
|
||||
}
|
||||
},
|
||||
"bridge": {
|
||||
|
||||
@@ -17,7 +17,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_DOWNLOAD_URL } from "../../constants/extension";
|
||||
import { EXTENSION_DOWNLOADS, extensionDownloadUrl, type ExtensionDownload } from "../../constants/extension";
|
||||
import extensionIcon from "../../assets/logos/thingport-icon-color.svg";
|
||||
|
||||
const OS_ICON = { windows: LaptopWindowsIcon, macos: AppleIcon, linux: TerminalIcon };
|
||||
@@ -81,7 +81,22 @@ export default function DownloadPage() {
|
||||
usePageHeader({ title: t("sidebar.downloads") });
|
||||
|
||||
const [installOs, setInstallOs] = useState<BridgeDownload["os"] | null>(null);
|
||||
const [extensionInstallOpen, setExtensionInstallOpen] = useState(false);
|
||||
const [extensionInstallBrowser, setExtensionInstallBrowser] = useState<ExtensionDownload["browser"] | null>(null);
|
||||
|
||||
const extensionInstallSteps: Record<ExtensionDownload["browser"], 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") },
|
||||
],
|
||||
firefox: [
|
||||
{ text: t("download.extension.modal.firefox.step1") },
|
||||
{ text: t("download.extension.modal.firefox.step2") },
|
||||
{ text: t("download.extension.modal.firefox.step3") },
|
||||
],
|
||||
};
|
||||
|
||||
const installSteps: Record<BridgeDownload["os"], InstallStep[]> = {
|
||||
windows: [{ text: t("download.modal.windows.step1") }],
|
||||
@@ -116,22 +131,28 @@ export default function DownloadPage() {
|
||||
<Typography variant="body2" color="text.secondary">{t("download.extension.intro")}</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Paper variant="outlined" sx={{ p: 2.5, maxWidth: 340 }}>
|
||||
<Stack spacing={1.5} alignItems="flex-start">
|
||||
<Box component="img" src={extensionIcon} alt="" sx={{ width: 40, height: 40 }} />
|
||||
<Typography variant="subtitle2" fontWeight={600}>{t("download.extension.name")}</Typography>
|
||||
<Button
|
||||
component="a"
|
||||
href={EXTENSION_DOWNLOAD_URL}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={<DownloadIcon fontSize="small" />}
|
||||
onClick={() => setExtensionInstallOpen(true)}
|
||||
>
|
||||
{t("common:download")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
<Stack direction={{ xs: "column", sm: "row" }} spacing={2}>
|
||||
{EXTENSION_DOWNLOADS.map(({ browser, label, asset }) => (
|
||||
<Paper key={browser} variant="outlined" sx={{ p: 2.5, flex: 1, maxWidth: 340 }}>
|
||||
<Stack spacing={1.5} alignItems="flex-start">
|
||||
<Box component="img" src={extensionIcon} alt="" sx={{ width: 40, height: 40 }} />
|
||||
<Typography variant="subtitle2" fontWeight={600}>
|
||||
{t("download.extension.name")} ({label})
|
||||
</Typography>
|
||||
<Button
|
||||
component="a"
|
||||
href={extensionDownloadUrl(asset)}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={<DownloadIcon fontSize="small" />}
|
||||
onClick={() => setExtensionInstallBrowser(browser)}
|
||||
>
|
||||
{t("common:download")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Divider />
|
||||
@@ -191,25 +212,26 @@ export default function DownloadPage() {
|
||||
)}
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={extensionInstallOpen} onClose={() => setExtensionInstallOpen(false)} maxWidth="xs" fullWidth>
|
||||
<DialogTitle>{t("download.extension.modal.heading")}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2.5 }}>
|
||||
{t("download.modal.startedNote")}
|
||||
</Typography>
|
||||
<InstallSteps
|
||||
steps={[
|
||||
{ text: t("download.extension.modal.step1") },
|
||||
{ text: t("download.extension.modal.step2") },
|
||||
{ text: t("download.extension.modal.step3") },
|
||||
{ text: t("download.extension.modal.step4") },
|
||||
{ text: t("download.extension.modal.step5") },
|
||||
]}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setExtensionInstallOpen(false)}>{t("common:close")}</Button>
|
||||
</DialogActions>
|
||||
<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>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user