feat: rebrand from PrintStash to Thingport
No files matched your search
@@ -2,13 +2,13 @@ PUID=1000
|
||||
PGID=1000
|
||||
|
||||
# Used by docker-compose.deploy.yml (pre-built images from GHCR). Defaults to :latest; pin a
|
||||
# specific build if you want, e.g. ghcr.io/tautvydasderzinskas/printstash-backend:sha-a8fef7d
|
||||
BACKEND_IMAGE=ghcr.io/tautvydasderzinskas/printstash-backend:latest
|
||||
FRONTEND_IMAGE=ghcr.io/tautvydasderzinskas/printstash-frontend:latest
|
||||
# specific build if you want, e.g. ghcr.io/tautvydasderzinskas/thingport-backend:sha-a8fef7d
|
||||
BACKEND_IMAGE=ghcr.io/tautvydasderzinskas/thingport-backend:latest
|
||||
FRONTEND_IMAGE=ghcr.io/tautvydasderzinskas/thingport-frontend:latest
|
||||
|
||||
POSTGRES_USER=printstash
|
||||
POSTGRES_PASSWORD=printstash
|
||||
POSTGRES_DB=printstash
|
||||
POSTGRES_USER=thingport
|
||||
POSTGRES_PASSWORD=thingport
|
||||
POSTGRES_DB=thingport
|
||||
# The only port you publish -- nginx (the frontend container) serves the UI and reverse-proxies
|
||||
# /api/* to the backend container internally, so the backend itself is never exposed to the host.
|
||||
WEB_PORT=80
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
uses: ./.github/workflows/build-image.yml
|
||||
with:
|
||||
project_dir: backend
|
||||
image_name: ghcr.io/${{ github.repository_owner }}/printstash-backend
|
||||
image_name: ghcr.io/${{ github.repository_owner }}/thingport-backend
|
||||
context: ./backend
|
||||
dockerfile: backend/Dockerfile
|
||||
cache_scope: backend
|
||||
|
||||
@@ -38,11 +38,11 @@ jobs:
|
||||
CGO_ENABLED: 0
|
||||
GOOS: windows
|
||||
GOARCH: amd64
|
||||
run: go build -o dist/print-stash-bridge-windows-amd64.exe ./cmd/print-stash-bridge
|
||||
run: go build -o dist/thingport-bridge-windows-amd64.exe ./cmd/thingport-bridge
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bridge-windows-amd64
|
||||
path: bridge/dist/print-stash-bridge-windows-amd64.exe
|
||||
path: bridge/dist/thingport-bridge-windows-amd64.exe
|
||||
if-no-files-found: error
|
||||
|
||||
build-linux:
|
||||
@@ -61,15 +61,15 @@ jobs:
|
||||
CGO_ENABLED: 0
|
||||
GOOS: linux
|
||||
GOARCH: amd64
|
||||
run: go build -o dist/print-stash-bridge-linux-amd64 ./cmd/print-stash-bridge
|
||||
run: go build -o dist/thingport-bridge-linux-amd64 ./cmd/thingport-bridge
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bridge-linux-amd64
|
||||
path: bridge/dist/print-stash-bridge-linux-amd64
|
||||
path: bridge/dist/thingport-bridge-linux-amd64
|
||||
if-no-files-found: error
|
||||
|
||||
build-macos:
|
||||
# Custom-URL-scheme handling on macOS goes through Cocoa (see cmd/print-stash-bridge/
|
||||
# Custom-URL-scheme handling on macOS goes through Cocoa (see cmd/thingport-bridge/
|
||||
# urlhandler_darwin.m), so this binary needs cgo + the Xcode toolchain and can only be
|
||||
# built on an actual Mac runner -- unlike Windows/Linux, it can't be cross-compiled.
|
||||
runs-on: macos-latest
|
||||
@@ -86,18 +86,18 @@ jobs:
|
||||
env:
|
||||
CGO_ENABLED: 1
|
||||
run: |
|
||||
GOARCH=arm64 go build -o dist/print-stash-bridge-darwin-arm64 ./cmd/print-stash-bridge
|
||||
GOARCH=amd64 go build -o dist/print-stash-bridge-darwin-amd64 ./cmd/print-stash-bridge
|
||||
GOARCH=arm64 go build -o dist/thingport-bridge-darwin-arm64 ./cmd/thingport-bridge
|
||||
GOARCH=amd64 go build -o dist/thingport-bridge-darwin-amd64 ./cmd/thingport-bridge
|
||||
- name: Combine into a universal binary
|
||||
working-directory: bridge
|
||||
run: lipo -create -output dist/print-stash-bridge-macos dist/print-stash-bridge-darwin-arm64 dist/print-stash-bridge-darwin-amd64
|
||||
run: lipo -create -output dist/thingport-bridge-macos dist/thingport-bridge-darwin-arm64 dist/thingport-bridge-darwin-amd64
|
||||
- name: Re-sign universal binary
|
||||
working-directory: bridge
|
||||
run: codesign --force -s - dist/print-stash-bridge-macos
|
||||
run: codesign --force -s - dist/thingport-bridge-macos
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bridge-macos
|
||||
path: bridge/dist/print-stash-bridge-macos
|
||||
path: bridge/dist/thingport-bridge-macos
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
@@ -114,10 +114,10 @@ jobs:
|
||||
- name: Flatten downloaded artifacts
|
||||
run: |
|
||||
mkdir -p release
|
||||
mv dist/bridge-windows-amd64/print-stash-bridge-windows-amd64.exe release/
|
||||
mv dist/bridge-linux-amd64/print-stash-bridge-linux-amd64 release/
|
||||
mv dist/bridge-macos/print-stash-bridge-macos release/
|
||||
chmod +x release/print-stash-bridge-linux-amd64 release/print-stash-bridge-macos
|
||||
mv dist/bridge-windows-amd64/thingport-bridge-windows-amd64.exe release/
|
||||
mv dist/bridge-linux-amd64/thingport-bridge-linux-amd64 release/
|
||||
mv dist/bridge-macos/thingport-bridge-macos release/
|
||||
chmod +x release/thingport-bridge-linux-amd64 release/thingport-bridge-macos
|
||||
- name: Compute release tag
|
||||
id: tag
|
||||
run: echo "tag=bridge-$(date +%Y%m%d)-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
|
||||
@@ -125,16 +125,16 @@ jobs:
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ steps.tag.outputs.tag }}
|
||||
name: PrintStash Bridge ${{ steps.tag.outputs.tag }}
|
||||
name: Thingport Bridge ${{ steps.tag.outputs.tag }}
|
||||
body: |
|
||||
Automated build of the PrintStash slicer bridge (see `bridge/README.md`) from
|
||||
Automated build of the Thingport slicer bridge (see `bridge/README.md`) from
|
||||
${{ github.sha }}.
|
||||
|
||||
- **Windows**: `print-stash-bridge-windows-amd64.exe` — run it once (double-click is fine) to install.
|
||||
- **Linux**: `print-stash-bridge-linux-amd64` — `chmod +x`, then run it once to install.
|
||||
- **macOS**: `print-stash-bridge-macos` (universal) — `chmod +x`, remove the quarantine flag, then run with `--install`. See the README for the exact commands.
|
||||
- **Windows**: `thingport-bridge-windows-amd64.exe` — run it once (double-click is fine) to install.
|
||||
- **Linux**: `thingport-bridge-linux-amd64` — `chmod +x`, then run it once to install.
|
||||
- **macOS**: `thingport-bridge-macos` (universal) — `chmod +x`, remove the quarantine flag, then run with `--install`. See the README for the exact commands.
|
||||
make_latest: true
|
||||
files: |
|
||||
release/print-stash-bridge-windows-amd64.exe
|
||||
release/print-stash-bridge-linux-amd64
|
||||
release/print-stash-bridge-macos
|
||||
release/thingport-bridge-windows-amd64.exe
|
||||
release/thingport-bridge-linux-amd64
|
||||
release/thingport-bridge-macos
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
required: true
|
||||
type: string
|
||||
image_name:
|
||||
description: "Fully-qualified image name, e.g. ghcr.io/owner/printstash-backend"
|
||||
description: "Fully-qualified image name, e.g. ghcr.io/owner/thingport-backend"
|
||||
required: true
|
||||
type: string
|
||||
context:
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
uses: ./.github/workflows/build-image.yml
|
||||
with:
|
||||
project_dir: frontend
|
||||
image_name: ghcr.io/${{ github.repository_owner }}/printstash-frontend
|
||||
image_name: ghcr.io/${{ github.repository_owner }}/thingport-frontend
|
||||
context: ./frontend
|
||||
dockerfile: frontend/Dockerfile
|
||||
cache_scope: frontend
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
uses: ./.github/workflows/build-image.yml
|
||||
with:
|
||||
project_dir: backend
|
||||
image_name: ghcr.io/${{ github.repository_owner }}/printstash-backend
|
||||
image_name: ghcr.io/${{ github.repository_owner }}/thingport-backend
|
||||
context: ./backend
|
||||
dockerfile: backend/Dockerfile
|
||||
cache_scope: backend
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
uses: ./.github/workflows/build-image.yml
|
||||
with:
|
||||
project_dir: frontend
|
||||
image_name: ghcr.io/${{ github.repository_owner }}/printstash-frontend
|
||||
image_name: ghcr.io/${{ github.repository_owner }}/thingport-frontend
|
||||
context: ./frontend
|
||||
dockerfile: frontend/Dockerfile
|
||||
cache_scope: frontend
|
||||
|
||||
@@ -17,7 +17,7 @@ yarn-error.log*
|
||||
# Prisma
|
||||
backend/prisma/dev.db*
|
||||
|
||||
# PrintStash runtime content
|
||||
# Thingport runtime content
|
||||
data/
|
||||
imports/
|
||||
backend/storage/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
PUID=1000
|
||||
PGID=1000
|
||||
DATABASE_URL=postgresql://printstash:printstash@db:5432/printstash?schema=public
|
||||
DATABASE_URL=postgresql://thingport:thingport@db:5432/thingport?schema=public
|
||||
FILE_STORAGE=/app/storage
|
||||
API_PORT=8000
|
||||
PUBLIC_URL=
|
||||
@@ -18,4 +18,4 @@ SMTP_PORT=587
|
||||
SMTP_SECURE=false
|
||||
SMTP_USER=
|
||||
SMTP_PASS=
|
||||
SMTP_FROM=PrintStash <no-reply@localhost>
|
||||
SMTP_FROM=Thingport <no-reply@localhost>
|
||||
@@ -7,8 +7,8 @@ RUN npm ci --no-audit --no-fund && npx prisma generate
|
||||
|
||||
FROM dependencies AS test
|
||||
COPY . .
|
||||
ENV DATABASE_URL="postgresql://printstash:printstash@localhost:5432/printstash_test?schema=public" \
|
||||
FILE_STORAGE=/tmp/printstash-storage
|
||||
ENV DATABASE_URL="postgresql://thingport:thingport@localhost:5432/thingport_test?schema=public" \
|
||||
FILE_STORAGE=/tmp/thingport-storage
|
||||
CMD ["npm", "test"]
|
||||
|
||||
FROM dependencies AS build
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "printstash-backend",
|
||||
"name": "thingport-backend",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "printstash-backend",
|
||||
"name": "thingport-backend",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.20.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "printstash-backend",
|
||||
"name": "thingport-backend",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "commonjs",
|
||||
|
||||
@@ -80,7 +80,7 @@ export const IMPORT_MAKERWORLD_CALL_DELAY_MS = envInt("IMPORT_MAKERWORLD_CALL_DE
|
||||
// confirmed to share the same anti-abuse bucket as MakerWorld's design/download-resolution API
|
||||
// -- this is a cheap precaution, not a proven-necessary one.
|
||||
export const IMPORT_PREVIEW_IMAGE_DELAY_MS = envInt("IMPORT_PREVIEW_IMAGE_DELAY_MS", 250);
|
||||
export const IMPORT_USER_AGENT = "PrintStash/1.0";
|
||||
export const IMPORT_USER_AGENT = "Thingport/1.0";
|
||||
export const IMPORT_BROWSER_USER_AGENT =
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36";
|
||||
|
||||
|
||||
@@ -116,8 +116,8 @@ router.post(
|
||||
} catch (err) {
|
||||
if (err instanceof MakerworldCaptchaError) throw new HttpError(429, err.message);
|
||||
// 400, not 401: this is MakerWorld's own session rejecting our request, not the caller's
|
||||
// PrintStash session -- the frontend's generic API client treats any 401 as "your
|
||||
// PrintStash session expired" and force-logs-out, which would be exactly wrong here. See
|
||||
// Thingport session -- the frontend's generic API client treats any 401 as "your
|
||||
// Thingport session expired" and force-logs-out, which would be exactly wrong here. See
|
||||
// the same reasoning at importService.ts's tryMakerworldCloudApi/importThingiverseThing.
|
||||
if (err instanceof MakerworldAuthError) throw new HttpError(400, err.message);
|
||||
throw err;
|
||||
|
||||
@@ -299,7 +299,7 @@ router.post(
|
||||
prints = prints.filter((p) => p.tags.includes(tag));
|
||||
}
|
||||
|
||||
let downloadName = body.filename || "printstash.zip";
|
||||
let downloadName = body.filename || "thingport.zip";
|
||||
if (body.tag) {
|
||||
const safeTag = body.tag.replace(/ /g, "_").slice(0, 50) || "tag";
|
||||
downloadName = `${safeTag}.zip`;
|
||||
|
||||
@@ -15,5 +15,5 @@ prisma.importJob
|
||||
.catch((err) => console.error("Failed to recover stale import jobs on startup:", err));
|
||||
|
||||
app.listen(API_PORT, () => {
|
||||
console.log(`PrintStash API listening on port ${API_PORT}`);
|
||||
console.log(`Thingport API listening on port ${API_PORT}`);
|
||||
});
|
||||
@@ -109,7 +109,7 @@ async function fetchRecentlyAdded(userId: string, limit: number): Promise<Dashbo
|
||||
|
||||
// Ranked by how many models this user has from each author, not by anything the author
|
||||
// themselves did -- groupBy can't orderBy an aggregate reliably here, so the sort happens in JS
|
||||
// (the same pattern print-stash's admin/adminService.ts and youtube-mp3-vault's dashboard both
|
||||
// (the same pattern thingport's admin/adminService.ts and youtube-mp3-vault's dashboard both
|
||||
// use for "top X by count").
|
||||
async function fetchTopAuthors(userId: string, limit: number): Promise<DashboardAuthor[]> {
|
||||
const grouped = await prisma.print.groupBy({
|
||||
@@ -140,7 +140,7 @@ async function fetchTopAuthors(userId: string, limit: number): Promise<Dashboard
|
||||
}
|
||||
|
||||
// A fixed, small set in practice (today: "makerworld", "thingiverse", or null for anything
|
||||
// uploaded/zip-imported directly, bucketed here as "printstash") -- no "see more" needed on the
|
||||
// uploaded/zip-imported directly, bucketed here as "thingport") -- no "see more" needed on the
|
||||
// frontend for this one.
|
||||
async function fetchTopProviders(userId: string): Promise<DashboardProvider[]> {
|
||||
const grouped = await prisma.print.groupBy({
|
||||
@@ -150,7 +150,7 @@ async function fetchTopProviders(userId: string): Promise<DashboardProvider[]> {
|
||||
});
|
||||
const buckets = new Map<string, number>();
|
||||
for (const g of grouped) {
|
||||
const key = g.sourceProvider ?? "printstash";
|
||||
const key = g.sourceProvider ?? "thingport";
|
||||
buckets.set(key, (buckets.get(key) ?? 0) + g._count);
|
||||
}
|
||||
return [...buckets.entries()]
|
||||
|
||||
@@ -68,7 +68,7 @@ function friendlyConnectionError(err: unknown): string {
|
||||
if (/password authentication failed/i.test(message)) return "Authentication failed for that user/password.";
|
||||
if (/database .* does not exist/i.test(message)) return "That database does not exist.";
|
||||
if (/relation .?"?User"?.? does not exist/i.test(message)) {
|
||||
return "Connected, but that database has no PrintStash tables yet -- run migrations against it first.";
|
||||
return "Connected, but that database has no Thingport tables yet -- run migrations against it first.";
|
||||
}
|
||||
return `Could not connect: ${message}`;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export async function sendPrintsZip(res: Response, prints: PrintWithPlatesAndFol
|
||||
const entries = await buildZipEntries(prints);
|
||||
if (!entries.length) throw new HttpError(404, "No files available for download");
|
||||
|
||||
const tmpPath = path.join(os.tmpdir(), `printstash-zip-${crypto.randomBytes(8).toString("hex")}.zip`);
|
||||
const tmpPath = path.join(os.tmpdir(), `thingport-zip-${crypto.randomBytes(8).toString("hex")}.zip`);
|
||||
await writeZip(tmpPath, entries);
|
||||
res.download(tmpPath, downloadName, (err) => {
|
||||
fs.rm(tmpPath, { force: true }, () => undefined);
|
||||
|
||||
@@ -173,7 +173,7 @@ export async function runCollectionImportJob(jobId: string, userId: string, body
|
||||
* official API hasn't shown the same anti-abuse behavior MakerWorld's has), then files every
|
||||
* successful import into `collectionTitle` (created on first use, reused on every later import
|
||||
* that resolves to the same title -- e.g. every Likes import shares one "Thingiverse Likes"
|
||||
* collection; a named Thingiverse Collection gets/reuses a PrintStash Collection of that same
|
||||
* collection; a named Thingiverse Collection gets/reuses a Thingport Collection of that same
|
||||
* name). `resolveCollectionTitle` runs after the per-item loop (not before) so a Collection's
|
||||
* real name -- an extra API call -- is only fetched once real work has actually happened. */
|
||||
async function runThingiverseThingsImportJob(
|
||||
@@ -294,7 +294,7 @@ export async function runThingiverseLikesImportJob(jobId: string, userId: string
|
||||
|
||||
/** Runs a named Thingiverse Collection's batch import in the background -- see
|
||||
* routes/imports.ts's POST /import/thingiverse-collection. Unlike Likes, a Collection has a real
|
||||
* user-given name (fetchThingiverseCollectionTitle) -- successful imports land in a PrintStash
|
||||
* user-given name (fetchThingiverseCollectionTitle) -- successful imports land in a Thingport
|
||||
* Collection of that same name, created on first use and reused if the same Thingiverse
|
||||
* Collection is ever imported again. */
|
||||
export async function runThingiverseCollectionImportJob(
|
||||
@@ -315,7 +315,7 @@ export async function runThingiverseCollectionImportJob(
|
||||
/** Runs a named Printables Collection's batch import in the background -- see
|
||||
* routes/imports.ts's POST /import/printables-collection. Mirrors
|
||||
* runThingiverseThingsImportJob's shape (own resolver, same sequential pacing, files successful
|
||||
* imports into a PrintStash Collection named after the real Printables Collection name) but
|
||||
* imports into a Thingport Collection named after the real Printables Collection name) but
|
||||
* simpler: Printables needs no access token, so there's no equivalent "isn't configured for this
|
||||
* instance" failure mode to handle up front. */
|
||||
export async function runPrintablesCollectionImportJob(jobId: string, userId: string, body: PrintablesCollectionImportJobBody): Promise<void> {
|
||||
|
||||
@@ -176,8 +176,8 @@ async function tryMakerworldCloudApi(url: string, body: ImportRequestBody): Prom
|
||||
} catch (err) {
|
||||
if (err instanceof MakerworldCaptchaError) throw new HttpError(429, err.message);
|
||||
// 400, not 401: this is MakerWorld's own session rejecting our request, not the caller's
|
||||
// PrintStash session -- the frontend's generic API client treats any 401 as "your
|
||||
// PrintStash session expired" and force-logs-out, which would be exactly wrong here.
|
||||
// Thingport session -- the frontend's generic API client treats any 401 as "your
|
||||
// Thingport session expired" and force-logs-out, which would be exactly wrong here.
|
||||
if (err instanceof MakerworldAuthError) throw new HttpError(400, err.message);
|
||||
throw err;
|
||||
}
|
||||
@@ -323,7 +323,7 @@ export async function downloadImportToTemp(
|
||||
const filename = buildImportFilename(finalUrl, response.headers, body.filename ?? meta.filename);
|
||||
const mime = mimeFromContentType(response.headers.get("content-type"), filename);
|
||||
const suffix = path.extname(filename) || "";
|
||||
const tempPath = path.join(os.tmpdir(), `printstash-import-${crypto.randomBytes(8).toString("hex")}${suffix}`);
|
||||
const tempPath = path.join(os.tmpdir(), `thingport-import-${crypto.randomBytes(8).toString("hex")}${suffix}`);
|
||||
try {
|
||||
await streamToFileCapped(response, tempPath, IMPORT_MAX_BYTES);
|
||||
} catch (err) {
|
||||
@@ -525,7 +525,7 @@ async function downloadPlainFileToTemp(url: string, suggestedName: string): Prom
|
||||
const filename = sanitizeFilename(suggestedName);
|
||||
const tempFilePath = path.join(
|
||||
os.tmpdir(),
|
||||
`printstash-thingiverse-${crypto.randomBytes(8).toString("hex")}${path.extname(filename)}`,
|
||||
`thingport-thingiverse-${crypto.randomBytes(8).toString("hex")}${path.extname(filename)}`,
|
||||
);
|
||||
await streamToFileCapped(res, tempFilePath, IMPORT_MAX_BYTES);
|
||||
return { input: { filename, mime: guessMimeFromPath(filename), tempFilePath } };
|
||||
@@ -559,7 +559,7 @@ async function importThingiverseThing(
|
||||
} catch (err) {
|
||||
if (err instanceof ThingiverseRateLimitError) throw new HttpError(429, err.message);
|
||||
// 400, not 401: this is the server's configured Thingiverse Access Token being rejected, not
|
||||
// the caller's PrintStash session -- see the identical reasoning at tryMakerworldCloudApi
|
||||
// the caller's Thingport session -- see the identical reasoning at tryMakerworldCloudApi
|
||||
// above for why 401 specifically would mislead the frontend into logging the user out.
|
||||
if (err instanceof ThingiverseAuthError) throw new HttpError(400, err.message);
|
||||
throw err;
|
||||
|
||||
@@ -22,8 +22,8 @@ export async function sendVerificationEmail(to: string, displayName: string, tok
|
||||
await transporter.sendMail({
|
||||
from: smtp.from,
|
||||
to,
|
||||
subject: "Confirm your PrintStash account",
|
||||
text: `Hi ${displayName},\n\nConfirm your email address to finish creating your PrintStash account:\n${link}\n\nThis link expires in 24 hours.`,
|
||||
html: `<p>Hi ${displayName},</p><p>Confirm your email address to finish creating your PrintStash account:</p><p><a href="${link}">${link}</a></p><p>This link expires in 24 hours.</p>`,
|
||||
subject: "Confirm your Thingport account",
|
||||
text: `Hi ${displayName},\n\nConfirm your email address to finish creating your Thingport account:\n${link}\n\nThis link expires in 24 hours.`,
|
||||
html: `<p>Hi ${displayName},</p><p>Confirm your email address to finish creating your Thingport account:</p><p><a href="${link}">${link}</a></p><p>This link expires in 24 hours.</p>`,
|
||||
});
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import { readZipEntry, walkZipEntries } from "../utils/zipReader";
|
||||
// extruder-resolution priority, same plate-assignment fallbacks, same component p:path handling
|
||||
// -- but not its parsing mechanism. That file walks the DOM node-by-node; a Bambu-sliced 3MF for
|
||||
// a genuinely large model can have millions of <vertex>/<triangle> elements, and DOM attribute
|
||||
// access at that scale is what causes PrintStash's "3D Preview" to hang indefinitely (root-caused
|
||||
// access at that scale is what causes Thingport's "3D Preview" to hang indefinitely (root-caused
|
||||
// against a real 224MB/2.66M-triangle repro). This file never builds a DOM for the bulk mesh data
|
||||
// -- it scans the raw XML text with a handful of fixed-shape regexes straight into typed arrays,
|
||||
// which is the actual fix. It only runs once per plate (in the background, off the request path)
|
||||
@@ -510,7 +510,7 @@ async function buildGlbGroup(parsed: ParsedModel): Promise<import("three").Group
|
||||
const effectivePlateIndexes = hasPlateAssignments ? plateIndexes : [0];
|
||||
|
||||
const root = new THREE.Group();
|
||||
root.name = "printstash-preview-root";
|
||||
root.name = "thingport-preview-root";
|
||||
|
||||
const plates: PlateSummary[] = [];
|
||||
for (const plateIndex of effectivePlateIndexes) {
|
||||
@@ -574,7 +574,7 @@ async function buildGlbGroup(parsed: ParsedModel): Promise<import("three").Group
|
||||
}
|
||||
|
||||
root.userData = {
|
||||
printstashPreview: JSON.stringify({
|
||||
thingportPreview: JSON.stringify({
|
||||
plates,
|
||||
plateThumbnails: Object.fromEntries(parsed.plateThumbnails),
|
||||
filamentColors: parsed.filamentColors,
|
||||
|
||||
@@ -265,7 +265,7 @@ function titleFromSlug(slug: string): string {
|
||||
const COLLECTION_TITLE_QUERY = `query ($id: ID!) { collection(id: $id) { id name } }`;
|
||||
|
||||
/** Cheap, title-only fetch -- used by the job runner once the batch import is done (see
|
||||
* importJobRunner.ts's runPrintablesCollectionImportJob), so the PrintStash Collection it files
|
||||
* importJobRunner.ts's runPrintablesCollectionImportJob), so the Thingport Collection it files
|
||||
* results into doesn't require re-running the full listing. */
|
||||
export async function fetchPrintablesCollectionTitle(collectionId: string): Promise<string | null> {
|
||||
const data = (await fetchPrintablesGraphql(COLLECTION_TITLE_QUERY, { id: collectionId })) as
|
||||
|
||||
@@ -55,7 +55,7 @@ const THINGIVERSE_ACCESS_TOKEN_KEY = "thingiverse_access_token";
|
||||
|
||||
// Instance-wide, not per-user: it's a credential for api.thingiverse.com (the official
|
||||
// Developer API -- see thingiverseApi.ts), tied to whichever Thingiverse account registered
|
||||
// the app at thingiverse.com/apps/create, not to any one PrintStash user's own account. Every
|
||||
// the app at thingiverse.com/apps/create, not to any one Thingport user's own account. Every
|
||||
// user's Thingiverse imports share it, same as the storage template and preview mode above.
|
||||
export async function getThingiverseAccessToken(): Promise<string | null> {
|
||||
const row = await prisma.setting.findUnique({ where: { key: THINGIVERSE_ACCESS_TOKEN_KEY } });
|
||||
@@ -86,7 +86,7 @@ export type SmtpSettings = {
|
||||
};
|
||||
|
||||
const SMTP_SETTINGS_KEY = "smtp_settings";
|
||||
const DEFAULT_SMTP_FROM = "PrintStash <no-reply@localhost>";
|
||||
const DEFAULT_SMTP_FROM = "Thingport <no-reply@localhost>";
|
||||
|
||||
function envInt(name: string, fallback: number): number {
|
||||
const parsed = Number.parseInt(process.env[name] || "", 10);
|
||||
|
||||
@@ -299,7 +299,7 @@ export async function fetchThingiverseUserLikes(
|
||||
/** A user-curated, named "Collection" -- the site's other bookmark mechanism besides the
|
||||
* automatic per-account "Likes" list (see fetchThingiverseUserLikes above). Unlike Likes, a
|
||||
* Collection has a real user-given name (fetchThingiverseCollectionTitle below), which is what
|
||||
* lets the import land in a PrintStash Collection named after it instead of a generic bucket. */
|
||||
* lets the import land in a Thingport Collection named after it instead of a generic bucket. */
|
||||
export async function fetchThingiverseCollectionThings(
|
||||
collectionId: string,
|
||||
accessToken: string,
|
||||
|
||||
@@ -151,7 +151,7 @@ export async function extractZipEntriesToPrints(
|
||||
const buffer = await readZipEntry(zipPath, entryName, IMPORT_MAX_BYTES);
|
||||
if (!buffer) throw new Error("Extracted file exceeds size limit or could not be read");
|
||||
|
||||
tempPath = path.join(os.tmpdir(), `printstash-zip-${crypto.randomBytes(8).toString("hex")}`);
|
||||
tempPath = path.join(os.tmpdir(), `thingport-zip-${crypto.randomBytes(8).toString("hex")}`);
|
||||
await fs.writeFile(tempPath, buffer);
|
||||
|
||||
// A resolved page title (e.g. MakerWorld's design name) is the same for every entry in
|
||||
|
||||
@@ -8,7 +8,7 @@ const diskStorage = multer.diskStorage({
|
||||
destination: (_req, _file, cb) => cb(null, os.tmpdir()),
|
||||
filename: (_req, file, cb) => {
|
||||
const ext = path.extname(file.originalname || "");
|
||||
cb(null, `printstash-upload-${crypto.randomBytes(8).toString("hex")}${ext}`);
|
||||
cb(null, `thingport-upload-${crypto.randomBytes(8).toString("hex")}${ext}`);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ let memberToken: string;
|
||||
let memberUserId: string;
|
||||
|
||||
function tmpFile(name: string, contents: string): string {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "printstash-test-"));
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "thingport-test-"));
|
||||
const p = path.join(dir, name);
|
||||
fs.writeFileSync(p, contents);
|
||||
return p;
|
||||
|
||||
@@ -16,7 +16,7 @@ const createdFolderIds: string[] = [];
|
||||
function tmpFile(name: string, contents: string): string {
|
||||
// supertest's .attach() uses the path's basename as the uploaded filename, so each file
|
||||
// needs its own directory to keep that basename exactly as given (e.g. "solo.stl").
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "printstash-test-"));
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "thingport-test-"));
|
||||
const p = path.join(dir, name);
|
||||
fs.writeFileSync(p, contents);
|
||||
return p;
|
||||
|
||||
@@ -10,7 +10,7 @@ const app = createApp();
|
||||
let token: string;
|
||||
|
||||
function tmpFile(name: string, contents: string): string {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "printstash-test-"));
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "thingport-test-"));
|
||||
const p = path.join(dir, name);
|
||||
fs.writeFileSync(p, contents);
|
||||
return p;
|
||||
|
||||
@@ -367,7 +367,7 @@ describe("runThingiverseCollectionImportJob", () => {
|
||||
vi.mocked(fetchThingiverseCollectionTitle).mockReset();
|
||||
});
|
||||
|
||||
it("files successful imports into a PrintStash Collection named after the real Thingiverse Collection", async () => {
|
||||
it("files successful imports into a Thingport Collection named after the real Thingiverse Collection", async () => {
|
||||
await setThingiverseAccessToken("test-token");
|
||||
vi.mocked(fetchThingiverseCollectionTitle).mockResolvedValue("Things to Make");
|
||||
const mockedImport = vi.mocked(importPrintFromUrl);
|
||||
|
||||
@@ -14,7 +14,7 @@ let token: string;
|
||||
let userId: string;
|
||||
|
||||
function tmpFile(name: string, contents: string): string {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "printstash-test-"));
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "thingport-test-"));
|
||||
const p = path.join(dir, name);
|
||||
fs.writeFileSync(p, contents);
|
||||
return p;
|
||||
|
||||
@@ -74,7 +74,7 @@ const PROJECT_SETTINGS_JSON = JSON.stringify({
|
||||
});
|
||||
|
||||
async function buildFixture3mf(destPath: string): Promise<void> {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "printstash-3mf-fixture-"));
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "thingport-3mf-fixture-"));
|
||||
const modelPath = path.join(dir, "3dmodel.model");
|
||||
const settingsPath = path.join(dir, "model_settings.config");
|
||||
const projectPath = path.join(dir, "project_settings.config");
|
||||
@@ -135,7 +135,7 @@ const WRAPPER_MODEL_SETTINGS_XML = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
`;
|
||||
|
||||
async function buildWrapperFixture3mf(destPath: string): Promise<void> {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "printstash-3mf-wrapper-fixture-"));
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "thingport-3mf-wrapper-fixture-"));
|
||||
const modelPath = path.join(dir, "3dmodel.model");
|
||||
const settingsPath = path.join(dir, "model_settings.config");
|
||||
const projectPath = path.join(dir, "project_settings.config");
|
||||
@@ -158,7 +158,7 @@ describe("modelPreviewCache", () => {
|
||||
// only needs the model-previews cache directory, which the running app normally gets via
|
||||
// src/db.ts's startup side effect. Recreate that here so the test stays DB-independent.
|
||||
await fs.mkdir(path.dirname(modelPreviewGlbPath("x")), { recursive: true });
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "printstash-3mf-out-"));
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "thingport-3mf-out-"));
|
||||
fixturePath = path.join(dir, "fixture.3mf");
|
||||
await buildFixture3mf(fixturePath);
|
||||
});
|
||||
@@ -182,10 +182,10 @@ describe("modelPreviewCache", () => {
|
||||
|
||||
let root: any = null;
|
||||
gltf.scene.traverse((obj: any) => {
|
||||
if (obj.userData?.printstashPreview) root = obj;
|
||||
if (obj.userData?.thingportPreview) root = obj;
|
||||
});
|
||||
expect(root).toBeTruthy();
|
||||
const meta = JSON.parse(root.userData.printstashPreview);
|
||||
const meta = JSON.parse(root.userData.thingportPreview);
|
||||
expect(meta.plates).toEqual([
|
||||
{ index: 1, name: "First plate", objectCount: 1 },
|
||||
{ index: 2, name: "Second plate", objectCount: 1 },
|
||||
@@ -229,7 +229,7 @@ describe("modelPreviewCache -- internal <component> references", () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await fs.mkdir(path.dirname(modelPreviewGlbPath("x")), { recursive: true });
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "printstash-3mf-wrapper-out-"));
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "thingport-3mf-wrapper-out-"));
|
||||
wrapperFixturePath = path.join(dir, "wrapper.3mf");
|
||||
await buildWrapperFixture3mf(wrapperFixturePath);
|
||||
});
|
||||
@@ -252,10 +252,10 @@ describe("modelPreviewCache -- internal <component> references", () => {
|
||||
|
||||
let root: any = null;
|
||||
gltf.scene.traverse((obj: any) => {
|
||||
if (obj.userData?.printstashPreview) root = obj;
|
||||
if (obj.userData?.thingportPreview) root = obj;
|
||||
});
|
||||
expect(root).toBeTruthy();
|
||||
const meta = JSON.parse(root.userData.printstashPreview);
|
||||
const meta = JSON.parse(root.userData.thingportPreview);
|
||||
// The wrapper object (id 2) has no <mesh> of its own -- its single mesh only exists because
|
||||
// the component reference to object 1 was resolved and merged in.
|
||||
expect(meta.plates).toEqual([{ index: 1, name: "Only plate", objectCount: 1 }]);
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
# PrintStash Bridge
|
||||
# Thingport Bridge
|
||||
|
||||
This helper registers the custom protocol `print-stash://`, downloads the requested file, and
|
||||
launches it in the slicer PrintStash asked for. It exists because Bambu Studio's own
|
||||
This helper registers the custom protocol `thingport://`, downloads the requested file, and
|
||||
launches it in the slicer Thingport asked for. It exists because Bambu Studio's own
|
||||
`bambustudio://` handler only accepts links from a small allowlist of domains it trusts (Bambu
|
||||
Lab's own storefronts, mainly) -- a self-hosted PrintStash instance is never on that list, so
|
||||
Lab's own storefronts, mainly) -- a self-hosted Thingport instance is never on that list, so
|
||||
"Open in Bambu Studio" silently does nothing without this bridge in between. OrcaSlicer and
|
||||
PrusaSlicer's protocol handlers accept any HTTP(S) URL directly, so they're launched straight
|
||||
from the browser and never need it.
|
||||
|
||||
Protocol format:
|
||||
`print-stash://open?url=<download-url>&slicer=<id>&filename=<name>`
|
||||
`thingport://open?url=<download-url>&slicer=<id>&filename=<name>`
|
||||
|
||||
The `url` parameter should be a direct HTTP(s) download link. PrintStash already includes the
|
||||
The `url` parameter should be a direct HTTP(s) download link. Thingport already includes the
|
||||
auth token in that URL.
|
||||
|
||||
## Quick install (no shell commands)
|
||||
|
||||
Download the latest Bridge binary for your OS from the
|
||||
[PrintStash releases](https://github.com/TautvydasDerzinskas/print-stash/releases/latest) page
|
||||
(also linked from the in-app Download page) and run it once. It registers the `print-stash://`
|
||||
[Thingport releases](https://github.com/TautvydasDerzinskas/Thingport/releases/latest) page
|
||||
(also linked from the in-app Download page) and run it once. It registers the `thingport://`
|
||||
protocol handler and copies itself into a stable location:
|
||||
|
||||
- Windows: `%LOCALAPPDATA%\PrintStash\Bridge\print-stash-bridge.exe`
|
||||
- Linux: `~/.local/bin/print-stash-bridge`
|
||||
- macOS: `~/Applications/PrintStash Bridge.app`
|
||||
- Windows: `%LOCALAPPDATA%\Thingport\Bridge\thingport-bridge.exe`
|
||||
- Linux: `~/.local/bin/thingport-bridge`
|
||||
- macOS: `~/Applications/Thingport Bridge.app`
|
||||
|
||||
### Windows / Linux
|
||||
|
||||
@@ -36,19 +36,19 @@ macOS only recognizes a real `.app` bundle as a protocol handler, so install her
|
||||
step rather than a plain double-click:
|
||||
|
||||
```bash
|
||||
chmod +x ~/Downloads/print-stash-bridge-macos
|
||||
xattr -d com.apple.quarantine ~/Downloads/print-stash-bridge-macos # downloaded files are quarantined by Gatekeeper
|
||||
~/Downloads/print-stash-bridge-macos --install
|
||||
chmod +x ~/Downloads/thingport-bridge-macos
|
||||
xattr -d com.apple.quarantine ~/Downloads/thingport-bridge-macos # downloaded files are quarantined by Gatekeeper
|
||||
~/Downloads/thingport-bridge-macos --install
|
||||
```
|
||||
|
||||
This copies itself into `~/Applications/PrintStash Bridge.app` (a background, Dock-less agent)
|
||||
and registers the `print-stash://` scheme with Launch Services. If macOS still refuses to run it,
|
||||
This copies itself into `~/Applications/Thingport Bridge.app` (a background, Dock-less agent)
|
||||
and registers the `thingport://` scheme with Launch Services. If macOS still refuses to run it,
|
||||
open System Settings -> Privacy & Security and allow it there instead of using `xattr`.
|
||||
|
||||
If the terminal instead prints `Killed: 9` (or `[1] ... killed`) the moment you run it, that's a
|
||||
stale, invalidly-signed binary rather than a Gatekeeper block -- re-sign it yourself and retry:
|
||||
```bash
|
||||
codesign --force -s - ~/Downloads/print-stash-bridge-macos
|
||||
codesign --force -s - ~/Downloads/thingport-bridge-macos
|
||||
```
|
||||
|
||||
## Build
|
||||
@@ -60,22 +60,22 @@ on macOS itself.
|
||||
Windows (PowerShell):
|
||||
```powershell
|
||||
cd bridge
|
||||
go build -o dist\print-stash-bridge.exe .\cmd\print-stash-bridge
|
||||
go build -o dist\thingport-bridge.exe .\cmd\thingport-bridge
|
||||
```
|
||||
|
||||
Linux (bash):
|
||||
```bash
|
||||
cd bridge
|
||||
go build -o dist/print-stash-bridge ./cmd/print-stash-bridge
|
||||
go build -o dist/thingport-bridge ./cmd/thingport-bridge
|
||||
```
|
||||
|
||||
macOS (bash), universal binary:
|
||||
```bash
|
||||
cd bridge
|
||||
GOARCH=arm64 go build -o dist/print-stash-bridge-arm64 ./cmd/print-stash-bridge
|
||||
GOARCH=amd64 go build -o dist/print-stash-bridge-amd64 ./cmd/print-stash-bridge
|
||||
lipo -create -output dist/print-stash-bridge-macos dist/print-stash-bridge-arm64 dist/print-stash-bridge-amd64
|
||||
codesign --force -s - dist/print-stash-bridge-macos # lipo invalidates each slice's signature;
|
||||
GOARCH=arm64 go build -o dist/thingport-bridge-arm64 ./cmd/thingport-bridge
|
||||
GOARCH=amd64 go build -o dist/thingport-bridge-amd64 ./cmd/thingport-bridge
|
||||
lipo -create -output dist/thingport-bridge-macos dist/thingport-bridge-arm64 dist/thingport-bridge-amd64
|
||||
codesign --force -s - dist/thingport-bridge-macos # lipo invalidates each slice's signature;
|
||||
# without this, arm64 Macs SIGKILL it on launch
|
||||
```
|
||||
|
||||
@@ -84,16 +84,16 @@ codesign --force -s - dist/print-stash-bridge-macos # lipo invalidates each sl
|
||||
Windows (PowerShell):
|
||||
```powershell
|
||||
cd bridge\scripts
|
||||
.\install-windows.ps1 -BridgePath "..\dist\print-stash-bridge.exe"
|
||||
.\install-windows.ps1 -BridgePath "..\dist\thingport-bridge.exe"
|
||||
```
|
||||
|
||||
Linux (bash):
|
||||
```bash
|
||||
cd bridge/scripts
|
||||
./install-linux.sh ../dist/print-stash-bridge
|
||||
./install-linux.sh ../dist/thingport-bridge
|
||||
```
|
||||
|
||||
You can also run the binary directly: `print-stash-bridge --install` (this is required, not just
|
||||
You can also run the binary directly: `thingport-bridge --install` (this is required, not just
|
||||
convenient, on macOS -- see above).
|
||||
|
||||
If your shell blocks scripts, set the execution policy for the current user:
|
||||
@@ -116,9 +116,9 @@ pre-warm).
|
||||
If you still want it, on macOS:
|
||||
|
||||
**Easiest** -- System Settings -> General -> Login Items & Extensions -> click **+** and add
|
||||
`PrintStash Bridge` from `~/Applications`.
|
||||
`Thingport Bridge` from `~/Applications`.
|
||||
|
||||
**Or, a LaunchAgent** -- save as `~/Library/LaunchAgents/com.printstash.bridge.plist` (replace
|
||||
**Or, a LaunchAgent** -- save as `~/Library/LaunchAgents/com.thingport.bridge.plist` (replace
|
||||
`YOUR_USERNAME`):
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -126,10 +126,10 @@ If you still want it, on macOS:
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.printstash.bridge</string>
|
||||
<string>com.thingport.bridge</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Users/YOUR_USERNAME/Applications/PrintStash Bridge.app/Contents/MacOS/print-stash-bridge</string>
|
||||
<string>/Users/YOUR_USERNAME/Applications/Thingport Bridge.app/Contents/MacOS/thingport-bridge</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
@@ -139,21 +139,21 @@ If you still want it, on macOS:
|
||||
then:
|
||||
```bash
|
||||
mkdir -p ~/Library/LaunchAgents
|
||||
# save the file above as ~/Library/LaunchAgents/com.printstash.bridge.plist
|
||||
launchctl load ~/Library/LaunchAgents/com.printstash.bridge.plist
|
||||
# save the file above as ~/Library/LaunchAgents/com.thingport.bridge.plist
|
||||
launchctl load ~/Library/LaunchAgents/com.thingport.bridge.plist
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
Create a config file if you want custom slicer paths or arguments:
|
||||
|
||||
- Windows: `%APPDATA%\printstash-bridge\config.json`
|
||||
- Linux: `~/.config/printstash-bridge/config.json`
|
||||
- macOS: `~/Library/Application Support/printstash-bridge/config.json`
|
||||
- Windows: `%APPDATA%\thingport-bridge\config.json`
|
||||
- Linux: `~/.config/thingport-bridge/config.json`
|
||||
- macOS: `~/Library/Application Support/thingport-bridge/config.json`
|
||||
|
||||
Use `config.example.json` as a template.
|
||||
|
||||
Environment override (per slicer): `PRINTSTASH_SLICER_BAMBUSTUDIO=/path/to/BambuStudio`
|
||||
Environment override (per slicer): `THINGPORT_SLICER_BAMBUSTUDIO=/path/to/BambuStudio`
|
||||
|
||||
## Slicer IDs
|
||||
|
||||
@@ -165,13 +165,13 @@ Environment override (per slicer): `PRINTSTASH_SLICER_BAMBUSTUDIO=/path/to/Bambu
|
||||
## Testing
|
||||
|
||||
After install, paste this into your browser's address bar (replace the URL):
|
||||
`print-stash://open?url=https://example.com/model.3mf&slicer=bambustudio&filename=model.3mf`
|
||||
`thingport://open?url=https://example.com/model.3mf&slicer=bambustudio&filename=model.3mf`
|
||||
|
||||
## Logs
|
||||
|
||||
- Windows: `%APPDATA%\printstash-bridge\bridge.log`
|
||||
- Linux: `~/.config/printstash-bridge/bridge.log`
|
||||
- macOS: `~/Library/Application Support/printstash-bridge/bridge.log`
|
||||
- Windows: `%APPDATA%\thingport-bridge\bridge.log`
|
||||
- Linux: `~/.config/thingport-bridge/bridge.log`
|
||||
- macOS: `~/Library/Application Support/thingport-bridge/bridge.log`
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
@@ -11,16 +12,20 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
bundleName = "PrintStash Bridge.app"
|
||||
bundleID = "com.printstash.bridge"
|
||||
bundleExeName = "print-stash-bridge"
|
||||
lsregisterBin = "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister"
|
||||
bundleName = "Thingport Bridge.app"
|
||||
bundleID = "com.thingport.bridge"
|
||||
bundleExeName = "thingport-bridge"
|
||||
bundleIconName = "AppIcon.icns"
|
||||
lsregisterBin = "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister"
|
||||
)
|
||||
|
||||
//go:embed thingport.icns
|
||||
var bundleIcon []byte
|
||||
|
||||
// macOS has no per-invocation registry entry like Windows/Linux -- a custom URL scheme is only
|
||||
// honored for an actual .app bundle, discovered by LaunchServices from its Info.plist. So install
|
||||
// here means: copy this binary into a real app bundle under ~/Applications, write the
|
||||
// CFBundleURLTypes declaring the print-stash scheme, and re-register that bundle.
|
||||
// CFBundleURLTypes declaring the thingport scheme, and re-register that bundle.
|
||||
func installSelf() (string, error) {
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
@@ -39,6 +44,7 @@ func installSelf() (string, error) {
|
||||
appDir := filepath.Join(home, "Applications", bundleName)
|
||||
contentsDir := filepath.Join(appDir, "Contents")
|
||||
macosDir := filepath.Join(contentsDir, "MacOS")
|
||||
resourcesDir := filepath.Join(contentsDir, "Resources")
|
||||
targetExe := filepath.Join(macosDir, bundleExeName)
|
||||
|
||||
if !samePath(exePath, targetExe) {
|
||||
@@ -53,7 +59,18 @@ func installSelf() (string, error) {
|
||||
return "", fmt.Errorf("write Info.plist: %w", err)
|
||||
}
|
||||
|
||||
// Tell LaunchServices about the (re)written bundle so `print-stash://` resolves to it right
|
||||
// Without this the bundle shows the generic system app icon -- LSUIElement apps don't fall
|
||||
// back to anything derived from the executable, only CFBundleIconFile + this file actually
|
||||
// present in Resources/. Embedded (rather than a sibling asset) so the single downloaded
|
||||
// binary is still all that's needed to install.
|
||||
if err := os.MkdirAll(resourcesDir, 0755); err != nil {
|
||||
return "", fmt.Errorf("create Resources dir: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(resourcesDir, bundleIconName), bundleIcon, 0644); err != nil {
|
||||
return "", fmt.Errorf("write app icon: %w", err)
|
||||
}
|
||||
|
||||
// Tell LaunchServices about the (re)written bundle so `thingport://` resolves to it right
|
||||
// away, without waiting for the periodic system rescan. Best-effort: a launch still works
|
||||
// later even if this fails (e.g. lsregister moved in a future macOS release).
|
||||
if _, err := os.Stat(lsregisterBin); err == nil {
|
||||
@@ -69,9 +86,9 @@ func infoPlist() string {
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>PrintStash Bridge</string>
|
||||
<string>Thingport Bridge</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>PrintStash Bridge</string>
|
||||
<string>Thingport Bridge</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>` + bundleID + `</string>
|
||||
<key>CFBundleVersion</key>
|
||||
@@ -80,6 +97,8 @@ func infoPlist() string {
|
||||
<string>1.0</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>` + bundleExeName + `</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>` + bundleIconName + `</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
@@ -90,10 +109,10 @@ func infoPlist() string {
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>PrintStash Bridge Protocol</string>
|
||||
<string>Thingport Bridge Protocol</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>print-stash</string>
|
||||
<string>thingport</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
@@ -103,10 +122,10 @@ func infoPlist() string {
|
||||
}
|
||||
|
||||
// runDefaultLaunch always means "LaunchServices opened the installed app bundle from a
|
||||
// print-stash:// click" on macOS -- the URL itself arrives later as an Apple Event, never as
|
||||
// thingport:// click" on macOS -- the URL itself arrives later as an Apple Event, never as
|
||||
// argv, so there's nothing here to distinguish from any other bare launch. This blocks forever
|
||||
// servicing those events. Installing (or reinstalling, e.g. after an update) is instead always
|
||||
// explicit here: `print-stash-bridge --install` from Terminal.
|
||||
// explicit here: `thingport-bridge --install` from Terminal.
|
||||
func runDefaultLaunch() {
|
||||
cfg, cfgDir := loadConfig()
|
||||
setupLogging(cfg, cfgDir)
|
||||
@@ -24,7 +24,7 @@ func installSelf() (string, error) {
|
||||
return "", fmt.Errorf("resolve home dir: %w", err)
|
||||
}
|
||||
installDir := filepath.Join(home, ".local", "bin")
|
||||
targetPath := filepath.Join(installDir, "print-stash-bridge")
|
||||
targetPath := filepath.Join(installDir, "thingport-bridge")
|
||||
if !samePath(exePath, targetPath) {
|
||||
if err := copyFile(exePath, targetPath, 0755); err != nil {
|
||||
return "", fmt.Errorf("copy binary: %w", err)
|
||||
@@ -37,9 +37,9 @@ func installSelf() (string, error) {
|
||||
if err := os.MkdirAll(desktopDir, 0755); err != nil {
|
||||
return "", fmt.Errorf("create desktop dir: %w", err)
|
||||
}
|
||||
desktopFile := filepath.Join(desktopDir, "print-stash-bridge.desktop")
|
||||
desktopFile := filepath.Join(desktopDir, "thingport-bridge.desktop")
|
||||
desktop := fmt.Sprintf(
|
||||
"[Desktop Entry]\nName=PrintStash Bridge\nExec=%s %%u\nType=Application\nTerminal=false\nMimeType=x-scheme-handler/print-stash;\n",
|
||||
"[Desktop Entry]\nName=Thingport Bridge\nExec=%s %%u\nType=Application\nTerminal=false\nMimeType=x-scheme-handler/thingport;\n",
|
||||
targetPath,
|
||||
)
|
||||
if err := os.WriteFile(desktopFile, []byte(desktop), 0644); err != nil {
|
||||
@@ -47,7 +47,7 @@ func installSelf() (string, error) {
|
||||
}
|
||||
|
||||
if xdgMime, err := exec.LookPath("xdg-mime"); err == nil {
|
||||
_ = exec.Command(xdgMime, "default", "print-stash-bridge.desktop", "x-scheme-handler/print-stash").Run()
|
||||
_ = exec.Command(xdgMime, "default", "thingport-bridge.desktop", "x-scheme-handler/thingport").Run()
|
||||
}
|
||||
|
||||
return targetPath, nil
|
||||
@@ -28,15 +28,15 @@ func installSelf() (string, error) {
|
||||
if baseDir == "" {
|
||||
return "", fmt.Errorf("LOCALAPPDATA is not set")
|
||||
}
|
||||
targetDir := filepath.Join(baseDir, "PrintStash", "Bridge")
|
||||
targetExe := filepath.Join(targetDir, "print-stash-bridge.exe")
|
||||
targetDir := filepath.Join(baseDir, "Thingport", "Bridge")
|
||||
targetExe := filepath.Join(targetDir, "thingport-bridge.exe")
|
||||
if !samePath(exePath, targetExe) {
|
||||
if err := copyFile(exePath, targetExe, 0755); err != nil {
|
||||
return "", fmt.Errorf("copy binary: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := registerProtocol(scheme, "PrintStash Bridge", targetExe); err != nil {
|
||||
if err := registerProtocol(scheme, "Thingport Bridge", targetExe); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -28,17 +28,17 @@ type Config struct {
|
||||
Slicers map[string]SlicerConfig `json:"slicers,omitempty"`
|
||||
}
|
||||
|
||||
// scheme is only for protocols PrintStash can't hand off directly to a slicer's own URL handler
|
||||
// scheme is only for protocols Thingport can't hand off directly to a slicer's own URL handler
|
||||
// (Bambu Studio's bambustudio:// only accepts links from an allowlist of domains it trusts, which
|
||||
// print-stash's own download URLs aren't on). OrcaSlicer and PrusaSlicer accept any HTTP(S) URL
|
||||
// thingport's own download URLs aren't on). OrcaSlicer and PrusaSlicer accept any HTTP(S) URL
|
||||
// through their own protocol, so they're launched straight from the browser and never reach here.
|
||||
const scheme = "print-stash"
|
||||
const scheme = "thingport"
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
// On Windows/Linux the OS execs a fresh process with the URL as argv[1] every time, so a
|
||||
// bare "no args" launch unambiguously means "run me to install". macOS instead delivers a
|
||||
// print-stash:// open to an already-running app bundle out-of-band as an Apple Event --
|
||||
// thingport:// open to an already-running app bundle out-of-band as an Apple Event --
|
||||
// there's no argv equivalent -- so a bare launch there must be assumed to be that GUI
|
||||
// case; mac install instead requires the explicit --install flag below (see
|
||||
// runDefaultLaunch's darwin implementation).
|
||||
@@ -66,8 +66,8 @@ func main() {
|
||||
|
||||
func printUsage() {
|
||||
fmt.Println("Usage:")
|
||||
fmt.Println(" print-stash-bridge --install")
|
||||
fmt.Println(" print-stash-bridge <print-stash://open?...>")
|
||||
fmt.Println(" thingport-bridge --install")
|
||||
fmt.Println(" thingport-bridge <thingport://open?...>")
|
||||
}
|
||||
|
||||
// runInstallAndReport is the shared body of "explicitly asked to install" -- via --install on
|
||||
@@ -78,8 +78,8 @@ func runInstallAndReport() {
|
||||
fmt.Printf("Install failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Installed PrintStash Bridge at %s\n", target)
|
||||
fmt.Println("You can now use \"Open in Bambu Studio\" from PrintStash.")
|
||||
fmt.Printf("Installed Thingport Bridge at %s\n", target)
|
||||
fmt.Println("You can now use \"Open in Bambu Studio\" from Thingport.")
|
||||
}
|
||||
|
||||
func handleProtocol(raw string, cfg Config) error {
|
||||
@@ -182,9 +182,9 @@ func loadConfig() (Config, string) {
|
||||
func configDir() string {
|
||||
dir, err := os.UserConfigDir()
|
||||
if err != nil || dir == "" {
|
||||
return filepath.Join(os.TempDir(), "printstash-bridge")
|
||||
return filepath.Join(os.TempDir(), "thingport-bridge")
|
||||
}
|
||||
return filepath.Join(dir, "printstash-bridge")
|
||||
return filepath.Join(dir, "thingport-bridge")
|
||||
}
|
||||
|
||||
func resolveDownloadDir(cfg Config) string {
|
||||
@@ -193,9 +193,9 @@ func resolveDownloadDir(cfg Config) string {
|
||||
}
|
||||
dir, err := os.UserCacheDir()
|
||||
if err != nil || dir == "" {
|
||||
return filepath.Join(os.TempDir(), "printstash-bridge")
|
||||
return filepath.Join(os.TempDir(), "thingport-bridge")
|
||||
}
|
||||
return filepath.Join(dir, "printstash-bridge")
|
||||
return filepath.Join(dir, "thingport-bridge")
|
||||
}
|
||||
|
||||
func sanitizeFilename(name string) string {
|
||||
@@ -270,7 +270,7 @@ func resolveSlicerCommand(id string, cfg Config) (string, []string) {
|
||||
}
|
||||
}
|
||||
|
||||
envKey := "PRINTSTASH_SLICER_" + strings.ToUpper(strings.ReplaceAll(id, "-", "_"))
|
||||
envKey := "THINGPORT_SLICER_" + strings.ToUpper(strings.ReplaceAll(id, "-", "_"))
|
||||
if val := strings.TrimSpace(os.Getenv(envKey)); val != "" {
|
||||
return val, nil
|
||||
}
|
||||
@@ -21,7 +21,7 @@ func TestSanitizeFilename(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRedactURLHidesTokenQueryValue(t *testing.T) {
|
||||
redacted := redactURL("https://printstash.example/file?id=asset-1&token=secret-value")
|
||||
redacted := redactURL("https://thingport.example/file?id=asset-1&token=secret-value")
|
||||
if strings.Contains(redacted, "secret-value") {
|
||||
t.Fatalf("redacted URL still contains the token: %s", redacted)
|
||||
}
|
||||
@@ -32,10 +32,10 @@ func TestRedactURLHidesTokenQueryValue(t *testing.T) {
|
||||
|
||||
func TestHandleProtocolRejectsUntrustedShapesBeforeDownload(t *testing.T) {
|
||||
tests := []string{
|
||||
"https://printstash.example/file",
|
||||
"print-stash://delete?url=https://printstash.example/file",
|
||||
"print-stash://open",
|
||||
"print-stash://open?url=file:///tmp/model.stl",
|
||||
"https://thingport.example/file",
|
||||
"thingport://delete?url=https://thingport.example/file",
|
||||
"thingport://open",
|
||||
"thingport://open?url=file:///tmp/model.stl",
|
||||
}
|
||||
|
||||
for _, raw := range tests {
|
||||
@@ -13,7 +13,7 @@ import "C"
|
||||
import "log"
|
||||
|
||||
// onOpenURLCallback is set once before RunURLListener blocks the main thread inside Cocoa's run
|
||||
// loop -- handleGetURLEvent (urlhandler_darwin.m) calls back into it for every print-stash://
|
||||
// loop -- handleGetURLEvent (urlhandler_darwin.m) calls back into it for every thingport://
|
||||
// link the OS delivers via Apple Events, which is how macOS hands a custom URL scheme to an
|
||||
// already-registered app bundle (there is no argv equivalent, unlike Windows/Linux).
|
||||
var onOpenURLCallback func(raw string)
|
||||
@@ -29,7 +29,7 @@ func goHandleOpenURL(cURL *C.char) {
|
||||
}
|
||||
|
||||
// listenForAppleEventURLs blocks forever, running as a Dock-less (LSUIElement) background agent
|
||||
// that hands every print-stash:// open to handle. macOS reuses this running instance for
|
||||
// that hands every thingport:// open to handle. macOS reuses this running instance for
|
||||
// subsequent link clicks instead of relaunching, so it just keeps servicing callback until the
|
||||
// process is terminated.
|
||||
func listenForAppleEventURLs(handle func(raw string)) {
|
||||
@@ -1,13 +1,13 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include "_cgo_export.h"
|
||||
|
||||
@interface PSBridgeAppDelegate : NSObject <NSApplicationDelegate>
|
||||
@interface TPBridgeAppDelegate : NSObject <NSApplicationDelegate>
|
||||
@end
|
||||
|
||||
@implementation PSBridgeAppDelegate
|
||||
@implementation TPBridgeAppDelegate
|
||||
|
||||
// Registered in applicationWillFinishLaunching, not applicationDidFinishLaunching: when this
|
||||
// process is launched *by* a print-stash:// open (the common case -- the app isn't already
|
||||
// process is launched *by* a thingport:// open (the common case -- the app isn't already
|
||||
// running), the GetURL event carrying that very URL can be dispatched before "did finish" fires,
|
||||
// and would otherwise be silently dropped. "Will finish" runs early enough to catch it.
|
||||
- (void)applicationWillFinishLaunching:(NSNotification *)notification {
|
||||
@@ -35,7 +35,7 @@ void RunURLListener(void) {
|
||||
@autoreleasepool {
|
||||
NSApplication *app = [NSApplication sharedApplication];
|
||||
[app setActivationPolicy:NSApplicationActivationPolicyAccessory];
|
||||
PSBridgeAppDelegate *delegate = [[PSBridgeAppDelegate alloc] init];
|
||||
TPBridgeAppDelegate *delegate = [[TPBridgeAppDelegate alloc] init];
|
||||
[app setDelegate:delegate];
|
||||
[app run];
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
module printstash/bridge
|
||||
module thingport/bridge
|
||||
|
||||
go 1.21
|
||||
@@ -4,7 +4,7 @@ set -e
|
||||
BIN_SOURCE="${1:-}"
|
||||
if [ -z "$BIN_SOURCE" ]; then
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
BIN_SOURCE="$SCRIPT_DIR/../dist/print-stash-bridge"
|
||||
BIN_SOURCE="$SCRIPT_DIR/../dist/thingport-bridge"
|
||||
fi
|
||||
|
||||
if [ ! -f "$BIN_SOURCE" ]; then
|
||||
@@ -14,21 +14,21 @@ fi
|
||||
|
||||
INSTALL_DIR="$HOME/.local/bin"
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
BIN_TARGET="$INSTALL_DIR/print-stash-bridge"
|
||||
BIN_TARGET="$INSTALL_DIR/thingport-bridge"
|
||||
install -m 755 "$BIN_SOURCE" "$BIN_TARGET"
|
||||
|
||||
DESKTOP_DIR="$HOME/.local/share/applications"
|
||||
mkdir -p "$DESKTOP_DIR"
|
||||
DESKTOP_FILE="$DESKTOP_DIR/print-stash-bridge.desktop"
|
||||
DESKTOP_FILE="$DESKTOP_DIR/thingport-bridge.desktop"
|
||||
cat > "$DESKTOP_FILE" <<EOF
|
||||
[Desktop Entry]
|
||||
Name=PrintStash Bridge
|
||||
Name=Thingport Bridge
|
||||
Exec=$BIN_TARGET %u
|
||||
Type=Application
|
||||
Terminal=false
|
||||
MimeType=x-scheme-handler/print-stash;
|
||||
MimeType=x-scheme-handler/thingport;
|
||||
EOF
|
||||
|
||||
xdg-mime default print-stash-bridge.desktop x-scheme-handler/print-stash >/dev/null 2>&1 || true
|
||||
xdg-mime default thingport-bridge.desktop x-scheme-handler/thingport >/dev/null 2>&1 || true
|
||||
|
||||
echo "Installed protocol handler at $BIN_TARGET"
|
||||
@@ -6,12 +6,12 @@ $ErrorActionPreference = "Stop"
|
||||
|
||||
if (-not $BridgePath) {
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$BridgePath = Join-Path $scriptDir "..\dist\print-stash-bridge.exe"
|
||||
$BridgePath = Join-Path $scriptDir "..\dist\thingport-bridge.exe"
|
||||
}
|
||||
|
||||
$BridgePath = (Resolve-Path $BridgePath).Path
|
||||
$targetDir = Join-Path $env:LOCALAPPDATA "PrintStash\Bridge"
|
||||
$targetExe = Join-Path $targetDir "print-stash-bridge.exe"
|
||||
$targetDir = Join-Path $env:LOCALAPPDATA "Thingport\Bridge"
|
||||
$targetExe = Join-Path $targetDir "thingport-bridge.exe"
|
||||
|
||||
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
||||
Copy-Item -Path $BridgePath -Destination $targetExe -Force
|
||||
@@ -31,6 +31,6 @@ function Register-Protocol {
|
||||
Set-ItemProperty -Path $commandKey -Name "(Default)" -Value "`"$TargetExe`" `"%1`"" -Force
|
||||
}
|
||||
|
||||
Register-Protocol -Protocol "print-stash" -Label "PrintStash Bridge" -TargetExe $targetExe
|
||||
Register-Protocol -Protocol "thingport" -Label "Thingport Bridge" -TargetExe $targetExe
|
||||
|
||||
Write-Host "Installed protocol handler at $targetExe"
|
||||
@@ -3,15 +3,15 @@ services:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_USER=${POSTGRES_USER:-printstash}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-printstash}
|
||||
- POSTGRES_DB=${POSTGRES_DB:-printstash}
|
||||
- POSTGRES_USER=${POSTGRES_USER:-thingport}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-thingport}
|
||||
- POSTGRES_DB=${POSTGRES_DB:-thingport}
|
||||
volumes:
|
||||
- printstash_db:/var/lib/postgresql/data
|
||||
- thingport_db:/var/lib/postgresql/data
|
||||
networks:
|
||||
- app-net
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-printstash}"]
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-thingport}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
@@ -27,8 +27,8 @@ services:
|
||||
|
||||
backend:
|
||||
# Pin to a specific build instead of :latest, e.g.:
|
||||
# BACKEND_IMAGE=ghcr.io/tautvydasderzinskas/printstash-backend:sha-a8fef7d
|
||||
image: ${BACKEND_IMAGE:-ghcr.io/tautvydasderzinskas/printstash-backend:latest}
|
||||
# BACKEND_IMAGE=ghcr.io/tautvydasderzinskas/thingport-backend:sha-a8fef7d
|
||||
image: ${BACKEND_IMAGE:-ghcr.io/tautvydasderzinskas/thingport-backend:latest}
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PUID=${PUID:-1000}
|
||||
@@ -42,9 +42,9 @@ services:
|
||||
- SMTP_SECURE=${SMTP_SECURE:-false}
|
||||
- SMTP_USER=${SMTP_USER:-}
|
||||
- SMTP_PASS=${SMTP_PASS:-}
|
||||
- SMTP_FROM=${SMTP_FROM:-PrintStash <no-reply@localhost>}
|
||||
- SMTP_FROM=${SMTP_FROM:-Thingport <no-reply@localhost>}
|
||||
- FILE_STORAGE=/app/storage
|
||||
- DATABASE_URL=postgresql://${POSTGRES_USER:-printstash}:${POSTGRES_PASSWORD:-printstash}@db:5432/${POSTGRES_DB:-printstash}?schema=public
|
||||
- DATABASE_URL=postgresql://${POSTGRES_USER:-thingport}:${POSTGRES_PASSWORD:-thingport}@db:5432/${POSTGRES_DB:-thingport}?schema=public
|
||||
- CORS_ORIGINS=${CORS_ORIGINS:-}
|
||||
- FLARESOLVERR_URL=${FLARESOLVERR_URL:-http://flaresolverr:8191/v1}
|
||||
depends_on:
|
||||
@@ -53,7 +53,7 @@ services:
|
||||
flaresolverr:
|
||||
condition: service_started
|
||||
volumes:
|
||||
- printstash_storage:/app/storage
|
||||
- thingport_storage:/app/storage
|
||||
networks:
|
||||
- app-net
|
||||
|
||||
@@ -62,8 +62,8 @@ services:
|
||||
# pattern as docker-compose.yml, just built from a published image instead of local source.
|
||||
frontend:
|
||||
# Pin to a specific build instead of :latest, e.g.:
|
||||
# FRONTEND_IMAGE=ghcr.io/tautvydasderzinskas/printstash-frontend:sha-a8fef7d
|
||||
image: ${FRONTEND_IMAGE:-ghcr.io/tautvydasderzinskas/printstash-frontend:latest}
|
||||
# FRONTEND_IMAGE=ghcr.io/tautvydasderzinskas/thingport-frontend:sha-a8fef7d
|
||||
image: ${FRONTEND_IMAGE:-ghcr.io/tautvydasderzinskas/thingport-frontend:latest}
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${WEB_PORT:-80}:80"
|
||||
@@ -73,8 +73,8 @@ services:
|
||||
- app-net
|
||||
|
||||
volumes:
|
||||
printstash_storage:
|
||||
printstash_db:
|
||||
thingport_storage:
|
||||
thingport_db:
|
||||
|
||||
networks:
|
||||
app-net:
|
||||
|
||||
@@ -3,15 +3,15 @@ services:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_USER=${POSTGRES_USER:-printstash}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-printstash}
|
||||
- POSTGRES_DB=${POSTGRES_DB:-printstash}
|
||||
- POSTGRES_USER=${POSTGRES_USER:-thingport}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-thingport}
|
||||
- POSTGRES_DB=${POSTGRES_DB:-thingport}
|
||||
volumes:
|
||||
- ./data/postgres:/var/lib/postgresql/data
|
||||
networks:
|
||||
- app-net
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-printstash}"]
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-thingport}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
@@ -34,7 +34,7 @@ services:
|
||||
- PUID=${PUID:-1000}
|
||||
- PGID=${PGID:-1000}
|
||||
- FILE_STORAGE=/app/storage
|
||||
- DATABASE_URL=postgresql://${POSTGRES_USER:-printstash}:${POSTGRES_PASSWORD:-printstash}@db:5432/${POSTGRES_DB:-printstash}?schema=public
|
||||
- DATABASE_URL=postgresql://${POSTGRES_USER:-thingport}:${POSTGRES_PASSWORD:-thingport}@db:5432/${POSTGRES_DB:-thingport}?schema=public
|
||||
- CORS_ORIGINS=${CORS_ORIGINS:-}
|
||||
- INITIAL_ADMIN_EMAIL=${INITIAL_ADMIN_EMAIL:-}
|
||||
- AUTH_SECRET=${AUTH_SECRET:-changeme-secret}
|
||||
@@ -45,7 +45,7 @@ services:
|
||||
- SMTP_SECURE=${SMTP_SECURE:-false}
|
||||
- SMTP_USER=${SMTP_USER:-}
|
||||
- SMTP_PASS=${SMTP_PASS:-}
|
||||
- SMTP_FROM=${SMTP_FROM:-PrintStash <no-reply@localhost>}
|
||||
- SMTP_FROM=${SMTP_FROM:-Thingport <no-reply@localhost>}
|
||||
- FLARESOLVERR_URL=${FLARESOLVERR_URL:-http://flaresolverr:8191/v1}
|
||||
depends_on:
|
||||
db:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# PrintStash API contract
|
||||
# Thingport API contract
|
||||
|
||||
Node.js + Express + Prisma (Postgres) backend, ported from MakersVault's FastAPI/SQLModel/SQLite
|
||||
backend, with one structural change: a **Print** is a set of one or more **Plates** (multi-part
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>PrintStash</title>
|
||||
<title>Thingport</title>
|
||||
<!-- SVG favicon for browsers that support it; Safari (no SVG favicon support) and other
|
||||
legacy/PWA contexts fall back to the rasterized PNGs below. -->
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "printstash-frontend",
|
||||
"name": "thingport-frontend",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "printstash-frontend",
|
||||
"name": "thingport-frontend",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.13.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "printstash-frontend",
|
||||
"name": "thingport-frontend",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
|
||||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 361 B After Width: | Height: | Size: 502 B |
|
Before Width: | Height: | Size: 685 B After Width: | Height: | Size: 957 B |
@@ -1,37 +1,8 @@
|
||||
<svg width="256" height="256" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="512" height="512" rx="96" fill="#ffffff" />
|
||||
<g transform="translate(256 256) scale(0.72) translate(-256 -256)" fill="#00b800">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M319.315,160.192l86.129,49.694v172.528l-149.387,86.292
|
||||
l-149.386-86.235h-0.059V209.769l85.956-49.576l28.923,16.644v32.876l34.51,19.9l34.567-19.96v-32.876L319.315,160.192z
|
||||
M256.233,284.6l-102.655-59.293c-2.421-1.361-4.963-1.673-7.625-0.932c-2.654,0.716-4.671,2.269-6.051,4.655
|
||||
c-1.382,2.411-1.712,4.952-0.991,7.623c0.725,2.652,2.277,4.671,4.655,6.051l102.542,59.177L245.99,420.47
|
||||
c-0.005,2.759,0.964,5.128,2.909,7.099c1.969,1.945,4.335,2.916,7.102,2.909c2.742,0.009,5.088-0.962,7.04-2.909
|
||||
c1.944-1.967,2.915-4.333,2.91-7.099l0.175-118.529l102.539-59.177c2.39-1.389,3.939-3.405,4.656-6.054
|
||||
c0.702-2.654,0.352-5.175-1.049-7.564c-1.382-2.409-3.4-3.979-6.051-4.712c-2.653-0.68-5.174-0.309-7.565,1.105L256.233,284.6z"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M120.93,73.492h63.258c0-8.494,0-16.99,0-25.487
|
||||
c0.016,0.009,0.036,0.009,0.059,0h143.683c0,8.497,0,16.993,0,25.487h-71.346c-0.157,0.004-0.313,0.004-0.466,0
|
||||
c-2.039,0.125-3.919,0.63-5.645,1.514c-1.339,0.673-2.58,1.584-3.726,2.734c-2.749,2.773-4.165,6.089-4.249,9.951v0.698
|
||||
c0.084,3.841,1.5,7.14,4.249,9.892c1.146,1.168,2.387,2.101,3.726,2.794c1.726,0.863,3.605,1.348,5.645,1.454
|
||||
c0.152-0.004,0.309-0.004,0.466,0c0.036,0.019,0.075,0.039,0.116,0.057h71.289v29.619h-0.116
|
||||
c-19.089,11.024-38.177,22.042-57.266,33.05v36.02l-14.489,8.378l-14.608-8.437v-35.902l-57.321-33.108
|
||||
c0-9.892,0-19.783,0-29.676H120.93c-2.219-0.034-4.256-0.521-6.11-1.454c-1.337-0.693-2.578-1.626-3.724-2.794
|
||||
c-2.75-2.752-4.167-6.051-4.249-9.892v-0.698c0.082-3.862,1.499-7.178,4.249-9.951c1.146-1.15,2.387-2.062,3.724-2.734
|
||||
C116.674,74.049,118.711,73.544,120.93,73.492z"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M397.24,75.006c1.356,0.673,2.616,1.584,3.782,2.734
|
||||
c2.748,2.773,4.165,6.089,4.249,9.951v0.698c-0.084,3.841-1.501,7.14-4.249,9.892c-1.166,1.168-2.426,2.101-3.782,2.794
|
||||
c-1.855,0.934-3.892,1.42-6.11,1.454h-12.337c-14.826,0.01-29.648,0.01-44.463,0h-6.401V73.492h63.201
|
||||
C393.349,73.544,395.385,74.049,397.24,75.006z"
|
||||
/>
|
||||
</g>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none" role="img" aria-labelledby="title">
|
||||
<title id="title">Thingport icon — Color</title>
|
||||
<path d="M35.382 11.301Q40 8.635 44.618 11.301L62.545 21.651Q67.163 24.317 67.163 29.65L67.163 50.35Q67.163 55.682 62.545 58.349L44.618 68.699Q40 71.365 35.382 68.699L17.455 58.349Q12.837 55.683 12.837 50.35L12.837 29.65Q12.837 24.318 17.455 21.651Z" stroke="#1f1f1f" stroke-width="5.27" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path fill="#00b800" d="M42.264 37.285Q40 38.592 37.736 37.285L27.286 31.252Q25.022 29.945 27.286 28.637L37.736 22.604Q40 21.297 42.264 22.604L52.714 28.637Q54.978 29.944 52.714 31.252Z"/>
|
||||
<path fill="#00b800" d="M36.517 39.397Q38.781 40.704 38.781 43.318L38.781 55.385Q38.781 57.999 36.517 56.692L26.067 50.659Q23.803 49.352 23.803 46.737L23.803 34.671Q23.803 32.056 26.067 33.363Z"/>
|
||||
<path fill="#00b800" d="M41.219 43.318Q41.219 40.704 43.483 39.397L53.933 33.363Q56.197 32.056 56.197 34.671L56.197 46.737Q56.197 49.352 53.933 50.659L43.483 56.692Q41.219 57.999 41.219 55.385Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -265,7 +265,7 @@ export default function App() {
|
||||
<CssBaseline />
|
||||
<Box
|
||||
sx={{
|
||||
background: (theme) => theme.printstash.pageBackground,
|
||||
background: (theme) => theme.thingport.pageBackground,
|
||||
minHeight: "100vh",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
|
||||
@@ -219,7 +219,7 @@ export const importsApi = {
|
||||
|
||||
/** Registers a background job for the selected Things and returns immediately -- see
|
||||
* ImportJobContext.startThingiverseCollectionImport, which follows up with the actual
|
||||
* polling. Every successful import lands in a PrintStash Collection named after the real
|
||||
* polling. Every successful import lands in a Thingport Collection named after the real
|
||||
* Thingiverse Collection name. */
|
||||
fromThingiverseCollection: async (payload: ImportLinkPayload & { thing_ids: string[] }): Promise<{ job_id: string }> => {
|
||||
const res = await fetch(`${apiBase()}/import/thingiverse-collection`, {
|
||||
@@ -255,7 +255,7 @@ export const importsApi = {
|
||||
|
||||
/** Registers a background job for the selected models and returns immediately -- see
|
||||
* ImportJobContext.startPrintablesCollectionImport, which follows up with the actual polling.
|
||||
* Every successful import lands in a PrintStash Collection named after the real Printables
|
||||
* Every successful import lands in a Thingport Collection named after the real Printables
|
||||
* Collection name. */
|
||||
fromPrintablesCollection: async (payload: ImportLinkPayload & { model_ids: string[] }): Promise<{ job_id: string }> => {
|
||||
const res = await fetch(`${apiBase()}/import/printables-collection`, {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none" role="img" aria-labelledby="title">
|
||||
<title id="title">Thingport icon — Color</title>
|
||||
<path d="M35.382 11.301Q40 8.635 44.618 11.301L62.545 21.651Q67.163 24.317 67.163 29.65L67.163 50.35Q67.163 55.682 62.545 58.349L44.618 68.699Q40 71.365 35.382 68.699L17.455 58.349Q12.837 55.683 12.837 50.35L12.837 29.65Q12.837 24.318 17.455 21.651Z" stroke="#1f1f1f" stroke-width="5.27" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path fill="#00b800" d="M42.264 37.285Q40 38.592 37.736 37.285L27.286 31.252Q25.022 29.945 27.286 28.637L37.736 22.604Q40 21.297 42.264 22.604L52.714 28.637Q54.978 29.944 52.714 31.252Z"/>
|
||||
<path fill="#00b800" d="M36.517 39.397Q38.781 40.704 38.781 43.318L38.781 55.385Q38.781 57.999 36.517 56.692L26.067 50.659Q23.803 49.352 23.803 46.737L23.803 34.671Q23.803 32.056 26.067 33.363Z"/>
|
||||
<path fill="#00b800" d="M41.219 43.318Q41.219 40.704 43.483 39.397L53.933 33.363Q56.197 32.056 56.197 34.671L56.197 46.737Q56.197 49.352 53.933 50.659L43.483 56.692Q41.219 57.999 41.219 55.385Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none" role="img" aria-labelledby="title">
|
||||
<title id="title">Thingport icon — Dark</title>
|
||||
<path d="M35.382 11.301Q40 8.635 44.618 11.301L62.545 21.651Q67.163 24.317 67.163 29.65L67.163 50.35Q67.163 55.682 62.545 58.349L44.618 68.699Q40 71.365 35.382 68.699L17.455 58.349Q12.837 55.683 12.837 50.35L12.837 29.65Q12.837 24.318 17.455 21.651Z" stroke="#1f1f1f" stroke-width="5.27" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path fill="#1f1f1f" d="M42.264 37.285Q40 38.592 37.736 37.285L27.286 31.252Q25.022 29.945 27.286 28.637L37.736 22.604Q40 21.297 42.264 22.604L52.714 28.637Q54.978 29.944 52.714 31.252Z"/>
|
||||
<path fill="#1f1f1f" d="M36.517 39.397Q38.781 40.704 38.781 43.318L38.781 55.385Q38.781 57.999 36.517 56.692L26.067 50.659Q23.803 49.352 23.803 46.737L23.803 34.671Q23.803 32.056 26.067 33.363Z"/>
|
||||
<path fill="#1f1f1f" d="M41.219 43.318Q41.219 40.704 43.483 39.397L53.933 33.363Q56.197 32.056 56.197 34.671L56.197 46.737Q56.197 49.352 53.933 50.659L43.483 56.692Q41.219 57.999 41.219 55.385Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none" role="img" aria-labelledby="title">
|
||||
<title id="title">Thingport icon — Green</title>
|
||||
<path d="M35.382 11.301Q40 8.635 44.618 11.301L62.545 21.651Q67.163 24.317 67.163 29.65L67.163 50.35Q67.163 55.682 62.545 58.349L44.618 68.699Q40 71.365 35.382 68.699L17.455 58.349Q12.837 55.683 12.837 50.35L12.837 29.65Q12.837 24.318 17.455 21.651Z" stroke="#00b800" stroke-width="5.27" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path fill="#00b800" d="M42.264 37.285Q40 38.592 37.736 37.285L27.286 31.252Q25.022 29.945 27.286 28.637L37.736 22.604Q40 21.297 42.264 22.604L52.714 28.637Q54.978 29.944 52.714 31.252Z"/>
|
||||
<path fill="#00b800" d="M36.517 39.397Q38.781 40.704 38.781 43.318L38.781 55.385Q38.781 57.999 36.517 56.692L26.067 50.659Q23.803 49.352 23.803 46.737L23.803 34.671Q23.803 32.056 26.067 33.363Z"/>
|
||||
<path fill="#00b800" d="M41.219 43.318Q41.219 40.704 43.483 39.397L53.933 33.363Q56.197 32.056 56.197 34.671L56.197 46.737Q56.197 49.352 53.933 50.659L43.483 56.692Q41.219 57.999 41.219 55.385Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none" role="img" aria-labelledby="title">
|
||||
<title id="title">Thingport icon — On dark</title>
|
||||
<path d="M35.382 11.301Q40 8.635 44.618 11.301L62.545 21.651Q67.163 24.317 67.163 29.65L67.163 50.35Q67.163 55.682 62.545 58.349L44.618 68.699Q40 71.365 35.382 68.699L17.455 58.349Q12.837 55.683 12.837 50.35L12.837 29.65Q12.837 24.318 17.455 21.651Z" stroke="#FFFFFF" stroke-width="5.27" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path fill="#00b800" d="M42.264 37.285Q40 38.592 37.736 37.285L27.286 31.252Q25.022 29.945 27.286 28.637L37.736 22.604Q40 21.297 42.264 22.604L52.714 28.637Q54.978 29.944 52.714 31.252Z"/>
|
||||
<path fill="#00b800" d="M36.517 39.397Q38.781 40.704 38.781 43.318L38.781 55.385Q38.781 57.999 36.517 56.692L26.067 50.659Q23.803 49.352 23.803 46.737L23.803 34.671Q23.803 32.056 26.067 33.363Z"/>
|
||||
<path fill="#00b800" d="M41.219 43.318Q41.219 40.704 43.483 39.397L53.933 33.363Q56.197 32.056 56.197 34.671L56.197 46.737Q56.197 49.352 53.933 50.659L43.483 56.692Q41.219 57.999 41.219 55.385Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none" role="img" aria-labelledby="title">
|
||||
<title id="title">Thingport icon — White</title>
|
||||
<path d="M35.382 11.301Q40 8.635 44.618 11.301L62.545 21.651Q67.163 24.317 67.163 29.65L67.163 50.35Q67.163 55.682 62.545 58.349L44.618 68.699Q40 71.365 35.382 68.699L17.455 58.349Q12.837 55.683 12.837 50.35L12.837 29.65Q12.837 24.318 17.455 21.651Z" stroke="#FFFFFF" stroke-width="5.27" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path fill="#FFFFFF" d="M42.264 37.285Q40 38.592 37.736 37.285L27.286 31.252Q25.022 29.945 27.286 28.637L37.736 22.604Q40 21.297 42.264 22.604L52.714 28.637Q54.978 29.944 52.714 31.252Z"/>
|
||||
<path fill="#FFFFFF" d="M36.517 39.397Q38.781 40.704 38.781 43.318L38.781 55.385Q38.781 57.999 36.517 56.692L26.067 50.659Q23.803 49.352 23.803 46.737L23.803 34.671Q23.803 32.056 26.067 33.363Z"/>
|
||||
<path fill="#FFFFFF" d="M41.219 43.318Q41.219 40.704 43.483 39.397L53.933 33.363Q56.197 32.056 56.197 34.671L56.197 46.737Q56.197 49.352 53.933 50.659L43.483 56.692Q41.219 57.999 41.219 55.385Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none" role="img" aria-labelledby="title">
|
||||
<title id="title">Thingport icon — Color</title>
|
||||
<path d="M35.382 11.301Q40 8.635 44.618 11.301L62.545 21.651Q67.163 24.317 67.163 29.65L67.163 50.35Q67.163 55.682 62.545 58.349L44.618 68.699Q40 71.365 35.382 68.699L17.455 58.349Q12.837 55.683 12.837 50.35L12.837 29.65Q12.837 24.318 17.455 21.651Z" stroke="#1f1f1f" stroke-width="5.27" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path fill="#00b800" d="M42.264 37.285Q40 38.592 37.736 37.285L27.286 31.252Q25.022 29.945 27.286 28.637L37.736 22.604Q40 21.297 42.264 22.604L52.714 28.637Q54.978 29.944 52.714 31.252Z"/>
|
||||
<path fill="#00b800" d="M36.517 39.397Q38.781 40.704 38.781 43.318L38.781 55.385Q38.781 57.999 36.517 56.692L26.067 50.659Q23.803 49.352 23.803 46.737L23.803 34.671Q23.803 32.056 26.067 33.363Z"/>
|
||||
<path fill="#00b800" d="M41.219 43.318Q41.219 40.704 43.483 39.397L53.933 33.363Q56.197 32.056 56.197 34.671L56.197 46.737Q56.197 49.352 53.933 50.659L43.483 56.692Q41.219 57.999 41.219 55.385Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 411.152 100" fill="none" role="img" aria-labelledby="title">
|
||||
<title id="title">Thingport — Color</title>
|
||||
<g transform="translate(8 8)">
|
||||
<path d="M36.975 10.769Q42 7.867 47.025 10.769L66.534 22.032Q71.56 24.934 71.56 30.736L71.56 53.264Q71.56 59.066 66.534 61.968L47.025 73.231Q42 76.132 36.975 73.231L17.466 61.968Q12.44 59.066 12.44 53.264L12.44 30.736Q12.44 24.934 17.466 22.032Z" stroke="#1f1f1f" stroke-width="5.735" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path fill="#00b800" d="M44.464 39.045Q42 40.468 39.536 39.045L28.164 32.48Q25.7 31.057 28.164 29.635L39.536 23.069Q42 21.646 44.464 23.069L55.836 29.635Q58.3 31.057 55.836 32.48Z"/>
|
||||
<path fill="#00b800" d="M38.209 41.343Q40.673 42.766 40.673 45.611L40.673 58.742Q40.673 61.588 38.209 60.165L26.837 53.599Q24.373 52.177 24.373 49.332L24.373 36.2Q24.373 33.355 26.837 34.778Z"/>
|
||||
<path fill="#00b800" d="M43.327 45.611Q43.327 42.766 45.791 41.343L57.163 34.778Q59.627 33.355 59.627 36.2L59.627 49.332Q59.627 52.177 57.163 53.599L45.791 60.165Q43.327 61.588 43.327 58.742Z"/>
|
||||
</g>
|
||||
<g transform="translate(114 20.752)">
|
||||
<path fill="#1f1f1f" d="M21.504 45.696L13.056 45.696L13.056 8.576L0 8.576L0 0.896L34.560 0.896L34.560 8.576L21.504 8.576 M46.720 45.696L38.656 45.696L38.656 0.896L46.720 0.896L46.720 17.856L47.872 17.856Q48.384 16.832 49.472 15.808Q50.560 14.784 52.384 14.112Q54.208 13.440 57.024 13.440Q60.736 13.440 63.520 15.136Q66.304 16.832 67.840 19.808Q69.376 22.784 69.376 26.752L69.376 45.696L61.312 45.696L61.312 27.392Q61.312 23.808 59.552 22.016Q57.792 20.224 54.528 20.224Q50.816 20.224 48.768 22.688Q46.720 25.152 46.720 29.568 M84.224 45.696L76.160 45.696L76.160 13.952L84.224 13.952L84.224 45.696M80.192 10.240Q78.016 10.240 76.512 8.832Q75.008 7.424 75.008 5.120Q75.008 2.816 76.512 1.408Q78.016 0 80.192 0Q82.432 0 83.904 1.408Q85.376 2.816 85.376 5.120Q85.376 7.424 83.904 8.832Q82.432 10.240 80.192 10.240 M99.328 45.696L91.264 45.696L91.264 13.952L99.200 13.952L99.200 18.112L100.352 18.112Q101.120 16.448 103.232 14.944Q105.344 13.440 109.632 13.440Q113.344 13.440 116.128 15.136Q118.912 16.832 120.448 19.808Q121.984 22.784 121.984 26.752L121.984 45.696L113.920 45.696L113.920 27.392Q113.920 23.808 112.160 22.016Q110.400 20.224 107.136 20.224Q103.424 20.224 101.376 22.688Q99.328 25.152 99.328 29.568 M127.232 29.952L127.232 28.928Q127.232 23.936 129.216 20.384Q131.200 16.832 134.496 14.944Q137.792 13.056 141.696 13.056Q146.048 13.056 148.288 14.592Q150.528 16.128 151.552 17.792L152.704 17.792L152.704 13.952L160.640 13.952L160.640 51.328Q160.640 54.592 158.720 56.544Q156.800 58.496 153.600 58.496L132.352 58.496L132.352 51.456L150.784 51.456Q152.576 51.456 152.576 49.536L152.576 41.280L151.424 41.280Q150.784 42.304 149.632 43.360Q148.480 44.416 146.560 45.120Q144.640 45.824 141.696 45.824Q137.792 45.824 134.496 43.936Q131.200 42.048 129.216 38.496Q127.232 34.944 127.232 29.952M144 38.784Q147.712 38.784 150.208 36.416Q152.704 34.048 152.704 29.760L152.704 29.120Q152.704 24.768 150.240 22.432Q147.776 20.096 144 20.096Q140.288 20.096 137.792 22.432Q135.296 24.768 135.296 29.120L135.296 29.760Q135.296 34.048 137.792 36.416Q140.288 38.784 144 38.784 M175.744 58.496L167.680 58.496L167.680 13.952L175.616 13.952L175.616 17.792L176.768 17.792Q177.856 15.936 180.160 14.496Q182.464 13.056 186.752 13.056Q190.592 13.056 193.856 14.944Q197.120 16.832 199.104 20.480Q201.088 24.128 201.088 29.312L201.088 30.336Q201.088 35.520 199.104 39.168Q197.120 42.816 193.856 44.704Q190.592 46.592 186.752 46.592Q183.872 46.592 181.920 45.920Q179.968 45.248 178.784 44.192Q177.600 43.136 176.896 42.048L175.744 42.048L175.744 58.496M184.320 39.552Q188.096 39.552 190.560 37.152Q193.024 34.752 193.024 30.144L193.024 29.504Q193.024 24.896 190.528 22.496Q188.032 20.096 184.320 20.096Q180.608 20.096 178.112 22.496Q175.616 24.896 175.616 29.504L175.616 30.144Q175.616 34.752 178.112 37.152Q180.608 39.552 184.320 39.552 M221.696 46.592Q216.960 46.592 213.184 44.672Q209.408 42.752 207.232 39.104Q205.056 35.456 205.056 30.336L205.056 29.312Q205.056 24.192 207.232 20.544Q209.408 16.896 213.184 14.976Q216.960 13.056 221.696 13.056Q226.432 13.056 230.208 14.976Q233.984 16.896 236.160 20.544Q238.336 24.192 238.336 29.312L238.336 30.336Q238.336 35.456 236.160 39.104Q233.984 42.752 230.208 44.672Q226.432 46.592 221.696 46.592M221.696 39.424Q225.408 39.424 227.840 37.024Q230.272 34.624 230.272 30.144L230.272 29.504Q230.272 25.024 227.872 22.624Q225.472 20.224 221.696 20.224Q217.984 20.224 215.552 22.624Q213.120 25.024 213.120 29.504L213.120 30.144Q213.120 34.624 215.552 37.024Q217.984 39.424 221.696 39.424 M251.904 45.696L243.840 45.696L243.840 13.952L251.776 13.952L251.776 17.536L252.928 17.536Q253.632 15.616 255.264 14.720Q256.896 13.824 259.072 13.824L262.912 13.824L262.912 20.992L258.944 20.992Q255.872 20.992 253.888 22.624Q251.904 24.256 251.904 27.648 M288.384 45.696L279.424 45.696Q276.288 45.696 274.336 43.744Q272.384 41.792 272.384 38.528L272.384 20.608L264.448 20.608L264.448 13.952L272.384 13.952L272.384 4.096L280.448 4.096L280.448 13.952L289.152 13.952L289.152 20.608L280.448 20.608L280.448 37.120Q280.448 39.040 282.240 39.040L288.384 39.040"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 411.152 100" fill="none" role="img" aria-labelledby="title">
|
||||
<title id="title">Thingport — Dark</title>
|
||||
<g transform="translate(8 8)">
|
||||
<path d="M36.975 10.769Q42 7.867 47.025 10.769L66.534 22.032Q71.56 24.934 71.56 30.736L71.56 53.264Q71.56 59.066 66.534 61.968L47.025 73.231Q42 76.132 36.975 73.231L17.466 61.968Q12.44 59.066 12.44 53.264L12.44 30.736Q12.44 24.934 17.466 22.032Z" stroke="#1f1f1f" stroke-width="5.735" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path fill="#1f1f1f" d="M44.464 39.045Q42 40.468 39.536 39.045L28.164 32.48Q25.7 31.057 28.164 29.635L39.536 23.069Q42 21.646 44.464 23.069L55.836 29.635Q58.3 31.057 55.836 32.48Z"/>
|
||||
<path fill="#1f1f1f" d="M38.209 41.343Q40.673 42.766 40.673 45.611L40.673 58.742Q40.673 61.588 38.209 60.165L26.837 53.599Q24.373 52.177 24.373 49.332L24.373 36.2Q24.373 33.355 26.837 34.778Z"/>
|
||||
<path fill="#1f1f1f" d="M43.327 45.611Q43.327 42.766 45.791 41.343L57.163 34.778Q59.627 33.355 59.627 36.2L59.627 49.332Q59.627 52.177 57.163 53.599L45.791 60.165Q43.327 61.588 43.327 58.742Z"/>
|
||||
</g>
|
||||
<g transform="translate(114 20.752)">
|
||||
<path fill="#1f1f1f" d="M21.504 45.696L13.056 45.696L13.056 8.576L0 8.576L0 0.896L34.560 0.896L34.560 8.576L21.504 8.576 M46.720 45.696L38.656 45.696L38.656 0.896L46.720 0.896L46.720 17.856L47.872 17.856Q48.384 16.832 49.472 15.808Q50.560 14.784 52.384 14.112Q54.208 13.440 57.024 13.440Q60.736 13.440 63.520 15.136Q66.304 16.832 67.840 19.808Q69.376 22.784 69.376 26.752L69.376 45.696L61.312 45.696L61.312 27.392Q61.312 23.808 59.552 22.016Q57.792 20.224 54.528 20.224Q50.816 20.224 48.768 22.688Q46.720 25.152 46.720 29.568 M84.224 45.696L76.160 45.696L76.160 13.952L84.224 13.952L84.224 45.696M80.192 10.240Q78.016 10.240 76.512 8.832Q75.008 7.424 75.008 5.120Q75.008 2.816 76.512 1.408Q78.016 0 80.192 0Q82.432 0 83.904 1.408Q85.376 2.816 85.376 5.120Q85.376 7.424 83.904 8.832Q82.432 10.240 80.192 10.240 M99.328 45.696L91.264 45.696L91.264 13.952L99.200 13.952L99.200 18.112L100.352 18.112Q101.120 16.448 103.232 14.944Q105.344 13.440 109.632 13.440Q113.344 13.440 116.128 15.136Q118.912 16.832 120.448 19.808Q121.984 22.784 121.984 26.752L121.984 45.696L113.920 45.696L113.920 27.392Q113.920 23.808 112.160 22.016Q110.400 20.224 107.136 20.224Q103.424 20.224 101.376 22.688Q99.328 25.152 99.328 29.568 M127.232 29.952L127.232 28.928Q127.232 23.936 129.216 20.384Q131.200 16.832 134.496 14.944Q137.792 13.056 141.696 13.056Q146.048 13.056 148.288 14.592Q150.528 16.128 151.552 17.792L152.704 17.792L152.704 13.952L160.640 13.952L160.640 51.328Q160.640 54.592 158.720 56.544Q156.800 58.496 153.600 58.496L132.352 58.496L132.352 51.456L150.784 51.456Q152.576 51.456 152.576 49.536L152.576 41.280L151.424 41.280Q150.784 42.304 149.632 43.360Q148.480 44.416 146.560 45.120Q144.640 45.824 141.696 45.824Q137.792 45.824 134.496 43.936Q131.200 42.048 129.216 38.496Q127.232 34.944 127.232 29.952M144 38.784Q147.712 38.784 150.208 36.416Q152.704 34.048 152.704 29.760L152.704 29.120Q152.704 24.768 150.240 22.432Q147.776 20.096 144 20.096Q140.288 20.096 137.792 22.432Q135.296 24.768 135.296 29.120L135.296 29.760Q135.296 34.048 137.792 36.416Q140.288 38.784 144 38.784 M175.744 58.496L167.680 58.496L167.680 13.952L175.616 13.952L175.616 17.792L176.768 17.792Q177.856 15.936 180.160 14.496Q182.464 13.056 186.752 13.056Q190.592 13.056 193.856 14.944Q197.120 16.832 199.104 20.480Q201.088 24.128 201.088 29.312L201.088 30.336Q201.088 35.520 199.104 39.168Q197.120 42.816 193.856 44.704Q190.592 46.592 186.752 46.592Q183.872 46.592 181.920 45.920Q179.968 45.248 178.784 44.192Q177.600 43.136 176.896 42.048L175.744 42.048L175.744 58.496M184.320 39.552Q188.096 39.552 190.560 37.152Q193.024 34.752 193.024 30.144L193.024 29.504Q193.024 24.896 190.528 22.496Q188.032 20.096 184.320 20.096Q180.608 20.096 178.112 22.496Q175.616 24.896 175.616 29.504L175.616 30.144Q175.616 34.752 178.112 37.152Q180.608 39.552 184.320 39.552 M221.696 46.592Q216.960 46.592 213.184 44.672Q209.408 42.752 207.232 39.104Q205.056 35.456 205.056 30.336L205.056 29.312Q205.056 24.192 207.232 20.544Q209.408 16.896 213.184 14.976Q216.960 13.056 221.696 13.056Q226.432 13.056 230.208 14.976Q233.984 16.896 236.160 20.544Q238.336 24.192 238.336 29.312L238.336 30.336Q238.336 35.456 236.160 39.104Q233.984 42.752 230.208 44.672Q226.432 46.592 221.696 46.592M221.696 39.424Q225.408 39.424 227.840 37.024Q230.272 34.624 230.272 30.144L230.272 29.504Q230.272 25.024 227.872 22.624Q225.472 20.224 221.696 20.224Q217.984 20.224 215.552 22.624Q213.120 25.024 213.120 29.504L213.120 30.144Q213.120 34.624 215.552 37.024Q217.984 39.424 221.696 39.424 M251.904 45.696L243.840 45.696L243.840 13.952L251.776 13.952L251.776 17.536L252.928 17.536Q253.632 15.616 255.264 14.720Q256.896 13.824 259.072 13.824L262.912 13.824L262.912 20.992L258.944 20.992Q255.872 20.992 253.888 22.624Q251.904 24.256 251.904 27.648 M288.384 45.696L279.424 45.696Q276.288 45.696 274.336 43.744Q272.384 41.792 272.384 38.528L272.384 20.608L264.448 20.608L264.448 13.952L272.384 13.952L272.384 4.096L280.448 4.096L280.448 13.952L289.152 13.952L289.152 20.608L280.448 20.608L280.448 37.120Q280.448 39.040 282.240 39.040L288.384 39.040"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 411.152 100" fill="none" role="img" aria-labelledby="title">
|
||||
<title id="title">Thingport — Green</title>
|
||||
<g transform="translate(8 8)">
|
||||
<path d="M36.975 10.769Q42 7.867 47.025 10.769L66.534 22.032Q71.56 24.934 71.56 30.736L71.56 53.264Q71.56 59.066 66.534 61.968L47.025 73.231Q42 76.132 36.975 73.231L17.466 61.968Q12.44 59.066 12.44 53.264L12.44 30.736Q12.44 24.934 17.466 22.032Z" stroke="#00b800" stroke-width="5.735" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path fill="#00b800" d="M44.464 39.045Q42 40.468 39.536 39.045L28.164 32.48Q25.7 31.057 28.164 29.635L39.536 23.069Q42 21.646 44.464 23.069L55.836 29.635Q58.3 31.057 55.836 32.48Z"/>
|
||||
<path fill="#00b800" d="M38.209 41.343Q40.673 42.766 40.673 45.611L40.673 58.742Q40.673 61.588 38.209 60.165L26.837 53.599Q24.373 52.177 24.373 49.332L24.373 36.2Q24.373 33.355 26.837 34.778Z"/>
|
||||
<path fill="#00b800" d="M43.327 45.611Q43.327 42.766 45.791 41.343L57.163 34.778Q59.627 33.355 59.627 36.2L59.627 49.332Q59.627 52.177 57.163 53.599L45.791 60.165Q43.327 61.588 43.327 58.742Z"/>
|
||||
</g>
|
||||
<g transform="translate(114 20.752)">
|
||||
<path fill="#00b800" d="M21.504 45.696L13.056 45.696L13.056 8.576L0 8.576L0 0.896L34.560 0.896L34.560 8.576L21.504 8.576 M46.720 45.696L38.656 45.696L38.656 0.896L46.720 0.896L46.720 17.856L47.872 17.856Q48.384 16.832 49.472 15.808Q50.560 14.784 52.384 14.112Q54.208 13.440 57.024 13.440Q60.736 13.440 63.520 15.136Q66.304 16.832 67.840 19.808Q69.376 22.784 69.376 26.752L69.376 45.696L61.312 45.696L61.312 27.392Q61.312 23.808 59.552 22.016Q57.792 20.224 54.528 20.224Q50.816 20.224 48.768 22.688Q46.720 25.152 46.720 29.568 M84.224 45.696L76.160 45.696L76.160 13.952L84.224 13.952L84.224 45.696M80.192 10.240Q78.016 10.240 76.512 8.832Q75.008 7.424 75.008 5.120Q75.008 2.816 76.512 1.408Q78.016 0 80.192 0Q82.432 0 83.904 1.408Q85.376 2.816 85.376 5.120Q85.376 7.424 83.904 8.832Q82.432 10.240 80.192 10.240 M99.328 45.696L91.264 45.696L91.264 13.952L99.200 13.952L99.200 18.112L100.352 18.112Q101.120 16.448 103.232 14.944Q105.344 13.440 109.632 13.440Q113.344 13.440 116.128 15.136Q118.912 16.832 120.448 19.808Q121.984 22.784 121.984 26.752L121.984 45.696L113.920 45.696L113.920 27.392Q113.920 23.808 112.160 22.016Q110.400 20.224 107.136 20.224Q103.424 20.224 101.376 22.688Q99.328 25.152 99.328 29.568 M127.232 29.952L127.232 28.928Q127.232 23.936 129.216 20.384Q131.200 16.832 134.496 14.944Q137.792 13.056 141.696 13.056Q146.048 13.056 148.288 14.592Q150.528 16.128 151.552 17.792L152.704 17.792L152.704 13.952L160.640 13.952L160.640 51.328Q160.640 54.592 158.720 56.544Q156.800 58.496 153.600 58.496L132.352 58.496L132.352 51.456L150.784 51.456Q152.576 51.456 152.576 49.536L152.576 41.280L151.424 41.280Q150.784 42.304 149.632 43.360Q148.480 44.416 146.560 45.120Q144.640 45.824 141.696 45.824Q137.792 45.824 134.496 43.936Q131.200 42.048 129.216 38.496Q127.232 34.944 127.232 29.952M144 38.784Q147.712 38.784 150.208 36.416Q152.704 34.048 152.704 29.760L152.704 29.120Q152.704 24.768 150.240 22.432Q147.776 20.096 144 20.096Q140.288 20.096 137.792 22.432Q135.296 24.768 135.296 29.120L135.296 29.760Q135.296 34.048 137.792 36.416Q140.288 38.784 144 38.784 M175.744 58.496L167.680 58.496L167.680 13.952L175.616 13.952L175.616 17.792L176.768 17.792Q177.856 15.936 180.160 14.496Q182.464 13.056 186.752 13.056Q190.592 13.056 193.856 14.944Q197.120 16.832 199.104 20.480Q201.088 24.128 201.088 29.312L201.088 30.336Q201.088 35.520 199.104 39.168Q197.120 42.816 193.856 44.704Q190.592 46.592 186.752 46.592Q183.872 46.592 181.920 45.920Q179.968 45.248 178.784 44.192Q177.600 43.136 176.896 42.048L175.744 42.048L175.744 58.496M184.320 39.552Q188.096 39.552 190.560 37.152Q193.024 34.752 193.024 30.144L193.024 29.504Q193.024 24.896 190.528 22.496Q188.032 20.096 184.320 20.096Q180.608 20.096 178.112 22.496Q175.616 24.896 175.616 29.504L175.616 30.144Q175.616 34.752 178.112 37.152Q180.608 39.552 184.320 39.552 M221.696 46.592Q216.960 46.592 213.184 44.672Q209.408 42.752 207.232 39.104Q205.056 35.456 205.056 30.336L205.056 29.312Q205.056 24.192 207.232 20.544Q209.408 16.896 213.184 14.976Q216.960 13.056 221.696 13.056Q226.432 13.056 230.208 14.976Q233.984 16.896 236.160 20.544Q238.336 24.192 238.336 29.312L238.336 30.336Q238.336 35.456 236.160 39.104Q233.984 42.752 230.208 44.672Q226.432 46.592 221.696 46.592M221.696 39.424Q225.408 39.424 227.840 37.024Q230.272 34.624 230.272 30.144L230.272 29.504Q230.272 25.024 227.872 22.624Q225.472 20.224 221.696 20.224Q217.984 20.224 215.552 22.624Q213.120 25.024 213.120 29.504L213.120 30.144Q213.120 34.624 215.552 37.024Q217.984 39.424 221.696 39.424 M251.904 45.696L243.840 45.696L243.840 13.952L251.776 13.952L251.776 17.536L252.928 17.536Q253.632 15.616 255.264 14.720Q256.896 13.824 259.072 13.824L262.912 13.824L262.912 20.992L258.944 20.992Q255.872 20.992 253.888 22.624Q251.904 24.256 251.904 27.648 M288.384 45.696L279.424 45.696Q276.288 45.696 274.336 43.744Q272.384 41.792 272.384 38.528L272.384 20.608L264.448 20.608L264.448 13.952L272.384 13.952L272.384 4.096L280.448 4.096L280.448 13.952L289.152 13.952L289.152 20.608L280.448 20.608L280.448 37.120Q280.448 39.040 282.240 39.040L288.384 39.040"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 411.152 100" fill="none" role="img" aria-labelledby="title">
|
||||
<title id="title">Thingport — On dark</title>
|
||||
<g transform="translate(8 8)">
|
||||
<path d="M36.975 10.769Q42 7.867 47.025 10.769L66.534 22.032Q71.56 24.934 71.56 30.736L71.56 53.264Q71.56 59.066 66.534 61.968L47.025 73.231Q42 76.132 36.975 73.231L17.466 61.968Q12.44 59.066 12.44 53.264L12.44 30.736Q12.44 24.934 17.466 22.032Z" stroke="#FFFFFF" stroke-width="5.735" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path fill="#00b800" d="M44.464 39.045Q42 40.468 39.536 39.045L28.164 32.48Q25.7 31.057 28.164 29.635L39.536 23.069Q42 21.646 44.464 23.069L55.836 29.635Q58.3 31.057 55.836 32.48Z"/>
|
||||
<path fill="#00b800" d="M38.209 41.343Q40.673 42.766 40.673 45.611L40.673 58.742Q40.673 61.588 38.209 60.165L26.837 53.599Q24.373 52.177 24.373 49.332L24.373 36.2Q24.373 33.355 26.837 34.778Z"/>
|
||||
<path fill="#00b800" d="M43.327 45.611Q43.327 42.766 45.791 41.343L57.163 34.778Q59.627 33.355 59.627 36.2L59.627 49.332Q59.627 52.177 57.163 53.599L45.791 60.165Q43.327 61.588 43.327 58.742Z"/>
|
||||
</g>
|
||||
<g transform="translate(114 20.752)">
|
||||
<path fill="#FFFFFF" d="M21.504 45.696L13.056 45.696L13.056 8.576L0 8.576L0 0.896L34.560 0.896L34.560 8.576L21.504 8.576 M46.720 45.696L38.656 45.696L38.656 0.896L46.720 0.896L46.720 17.856L47.872 17.856Q48.384 16.832 49.472 15.808Q50.560 14.784 52.384 14.112Q54.208 13.440 57.024 13.440Q60.736 13.440 63.520 15.136Q66.304 16.832 67.840 19.808Q69.376 22.784 69.376 26.752L69.376 45.696L61.312 45.696L61.312 27.392Q61.312 23.808 59.552 22.016Q57.792 20.224 54.528 20.224Q50.816 20.224 48.768 22.688Q46.720 25.152 46.720 29.568 M84.224 45.696L76.160 45.696L76.160 13.952L84.224 13.952L84.224 45.696M80.192 10.240Q78.016 10.240 76.512 8.832Q75.008 7.424 75.008 5.120Q75.008 2.816 76.512 1.408Q78.016 0 80.192 0Q82.432 0 83.904 1.408Q85.376 2.816 85.376 5.120Q85.376 7.424 83.904 8.832Q82.432 10.240 80.192 10.240 M99.328 45.696L91.264 45.696L91.264 13.952L99.200 13.952L99.200 18.112L100.352 18.112Q101.120 16.448 103.232 14.944Q105.344 13.440 109.632 13.440Q113.344 13.440 116.128 15.136Q118.912 16.832 120.448 19.808Q121.984 22.784 121.984 26.752L121.984 45.696L113.920 45.696L113.920 27.392Q113.920 23.808 112.160 22.016Q110.400 20.224 107.136 20.224Q103.424 20.224 101.376 22.688Q99.328 25.152 99.328 29.568 M127.232 29.952L127.232 28.928Q127.232 23.936 129.216 20.384Q131.200 16.832 134.496 14.944Q137.792 13.056 141.696 13.056Q146.048 13.056 148.288 14.592Q150.528 16.128 151.552 17.792L152.704 17.792L152.704 13.952L160.640 13.952L160.640 51.328Q160.640 54.592 158.720 56.544Q156.800 58.496 153.600 58.496L132.352 58.496L132.352 51.456L150.784 51.456Q152.576 51.456 152.576 49.536L152.576 41.280L151.424 41.280Q150.784 42.304 149.632 43.360Q148.480 44.416 146.560 45.120Q144.640 45.824 141.696 45.824Q137.792 45.824 134.496 43.936Q131.200 42.048 129.216 38.496Q127.232 34.944 127.232 29.952M144 38.784Q147.712 38.784 150.208 36.416Q152.704 34.048 152.704 29.760L152.704 29.120Q152.704 24.768 150.240 22.432Q147.776 20.096 144 20.096Q140.288 20.096 137.792 22.432Q135.296 24.768 135.296 29.120L135.296 29.760Q135.296 34.048 137.792 36.416Q140.288 38.784 144 38.784 M175.744 58.496L167.680 58.496L167.680 13.952L175.616 13.952L175.616 17.792L176.768 17.792Q177.856 15.936 180.160 14.496Q182.464 13.056 186.752 13.056Q190.592 13.056 193.856 14.944Q197.120 16.832 199.104 20.480Q201.088 24.128 201.088 29.312L201.088 30.336Q201.088 35.520 199.104 39.168Q197.120 42.816 193.856 44.704Q190.592 46.592 186.752 46.592Q183.872 46.592 181.920 45.920Q179.968 45.248 178.784 44.192Q177.600 43.136 176.896 42.048L175.744 42.048L175.744 58.496M184.320 39.552Q188.096 39.552 190.560 37.152Q193.024 34.752 193.024 30.144L193.024 29.504Q193.024 24.896 190.528 22.496Q188.032 20.096 184.320 20.096Q180.608 20.096 178.112 22.496Q175.616 24.896 175.616 29.504L175.616 30.144Q175.616 34.752 178.112 37.152Q180.608 39.552 184.320 39.552 M221.696 46.592Q216.960 46.592 213.184 44.672Q209.408 42.752 207.232 39.104Q205.056 35.456 205.056 30.336L205.056 29.312Q205.056 24.192 207.232 20.544Q209.408 16.896 213.184 14.976Q216.960 13.056 221.696 13.056Q226.432 13.056 230.208 14.976Q233.984 16.896 236.160 20.544Q238.336 24.192 238.336 29.312L238.336 30.336Q238.336 35.456 236.160 39.104Q233.984 42.752 230.208 44.672Q226.432 46.592 221.696 46.592M221.696 39.424Q225.408 39.424 227.840 37.024Q230.272 34.624 230.272 30.144L230.272 29.504Q230.272 25.024 227.872 22.624Q225.472 20.224 221.696 20.224Q217.984 20.224 215.552 22.624Q213.120 25.024 213.120 29.504L213.120 30.144Q213.120 34.624 215.552 37.024Q217.984 39.424 221.696 39.424 M251.904 45.696L243.840 45.696L243.840 13.952L251.776 13.952L251.776 17.536L252.928 17.536Q253.632 15.616 255.264 14.720Q256.896 13.824 259.072 13.824L262.912 13.824L262.912 20.992L258.944 20.992Q255.872 20.992 253.888 22.624Q251.904 24.256 251.904 27.648 M288.384 45.696L279.424 45.696Q276.288 45.696 274.336 43.744Q272.384 41.792 272.384 38.528L272.384 20.608L264.448 20.608L264.448 13.952L272.384 13.952L272.384 4.096L280.448 4.096L280.448 13.952L289.152 13.952L289.152 20.608L280.448 20.608L280.448 37.120Q280.448 39.040 282.240 39.040L288.384 39.040"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 411.152 100" fill="none" role="img" aria-labelledby="title">
|
||||
<title id="title">Thingport — White</title>
|
||||
<g transform="translate(8 8)">
|
||||
<path d="M36.975 10.769Q42 7.867 47.025 10.769L66.534 22.032Q71.56 24.934 71.56 30.736L71.56 53.264Q71.56 59.066 66.534 61.968L47.025 73.231Q42 76.132 36.975 73.231L17.466 61.968Q12.44 59.066 12.44 53.264L12.44 30.736Q12.44 24.934 17.466 22.032Z" stroke="#FFFFFF" stroke-width="5.735" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path fill="#FFFFFF" d="M44.464 39.045Q42 40.468 39.536 39.045L28.164 32.48Q25.7 31.057 28.164 29.635L39.536 23.069Q42 21.646 44.464 23.069L55.836 29.635Q58.3 31.057 55.836 32.48Z"/>
|
||||
<path fill="#FFFFFF" d="M38.209 41.343Q40.673 42.766 40.673 45.611L40.673 58.742Q40.673 61.588 38.209 60.165L26.837 53.599Q24.373 52.177 24.373 49.332L24.373 36.2Q24.373 33.355 26.837 34.778Z"/>
|
||||
<path fill="#FFFFFF" d="M43.327 45.611Q43.327 42.766 45.791 41.343L57.163 34.778Q59.627 33.355 59.627 36.2L59.627 49.332Q59.627 52.177 57.163 53.599L45.791 60.165Q43.327 61.588 43.327 58.742Z"/>
|
||||
</g>
|
||||
<g transform="translate(114 20.752)">
|
||||
<path fill="#FFFFFF" d="M21.504 45.696L13.056 45.696L13.056 8.576L0 8.576L0 0.896L34.560 0.896L34.560 8.576L21.504 8.576 M46.720 45.696L38.656 45.696L38.656 0.896L46.720 0.896L46.720 17.856L47.872 17.856Q48.384 16.832 49.472 15.808Q50.560 14.784 52.384 14.112Q54.208 13.440 57.024 13.440Q60.736 13.440 63.520 15.136Q66.304 16.832 67.840 19.808Q69.376 22.784 69.376 26.752L69.376 45.696L61.312 45.696L61.312 27.392Q61.312 23.808 59.552 22.016Q57.792 20.224 54.528 20.224Q50.816 20.224 48.768 22.688Q46.720 25.152 46.720 29.568 M84.224 45.696L76.160 45.696L76.160 13.952L84.224 13.952L84.224 45.696M80.192 10.240Q78.016 10.240 76.512 8.832Q75.008 7.424 75.008 5.120Q75.008 2.816 76.512 1.408Q78.016 0 80.192 0Q82.432 0 83.904 1.408Q85.376 2.816 85.376 5.120Q85.376 7.424 83.904 8.832Q82.432 10.240 80.192 10.240 M99.328 45.696L91.264 45.696L91.264 13.952L99.200 13.952L99.200 18.112L100.352 18.112Q101.120 16.448 103.232 14.944Q105.344 13.440 109.632 13.440Q113.344 13.440 116.128 15.136Q118.912 16.832 120.448 19.808Q121.984 22.784 121.984 26.752L121.984 45.696L113.920 45.696L113.920 27.392Q113.920 23.808 112.160 22.016Q110.400 20.224 107.136 20.224Q103.424 20.224 101.376 22.688Q99.328 25.152 99.328 29.568 M127.232 29.952L127.232 28.928Q127.232 23.936 129.216 20.384Q131.200 16.832 134.496 14.944Q137.792 13.056 141.696 13.056Q146.048 13.056 148.288 14.592Q150.528 16.128 151.552 17.792L152.704 17.792L152.704 13.952L160.640 13.952L160.640 51.328Q160.640 54.592 158.720 56.544Q156.800 58.496 153.600 58.496L132.352 58.496L132.352 51.456L150.784 51.456Q152.576 51.456 152.576 49.536L152.576 41.280L151.424 41.280Q150.784 42.304 149.632 43.360Q148.480 44.416 146.560 45.120Q144.640 45.824 141.696 45.824Q137.792 45.824 134.496 43.936Q131.200 42.048 129.216 38.496Q127.232 34.944 127.232 29.952M144 38.784Q147.712 38.784 150.208 36.416Q152.704 34.048 152.704 29.760L152.704 29.120Q152.704 24.768 150.240 22.432Q147.776 20.096 144 20.096Q140.288 20.096 137.792 22.432Q135.296 24.768 135.296 29.120L135.296 29.760Q135.296 34.048 137.792 36.416Q140.288 38.784 144 38.784 M175.744 58.496L167.680 58.496L167.680 13.952L175.616 13.952L175.616 17.792L176.768 17.792Q177.856 15.936 180.160 14.496Q182.464 13.056 186.752 13.056Q190.592 13.056 193.856 14.944Q197.120 16.832 199.104 20.480Q201.088 24.128 201.088 29.312L201.088 30.336Q201.088 35.520 199.104 39.168Q197.120 42.816 193.856 44.704Q190.592 46.592 186.752 46.592Q183.872 46.592 181.920 45.920Q179.968 45.248 178.784 44.192Q177.600 43.136 176.896 42.048L175.744 42.048L175.744 58.496M184.320 39.552Q188.096 39.552 190.560 37.152Q193.024 34.752 193.024 30.144L193.024 29.504Q193.024 24.896 190.528 22.496Q188.032 20.096 184.320 20.096Q180.608 20.096 178.112 22.496Q175.616 24.896 175.616 29.504L175.616 30.144Q175.616 34.752 178.112 37.152Q180.608 39.552 184.320 39.552 M221.696 46.592Q216.960 46.592 213.184 44.672Q209.408 42.752 207.232 39.104Q205.056 35.456 205.056 30.336L205.056 29.312Q205.056 24.192 207.232 20.544Q209.408 16.896 213.184 14.976Q216.960 13.056 221.696 13.056Q226.432 13.056 230.208 14.976Q233.984 16.896 236.160 20.544Q238.336 24.192 238.336 29.312L238.336 30.336Q238.336 35.456 236.160 39.104Q233.984 42.752 230.208 44.672Q226.432 46.592 221.696 46.592M221.696 39.424Q225.408 39.424 227.840 37.024Q230.272 34.624 230.272 30.144L230.272 29.504Q230.272 25.024 227.872 22.624Q225.472 20.224 221.696 20.224Q217.984 20.224 215.552 22.624Q213.120 25.024 213.120 29.504L213.120 30.144Q213.120 34.624 215.552 37.024Q217.984 39.424 221.696 39.424 M251.904 45.696L243.840 45.696L243.840 13.952L251.776 13.952L251.776 17.536L252.928 17.536Q253.632 15.616 255.264 14.720Q256.896 13.824 259.072 13.824L262.912 13.824L262.912 20.992L258.944 20.992Q255.872 20.992 253.888 22.624Q251.904 24.256 251.904 27.648 M288.384 45.696L279.424 45.696Q276.288 45.696 274.336 43.744Q272.384 41.792 272.384 38.528L272.384 20.608L264.448 20.608L264.448 13.952L272.384 13.952L272.384 4.096L280.448 4.096L280.448 13.952L289.152 13.952L289.152 20.608L280.448 20.608L280.448 37.120Q280.448 39.040 282.240 39.040L288.384 39.040"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 259.972 211.838" fill="none" role="img" aria-labelledby="title">
|
||||
<title id="title">Thingport Projects stacked — Color</title>
|
||||
<g transform="translate(69.986 8)">
|
||||
<path d="M52.53 13.575Q60 9.263 67.47 13.575L96.47 30.319Q103.94 34.631 103.94 43.257L103.94 76.743Q103.94 85.369 96.47 89.681L67.47 106.425Q60 110.737 52.53 106.425L23.53 89.681Q16.06 85.369 16.06 76.743L16.06 43.257Q16.06 34.631 23.53 30.319Z" stroke="#1f1f1f" stroke-width="8.525" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path fill="#00b800" d="M63.663 55.608Q60 57.723 56.337 55.608L39.433 45.848Q35.77 43.734 39.433 41.619L56.337 31.859Q60 29.745 63.663 31.859L80.567 41.619Q84.23 43.734 80.567 45.848Z"/>
|
||||
<path fill="#00b800" d="M54.365 59.024Q58.028 61.139 58.028 65.368L58.028 84.887Q58.028 89.117 54.365 87.002L37.461 77.242Q33.798 75.128 33.798 70.898L33.798 51.379Q33.798 47.15 37.461 49.264Z"/>
|
||||
<path fill="#00b800" d="M61.972 65.368Q61.972 61.139 65.635 59.024L82.539 49.264Q86.202 47.15 86.202 51.379L86.202 70.898Q86.202 75.128 82.539 77.242L65.635 87.002Q61.972 89.117 61.972 84.887Z"/>
|
||||
</g>
|
||||
<g transform="translate(8 146)">
|
||||
<path fill="#1f1f1f" d="M18.144 38.556L11.016 38.556L11.016 7.236L0 7.236L0 0.756L29.160 0.756L29.160 7.236L18.144 7.236 M39.420 38.556L32.616 38.556L32.616 0.756L39.420 0.756L39.420 15.066L40.392 15.066Q40.824 14.202 41.742 13.338Q42.660 12.474 44.199 11.907Q45.738 11.340 48.114 11.340Q51.246 11.340 53.595 12.771Q55.944 14.202 57.240 16.713Q58.536 19.224 58.536 22.572L58.536 38.556L51.732 38.556L51.732 23.112Q51.732 20.088 50.247 18.576Q48.762 17.064 46.008 17.064Q42.876 17.064 41.148 19.143Q39.420 21.222 39.420 24.948 M71.064 38.556L64.260 38.556L64.260 11.772L71.064 11.772L71.064 38.556M67.662 8.640Q65.826 8.640 64.557 7.452Q63.288 6.264 63.288 4.320Q63.288 2.376 64.557 1.188Q65.826 0 67.662 0Q69.552 0 70.794 1.188Q72.036 2.376 72.036 4.320Q72.036 6.264 70.794 7.452Q69.552 8.640 67.662 8.640 M83.808 38.556L77.004 38.556L77.004 11.772L83.700 11.772L83.700 15.282L84.672 15.282Q85.320 13.878 87.102 12.609Q88.884 11.340 92.502 11.340Q95.634 11.340 97.983 12.771Q100.332 14.202 101.628 16.713Q102.924 19.224 102.924 22.572L102.924 38.556L96.120 38.556L96.120 23.112Q96.120 20.088 94.635 18.576Q93.150 17.064 90.396 17.064Q87.264 17.064 85.536 19.143Q83.808 21.222 83.808 24.948 M107.352 25.272L107.352 24.408Q107.352 20.196 109.026 17.199Q110.700 14.202 113.481 12.609Q116.262 11.016 119.556 11.016Q123.228 11.016 125.118 12.312Q127.008 13.608 127.872 15.012L128.844 15.012L128.844 11.772L135.540 11.772L135.540 43.308Q135.540 46.062 133.920 47.709Q132.300 49.356 129.600 49.356L111.672 49.356L111.672 43.416L127.224 43.416Q128.736 43.416 128.736 41.796L128.736 34.830L127.764 34.830Q127.224 35.694 126.252 36.585Q125.280 37.476 123.660 38.070Q122.040 38.664 119.556 38.664Q116.262 38.664 113.481 37.071Q110.700 35.478 109.026 32.481Q107.352 29.484 107.352 25.272M121.500 32.724Q124.632 32.724 126.738 30.726Q128.844 28.728 128.844 25.110L128.844 24.570Q128.844 20.898 126.765 18.927Q124.686 16.956 121.500 16.956Q118.368 16.956 116.262 18.927Q114.156 20.898 114.156 24.570L114.156 25.110Q114.156 28.728 116.262 30.726Q118.368 32.724 121.500 32.724 M148.284 49.356L141.480 49.356L141.480 11.772L148.176 11.772L148.176 15.012L149.148 15.012Q150.066 13.446 152.010 12.231Q153.954 11.016 157.572 11.016Q160.812 11.016 163.566 12.609Q166.320 14.202 167.994 17.280Q169.668 20.358 169.668 24.732L169.668 25.596Q169.668 29.970 167.994 33.048Q166.320 36.126 163.566 37.719Q160.812 39.312 157.572 39.312Q155.142 39.312 153.495 38.745Q151.848 38.178 150.849 37.287Q149.850 36.396 149.256 35.478L148.284 35.478L148.284 49.356M155.520 33.372Q158.706 33.372 160.785 31.347Q162.864 29.322 162.864 25.434L162.864 24.894Q162.864 21.006 160.758 18.981Q158.652 16.956 155.520 16.956Q152.388 16.956 150.282 18.981Q148.176 21.006 148.176 24.894L148.176 25.434Q148.176 29.322 150.282 31.347Q152.388 33.372 155.520 33.372 M187.056 39.312Q183.060 39.312 179.874 37.692Q176.688 36.072 174.852 32.994Q173.016 29.916 173.016 25.596L173.016 24.732Q173.016 20.412 174.852 17.334Q176.688 14.256 179.874 12.636Q183.060 11.016 187.056 11.016Q191.052 11.016 194.238 12.636Q197.424 14.256 199.260 17.334Q201.096 20.412 201.096 24.732L201.096 25.596Q201.096 29.916 199.260 32.994Q197.424 36.072 194.238 37.692Q191.052 39.312 187.056 39.312M187.056 33.264Q190.188 33.264 192.240 31.239Q194.292 29.214 194.292 25.434L194.292 24.894Q194.292 21.114 192.267 19.089Q190.242 17.064 187.056 17.064Q183.924 17.064 181.872 19.089Q179.820 21.114 179.820 24.894L179.820 25.434Q179.820 29.214 181.872 31.239Q183.924 33.264 187.056 33.264 M212.544 38.556L205.740 38.556L205.740 11.772L212.436 11.772L212.436 14.796L213.408 14.796Q214.002 13.176 215.379 12.420Q216.756 11.664 218.592 11.664L221.832 11.664L221.832 17.712L218.484 17.712Q215.892 17.712 214.218 19.089Q212.544 20.466 212.544 23.328 M243.324 38.556L235.764 38.556Q233.118 38.556 231.471 36.909Q229.824 35.262 229.824 32.508L229.824 17.388L223.128 17.388L223.128 11.772L229.824 11.772L229.824 3.456L236.628 3.456L236.628 11.772L243.972 11.772L243.972 17.388L236.628 17.388L236.628 31.320Q236.628 32.940 238.140 32.940L243.324 32.940"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 259.972 211.838" fill="none" role="img" aria-labelledby="title">
|
||||
<title id="title">Thingport Projects stacked — Dark</title>
|
||||
<g transform="translate(69.986 8)">
|
||||
<path d="M52.53 13.575Q60 9.263 67.47 13.575L96.47 30.319Q103.94 34.631 103.94 43.257L103.94 76.743Q103.94 85.369 96.47 89.681L67.47 106.425Q60 110.737 52.53 106.425L23.53 89.681Q16.06 85.369 16.06 76.743L16.06 43.257Q16.06 34.631 23.53 30.319Z" stroke="#1f1f1f" stroke-width="8.525" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path fill="#1f1f1f" d="M63.663 55.608Q60 57.723 56.337 55.608L39.433 45.848Q35.77 43.734 39.433 41.619L56.337 31.859Q60 29.745 63.663 31.859L80.567 41.619Q84.23 43.734 80.567 45.848Z"/>
|
||||
<path fill="#1f1f1f" d="M54.365 59.024Q58.028 61.139 58.028 65.368L58.028 84.887Q58.028 89.117 54.365 87.002L37.461 77.242Q33.798 75.128 33.798 70.898L33.798 51.379Q33.798 47.15 37.461 49.264Z"/>
|
||||
<path fill="#1f1f1f" d="M61.972 65.368Q61.972 61.139 65.635 59.024L82.539 49.264Q86.202 47.15 86.202 51.379L86.202 70.898Q86.202 75.128 82.539 77.242L65.635 87.002Q61.972 89.117 61.972 84.887Z"/>
|
||||
</g>
|
||||
<g transform="translate(8 146)">
|
||||
<path fill="#1f1f1f" d="M18.144 38.556L11.016 38.556L11.016 7.236L0 7.236L0 0.756L29.160 0.756L29.160 7.236L18.144 7.236 M39.420 38.556L32.616 38.556L32.616 0.756L39.420 0.756L39.420 15.066L40.392 15.066Q40.824 14.202 41.742 13.338Q42.660 12.474 44.199 11.907Q45.738 11.340 48.114 11.340Q51.246 11.340 53.595 12.771Q55.944 14.202 57.240 16.713Q58.536 19.224 58.536 22.572L58.536 38.556L51.732 38.556L51.732 23.112Q51.732 20.088 50.247 18.576Q48.762 17.064 46.008 17.064Q42.876 17.064 41.148 19.143Q39.420 21.222 39.420 24.948 M71.064 38.556L64.260 38.556L64.260 11.772L71.064 11.772L71.064 38.556M67.662 8.640Q65.826 8.640 64.557 7.452Q63.288 6.264 63.288 4.320Q63.288 2.376 64.557 1.188Q65.826 0 67.662 0Q69.552 0 70.794 1.188Q72.036 2.376 72.036 4.320Q72.036 6.264 70.794 7.452Q69.552 8.640 67.662 8.640 M83.808 38.556L77.004 38.556L77.004 11.772L83.700 11.772L83.700 15.282L84.672 15.282Q85.320 13.878 87.102 12.609Q88.884 11.340 92.502 11.340Q95.634 11.340 97.983 12.771Q100.332 14.202 101.628 16.713Q102.924 19.224 102.924 22.572L102.924 38.556L96.120 38.556L96.120 23.112Q96.120 20.088 94.635 18.576Q93.150 17.064 90.396 17.064Q87.264 17.064 85.536 19.143Q83.808 21.222 83.808 24.948 M107.352 25.272L107.352 24.408Q107.352 20.196 109.026 17.199Q110.700 14.202 113.481 12.609Q116.262 11.016 119.556 11.016Q123.228 11.016 125.118 12.312Q127.008 13.608 127.872 15.012L128.844 15.012L128.844 11.772L135.540 11.772L135.540 43.308Q135.540 46.062 133.920 47.709Q132.300 49.356 129.600 49.356L111.672 49.356L111.672 43.416L127.224 43.416Q128.736 43.416 128.736 41.796L128.736 34.830L127.764 34.830Q127.224 35.694 126.252 36.585Q125.280 37.476 123.660 38.070Q122.040 38.664 119.556 38.664Q116.262 38.664 113.481 37.071Q110.700 35.478 109.026 32.481Q107.352 29.484 107.352 25.272M121.500 32.724Q124.632 32.724 126.738 30.726Q128.844 28.728 128.844 25.110L128.844 24.570Q128.844 20.898 126.765 18.927Q124.686 16.956 121.500 16.956Q118.368 16.956 116.262 18.927Q114.156 20.898 114.156 24.570L114.156 25.110Q114.156 28.728 116.262 30.726Q118.368 32.724 121.500 32.724 M148.284 49.356L141.480 49.356L141.480 11.772L148.176 11.772L148.176 15.012L149.148 15.012Q150.066 13.446 152.010 12.231Q153.954 11.016 157.572 11.016Q160.812 11.016 163.566 12.609Q166.320 14.202 167.994 17.280Q169.668 20.358 169.668 24.732L169.668 25.596Q169.668 29.970 167.994 33.048Q166.320 36.126 163.566 37.719Q160.812 39.312 157.572 39.312Q155.142 39.312 153.495 38.745Q151.848 38.178 150.849 37.287Q149.850 36.396 149.256 35.478L148.284 35.478L148.284 49.356M155.520 33.372Q158.706 33.372 160.785 31.347Q162.864 29.322 162.864 25.434L162.864 24.894Q162.864 21.006 160.758 18.981Q158.652 16.956 155.520 16.956Q152.388 16.956 150.282 18.981Q148.176 21.006 148.176 24.894L148.176 25.434Q148.176 29.322 150.282 31.347Q152.388 33.372 155.520 33.372 M187.056 39.312Q183.060 39.312 179.874 37.692Q176.688 36.072 174.852 32.994Q173.016 29.916 173.016 25.596L173.016 24.732Q173.016 20.412 174.852 17.334Q176.688 14.256 179.874 12.636Q183.060 11.016 187.056 11.016Q191.052 11.016 194.238 12.636Q197.424 14.256 199.260 17.334Q201.096 20.412 201.096 24.732L201.096 25.596Q201.096 29.916 199.260 32.994Q197.424 36.072 194.238 37.692Q191.052 39.312 187.056 39.312M187.056 33.264Q190.188 33.264 192.240 31.239Q194.292 29.214 194.292 25.434L194.292 24.894Q194.292 21.114 192.267 19.089Q190.242 17.064 187.056 17.064Q183.924 17.064 181.872 19.089Q179.820 21.114 179.820 24.894L179.820 25.434Q179.820 29.214 181.872 31.239Q183.924 33.264 187.056 33.264 M212.544 38.556L205.740 38.556L205.740 11.772L212.436 11.772L212.436 14.796L213.408 14.796Q214.002 13.176 215.379 12.420Q216.756 11.664 218.592 11.664L221.832 11.664L221.832 17.712L218.484 17.712Q215.892 17.712 214.218 19.089Q212.544 20.466 212.544 23.328 M243.324 38.556L235.764 38.556Q233.118 38.556 231.471 36.909Q229.824 35.262 229.824 32.508L229.824 17.388L223.128 17.388L223.128 11.772L229.824 11.772L229.824 3.456L236.628 3.456L236.628 11.772L243.972 11.772L243.972 17.388L236.628 17.388L236.628 31.320Q236.628 32.940 238.140 32.940L243.324 32.940"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 259.972 211.838" fill="none" role="img" aria-labelledby="title">
|
||||
<title id="title">Thingport Projects stacked — Green</title>
|
||||
<g transform="translate(69.986 8)">
|
||||
<path d="M52.53 13.575Q60 9.263 67.47 13.575L96.47 30.319Q103.94 34.631 103.94 43.257L103.94 76.743Q103.94 85.369 96.47 89.681L67.47 106.425Q60 110.737 52.53 106.425L23.53 89.681Q16.06 85.369 16.06 76.743L16.06 43.257Q16.06 34.631 23.53 30.319Z" stroke="#00b800" stroke-width="8.525" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path fill="#00b800" d="M63.663 55.608Q60 57.723 56.337 55.608L39.433 45.848Q35.77 43.734 39.433 41.619L56.337 31.859Q60 29.745 63.663 31.859L80.567 41.619Q84.23 43.734 80.567 45.848Z"/>
|
||||
<path fill="#00b800" d="M54.365 59.024Q58.028 61.139 58.028 65.368L58.028 84.887Q58.028 89.117 54.365 87.002L37.461 77.242Q33.798 75.128 33.798 70.898L33.798 51.379Q33.798 47.15 37.461 49.264Z"/>
|
||||
<path fill="#00b800" d="M61.972 65.368Q61.972 61.139 65.635 59.024L82.539 49.264Q86.202 47.15 86.202 51.379L86.202 70.898Q86.202 75.128 82.539 77.242L65.635 87.002Q61.972 89.117 61.972 84.887Z"/>
|
||||
</g>
|
||||
<g transform="translate(8 146)">
|
||||
<path fill="#00b800" d="M18.144 38.556L11.016 38.556L11.016 7.236L0 7.236L0 0.756L29.160 0.756L29.160 7.236L18.144 7.236 M39.420 38.556L32.616 38.556L32.616 0.756L39.420 0.756L39.420 15.066L40.392 15.066Q40.824 14.202 41.742 13.338Q42.660 12.474 44.199 11.907Q45.738 11.340 48.114 11.340Q51.246 11.340 53.595 12.771Q55.944 14.202 57.240 16.713Q58.536 19.224 58.536 22.572L58.536 38.556L51.732 38.556L51.732 23.112Q51.732 20.088 50.247 18.576Q48.762 17.064 46.008 17.064Q42.876 17.064 41.148 19.143Q39.420 21.222 39.420 24.948 M71.064 38.556L64.260 38.556L64.260 11.772L71.064 11.772L71.064 38.556M67.662 8.640Q65.826 8.640 64.557 7.452Q63.288 6.264 63.288 4.320Q63.288 2.376 64.557 1.188Q65.826 0 67.662 0Q69.552 0 70.794 1.188Q72.036 2.376 72.036 4.320Q72.036 6.264 70.794 7.452Q69.552 8.640 67.662 8.640 M83.808 38.556L77.004 38.556L77.004 11.772L83.700 11.772L83.700 15.282L84.672 15.282Q85.320 13.878 87.102 12.609Q88.884 11.340 92.502 11.340Q95.634 11.340 97.983 12.771Q100.332 14.202 101.628 16.713Q102.924 19.224 102.924 22.572L102.924 38.556L96.120 38.556L96.120 23.112Q96.120 20.088 94.635 18.576Q93.150 17.064 90.396 17.064Q87.264 17.064 85.536 19.143Q83.808 21.222 83.808 24.948 M107.352 25.272L107.352 24.408Q107.352 20.196 109.026 17.199Q110.700 14.202 113.481 12.609Q116.262 11.016 119.556 11.016Q123.228 11.016 125.118 12.312Q127.008 13.608 127.872 15.012L128.844 15.012L128.844 11.772L135.540 11.772L135.540 43.308Q135.540 46.062 133.920 47.709Q132.300 49.356 129.600 49.356L111.672 49.356L111.672 43.416L127.224 43.416Q128.736 43.416 128.736 41.796L128.736 34.830L127.764 34.830Q127.224 35.694 126.252 36.585Q125.280 37.476 123.660 38.070Q122.040 38.664 119.556 38.664Q116.262 38.664 113.481 37.071Q110.700 35.478 109.026 32.481Q107.352 29.484 107.352 25.272M121.500 32.724Q124.632 32.724 126.738 30.726Q128.844 28.728 128.844 25.110L128.844 24.570Q128.844 20.898 126.765 18.927Q124.686 16.956 121.500 16.956Q118.368 16.956 116.262 18.927Q114.156 20.898 114.156 24.570L114.156 25.110Q114.156 28.728 116.262 30.726Q118.368 32.724 121.500 32.724 M148.284 49.356L141.480 49.356L141.480 11.772L148.176 11.772L148.176 15.012L149.148 15.012Q150.066 13.446 152.010 12.231Q153.954 11.016 157.572 11.016Q160.812 11.016 163.566 12.609Q166.320 14.202 167.994 17.280Q169.668 20.358 169.668 24.732L169.668 25.596Q169.668 29.970 167.994 33.048Q166.320 36.126 163.566 37.719Q160.812 39.312 157.572 39.312Q155.142 39.312 153.495 38.745Q151.848 38.178 150.849 37.287Q149.850 36.396 149.256 35.478L148.284 35.478L148.284 49.356M155.520 33.372Q158.706 33.372 160.785 31.347Q162.864 29.322 162.864 25.434L162.864 24.894Q162.864 21.006 160.758 18.981Q158.652 16.956 155.520 16.956Q152.388 16.956 150.282 18.981Q148.176 21.006 148.176 24.894L148.176 25.434Q148.176 29.322 150.282 31.347Q152.388 33.372 155.520 33.372 M187.056 39.312Q183.060 39.312 179.874 37.692Q176.688 36.072 174.852 32.994Q173.016 29.916 173.016 25.596L173.016 24.732Q173.016 20.412 174.852 17.334Q176.688 14.256 179.874 12.636Q183.060 11.016 187.056 11.016Q191.052 11.016 194.238 12.636Q197.424 14.256 199.260 17.334Q201.096 20.412 201.096 24.732L201.096 25.596Q201.096 29.916 199.260 32.994Q197.424 36.072 194.238 37.692Q191.052 39.312 187.056 39.312M187.056 33.264Q190.188 33.264 192.240 31.239Q194.292 29.214 194.292 25.434L194.292 24.894Q194.292 21.114 192.267 19.089Q190.242 17.064 187.056 17.064Q183.924 17.064 181.872 19.089Q179.820 21.114 179.820 24.894L179.820 25.434Q179.820 29.214 181.872 31.239Q183.924 33.264 187.056 33.264 M212.544 38.556L205.740 38.556L205.740 11.772L212.436 11.772L212.436 14.796L213.408 14.796Q214.002 13.176 215.379 12.420Q216.756 11.664 218.592 11.664L221.832 11.664L221.832 17.712L218.484 17.712Q215.892 17.712 214.218 19.089Q212.544 20.466 212.544 23.328 M243.324 38.556L235.764 38.556Q233.118 38.556 231.471 36.909Q229.824 35.262 229.824 32.508L229.824 17.388L223.128 17.388L223.128 11.772L229.824 11.772L229.824 3.456L236.628 3.456L236.628 11.772L243.972 11.772L243.972 17.388L236.628 17.388L236.628 31.320Q236.628 32.940 238.140 32.940L243.324 32.940"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |