first commit
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules/
|
||||||
|
ui/
|
||||||
|
dist/
|
||||||
|
|
||||||
+149
@@ -0,0 +1,149 @@
|
|||||||
|
# Developing and Simulating on Linux
|
||||||
|
|
||||||
|
When developing custom CNC plugins—especially those manipulating Work Coordinate Systems (WCS)—testing on physical hardware can result in dangerous and costly crashes.
|
||||||
|
|
||||||
|
Fortunately, FluidNC provides a native PC port that can run directly on Linux. By pairing your local instance of gSender Edge with this virtual controller, you can rapidly test UI changes, G-code injection, and coordinate transformations in a 100% safe environment.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Ensure your Linux environment has the required build tools and dependencies. You will need Git, Python, `socat` (for the virtual serial cable), and `picocom` (for testing). To compile gSender locally, you will also need the C++ build chain, curl, and some specific UI libraries.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install git python3 python3-venv socat picocom build-essential libudev-dev libgtk-3-dev curl
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Set Up PlatformIO and Compile FluidNC
|
||||||
|
|
||||||
|
FluidNC uses PlatformIO for its build system. Before compiling, you need to create a Python virtual environment and install the PlatformIO CLI.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create and activate a Python virtual environment
|
||||||
|
python3 -m venv pio_env
|
||||||
|
source pio_env/bin/activate
|
||||||
|
|
||||||
|
# Install PlatformIO
|
||||||
|
pip install platformio
|
||||||
|
```
|
||||||
|
|
||||||
|
With PlatformIO active, clone the FluidNC repository and compile the native Linux 64-bit simulator.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/bdring/FluidNC.git
|
||||||
|
cd FluidNC
|
||||||
|
pio run -e linux_x86_64
|
||||||
|
```
|
||||||
|
*(This will download the necessary toolchains and output the compiled program to `./.pio/build/linux_x86_64/program`)*
|
||||||
|
|
||||||
|
## Step 2: Initialize Virtual Filesystem & Configuration
|
||||||
|
|
||||||
|
The Linux simulator expects a local folder named `native_localfs` to act as flash storage. Create the folder and copy in the standard test-drive configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From the root of the FluidNC directory:
|
||||||
|
mkdir native_localfs
|
||||||
|
cp ./FluidNC/data/config.yaml ./native_localfs/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Connect gSender (Choose Method A or B)
|
||||||
|
|
||||||
|
You can connect gSender to the simulator using either a Virtual Serial Port or a local Network Socket (Ethernet). The Ethernet method is faster, but the Serial method (see **Method B**) mimics physical hardware more closely.
|
||||||
|
|
||||||
|
### Method A: The Ethernet Shortcut (Recommended)
|
||||||
|
|
||||||
|
FluidNC natively simulates the ESP32's network stack by opening a Telnet server on port 23.
|
||||||
|
|
||||||
|
1. Grant the simulator permission to bind to privileged network ports:
|
||||||
|
```bash
|
||||||
|
sudo setcap 'cap_net_bind_service=+ep' ./.pio/build/linux_x86_64/program
|
||||||
|
```
|
||||||
|
2. Run the simulator normally:
|
||||||
|
```bash
|
||||||
|
./.pio/build/linux_x86_64/program
|
||||||
|
```
|
||||||
|
3. In gSender, select the **Ethernet** connection option and connect to `127.0.0.1` on port `23`.
|
||||||
|
4. **WAKE THE GUI:** Because Telnet connections do not trigger a hardware reset, FluidNC will not automatically send its welcome string, leaving gSender's UI disabled. **Open the Console tab in gSender, type `?`, and press Enter.** The controller will respond, and the UI will unlock into the `Idle` state.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Method B: Virtual Serial Port (socat)
|
||||||
|
|
||||||
|
If you need to test standard serial behavior, use `socat` to create a virtual null-modem cable. We name the gSender side `ttyCNC` to trick gSender's strict hardware filters into listing the port.
|
||||||
|
|
||||||
|
1. Open **Terminal A** and create the virtual cable:
|
||||||
|
```bash
|
||||||
|
sudo socat -d -d \
|
||||||
|
pty,link=/dev/ttyCNC,raw,echo=0,b115200,mode=666 \
|
||||||
|
pty,link=/dev/ttyVIRT,raw,echo=0,b115200,mode=666
|
||||||
|
```
|
||||||
|
2. Open **Terminal B** and run the simulator, piping its input/output directly into the virtual cable:
|
||||||
|
```bash
|
||||||
|
./.pio/build/linux_x86_64/program < /dev/ttyVIRT > /dev/ttyVIRT
|
||||||
|
```
|
||||||
|
*(Note: This terminal will appear to freeze. This is correct! All output is being routed into the virtual cable.)*
|
||||||
|
3. Add **ports** section into `~/.sender_rc`. It should look like this:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"path": "/dev/ttyCNC",
|
||||||
|
"manufacturer": "Virtual CNC",
|
||||||
|
"vendorId": "0403",
|
||||||
|
"productId": "6001"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"events": {},
|
||||||
|
// other definitions
|
||||||
|
}
|
||||||
|
```
|
||||||
|
4. Open **gSender**, select **Serial**, choose `ttyCNC` from the Ports list, (make sure the baudrate is set to `115200` in the **Config->Basic->Baud** rate), and click **Connect**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: Test Your Plugin
|
||||||
|
|
||||||
|
Your fully functional virtual CNC machine is now running!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5: Run gSender for Development
|
||||||
|
|
||||||
|
If you're actively developing custom CNC plugins or modifying gSender's UI, it is highly recommended to run gSender from its source code. This enables hot-reloading for faster iteration. The following instructions are adapted from the official [Sienci Compile gSender guide](https://resources.sienci.com/view/gs-compile/).
|
||||||
|
|
||||||
|
### 1. Install Node.js and Yarn
|
||||||
|
gSender is a Node.js/Electron application. We will use Node Version Manager (NVM) to install the necessary Node.js version.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install Node Version Manager (NVM)
|
||||||
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
|
||||||
|
source "$HOME/.nvm/nvm.sh"
|
||||||
|
|
||||||
|
# Install Node.js (v24.x is currently recommended)
|
||||||
|
nvm install 24
|
||||||
|
|
||||||
|
# Enable Yarn package manager
|
||||||
|
corepack enable yarn
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Clone gSender and Install Dependencies
|
||||||
|
```bash
|
||||||
|
# Clone the gSender repository
|
||||||
|
git clone https://github.com/Sienci-Labs/gsender.git
|
||||||
|
cd gsender
|
||||||
|
|
||||||
|
# Install all required packages (this may take some time to compile native modules)
|
||||||
|
yarn install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Launch Development Mode
|
||||||
|
To start gSender in development mode with hot-reloading enabled:
|
||||||
|
```bash
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
*(This launches the local development server. You can open gSender in your browser at `http://localhost:8000`, or via the automatically launched Electron window. Any changes you make to the source code will automatically reload.)*
|
||||||
|
|
||||||
|
Once running, you can connect this development instance of gSender directly to your FluidNC simulator using either the Ethernet or Virtual Serial method outlined in Step 3!
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
# gSender Vision Alignment Plugin (FluidNC)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
The **Vision Alignment Plugin** is a custom extension for gSender (v1.7.0-Edge-1 and later) running on **Linux**. It interfaces with FluidNC-based CNC controllers and uses a live camera feed with a digital cross-hair overlay for highly accurate stock alignment.
|
||||||
|
|
||||||
|
By guiding the user to locate three known fiducial markers on their stock, the plugin mathematically determines the exact X/Y workspace origin and the angular deviation of the material. Since FluidNC does not natively support hardware-level coordinate rotation (`G68`), the plugin automatically sets the work zero and **transforms the loaded G-code in software**—applying a rotation matrix to compensate for the skewed stock—completely eliminating the need for perfect physical stock alignment.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
* **Live Camera Feed:** Streams real-time video directly within the gSender interface using any standard USB webcam or microscope camera.
|
||||||
|
* **Software Cross-Hair Overlay:** Provides a customizable, digital center-target for high-precision visual probing.
|
||||||
|
* **Three-Point Fiducial Registration:**
|
||||||
|
* Captures machine coordinates at three distinct points on the stock.
|
||||||
|
* Point 1 establishes the reference origin.
|
||||||
|
* Points 2 and 3 establish the true X/Y axes vectors and verify scale/skew.
|
||||||
|
* **Software-Side Skew Compensation:** Calculates the exact rotational angle (θ) of the stock. The plugin then parses your loaded G-code and applies a rotational matrix (adjusting all `X`, `Y`, `I`, and `J` values) to dynamically match the skew of your stock before sending the job to the controller.
|
||||||
|
* **Automated Work Zeroing:** Automatically calculates and applies camera-to-spindle offsets and issues `G10 L20` commands to establish the new Work Coordinate System (WCS).
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
* **gSender:** v1.7.0-Edge-1 or later on Linux (plugin system required).
|
||||||
|
* **Controller:** A CNC machine running **FluidNC** firmware (or any Grbl / grblHAL-based firmware).
|
||||||
|
* **Hardware:** A rigidly mounted camera (e.g., attached to the spindle or Z-axis carriage) with calibrated X/Y offsets.
|
||||||
|
* **Development Environment:** Node.js 18+ and yarn (or npm).
|
||||||
|
|
||||||
|
## Installation (Linux end users)
|
||||||
|
|
||||||
|
gSender loads plugins from:
|
||||||
|
|
||||||
|
```text
|
||||||
|
~/.config/gSender/plugins/
|
||||||
|
```
|
||||||
|
|
||||||
|
(The exact path is also shown under **Tools → Plugins** inside the app.)
|
||||||
|
|
||||||
|
1. Obtain a **built** plugin folder (one that already contains `gsender-plugin.json` **and** a `ui/` directory with `index.html`).
|
||||||
|
2. Copy that folder into `~/.config/gSender/plugins/`, **or** use **Tools → Plugins → Import** and select the folder.
|
||||||
|
3. Restart gSender. The plugin appears on the **Tools** page.
|
||||||
|
|
||||||
|
> Plugins with a `com.sienci.*` id are labelled “Sienci official”; all others appear as “Community”.
|
||||||
|
|
||||||
|
## Usage Guide
|
||||||
|
1. Open the **Tools** page and launch **Vision Alignment**.
|
||||||
|
2. Select your camera device from the dropdown to initialize the feed.
|
||||||
|
3. **Calibrate Camera Offset (if not already done):** Enter the physical X and Y distance between your spindle center and camera center.
|
||||||
|
4. **Point 1 (Origin):** Jog the machine until the cross-hair is perfectly centered on your first fiducial. Click **"Mark Point 1"**.
|
||||||
|
5. **Point 2 (Axis Reference):** Jog to the second fiducial (defines your primary axis line) and click **"Mark Point 2"**.
|
||||||
|
6. **Point 3 (Verification):** Jog to the third fiducial and click **"Mark Point 3"**.
|
||||||
|
7. Click **"Align & Zero Workspace"**.
|
||||||
|
* The plugin processes the affine transformation math.
|
||||||
|
* It zeros the workspace to the calculated origin point.
|
||||||
|
* It applies a rotation matrix to the loaded G-code so the toolpath matches the rotational skew of your stock.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Plugin architecture (gSender 1.7+ on Linux)
|
||||||
|
|
||||||
|
gSender plugins are self-contained SPA folders. Each plugin **must** contain:
|
||||||
|
|
||||||
|
* `gsender-plugin.json` – manifest
|
||||||
|
* `ui/` – **built** frontend (produced by Vite; not present until you build)
|
||||||
|
|
||||||
|
### Manifest (`gsender-plugin.json`)
|
||||||
|
|
||||||
|
| Field | Required | Description |
|
||||||
|
|--------------------|----------|-------------|
|
||||||
|
| `id` | yes | Unique reverse-DNS id (e.g. `com.yourname.vision-alignment`). |
|
||||||
|
| `name` | yes | Display name shown on the Tools page. |
|
||||||
|
| `version` | yes | Semver string. |
|
||||||
|
| `description` | no | Short blurb on the plugin card. |
|
||||||
|
| `engine` | no | Compatible gSender version range (e.g. `>=1.7.0`). |
|
||||||
|
| `ui.entry` | yes | Path to the built entry HTML (usually `ui/index.html`). |
|
||||||
|
| `ui.contributions` | no | Array of `{ slot, route, label }` – most plugins use `"slot": "tools-page"`. |
|
||||||
|
| `capabilities` | no | Bridge permissions the plugin is allowed to use. |
|
||||||
|
|
||||||
|
Example skeleton:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "com.yourname.vision-alignment",
|
||||||
|
"name": "Vision Alignment",
|
||||||
|
"description": "Camera-based stock alignment and software skew compensation for FluidNC.",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"engine": ">=1.7.0",
|
||||||
|
"ui": {
|
||||||
|
"entry": "ui/index.html",
|
||||||
|
"contributions": [
|
||||||
|
{
|
||||||
|
"slot": "tools-page",
|
||||||
|
"route": "vision-alignment",
|
||||||
|
"label": "Vision Alignment"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"capabilities": {
|
||||||
|
"requestTypes": ["gcode:load:to:visualizer"],
|
||||||
|
"topics": ["workspace"],
|
||||||
|
"allowedFunctions": ["gcode", "machine", "useWorkspaceState"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The bridge **denies** any call the plugin was not granted.
|
||||||
|
|
||||||
|
### Official examples & SDK
|
||||||
|
|
||||||
|
Official examples live in the gSender repo under [`plugins/`](https://github.com/Sienci-Labs/gsender/tree/v1.7.0-Edge-1/plugins):
|
||||||
|
|
||||||
|
| Folder | Stack | Demonstrates |
|
||||||
|
|-------------------|--------------------------------|--------------|
|
||||||
|
| `example-hello/` | Plain JS + Vite | Bridge client, subscriptions |
|
||||||
|
| `react-ts-app/` | React + TypeScript + Vite | React hooks |
|
||||||
|
| `example-viewer/` | Plain JS + Vite | Embedded G-code preview |
|
||||||
|
| `basic-cam/` | React + TypeScript + Vite + Tailwind | Full reference CAM plugin |
|
||||||
|
|
||||||
|
SDK package: `@sienci/gsender-plugin-sdk` (source lives in `packages/plugin-sdk`).
|
||||||
|
|
||||||
|
| Import | Purpose |
|
||||||
|
|--------|---------|
|
||||||
|
| `@sienci/gsender-plugin-sdk` | Framework-agnostic bridge |
|
||||||
|
| `@sienci/gsender-plugin-sdk/react` | React hooks |
|
||||||
|
| `@sienci/gsender-plugin-sdk/viewer` | G-code viewer (`@sienci/gviewer`) |
|
||||||
|
| `@sienci/gsender-plugin-sdk/vite` | Vite plugin for correct externalisation & import map |
|
||||||
|
|
||||||
|
Always build plugins with the SDK’s Vite helper:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite.config.ts
|
||||||
|
import gsenderPlugin from "@sienci/gsender-plugin-sdk/vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react(), gsenderPlugin()],
|
||||||
|
base: "./",
|
||||||
|
build: { outDir: "ui", emptyOutDir: true },
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Dark mode is applied by gSender as the class `html.dark` on the plugin iframe—use class-based dark styles (or Tailwind `dark:` with class strategy), not `prefers-color-scheme`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Building plugins (Linux development)
|
||||||
|
|
||||||
|
### Important: build the SDK first
|
||||||
|
|
||||||
|
The example plugins depend on the local SDK via `file:../../packages/plugin-sdk`. That package’s `package.json` points at `./dist/…`, which **does not exist until you build it**. If you skip this step you will see:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Failed to resolve entry for package "@sienci/gsender-plugin-sdk"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step-by-step
|
||||||
|
|
||||||
|
From the **gSender repo root** (`~/proj/gsender` or similar):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Build the plugin SDK (creates packages/plugin-sdk/dist/)
|
||||||
|
cd packages/plugin-sdk
|
||||||
|
yarn install
|
||||||
|
yarn build
|
||||||
|
|
||||||
|
# 2. Build the example plugin(s) you need
|
||||||
|
cd ../../plugins/example-hello
|
||||||
|
yarn install
|
||||||
|
yarn build # creates ui/index.html
|
||||||
|
|
||||||
|
# Repeat for other examples if desired:
|
||||||
|
# cd ../react-ts-app && yarn install && yarn build
|
||||||
|
# cd ../example-viewer && yarn install && yarn build
|
||||||
|
# cd ../basic-cam && yarn install && yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
Or build everything in one go:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/proj/gsender
|
||||||
|
|
||||||
|
(cd packages/plugin-sdk && yarn install && yarn build)
|
||||||
|
|
||||||
|
for d in plugins/example-hello plugins/react-ts-app plugins/example-viewer plugins/basic-cam; do
|
||||||
|
echo "=== Building $d ==="
|
||||||
|
(cd "$d" && yarn install && yarn build)
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
After a successful build you should have:
|
||||||
|
|
||||||
|
```text
|
||||||
|
plugins/example-hello/ui/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
### “UI entry not found: ui/index.html”
|
||||||
|
|
||||||
|
This red message in **Tools → Plugins** means the plugin folder exists but has not been built (no `ui/` directory). Run `yarn build` inside the plugin folder (after the SDK is built) and refresh/restart gSender.
|
||||||
|
|
||||||
|
### Watch mode while developing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd plugins/example-hello # or your own plugin
|
||||||
|
yarn build -- --watch
|
||||||
|
```
|
||||||
|
|
||||||
|
gSender (when started with `yarn dev` / `NODE_ENV=development`) watches each plugin’s `ui/` directory and reloads the iframe automatically.
|
||||||
|
|
||||||
|
### Starting from an official template
|
||||||
|
|
||||||
|
1. Copy the closest example (`example-hello`, `react-ts-app`, `example-viewer`, or `basic-cam`).
|
||||||
|
2. Edit `gsender-plugin.json` – change `id`, `name`, `description`, `route`, and `label`.
|
||||||
|
3. Rename the folder if desired (the manifest `id` is what matters).
|
||||||
|
4. Ensure the SDK is built (`packages/plugin-sdk` → `yarn build`).
|
||||||
|
5. `yarn install && yarn build` inside the new plugin folder.
|
||||||
|
6. Place the folder in `~/.config/gSender/plugins/` **or** keep it under the gSender repo’s `plugins/` for local dev.
|
||||||
|
|
||||||
|
### Local development inside the gSender source tree
|
||||||
|
|
||||||
|
When gSender runs in development (`NODE_ENV=development`, e.g. `yarn dev` or `yarn electron:hot`):
|
||||||
|
|
||||||
|
* Plugins are loaded from **both** the repo’s `plugins/` folder **and** `~/.config/gSender/plugins/`.
|
||||||
|
* Repo plugins take precedence when ids collide.
|
||||||
|
* Extra search paths: `GSENDER_PLUGINS_DIRS` (colon-separated on Linux).
|
||||||
|
* Adding a brand-new plugin folder requires a one-time server restart; subsequent edits hot-reload once `ui/` is present.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Development & Testing Workflow
|
||||||
|
|
||||||
|
### Leveraging the FluidNC PC Simulation Feature
|
||||||
|
|
||||||
|
When developing a CNC plugin that manipulates Work Coordinate Systems and modifies G-code on the fly, testing on a physical machine carries a high risk of accidental crashes. **Because you are developing on Linux, the FluidNC PC Simulation feature is highly recommended.**
|
||||||
|
|
||||||
|
FluidNC provides a **PC port/simulation mode** that compiles and runs the firmware as a native executable, completely bypassing the need for an ESP32 microcontroller or physical steppers.
|
||||||
|
|
||||||
|
#### Why it is critical for this plugin’s development
|
||||||
|
1. **Zero Hardware Risk:** Safely test the trigonometry for software-side G-code transformation (linear moves and arcs) and workspace zeroing (`G10 L20`) without risk of plunging a real endmill into the spoilboard.
|
||||||
|
2. **Rapid Iteration:** Run gSender Edge and the FluidNC simulator side-by-side on the same Linux machine—no workshop computer or hardware cabling required for UI/math work.
|
||||||
|
3. **Toolpath Verification:** The simulator behaves identically to real FluidNC firmware. Send the dynamically rotated G-code from the plugin and query the controller (`?` or `$#`) to confirm the virtual machine executes the skewed toolpath correctly.
|
||||||
|
|
||||||
|
#### Setup Guide for the Simulator
|
||||||
|
See the [Development](Development.md) guide.
|
||||||
|
|
||||||
|
## License
|
||||||
|
MIT License. See [LICENSE](LICENSE) for more information.
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"id": "com.example.vision-alignment",
|
||||||
|
"name": "Vision Alignment",
|
||||||
|
"description": "Camera-based stock alignment with three-point registration and software skew compensation for FluidNC/Grbl.",
|
||||||
|
"version": "0.2.1",
|
||||||
|
"engine": ">=1.7.0",
|
||||||
|
"ui": {
|
||||||
|
"entry": "ui/index.html",
|
||||||
|
"contributions": [
|
||||||
|
{
|
||||||
|
"slot": "tools-page",
|
||||||
|
"route": "vision-alignment",
|
||||||
|
"label": "Vision Alignment"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"capabilities": {
|
||||||
|
"requestTypes": [
|
||||||
|
"gcode:load:to:visualizer",
|
||||||
|
"machine:command",
|
||||||
|
"machine:get:context",
|
||||||
|
"workspace:get:state",
|
||||||
|
"redux:get:state"
|
||||||
|
],
|
||||||
|
"topics": ["workspace", "redux"],
|
||||||
|
"allowedFunctions": ["gcode", "machine", "useWorkspaceState", "redux"]
|
||||||
|
}
|
||||||
|
}
|
||||||
+178
@@ -0,0 +1,178 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vision Alignment</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<header class="header">
|
||||||
|
<h1>Vision Alignment</h1>
|
||||||
|
<p class="subtitle">
|
||||||
|
Three-point fiducial registration · camera offset · software skew compensation
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="controls">
|
||||||
|
<label class="field">
|
||||||
|
<span>Camera</span>
|
||||||
|
<select id="camera-select">
|
||||||
|
<option value="">— select after starting —</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<div class="btn-row">
|
||||||
|
<button type="button" id="btn-start" class="btn primary">Start camera</button>
|
||||||
|
<button type="button" id="btn-stop" class="btn" disabled>Stop</button>
|
||||||
|
</div>
|
||||||
|
<p id="status" class="status" role="status">Camera idle</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="viewport-wrap">
|
||||||
|
<div class="camera-viewport" id="viewport">
|
||||||
|
<video id="cam" autoplay playsinline muted></video>
|
||||||
|
<div class="crosshair" aria-hidden="true"><span class="ring"></span></div>
|
||||||
|
<div class="placeholder" id="placeholder">
|
||||||
|
Start the camera, jog so the cross-hair is on a fiducial, then mark points.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="controls jog-section">
|
||||||
|
<h2 class="section-title">Jog</h2>
|
||||||
|
<div class="jog-layout">
|
||||||
|
<div class="jog-xy" aria-label="XY jog">
|
||||||
|
<button type="button" class="btn jog-btn" data-jog="y+" title="Y+">Y+</button>
|
||||||
|
<button type="button" class="btn jog-btn" data-jog="x-" title="X-">X-</button>
|
||||||
|
<button type="button" class="btn jog-btn jog-stop" data-jog="stop" title="Stop jog">⏹</button>
|
||||||
|
<button type="button" class="btn jog-btn" data-jog="x+" title="X+">X+</button>
|
||||||
|
<button type="button" class="btn jog-btn" data-jog="y-" title="Y-">Y-</button>
|
||||||
|
</div>
|
||||||
|
<div class="jog-z" aria-label="Z jog">
|
||||||
|
<button type="button" class="btn jog-btn" data-jog="z+" title="Z+">Z+</button>
|
||||||
|
<button type="button" class="btn jog-btn" data-jog="z-" title="Z-">Z-</button>
|
||||||
|
</div>
|
||||||
|
<div class="jog-settings">
|
||||||
|
<div class="jog-dro">
|
||||||
|
<div class="dro-row"><span>MPos</span> <code id="jog-mpos">X — Y — Z —</code></div>
|
||||||
|
<div class="dro-row"><span>WPos</span> <code id="jog-wpos">X — Y — Z —</code></div>
|
||||||
|
</div>
|
||||||
|
<div class="jog-selects">
|
||||||
|
<label class="field compact">
|
||||||
|
<span>Step</span>
|
||||||
|
<select id="jog-step">
|
||||||
|
<option value="0.1">0.1</option>
|
||||||
|
<option value="1" selected>1</option>
|
||||||
|
<option value="5">5</option>
|
||||||
|
<option value="10">10</option>
|
||||||
|
<option value="25">25</option>
|
||||||
|
<option value="50">50</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="field compact">
|
||||||
|
<span>Feed</span>
|
||||||
|
<select id="jog-feed">
|
||||||
|
<option value="100">100</option>
|
||||||
|
<option value="500" selected>500</option>
|
||||||
|
<option value="1000">1000</option>
|
||||||
|
<option value="2000">2000</option>
|
||||||
|
<option value="3000">3000</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label class="field checkbox">
|
||||||
|
<input type="checkbox" id="jog-continuous" />
|
||||||
|
<span>Hold = continuous</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="hint">Uses Grbl <code>$J=</code> jogging. Hold buttons for continuous when enabled; release or ⏹ to cancel.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="controls">
|
||||||
|
<h2 class="section-title">Camera-to-spindle offset</h2>
|
||||||
|
<p class="hint">
|
||||||
|
Distance from <strong>camera centre</strong> to <strong>spindle centre</strong>
|
||||||
|
(positive X = spindle is to the +X of the camera).
|
||||||
|
</p>
|
||||||
|
<div class="row-2">
|
||||||
|
<label class="field">
|
||||||
|
<span>Offset X</span>
|
||||||
|
<input type="number" id="offset-x" step="0.001" value="0" />
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Offset Y</span>
|
||||||
|
<input type="number" id="offset-y" step="0.001" value="0" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="controls">
|
||||||
|
<h2 class="section-title">Fiducial points</h2>
|
||||||
|
<p class="pos-line">
|
||||||
|
Live: <code id="live-pos">X — Y —</code>
|
||||||
|
<span id="conn-badge" class="badge">…</span>
|
||||||
|
</p>
|
||||||
|
<div class="points-grid">
|
||||||
|
<div class="point-card">
|
||||||
|
<strong>Point 1 · Origin</strong>
|
||||||
|
<code id="pt1-display">not set</code>
|
||||||
|
<button type="button" id="btn-mark-1" class="btn primary small-btn">Mark Point 1</button>
|
||||||
|
</div>
|
||||||
|
<div class="point-card">
|
||||||
|
<strong>Point 2 · Axis</strong>
|
||||||
|
<code id="pt2-display">not set</code>
|
||||||
|
<button type="button" id="btn-mark-2" class="btn primary small-btn">Mark Point 2</button>
|
||||||
|
</div>
|
||||||
|
<div class="point-card">
|
||||||
|
<strong>Point 3 · Verify</strong>
|
||||||
|
<code id="pt3-display">not set</code>
|
||||||
|
<button type="button" id="btn-mark-3" class="btn primary small-btn">Mark Point 3</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="btn-row">
|
||||||
|
<button type="button" id="btn-clear-points" class="btn">Clear points</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="controls">
|
||||||
|
<h2 class="section-title">Alignment result</h2>
|
||||||
|
<pre id="align-result" class="result-box">Mark points 1 and 2 (3 optional) then Align.</pre>
|
||||||
|
<div class="btn-row">
|
||||||
|
<button type="button" id="btn-align" class="btn primary" disabled>
|
||||||
|
Align & Zero Workspace
|
||||||
|
</button>
|
||||||
|
<button type="button" id="btn-rotate-gcode" class="btn" disabled>
|
||||||
|
Rotate loaded G-code
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p class="hint">
|
||||||
|
<strong>Align & Zero</strong> computes θ from points 1→2, applies camera offset,
|
||||||
|
and issues <code>G10 L20</code> so spindle WCS origin matches fiducial 1.
|
||||||
|
<strong>Rotate loaded G-code</strong> rewrites X/Y/I/J by −θ and reloads the job
|
||||||
|
(FluidNC has no G68).
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="options">
|
||||||
|
<label class="field inline">
|
||||||
|
<span>Cross-hair colour</span>
|
||||||
|
<input type="color" id="crosshair-color" value="#00ff66" />
|
||||||
|
</label>
|
||||||
|
<label class="field inline">
|
||||||
|
<span>Thickness</span>
|
||||||
|
<input type="range" id="crosshair-thickness" min="1" max="4" step="1" value="2" />
|
||||||
|
</label>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="debug-panel">
|
||||||
|
<div class="debug-header">
|
||||||
|
<strong>Debug log</strong>
|
||||||
|
<button type="button" id="btn-clear-log" class="btn small">Clear</button>
|
||||||
|
</div>
|
||||||
|
<pre id="debug-log" class="debug-log" aria-live="polite"></pre>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "vision-camera",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@sienci/gsender-plugin-sdk": "file:../../packages/plugin-sdk"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "^6.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
+694
@@ -0,0 +1,694 @@
|
|||||||
|
import "./style.css";
|
||||||
|
import {
|
||||||
|
gsender,
|
||||||
|
subscribeWorkspaceState,
|
||||||
|
subscribeSelector,
|
||||||
|
} from "@sienci/gsender-plugin-sdk";
|
||||||
|
|
||||||
|
// ---- DOM ----
|
||||||
|
const videoEl = document.getElementById("cam");
|
||||||
|
const selectEl = document.getElementById("camera-select");
|
||||||
|
const btnStart = document.getElementById("btn-start");
|
||||||
|
const btnStop = document.getElementById("btn-stop");
|
||||||
|
const statusEl = document.getElementById("status");
|
||||||
|
const viewportEl = document.getElementById("viewport");
|
||||||
|
const colorInput = document.getElementById("crosshair-color");
|
||||||
|
const thicknessInput = document.getElementById("crosshair-thickness");
|
||||||
|
const debugLogEl = document.getElementById("debug-log");
|
||||||
|
const btnClearLog = document.getElementById("btn-clear-log");
|
||||||
|
const livePosEl = document.getElementById("live-pos");
|
||||||
|
const connBadge = document.getElementById("conn-badge");
|
||||||
|
const offsetXEl = document.getElementById("offset-x");
|
||||||
|
const offsetYEl = document.getElementById("offset-y");
|
||||||
|
const ptDisplay = [
|
||||||
|
document.getElementById("pt1-display"),
|
||||||
|
document.getElementById("pt2-display"),
|
||||||
|
document.getElementById("pt3-display"),
|
||||||
|
];
|
||||||
|
const btnMark = [
|
||||||
|
document.getElementById("btn-mark-1"),
|
||||||
|
document.getElementById("btn-mark-2"),
|
||||||
|
document.getElementById("btn-mark-3"),
|
||||||
|
];
|
||||||
|
const btnClearPoints = document.getElementById("btn-clear-points");
|
||||||
|
const btnAlign = document.getElementById("btn-align");
|
||||||
|
const btnRotateGcode = document.getElementById("btn-rotate-gcode");
|
||||||
|
const alignResultEl = document.getElementById("align-result");
|
||||||
|
|
||||||
|
// ---- State ----
|
||||||
|
let currentStream = null;
|
||||||
|
let livePos = { x: null, y: null, z: null };
|
||||||
|
/** @type {({x:number,y:number,z:number}|null)[]} */
|
||||||
|
const points = [null, null, null];
|
||||||
|
let lastAlign = null;
|
||||||
|
|
||||||
|
const logLines = [];
|
||||||
|
const MAX_LOG = 250;
|
||||||
|
|
||||||
|
function log(msg, data) {
|
||||||
|
const ts = new Date().toISOString().slice(11, 23);
|
||||||
|
let line = `[${ts}] ${msg}`;
|
||||||
|
if (data !== undefined) {
|
||||||
|
try {
|
||||||
|
line += "\n" + (typeof data === "string" ? data : JSON.stringify(data, null, 2));
|
||||||
|
} catch {
|
||||||
|
line += `\n${String(data)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logLines.push(line);
|
||||||
|
while (logLines.length > MAX_LOG) logLines.shift();
|
||||||
|
if (debugLogEl) {
|
||||||
|
debugLogEl.textContent = logLines.join("\n\n");
|
||||||
|
debugLogEl.scrollTop = debugLogEl.scrollHeight;
|
||||||
|
}
|
||||||
|
console.log(`[vision-align] ${msg}`, data !== undefined ? data : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(text, kind = "") {
|
||||||
|
statusEl.textContent = text;
|
||||||
|
statusEl.className = "status" + (kind ? ` ${kind}` : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function fmt(n, digits = 3) {
|
||||||
|
if (n == null || Number.isNaN(n)) return "—";
|
||||||
|
return Number(n).toFixed(digits);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyCrosshairStyle() {
|
||||||
|
document.documentElement.style.setProperty("--crosshair", colorInput.value);
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--crosshair-thickness",
|
||||||
|
`${thicknessInput.value}px`,
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
localStorage.setItem("va-crosshair-color", colorInput.value);
|
||||||
|
localStorage.setItem("va-crosshair-thickness", thicknessInput.value);
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadPrefs() {
|
||||||
|
try {
|
||||||
|
const c = localStorage.getItem("va-crosshair-color");
|
||||||
|
const t = localStorage.getItem("va-crosshair-thickness");
|
||||||
|
const ox = localStorage.getItem("va-offset-x");
|
||||||
|
const oy = localStorage.getItem("va-offset-y");
|
||||||
|
if (c) colorInput.value = c;
|
||||||
|
if (t) thicknessInput.value = t;
|
||||||
|
if (ox != null) offsetXEl.value = ox;
|
||||||
|
if (oy != null) offsetYEl.value = oy;
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
applyCrosshairStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveOffsets() {
|
||||||
|
try {
|
||||||
|
localStorage.setItem("va-offset-x", offsetXEl.value);
|
||||||
|
localStorage.setItem("va-offset-y", offsetYEl.value);
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listCameras() {
|
||||||
|
if (!navigator.mediaDevices?.enumerateDevices) return [];
|
||||||
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||||
|
return devices.filter((d) => d.kind === "videoinput");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshCameraList(preferredId) {
|
||||||
|
const cams = await listCameras();
|
||||||
|
selectEl.innerHTML = "";
|
||||||
|
if (!cams.length) {
|
||||||
|
selectEl.innerHTML = '<option value="">No cameras found</option>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const cam of cams) {
|
||||||
|
const opt = document.createElement("option");
|
||||||
|
opt.value = cam.deviceId;
|
||||||
|
opt.textContent = cam.label || `Camera ${cam.deviceId.slice(0, 8)}…`;
|
||||||
|
selectEl.appendChild(opt);
|
||||||
|
}
|
||||||
|
if (preferredId && cams.some((c) => c.deviceId === preferredId)) {
|
||||||
|
selectEl.value = preferredId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopCamera() {
|
||||||
|
if (currentStream) {
|
||||||
|
currentStream.getTracks().forEach((t) => t.stop());
|
||||||
|
currentStream = null;
|
||||||
|
}
|
||||||
|
videoEl.srcObject = null;
|
||||||
|
videoEl.removeAttribute("data-active");
|
||||||
|
viewportEl.removeAttribute("data-streaming");
|
||||||
|
btnStart.disabled = false;
|
||||||
|
btnStop.disabled = true;
|
||||||
|
setStatus("Camera stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startCamera(deviceId) {
|
||||||
|
stopCamera();
|
||||||
|
if (!navigator.mediaDevices?.getUserMedia) {
|
||||||
|
setStatus("getUserMedia not available", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const constraints = {
|
||||||
|
audio: false,
|
||||||
|
video: deviceId
|
||||||
|
? { deviceId: { exact: deviceId }, width: { ideal: 1280 }, height: { ideal: 720 } }
|
||||||
|
: { width: { ideal: 1280 }, height: { ideal: 720 } },
|
||||||
|
};
|
||||||
|
setStatus("Requesting camera…");
|
||||||
|
try {
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||||
|
currentStream = stream;
|
||||||
|
videoEl.srcObject = stream;
|
||||||
|
await videoEl.play();
|
||||||
|
videoEl.setAttribute("data-active", "true");
|
||||||
|
viewportEl.setAttribute("data-streaming", "true");
|
||||||
|
btnStart.disabled = true;
|
||||||
|
btnStop.disabled = false;
|
||||||
|
const track = stream.getVideoTracks()[0];
|
||||||
|
const settings = track?.getSettings?.() ?? {};
|
||||||
|
setStatus(
|
||||||
|
`Live — ${track?.label || "Camera"}${settings.width ? ` (${settings.width}×${settings.height})` : ""}`,
|
||||||
|
"live",
|
||||||
|
);
|
||||||
|
const id = settings.deviceId || deviceId;
|
||||||
|
await refreshCameraList(id);
|
||||||
|
if (id) {
|
||||||
|
try { localStorage.setItem("va-device-id", id); } catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const name = err?.name || "Error";
|
||||||
|
log("getUserMedia FAILED", { name, message: err?.message });
|
||||||
|
setStatus(`${name}: ${err?.message || err}`, "error");
|
||||||
|
btnStart.disabled = false;
|
||||||
|
btnStop.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAxis(obj) {
|
||||||
|
if (!obj || typeof obj !== "object") return null;
|
||||||
|
const x = Number(obj.x ?? obj.X);
|
||||||
|
const y = Number(obj.y ?? obj.Y);
|
||||||
|
const z = Number(obj.z ?? obj.Z);
|
||||||
|
if (Number.isFinite(x) && Number.isFinite(y)) {
|
||||||
|
return { x, y, z: Number.isFinite(z) ? z : 0 };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Prefer machine position for fiducial marks (WPos is often 0,0,0 after zeroing). */
|
||||||
|
function extractXY(ctx) {
|
||||||
|
if (!ctx || typeof ctx !== "object") return null;
|
||||||
|
const mpos =
|
||||||
|
parseAxis(ctx.position) ||
|
||||||
|
parseAxis(ctx.mpos) ||
|
||||||
|
parseAxis(ctx.machinePosition) ||
|
||||||
|
parseAxis(ctx.machine) ||
|
||||||
|
parseAxis(ctx.MPos);
|
||||||
|
if (mpos) return mpos;
|
||||||
|
const wpos =
|
||||||
|
parseAxis(ctx.workPosition) ||
|
||||||
|
parseAxis(ctx.wpos) ||
|
||||||
|
parseAxis(ctx.work) ||
|
||||||
|
parseAxis(ctx.WPos);
|
||||||
|
if (wpos) return wpos;
|
||||||
|
return parseAxis(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractBoth(ctx) {
|
||||||
|
if (!ctx || typeof ctx !== "object") return { mpos: null, wpos: null };
|
||||||
|
const mpos =
|
||||||
|
parseAxis(ctx.position) ||
|
||||||
|
parseAxis(ctx.mpos) ||
|
||||||
|
parseAxis(ctx.machinePosition) ||
|
||||||
|
parseAxis(ctx.machine) ||
|
||||||
|
parseAxis(ctx.MPos);
|
||||||
|
const wpos =
|
||||||
|
parseAxis(ctx.workPosition) ||
|
||||||
|
parseAxis(ctx.wpos) ||
|
||||||
|
parseAxis(ctx.work) ||
|
||||||
|
parseAxis(ctx.WPos);
|
||||||
|
return { mpos, wpos };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readCurrentPosition() {
|
||||||
|
try {
|
||||||
|
const ctx = await gsender.machine.getContext();
|
||||||
|
log("machine.getContext", ctx);
|
||||||
|
const p = extractXY(ctx);
|
||||||
|
if (p) return p;
|
||||||
|
} catch (e) {
|
||||||
|
log("machine.getContext failed", e?.message || String(e));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const ws = await gsender.workspace.getState();
|
||||||
|
log("workspace.getState", ws);
|
||||||
|
const p = extractXY(ws);
|
||||||
|
if (p) return p;
|
||||||
|
} catch (e) {
|
||||||
|
log("workspace.getState failed", e?.message || String(e));
|
||||||
|
}
|
||||||
|
if (livePos.x != null && livePos.y != null) {
|
||||||
|
return { ...livePos };
|
||||||
|
}
|
||||||
|
throw new Error("Could not read machine position from bridge");
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePointUI() {
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const p = points[i];
|
||||||
|
ptDisplay[i].textContent = p
|
||||||
|
? `X${fmt(p.x)} Y${fmt(p.y)} Z${fmt(p.z)}`
|
||||||
|
: "not set";
|
||||||
|
}
|
||||||
|
const ready = !!(points[0] && points[1]);
|
||||||
|
btnAlign.disabled = !ready;
|
||||||
|
const canRotate = !!lastAlign;
|
||||||
|
btnRotateGcode.disabled = !canRotate;
|
||||||
|
log("updatePointUI", {
|
||||||
|
ready,
|
||||||
|
canRotate,
|
||||||
|
lastAlignThetaDeg: lastAlign ? lastAlign.thetaDeg : null,
|
||||||
|
btnRotateDisabled: btnRotateGcode.disabled,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function markPoint(index) {
|
||||||
|
try {
|
||||||
|
const p = await readCurrentPosition();
|
||||||
|
points[index] = p;
|
||||||
|
log(`Marked point ${index + 1}`, p);
|
||||||
|
updatePointUI();
|
||||||
|
setStatus(`Point ${index + 1} marked`, "live");
|
||||||
|
} catch (e) {
|
||||||
|
log(`Mark point ${index + 1} failed`, e?.message || String(e));
|
||||||
|
setStatus(String(e?.message || e), "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearPoints() {
|
||||||
|
points[0] = points[1] = points[2] = null;
|
||||||
|
lastAlign = null;
|
||||||
|
updatePointUI();
|
||||||
|
alignResultEl.textContent = "Mark points 1 and 2 (3 optional) then Align.";
|
||||||
|
log("Points cleared");
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeAlignment(p1, p2, p3, offsetX, offsetY) {
|
||||||
|
const dx = p2.x - p1.x;
|
||||||
|
const dy = p2.y - p1.y;
|
||||||
|
const dist12 = Math.hypot(dx, dy);
|
||||||
|
if (dist12 < 1e-6) {
|
||||||
|
throw new Error("Point 1 and 2 are too close");
|
||||||
|
}
|
||||||
|
const theta = Math.atan2(dy, dx);
|
||||||
|
const thetaDeg = (theta * 180) / Math.PI;
|
||||||
|
const spindleAtP1 = { x: p1.x + offsetX, y: p1.y + offsetY };
|
||||||
|
let skewNote = "";
|
||||||
|
if (p3) {
|
||||||
|
const dx3 = p3.x - p1.x;
|
||||||
|
const dy3 = p3.y - p1.y;
|
||||||
|
const dist13 = Math.hypot(dx3, dy3);
|
||||||
|
const ang3 = Math.atan2(dy3, dx3);
|
||||||
|
const delta = ((ang3 - theta) * 180) / Math.PI;
|
||||||
|
let d = ((delta + 180) % 360) - 180;
|
||||||
|
if (d < -180) d += 360;
|
||||||
|
skewNote = `P1→P3 angle vs P1→P2: ${d.toFixed(2)}° (ideal ±90°). Dist P1–P3: ${fmt(dist13)}`;
|
||||||
|
}
|
||||||
|
return { theta, thetaDeg, dist12, spindleAtP1, offsetX, offsetY, skewNote };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function alignAndZero() {
|
||||||
|
if (!points[0] || !points[1]) {
|
||||||
|
setStatus("Need Point 1 and Point 2", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const offsetX = Number(offsetXEl.value) || 0;
|
||||||
|
const offsetY = Number(offsetYEl.value) || 0;
|
||||||
|
saveOffsets();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = computeAlignment(
|
||||||
|
points[0],
|
||||||
|
points[1],
|
||||||
|
points[2],
|
||||||
|
offsetX,
|
||||||
|
offsetY,
|
||||||
|
);
|
||||||
|
lastAlign = result;
|
||||||
|
btnRotateGcode.disabled = false;
|
||||||
|
log("lastAlign set — Rotate button should be enabled", {
|
||||||
|
thetaDeg: result.thetaDeg,
|
||||||
|
btnRotateDisabled: btnRotateGcode.disabled,
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = [
|
||||||
|
`Stock angle θ = ${result.thetaDeg.toFixed(4)}° (${result.theta.toFixed(6)} rad)`,
|
||||||
|
`P1→P2 distance = ${fmt(result.dist12)}`,
|
||||||
|
`Camera→spindle offset = X${fmt(offsetX)} Y${fmt(offsetY)}`,
|
||||||
|
`Spindle at fiducial 1 ≈ X${fmt(result.spindleAtP1.x)} Y${fmt(result.spindleAtP1.y)}`,
|
||||||
|
result.skewNote || "",
|
||||||
|
"",
|
||||||
|
"Issuing G10 L20 to set WCS origin at current spindle = fiducial 1…",
|
||||||
|
"(Jog so camera is on Point 1 before Align.)",
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("\n");
|
||||||
|
alignResultEl.textContent = text;
|
||||||
|
log("Alignment computed", result);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendRaw("G10 L20 P1 X0 Y0");
|
||||||
|
alignResultEl.textContent +=
|
||||||
|
"\n\nSent: G10 L20 P1 X0 Y0 (current tool position → WCS origin).\n" +
|
||||||
|
"Ensure the camera was still on Point 1 (or jog there) before Align.";
|
||||||
|
setStatus("Align & zero done", "live");
|
||||||
|
} catch (g10err) {
|
||||||
|
log("G10 zero failed (alignment math still kept)", g10err?.message || String(g10err));
|
||||||
|
alignResultEl.textContent +=
|
||||||
|
"\n\nG10 failed: " + (g10err?.message || g10err) +
|
||||||
|
"\nAngle is still available — use Rotate loaded G-code.";
|
||||||
|
setStatus("Aligned (G10 failed) — rotate still available", "error");
|
||||||
|
}
|
||||||
|
btnRotateGcode.disabled = !lastAlign;
|
||||||
|
log("after align", { lastAlign: !!lastAlign, btnRotateDisabled: btnRotateGcode.disabled });
|
||||||
|
} catch (e) {
|
||||||
|
log("Align failed", e?.message || String(e));
|
||||||
|
setStatus(String(e?.message || e), "error");
|
||||||
|
alignResultEl.textContent = `Error: ${e?.message || e}`;
|
||||||
|
btnRotateGcode.disabled = !lastAlign;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rotateGcodeText(gcode, thetaRad) {
|
||||||
|
const c = Math.cos(thetaRad);
|
||||||
|
const s = Math.sin(thetaRad);
|
||||||
|
const rot = (x, y) => ({ x: x * c - y * s, y: x * s + y * c });
|
||||||
|
const wordRe = /([A-Za-z])\s*([-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?)/g;
|
||||||
|
return gcode.split(/\r?\n/).map((line) => {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed || trimmed.startsWith(";") || trimmed.startsWith("(")) return line;
|
||||||
|
let x, y, iVal, jVal;
|
||||||
|
let m;
|
||||||
|
wordRe.lastIndex = 0;
|
||||||
|
const words = [];
|
||||||
|
while ((m = wordRe.exec(trimmed)) !== null) {
|
||||||
|
words.push({ letter: m[1].toUpperCase(), value: parseFloat(m[2]) });
|
||||||
|
}
|
||||||
|
if (!words.length) return line;
|
||||||
|
for (const w of words) {
|
||||||
|
if (w.letter === "X") x = w.value;
|
||||||
|
else if (w.letter === "Y") y = w.value;
|
||||||
|
else if (w.letter === "I") iVal = w.value;
|
||||||
|
else if (w.letter === "J") jVal = w.value;
|
||||||
|
}
|
||||||
|
if (x === undefined && y === undefined && iVal === undefined && jVal === undefined) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
const prefix = [];
|
||||||
|
for (const w of words) {
|
||||||
|
if ("XYIJ".includes(w.letter)) break;
|
||||||
|
prefix.push(`${w.letter}${w.value}`);
|
||||||
|
}
|
||||||
|
const geo = [];
|
||||||
|
if (x !== undefined || y !== undefined) {
|
||||||
|
const r = rot(x ?? 0, y ?? 0);
|
||||||
|
if (x !== undefined) geo.push(`X${roundG(r.x)}`);
|
||||||
|
if (y !== undefined) geo.push(`Y${roundG(r.y)}`);
|
||||||
|
}
|
||||||
|
if (iVal !== undefined || jVal !== undefined) {
|
||||||
|
const r = rot(iVal ?? 0, jVal ?? 0);
|
||||||
|
if (iVal !== undefined) geo.push(`I${roundG(r.x)}`);
|
||||||
|
if (jVal !== undefined) geo.push(`J${roundG(r.y)}`);
|
||||||
|
}
|
||||||
|
const suffix = [];
|
||||||
|
let seenGeo = false;
|
||||||
|
for (const w of words) {
|
||||||
|
if ("XYIJ".includes(w.letter)) { seenGeo = true; continue; }
|
||||||
|
if (!seenGeo) continue;
|
||||||
|
suffix.push(`${w.letter}${w.value}`);
|
||||||
|
}
|
||||||
|
return [...prefix, ...geo, ...suffix].join(" ");
|
||||||
|
}).join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function roundG(n) {
|
||||||
|
return String(Math.round(n * 1e6) / 1e6);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function rotateLoadedGcode() {
|
||||||
|
if (!lastAlign) {
|
||||||
|
setStatus("Run Align first", "error");
|
||||||
|
log("rotate blocked: lastAlign is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log("Rotate clicked", { thetaDeg: lastAlign.thetaDeg });
|
||||||
|
try {
|
||||||
|
let content = null;
|
||||||
|
let source = null;
|
||||||
|
|
||||||
|
// 1) Redux (needs redux:get:state in manifest)
|
||||||
|
try {
|
||||||
|
const state = await gsender.redux.getState();
|
||||||
|
log("redux keys", state && typeof state === "object" ? Object.keys(state) : state);
|
||||||
|
content =
|
||||||
|
state?.file?.content ||
|
||||||
|
state?.file?.gcode ||
|
||||||
|
state?.gcode?.content ||
|
||||||
|
state?.visualizer?.gcode ||
|
||||||
|
state?.controller?.gcode ||
|
||||||
|
null;
|
||||||
|
if (content) source = "redux";
|
||||||
|
|
||||||
|
if (!content && state && typeof state === "object") {
|
||||||
|
const stack = [state];
|
||||||
|
while (stack.length && !content) {
|
||||||
|
const cur = stack.pop();
|
||||||
|
if (!cur || typeof cur !== "object") continue;
|
||||||
|
for (const [k, v] of Object.entries(cur)) {
|
||||||
|
if (
|
||||||
|
typeof v === "string" &&
|
||||||
|
v.length > 80 &&
|
||||||
|
/G[0-3]\b/i.test(v) &&
|
||||||
|
/[XY]/i.test(v)
|
||||||
|
) {
|
||||||
|
content = v;
|
||||||
|
source = `redux nested (${k})`;
|
||||||
|
log(`Found gcode-like string via key ${k}, len=${v.length}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (v && typeof v === "object" && stack.length < 40) stack.push(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log("redux.getState failed", e?.message || String(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Workspace fallback
|
||||||
|
if (!content) {
|
||||||
|
try {
|
||||||
|
const ws = await gsender.workspace.getState();
|
||||||
|
log("workspace keys", ws && typeof ws === "object" ? Object.keys(ws) : ws);
|
||||||
|
content =
|
||||||
|
ws?.file?.content ||
|
||||||
|
ws?.gcode ||
|
||||||
|
ws?.content ||
|
||||||
|
null;
|
||||||
|
if (content) source = "workspace";
|
||||||
|
} catch (e) {
|
||||||
|
log("workspace.getState failed", e?.message || String(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!content || typeof content !== "string") {
|
||||||
|
setStatus("No loaded G-code found in host state", "error");
|
||||||
|
alignResultEl.textContent +=
|
||||||
|
"\n\nCould not find loaded G-code. Ensure a file is loaded in gSender, " +
|
||||||
|
"manifest includes redux:get:state, and gSender was restarted.";
|
||||||
|
log("rotate: no gcode content found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log("rotating gcode", { source, chars: content.length, thetaDeg: lastAlign.thetaDeg });
|
||||||
|
const rotated = rotateGcodeText(content, lastAlign.theta);
|
||||||
|
await gsender.gcode.loadToVisualizer(rotated, "vision-aligned.nc");
|
||||||
|
log("Rotated G-code loaded", { chars: rotated.length, thetaDeg: lastAlign.thetaDeg });
|
||||||
|
alignResultEl.textContent +=
|
||||||
|
`\n\nRotated G-code by θ=${lastAlign.thetaDeg.toFixed(4)}° (source=${source}) and loaded as vision-aligned.nc.`;
|
||||||
|
setStatus("Rotated G-code loaded into visualizer", "live");
|
||||||
|
} catch (e) {
|
||||||
|
log("Rotate G-code failed", e?.message || String(e));
|
||||||
|
setStatus(String(e?.message || e), "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
btnStart.addEventListener("click", () => {
|
||||||
|
let preferred = selectEl.value;
|
||||||
|
if (!preferred) {
|
||||||
|
try { preferred = localStorage.getItem("va-device-id") || ""; } catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
startCamera(preferred || undefined);
|
||||||
|
});
|
||||||
|
btnStop.addEventListener("click", () => stopCamera());
|
||||||
|
selectEl.addEventListener("change", () => {
|
||||||
|
if (currentStream && selectEl.value) startCamera(selectEl.value);
|
||||||
|
});
|
||||||
|
colorInput.addEventListener("input", applyCrosshairStyle);
|
||||||
|
thicknessInput.addEventListener("input", applyCrosshairStyle);
|
||||||
|
offsetXEl.addEventListener("change", saveOffsets);
|
||||||
|
offsetYEl.addEventListener("change", saveOffsets);
|
||||||
|
btnMark[0].addEventListener("click", () => markPoint(0));
|
||||||
|
btnMark[1].addEventListener("click", () => markPoint(1));
|
||||||
|
btnMark[2].addEventListener("click", () => markPoint(2));
|
||||||
|
btnClearPoints.addEventListener("click", () => clearPoints());
|
||||||
|
btnAlign.addEventListener("click", () => alignAndZero());
|
||||||
|
btnRotateGcode.addEventListener("click", () => rotateLoadedGcode());
|
||||||
|
btnClearLog.addEventListener("click", () => {
|
||||||
|
logLines.length = 0;
|
||||||
|
if (debugLogEl) debugLogEl.textContent = "";
|
||||||
|
});
|
||||||
|
window.addEventListener("pagehide", () => stopCamera());
|
||||||
|
|
||||||
|
const jogMposEl = document.getElementById("jog-mpos");
|
||||||
|
const jogWposEl = document.getElementById("jog-wpos");
|
||||||
|
|
||||||
|
function updateDro(mpos, wpos) {
|
||||||
|
if (mpos) {
|
||||||
|
livePos = mpos;
|
||||||
|
const t = `X ${fmt(mpos.x)} Y ${fmt(mpos.y)} Z ${fmt(mpos.z)}`;
|
||||||
|
livePosEl.textContent = t;
|
||||||
|
if (jogMposEl) jogMposEl.textContent = t;
|
||||||
|
}
|
||||||
|
if (wpos && jogWposEl) {
|
||||||
|
jogWposEl.textContent = `X ${fmt(wpos.x)} Y ${fmt(wpos.y)} Z ${fmt(wpos.z)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshContextDro() {
|
||||||
|
try {
|
||||||
|
const ctx = await gsender.machine.getContext();
|
||||||
|
const { mpos, wpos } = extractBoth(ctx);
|
||||||
|
updateDro(mpos, wpos);
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
subscribeWorkspaceState((ws) => {
|
||||||
|
const { mpos, wpos } = extractBoth(ws);
|
||||||
|
updateDro(mpos || extractXY(ws), wpos);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log("subscribeWorkspaceState failed", e?.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(() => { refreshContextDro(); }, 400);
|
||||||
|
|
||||||
|
try {
|
||||||
|
subscribeSelector(
|
||||||
|
(s) => s?.connection?.isConnected ?? s?.controller?.connected ?? null,
|
||||||
|
(connected) => {
|
||||||
|
if (connected === null) { connBadge.textContent = "pos?"; return; }
|
||||||
|
connBadge.textContent = connected ? "connected" : "disconnected";
|
||||||
|
connBadge.classList.toggle("ok", !!connected);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
log("subscribeSelector failed", e?.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const jogStepEl = document.getElementById("jog-step");
|
||||||
|
const jogFeedEl = document.getElementById("jog-feed");
|
||||||
|
const jogContinuousEl = document.getElementById("jog-continuous");
|
||||||
|
let jogActive = false;
|
||||||
|
|
||||||
|
async function sendRaw(line) {
|
||||||
|
// Matches gSender main UI: socket.command(port, "gcode", line)
|
||||||
|
await gsender.machine.command("gcode", line);
|
||||||
|
log("sent gcode", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function jogCancel() {
|
||||||
|
jogActive = false;
|
||||||
|
try {
|
||||||
|
await gsender.machine.command("jogCancel");
|
||||||
|
log("sent jogCancel");
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
log("jogCancel name failed", e?.message || String(e));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await sendRaw(String.fromCharCode(0x85));
|
||||||
|
} catch (e) {
|
||||||
|
log("0x85 cancel failed", e?.message || String(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function jogStart(direction, continuous) {
|
||||||
|
const step = continuous ? 250 : Number(jogStepEl.value) || 1;
|
||||||
|
const feed = Number(jogFeedEl.value) || 500;
|
||||||
|
let axis = "";
|
||||||
|
let sign = 1;
|
||||||
|
if (direction === "x+") { axis = "X"; sign = 1; }
|
||||||
|
else if (direction === "x-") { axis = "X"; sign = -1; }
|
||||||
|
else if (direction === "y+") { axis = "Y"; sign = 1; }
|
||||||
|
else if (direction === "y-") { axis = "Y"; sign = -1; }
|
||||||
|
else if (direction === "z+") { axis = "Z"; sign = 1; }
|
||||||
|
else if (direction === "z-") { axis = "Z"; sign = -1; }
|
||||||
|
else return;
|
||||||
|
|
||||||
|
const dist = sign * step;
|
||||||
|
// Exact format used by gSender UI:
|
||||||
|
// $J=G21 G91 X-5 F3000
|
||||||
|
const cmd = `$J=G21 G91 ${axis}${dist} F${feed}`;
|
||||||
|
jogActive = true;
|
||||||
|
try {
|
||||||
|
await sendRaw(cmd);
|
||||||
|
setStatus(`Jog ${direction} ${continuous ? "(continuous)" : `${step} mm`}`, "live");
|
||||||
|
} catch (e) {
|
||||||
|
jogActive = false;
|
||||||
|
log("jog failed", e?.message || String(e));
|
||||||
|
setStatus(`Jog failed: ${e?.message || e}`, "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function wireJogButtons() {
|
||||||
|
document.querySelectorAll("[data-jog]").forEach((btn) => {
|
||||||
|
const dir = btn.getAttribute("data-jog");
|
||||||
|
if (dir === "stop") {
|
||||||
|
btn.addEventListener("pointerdown", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
jogCancel();
|
||||||
|
setStatus("Jog cancelled");
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const onDown = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
btn.setPointerCapture?.(e.pointerId);
|
||||||
|
await jogStart(dir, !!jogContinuousEl?.checked);
|
||||||
|
};
|
||||||
|
const onUp = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (jogContinuousEl?.checked && jogActive) jogCancel();
|
||||||
|
};
|
||||||
|
btn.addEventListener("pointerdown", onDown);
|
||||||
|
btn.addEventListener("pointerup", onUp);
|
||||||
|
btn.addEventListener("pointercancel", onUp);
|
||||||
|
btn.addEventListener("pointerleave", (e) => {
|
||||||
|
if (jogContinuousEl?.checked && jogActive && e.buttons === 0) jogCancel();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
wireJogButtons();
|
||||||
|
loadPrefs();
|
||||||
|
updatePointUI();
|
||||||
|
log("Vision Alignment plugin boot", {
|
||||||
|
href: location.href,
|
||||||
|
isSecureContext: window.isSecureContext,
|
||||||
|
inIframe: window !== window.top,
|
||||||
|
});
|
||||||
|
setStatus("Camera idle — start camera, mark 3 points, Align");
|
||||||
|
refreshCameraList().catch(() => {});
|
||||||
+508
@@ -0,0 +1,508 @@
|
|||||||
|
/* Base — gSender applies html.dark for dark mode */
|
||||||
|
:root {
|
||||||
|
--bg: #f4f4f5;
|
||||||
|
--panel: #ffffff;
|
||||||
|
--text: #18181b;
|
||||||
|
--muted: #71717a;
|
||||||
|
--border: #e4e4e7;
|
||||||
|
--accent: #2563eb;
|
||||||
|
--accent-hover: #1d4ed8;
|
||||||
|
--danger: #dc2626;
|
||||||
|
--crosshair: #00ff66;
|
||||||
|
--crosshair-thickness: 2px;
|
||||||
|
--radius: 8px;
|
||||||
|
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark {
|
||||||
|
--bg: #09090b;
|
||||||
|
--panel: #18181b;
|
||||||
|
--text: #fafafa;
|
||||||
|
--muted: #a1a1aa;
|
||||||
|
--border: #27272a;
|
||||||
|
--accent: #3b82f6;
|
||||||
|
--accent-hover: #60a5fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
max-width: 720px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 1rem 1.25rem 2rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.35rem;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin: 0.25rem 0 0;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls,
|
||||||
|
.options {
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 0.85rem 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.35rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field.inline {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field span {
|
||||||
|
color: var(--muted);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
select,
|
||||||
|
input[type="color"] {
|
||||||
|
font: inherit;
|
||||||
|
color: var(--text);
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.45rem 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
select { width: 100%; }
|
||||||
|
|
||||||
|
input[type="color"] {
|
||||||
|
width: 3rem;
|
||||||
|
height: 2rem;
|
||||||
|
padding: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"] { width: 8rem; }
|
||||||
|
|
||||||
|
.btn-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
font: inherit;
|
||||||
|
font-weight: 560;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover:not(:disabled) { border-color: var(--muted); }
|
||||||
|
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
|
||||||
|
|
||||||
|
.btn.primary {
|
||||||
|
background: var(--accent);
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.primary:hover:not(:disabled) {
|
||||||
|
background: var(--accent-hover);
|
||||||
|
border-color: var(--accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.error { color: var(--danger); }
|
||||||
|
.status.live { color: #16a34a; }
|
||||||
|
html.dark .status.live { color: #4ade80; }
|
||||||
|
|
||||||
|
.viewport-wrap {
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-viewport {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 4 / 3;
|
||||||
|
background: #0a0a0a;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-viewport video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
display: block;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-viewport video:not([data-active="true"]) { opacity: 0; }
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
color: #a1a1aa;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-viewport[data-streaming="true"] .placeholder { display: none; }
|
||||||
|
|
||||||
|
.crosshair {
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
--c: var(--crosshair);
|
||||||
|
--t: var(--crosshair-thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
.crosshair::before,
|
||||||
|
.crosshair::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
background: var(--c);
|
||||||
|
opacity: 0.92;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crosshair::before {
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
height: var(--t);
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
var(--c) 0%,
|
||||||
|
var(--c) calc(50% - 18px),
|
||||||
|
transparent calc(50% - 18px),
|
||||||
|
transparent calc(50% + 18px),
|
||||||
|
var(--c) calc(50% + 18px),
|
||||||
|
var(--c) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.crosshair::after {
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 50%;
|
||||||
|
width: var(--t);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
var(--c) 0%,
|
||||||
|
var(--c) calc(50% - 18px),
|
||||||
|
transparent calc(50% - 18px),
|
||||||
|
transparent calc(50% + 18px),
|
||||||
|
var(--c) calc(50% + 18px),
|
||||||
|
var(--c) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.crosshair .ring {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
margin: -14px 0 0 -14px;
|
||||||
|
border: calc(var(--t) + 0.5px) solid var(--c);
|
||||||
|
border-radius: 50%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-panel {
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 0.75rem 1rem 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.small {
|
||||||
|
padding: 0.25rem 0.6rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-log {
|
||||||
|
margin: 0;
|
||||||
|
max-height: 280px;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 0.65rem 0.75rem;
|
||||||
|
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
background: #0a0a0a;
|
||||||
|
color: #d4d4d8;
|
||||||
|
border-radius: 6px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .debug-log {
|
||||||
|
background: #000;
|
||||||
|
color: #e4e4e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--muted);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint code { font-size: 0.78rem; }
|
||||||
|
|
||||||
|
.row-2 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-2 input[type="number"] {
|
||||||
|
font: inherit;
|
||||||
|
color: var(--text);
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.45rem 0.6rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pos-line {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pos-line code { font-size: 0.85rem; }
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
padding: 0.15rem 0.45rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--border);
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.ok {
|
||||||
|
background: #166534;
|
||||||
|
color: #bbf7d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.points-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.points-grid { grid-template-columns: 1fr; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-card {
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.6rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.4rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-card code {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
word-break: break-all;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-btn {
|
||||||
|
padding: 0.35rem 0.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-box {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.65rem 0.75rem;
|
||||||
|
font-family: ui-monospace, Menlo, Consolas, monospace;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Jog pad ---- */
|
||||||
|
|
||||||
|
.jog-layout {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jog-xy {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 2.6rem);
|
||||||
|
grid-template-rows: repeat(3, 2.6rem);
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jog-xy .jog-btn[data-jog="y+"] { grid-column: 2; grid-row: 1; }
|
||||||
|
.jog-xy .jog-btn[data-jog="x-"] { grid-column: 1; grid-row: 2; }
|
||||||
|
.jog-xy .jog-btn[data-jog="stop"] { grid-column: 2; grid-row: 2; }
|
||||||
|
.jog-xy .jog-btn[data-jog="x+"] { grid-column: 3; grid-row: 2; }
|
||||||
|
.jog-xy .jog-btn[data-jog="y-"] { grid-column: 2; grid-row: 3; }
|
||||||
|
|
||||||
|
.jog-z {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jog-z .jog-btn {
|
||||||
|
min-width: 2.6rem;
|
||||||
|
min-height: 2.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jog-btn {
|
||||||
|
min-width: 2.6rem;
|
||||||
|
min-height: 2.6rem;
|
||||||
|
padding: 0;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
user-select: none;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jog-btn.jog-stop {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
background: #450a0a;
|
||||||
|
border-color: #7f1d1d;
|
||||||
|
color: #fecaca;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jog-btn:active:not(:disabled) {
|
||||||
|
transform: scale(0.96);
|
||||||
|
background: var(--accent);
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jog-settings {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 8rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field.checkbox {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field.checkbox input { width: auto; }
|
||||||
|
|
||||||
|
.jog-dro {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dro-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dro-row span {
|
||||||
|
color: var(--muted);
|
||||||
|
font-weight: 500;
|
||||||
|
min-width: 2.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dro-row code { font-size: 0.8rem; }
|
||||||
|
|
||||||
|
.jog-selects {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field.compact {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field.compact select {
|
||||||
|
width: 5.5rem;
|
||||||
|
min-width: 5.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jog-settings select { width: 5.5rem; }
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import gsenderPlugin from "@sienci/gsender-plugin-sdk/vite";
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [gsenderPlugin()],
|
||||||
|
base: "./",
|
||||||
|
build: {
|
||||||
|
outDir: "ui",
|
||||||
|
emptyOutDir: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,563 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@esbuild/aix-ppc64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz#80fcbe36130e58b7670511e888b8e88a259ed76c"
|
||||||
|
integrity sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==
|
||||||
|
|
||||||
|
"@esbuild/aix-ppc64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz#bf6e10303bcf2e7c686975fa52f937ec2728d8bc"
|
||||||
|
integrity sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==
|
||||||
|
|
||||||
|
"@esbuild/android-arm64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz#8aa4965f8d0a7982dc21734bf6601323a66da752"
|
||||||
|
integrity sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==
|
||||||
|
|
||||||
|
"@esbuild/android-arm64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz#0c6246bc8d2c4d172aac2db3fb1190d72bd65504"
|
||||||
|
integrity sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==
|
||||||
|
|
||||||
|
"@esbuild/android-arm@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz#300712101f7f50f1d2627a162e6e09b109b6767a"
|
||||||
|
integrity sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==
|
||||||
|
|
||||||
|
"@esbuild/android-arm@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.28.2.tgz#2d84ece6a4e2684d92be26ee13d42757d831c381"
|
||||||
|
integrity sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==
|
||||||
|
|
||||||
|
"@esbuild/android-x64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz#87dfb27161202bdc958ef48bb61b09c758faee16"
|
||||||
|
integrity sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==
|
||||||
|
|
||||||
|
"@esbuild/android-x64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.28.2.tgz#fc38d4d6358d8dc1cf53f09f7589fe436eb64801"
|
||||||
|
integrity sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==
|
||||||
|
|
||||||
|
"@esbuild/darwin-arm64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz#79197898ec1ff745d21c071e1c7cc3c802f0c1fd"
|
||||||
|
integrity sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==
|
||||||
|
|
||||||
|
"@esbuild/darwin-arm64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz#f83afeeac1d7dac01c7a2fd012b3e451a0591fcc"
|
||||||
|
integrity sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==
|
||||||
|
|
||||||
|
"@esbuild/darwin-x64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz#146400a8562133f45c4d2eadcf37ddd09718079e"
|
||||||
|
integrity sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==
|
||||||
|
|
||||||
|
"@esbuild/darwin-x64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz#510147c055a795588dbbe14fd6b1b8ad0a2f30de"
|
||||||
|
integrity sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==
|
||||||
|
|
||||||
|
"@esbuild/freebsd-arm64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz#1c5f9ba7206e158fd2b24c59fa2d2c8bb47ca0fe"
|
||||||
|
integrity sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==
|
||||||
|
|
||||||
|
"@esbuild/freebsd-arm64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz#093b9200ecf0b115ba4e5e248a7485c9c5f8bd5e"
|
||||||
|
integrity sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==
|
||||||
|
|
||||||
|
"@esbuild/freebsd-x64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz#ea631f4a36beaac4b9279fa0fcc6ca29eaeeb2b3"
|
||||||
|
integrity sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==
|
||||||
|
|
||||||
|
"@esbuild/freebsd-x64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz#0be22b6df925d213e841ea87123af5df80b0faf7"
|
||||||
|
integrity sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==
|
||||||
|
|
||||||
|
"@esbuild/linux-arm64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz#e1066bce58394f1b1141deec8557a5f0a22f5977"
|
||||||
|
integrity sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==
|
||||||
|
|
||||||
|
"@esbuild/linux-arm64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz#1bdbc651cda9ba9995c53ed9c71ceaa65094762d"
|
||||||
|
integrity sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==
|
||||||
|
|
||||||
|
"@esbuild/linux-arm@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz#452cd66b20932d08bdc53a8b61c0e30baf4348b9"
|
||||||
|
integrity sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==
|
||||||
|
|
||||||
|
"@esbuild/linux-arm@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz#beb12ad72b84f72d28488cc1b8ee9f7eb141d753"
|
||||||
|
integrity sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==
|
||||||
|
|
||||||
|
"@esbuild/linux-ia32@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz#b24f8acc45bcf54192c7f2f3be1b53e6551eafe0"
|
||||||
|
integrity sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==
|
||||||
|
|
||||||
|
"@esbuild/linux-ia32@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz#b81f9d55529b45c206a46a138214b1aa6879696b"
|
||||||
|
integrity sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==
|
||||||
|
|
||||||
|
"@esbuild/linux-loong64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz#f9cfffa7fc8322571fbc4c8b3268caf15bd81ad0"
|
||||||
|
integrity sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==
|
||||||
|
|
||||||
|
"@esbuild/linux-loong64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz#598667241a04c99b76ed6ef940ac50038c419f98"
|
||||||
|
integrity sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==
|
||||||
|
|
||||||
|
"@esbuild/linux-mips64el@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz#575a14bd74644ffab891adc7d7e60d275296f2cd"
|
||||||
|
integrity sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==
|
||||||
|
|
||||||
|
"@esbuild/linux-mips64el@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz#1c51eb9cea903f53d97b5af3b1841db70f5596ca"
|
||||||
|
integrity sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==
|
||||||
|
|
||||||
|
"@esbuild/linux-ppc64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz#75b99c70a95fbd5f7739d7692befe60601591869"
|
||||||
|
integrity sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==
|
||||||
|
|
||||||
|
"@esbuild/linux-ppc64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz#63dd61f17ceb31a81227f413feac8a71bc2c51f2"
|
||||||
|
integrity sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==
|
||||||
|
|
||||||
|
"@esbuild/linux-riscv64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz#2e3259440321a44e79ddf7535c325057da875cd6"
|
||||||
|
integrity sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==
|
||||||
|
|
||||||
|
"@esbuild/linux-riscv64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz#3763b08fde5cf25ab1facb8e7752edfe45fbfc27"
|
||||||
|
integrity sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==
|
||||||
|
|
||||||
|
"@esbuild/linux-s390x@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz#17676cabbfe5928da5b2a0d6df5d58cd08db2663"
|
||||||
|
integrity sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==
|
||||||
|
|
||||||
|
"@esbuild/linux-s390x@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz#1a137ff293a82906eb3176385bd7e8e0e5cfb7cb"
|
||||||
|
integrity sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==
|
||||||
|
|
||||||
|
"@esbuild/linux-x64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz#0583775685ca82066d04c3507f09524d3cd7a306"
|
||||||
|
integrity sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==
|
||||||
|
|
||||||
|
"@esbuild/linux-x64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz#268b36211c146ca54f8fe12c578a8d6ef8979485"
|
||||||
|
integrity sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==
|
||||||
|
|
||||||
|
"@esbuild/netbsd-arm64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz#f04c4049cb2e252fe96b16fed90f70746b13f4a4"
|
||||||
|
integrity sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==
|
||||||
|
|
||||||
|
"@esbuild/netbsd-arm64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz#22571ad951d62bb6accc82d8d1fad5c8c1ac0ba1"
|
||||||
|
integrity sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==
|
||||||
|
|
||||||
|
"@esbuild/netbsd-x64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz#77da0d0a0d826d7c921eea3d40292548b258a076"
|
||||||
|
integrity sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==
|
||||||
|
|
||||||
|
"@esbuild/netbsd-x64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz#42fcc57297eb0a0ca3f5fc475291f4c1a3f7c0de"
|
||||||
|
integrity sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==
|
||||||
|
|
||||||
|
"@esbuild/openbsd-arm64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz#6296f5867aedef28a81b22ab2009c786a952dccd"
|
||||||
|
integrity sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==
|
||||||
|
|
||||||
|
"@esbuild/openbsd-arm64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz#9eb32af104ac3dacf4edca01f596664aab0c73ef"
|
||||||
|
integrity sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==
|
||||||
|
|
||||||
|
"@esbuild/openbsd-x64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz#f8d23303360e27b16cf065b23bbff43c14142679"
|
||||||
|
integrity sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==
|
||||||
|
|
||||||
|
"@esbuild/openbsd-x64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz#febed2402d6088225e91f20fb4ce2522ad0a4efd"
|
||||||
|
integrity sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==
|
||||||
|
|
||||||
|
"@esbuild/openharmony-arm64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz#49e0b768744a3924be0d7fd97dd6ce9b2923d88d"
|
||||||
|
integrity sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==
|
||||||
|
|
||||||
|
"@esbuild/openharmony-arm64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz#85641c3d466428bfbccea5f21c26836663fef5ce"
|
||||||
|
integrity sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==
|
||||||
|
|
||||||
|
"@esbuild/sunos-x64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz#a6ed7d6778d67e528c81fb165b23f4911b9b13d6"
|
||||||
|
integrity sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==
|
||||||
|
|
||||||
|
"@esbuild/sunos-x64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz#a736f9d8962481045fc4c3e54f5479f22c870fb4"
|
||||||
|
integrity sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==
|
||||||
|
|
||||||
|
"@esbuild/win32-arm64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz#9ac14c378e1b653af17d08e7d3ce34caef587323"
|
||||||
|
integrity sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==
|
||||||
|
|
||||||
|
"@esbuild/win32-arm64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz#ee5ab40fad186201b652a33f8a5eb149e9e42532"
|
||||||
|
integrity sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==
|
||||||
|
|
||||||
|
"@esbuild/win32-ia32@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz#918942dcbbb35cc14fca39afb91b5e6a3d127267"
|
||||||
|
integrity sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==
|
||||||
|
|
||||||
|
"@esbuild/win32-ia32@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz#c40d28a6d99a127da6711f2afd74b11cb63b06a7"
|
||||||
|
integrity sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==
|
||||||
|
|
||||||
|
"@esbuild/win32-x64@0.25.12":
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz#9bdad8176be7811ad148d1f8772359041f46c6c5"
|
||||||
|
integrity sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==
|
||||||
|
|
||||||
|
"@esbuild/win32-x64@0.28.2":
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz#b21affb804cc167c133d95f45b3a1dc1323b9a87"
|
||||||
|
integrity sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==
|
||||||
|
|
||||||
|
"@napi-rs/lzma-linux-x64-gnu@1.5.1":
|
||||||
|
version "1.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz#e57d4306966078662038094fb38eb9146dc3aea9"
|
||||||
|
integrity sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==
|
||||||
|
|
||||||
|
"@rollup/rollup-android-arm-eabi@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz#d03ba6ea54f9ec80688d153763cd325a2d2a5af6"
|
||||||
|
integrity sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==
|
||||||
|
|
||||||
|
"@rollup/rollup-android-arm64@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz#db5e36aa8a955b4b5e0b024d671230edc5cdc191"
|
||||||
|
integrity sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==
|
||||||
|
|
||||||
|
"@rollup/rollup-darwin-arm64@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz#1ed1c43922e7b9b5d020ef65d8402e3c81edc86e"
|
||||||
|
integrity sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==
|
||||||
|
|
||||||
|
"@rollup/rollup-darwin-x64@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz#0a86e782bf7a546e74f531e395e24fdb45c83527"
|
||||||
|
integrity sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==
|
||||||
|
|
||||||
|
"@rollup/rollup-freebsd-arm64@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz#82fa51c540185b5063c8b3c63aac79b15f2801ad"
|
||||||
|
integrity sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==
|
||||||
|
|
||||||
|
"@rollup/rollup-freebsd-x64@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz#df1d73b567fd62e0cf21c9b57dff7f44bfd69638"
|
||||||
|
integrity sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm-gnueabihf@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz#a585ba0418027a5b567693db3982e5e57544a4c7"
|
||||||
|
integrity sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm-musleabihf@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz#1326f0db22b690a92efd8eaa06e92268dc7d1bb6"
|
||||||
|
integrity sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-gnu@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz#9491939f7cc43a5b26a877417faeafe69bac79ac"
|
||||||
|
integrity sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-musl@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz#a53d93dac32acc671324af1153930ab5a04d8639"
|
||||||
|
integrity sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-loong64-gnu@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz#db6e06173efc870be49a2df692d0c0607417a679"
|
||||||
|
integrity sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-loong64-musl@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz#a60734a3de407bcf4bfe44d0b64222c0b9fb30bc"
|
||||||
|
integrity sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-ppc64-gnu@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz#28a67e15d7ba8630ef044980a39626e0627d6e73"
|
||||||
|
integrity sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-ppc64-musl@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz#77bb553a514942af54070756763dbb44c9c6cb2b"
|
||||||
|
integrity sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-riscv64-gnu@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz#ef9f31b917e3b310eac5b86d3b6626df7e543e3d"
|
||||||
|
integrity sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-riscv64-musl@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz#68cf61a2fc02171d1fa62568b73c1d942f82e80c"
|
||||||
|
integrity sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-s390x-gnu@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz#92d393ca47da0d03d1c1cffb3d7340f26c3a53d2"
|
||||||
|
integrity sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-gnu@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz#f6e5c5c51f96ae298617fa26da54675acd60e3bc"
|
||||||
|
integrity sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-musl@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz#227a949c481909c781d8a39c280f75553cdd16bc"
|
||||||
|
integrity sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==
|
||||||
|
|
||||||
|
"@rollup/rollup-openbsd-x64@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz#f57241ebdb73d3bc236e7b1252988408b2139c13"
|
||||||
|
integrity sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==
|
||||||
|
|
||||||
|
"@rollup/rollup-openharmony-arm64@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz#3a9ffe5af71e8316dd2716b57dd64287a8c1fa0b"
|
||||||
|
integrity sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-arm64-msvc@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz#7d4a40396ae79ebc1e3636c1d566c7d1838b800c"
|
||||||
|
integrity sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-ia32-msvc@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz#f234ab80141da45ebe85727f714eb20de2e72c2a"
|
||||||
|
integrity sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-x64-gnu@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz#5d5a664c23c8ff0526b9abd703b50ffe09703c2a"
|
||||||
|
integrity sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-x64-msvc@4.63.1":
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz#cd19d691330cbd52ebb13620acb6cf7140b95e80"
|
||||||
|
integrity sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==
|
||||||
|
|
||||||
|
"@sienci/gsender-plugin-sdk@file:../../packages/plugin-sdk":
|
||||||
|
version "0.2.1"
|
||||||
|
dependencies:
|
||||||
|
esbuild "^0.28.2"
|
||||||
|
|
||||||
|
"@types/estree@1.0.9":
|
||||||
|
version "1.0.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24"
|
||||||
|
integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==
|
||||||
|
|
||||||
|
esbuild@^0.25.0:
|
||||||
|
version "0.25.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.12.tgz#97a1d041f4ab00c2fce2f838d2b9969a2d2a97a5"
|
||||||
|
integrity sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==
|
||||||
|
optionalDependencies:
|
||||||
|
"@esbuild/aix-ppc64" "0.25.12"
|
||||||
|
"@esbuild/android-arm" "0.25.12"
|
||||||
|
"@esbuild/android-arm64" "0.25.12"
|
||||||
|
"@esbuild/android-x64" "0.25.12"
|
||||||
|
"@esbuild/darwin-arm64" "0.25.12"
|
||||||
|
"@esbuild/darwin-x64" "0.25.12"
|
||||||
|
"@esbuild/freebsd-arm64" "0.25.12"
|
||||||
|
"@esbuild/freebsd-x64" "0.25.12"
|
||||||
|
"@esbuild/linux-arm" "0.25.12"
|
||||||
|
"@esbuild/linux-arm64" "0.25.12"
|
||||||
|
"@esbuild/linux-ia32" "0.25.12"
|
||||||
|
"@esbuild/linux-loong64" "0.25.12"
|
||||||
|
"@esbuild/linux-mips64el" "0.25.12"
|
||||||
|
"@esbuild/linux-ppc64" "0.25.12"
|
||||||
|
"@esbuild/linux-riscv64" "0.25.12"
|
||||||
|
"@esbuild/linux-s390x" "0.25.12"
|
||||||
|
"@esbuild/linux-x64" "0.25.12"
|
||||||
|
"@esbuild/netbsd-arm64" "0.25.12"
|
||||||
|
"@esbuild/netbsd-x64" "0.25.12"
|
||||||
|
"@esbuild/openbsd-arm64" "0.25.12"
|
||||||
|
"@esbuild/openbsd-x64" "0.25.12"
|
||||||
|
"@esbuild/openharmony-arm64" "0.25.12"
|
||||||
|
"@esbuild/sunos-x64" "0.25.12"
|
||||||
|
"@esbuild/win32-arm64" "0.25.12"
|
||||||
|
"@esbuild/win32-ia32" "0.25.12"
|
||||||
|
"@esbuild/win32-x64" "0.25.12"
|
||||||
|
|
||||||
|
esbuild@^0.28.2:
|
||||||
|
version "0.28.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.28.2.tgz#0f43bd1bad955b72d24e2261e3abe5957ccf0816"
|
||||||
|
integrity sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==
|
||||||
|
optionalDependencies:
|
||||||
|
"@esbuild/aix-ppc64" "0.28.2"
|
||||||
|
"@esbuild/android-arm" "0.28.2"
|
||||||
|
"@esbuild/android-arm64" "0.28.2"
|
||||||
|
"@esbuild/android-x64" "0.28.2"
|
||||||
|
"@esbuild/darwin-arm64" "0.28.2"
|
||||||
|
"@esbuild/darwin-x64" "0.28.2"
|
||||||
|
"@esbuild/freebsd-arm64" "0.28.2"
|
||||||
|
"@esbuild/freebsd-x64" "0.28.2"
|
||||||
|
"@esbuild/linux-arm" "0.28.2"
|
||||||
|
"@esbuild/linux-arm64" "0.28.2"
|
||||||
|
"@esbuild/linux-ia32" "0.28.2"
|
||||||
|
"@esbuild/linux-loong64" "0.28.2"
|
||||||
|
"@esbuild/linux-mips64el" "0.28.2"
|
||||||
|
"@esbuild/linux-ppc64" "0.28.2"
|
||||||
|
"@esbuild/linux-riscv64" "0.28.2"
|
||||||
|
"@esbuild/linux-s390x" "0.28.2"
|
||||||
|
"@esbuild/linux-x64" "0.28.2"
|
||||||
|
"@esbuild/netbsd-arm64" "0.28.2"
|
||||||
|
"@esbuild/netbsd-x64" "0.28.2"
|
||||||
|
"@esbuild/openbsd-arm64" "0.28.2"
|
||||||
|
"@esbuild/openbsd-x64" "0.28.2"
|
||||||
|
"@esbuild/openharmony-arm64" "0.28.2"
|
||||||
|
"@esbuild/sunos-x64" "0.28.2"
|
||||||
|
"@esbuild/win32-arm64" "0.28.2"
|
||||||
|
"@esbuild/win32-ia32" "0.28.2"
|
||||||
|
"@esbuild/win32-x64" "0.28.2"
|
||||||
|
|
||||||
|
fdir@^6.4.4, fdir@^6.5.0:
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350"
|
||||||
|
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
|
||||||
|
|
||||||
|
fsevents@~2.3.2, fsevents@~2.3.3:
|
||||||
|
version "2.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||||
|
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||||
|
|
||||||
|
nanoid@^3.3.18:
|
||||||
|
version "3.3.18"
|
||||||
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.18.tgz#f66a2de1199ffde0fcf21c8a5f13106b1c081913"
|
||||||
|
integrity sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==
|
||||||
|
|
||||||
|
picocolors@^1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
|
||||||
|
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
|
||||||
|
|
||||||
|
picomatch@^4.0.2, picomatch@^4.0.4:
|
||||||
|
version "4.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.7.tgz#6313360034ccb36b3dc61ecbdff78121f90fe21f"
|
||||||
|
integrity sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==
|
||||||
|
|
||||||
|
postcss@^8.5.3:
|
||||||
|
version "8.5.28"
|
||||||
|
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.28.tgz#da4563a99a06e62d6c1cd1acae363224bcaed6e9"
|
||||||
|
integrity sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==
|
||||||
|
dependencies:
|
||||||
|
nanoid "^3.3.18"
|
||||||
|
picocolors "^1.1.1"
|
||||||
|
source-map-js "^1.2.1"
|
||||||
|
|
||||||
|
rollup@^4.34.9:
|
||||||
|
version "4.63.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.63.1.tgz#a9b96d5b2558d034babb12ad8b67a043bc870ac4"
|
||||||
|
integrity sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==
|
||||||
|
dependencies:
|
||||||
|
"@types/estree" "1.0.9"
|
||||||
|
optionalDependencies:
|
||||||
|
"@napi-rs/lzma-linux-x64-gnu" "1.5.1"
|
||||||
|
"@rollup/rollup-android-arm-eabi" "4.63.1"
|
||||||
|
"@rollup/rollup-android-arm64" "4.63.1"
|
||||||
|
"@rollup/rollup-darwin-arm64" "4.63.1"
|
||||||
|
"@rollup/rollup-darwin-x64" "4.63.1"
|
||||||
|
"@rollup/rollup-freebsd-arm64" "4.63.1"
|
||||||
|
"@rollup/rollup-freebsd-x64" "4.63.1"
|
||||||
|
"@rollup/rollup-linux-arm-gnueabihf" "4.63.1"
|
||||||
|
"@rollup/rollup-linux-arm-musleabihf" "4.63.1"
|
||||||
|
"@rollup/rollup-linux-arm64-gnu" "4.63.1"
|
||||||
|
"@rollup/rollup-linux-arm64-musl" "4.63.1"
|
||||||
|
"@rollup/rollup-linux-loong64-gnu" "4.63.1"
|
||||||
|
"@rollup/rollup-linux-loong64-musl" "4.63.1"
|
||||||
|
"@rollup/rollup-linux-ppc64-gnu" "4.63.1"
|
||||||
|
"@rollup/rollup-linux-ppc64-musl" "4.63.1"
|
||||||
|
"@rollup/rollup-linux-riscv64-gnu" "4.63.1"
|
||||||
|
"@rollup/rollup-linux-riscv64-musl" "4.63.1"
|
||||||
|
"@rollup/rollup-linux-s390x-gnu" "4.63.1"
|
||||||
|
"@rollup/rollup-linux-x64-gnu" "4.63.1"
|
||||||
|
"@rollup/rollup-linux-x64-musl" "4.63.1"
|
||||||
|
"@rollup/rollup-openbsd-x64" "4.63.1"
|
||||||
|
"@rollup/rollup-openharmony-arm64" "4.63.1"
|
||||||
|
"@rollup/rollup-win32-arm64-msvc" "4.63.1"
|
||||||
|
"@rollup/rollup-win32-ia32-msvc" "4.63.1"
|
||||||
|
"@rollup/rollup-win32-x64-gnu" "4.63.1"
|
||||||
|
"@rollup/rollup-win32-x64-msvc" "4.63.1"
|
||||||
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
|
source-map-js@^1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
||||||
|
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
||||||
|
|
||||||
|
tinyglobby@^0.2.13:
|
||||||
|
version "0.2.17"
|
||||||
|
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.17.tgz#562a9a6c9eb2b3b123d39719f9af5bb44fcd7631"
|
||||||
|
integrity sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==
|
||||||
|
dependencies:
|
||||||
|
fdir "^6.5.0"
|
||||||
|
picomatch "^4.0.4"
|
||||||
|
|
||||||
|
vite@^6.0.0:
|
||||||
|
version "6.4.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/vite/-/vite-6.4.3.tgz#85a164db7ce706f2a776812efa2b340f1721858e"
|
||||||
|
integrity sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==
|
||||||
|
dependencies:
|
||||||
|
esbuild "^0.25.0"
|
||||||
|
fdir "^6.4.4"
|
||||||
|
picomatch "^4.0.2"
|
||||||
|
postcss "^8.5.3"
|
||||||
|
rollup "^4.34.9"
|
||||||
|
tinyglobby "^0.2.13"
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "~2.3.3"
|
||||||
Reference in New Issue
Block a user