diff --git a/package-lock.json b/package-lock.json
index 22401649..f89f0a31 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1606,9 +1606,9 @@
]
},
"node_modules/@sveltejs/acorn-typescript": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz",
- "integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==",
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.10.tgz",
+ "integrity": "sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -2399,9 +2399,9 @@
}
},
"node_modules/devalue": {
- "version": "5.6.4",
- "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz",
- "integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==",
+ "version": "5.8.1",
+ "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz",
+ "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==",
"dev": true,
"license": "MIT"
},
@@ -2639,13 +2639,21 @@
}
},
"node_modules/esrap": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.3.tgz",
- "integrity": "sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==",
+ "version": "2.2.9",
+ "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.9.tgz",
+ "integrity": "sha512-4KijP+NxCWthMCUC3qHbE6n4vCjqgJS1uAYKhuT/GWfFTf1Qyive2TgOjep+gzbSzRfnNyaN/UU9YmdOt8Eg0A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/types": "^8.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@typescript-eslint/types": {
+ "optional": true
+ }
}
},
"node_modules/esrecurse": {
@@ -3276,9 +3284,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
+ "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
"dev": true,
"funding": [
{
@@ -3461,9 +3469,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.6",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
- "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "version": "8.5.15",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
+ "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"dev": true,
"funding": [
{
@@ -3481,7 +3489,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.11",
+ "nanoid": "^3.3.12",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -3956,24 +3964,24 @@
}
},
"node_modules/svelte": {
- "version": "5.53.7",
- "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.53.7.tgz",
- "integrity": "sha512-uxck1KI7JWtlfP3H6HOWi/94soAl23jsGJkBzN2BAWcQng0+lTrRNhxActFqORgnO9BHVd1hKJhG+ljRuIUWfQ==",
+ "version": "5.55.9",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.9.tgz",
+ "integrity": "sha512-fTjjT8cHLDwigcu2j3pv7Jq04LklXevPB8uBgyHNiTXv+RMNvVnrjS4UEYrLMkhuq1vpCodHjiW+z/95SDs/fg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
"@jridgewell/sourcemap-codec": "^1.5.0",
- "@sveltejs/acorn-typescript": "^1.0.5",
+ "@sveltejs/acorn-typescript": "^1.0.10",
"@types/estree": "^1.0.5",
"@types/trusted-types": "^2.0.7",
"acorn": "^8.12.1",
"aria-query": "5.3.1",
"axobject-query": "^4.1.0",
"clsx": "^2.1.1",
- "devalue": "^5.6.3",
+ "devalue": "^5.8.1",
"esm-env": "^1.2.1",
- "esrap": "^2.2.2",
+ "esrap": "^2.2.9",
"is-reference": "^3.0.3",
"locate-character": "^3.0.0",
"magic-string": "^0.30.11",
@@ -4483,9 +4491,9 @@
}
},
"node_modules/ws": {
- "version": "8.19.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
- "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
+ "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
"license": "MIT",
"optional": true,
"engines": {
diff --git a/src/App.svelte b/src/App.svelte
index ac63b2a3..d3a0bcf0 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -2,4 +2,4 @@
import MainPage from "$/components/MainPage.svelte";
-
+
\ No newline at end of file
diff --git a/src/components/LabelDesigner.svelte b/src/components/LabelDesigner.svelte
index fc92ab80..8648b1cc 100644
--- a/src/components/LabelDesigner.svelte
+++ b/src/components/LabelDesigner.svelte
@@ -1,4 +1,7 @@
@@ -130,6 +158,17 @@
{/each}
+
+
+
+
+
+
{/if}
{#if $printerMeta}
@@ -279,7 +318,7 @@
class="btn btn-primary"
disabled={$connectionState === "connecting" ||
(!featureSupport.capacitorBle && !featureSupport.webBluetooth && !featureSupport.webSerial)}
- onclick={onConnectClicked}>
+ onclick={() => onConnectClicked(false)}>
{/if}
@@ -296,4 +335,4 @@
width: 100vw;
max-width: 300px;
}
-
+
\ No newline at end of file
diff --git a/src/components/designer-controls/GenericObjectParamsControls.svelte b/src/components/designer-controls/GenericObjectParamsControls.svelte
index c785cd55..6fc644ad 100644
--- a/src/components/designer-controls/GenericObjectParamsControls.svelte
+++ b/src/components/designer-controls/GenericObjectParamsControls.svelte
@@ -4,7 +4,7 @@
import { appConfig } from "$/stores";
import MdIcon from "$/components/basic/MdIcon.svelte";
import ObjectPositionControls from "$/components/designer-controls/ObjectPositionControls.svelte";
-
+ import { Toasts } from "$/utils/toasts";
interface Props {
selectedObject: fabric.FabricObject;
@@ -14,6 +14,22 @@
let { selectedObject, editRevision, valueUpdated }: Props = $props();
+ const saveQuickTransform = () => {
+ appConfig.update((cfg) => ({
+ ...cfg,
+ pdfQuickTransform: {
+ scaleX: selectedObject.scaleX ?? 1,
+ scaleY: selectedObject.scaleY ?? 1,
+ left: selectedObject.left ?? 0,
+ top: selectedObject.top ?? 0,
+ angle: selectedObject.angle ?? 0,
+ originX: selectedObject.originX,
+ originY: selectedObject.originY,
+ }
+ }));
+ Toasts.message("Saved as Quick Transform for incoming PDFs");
+ };
+
const putToCenterV = () => {
selectedObject.canvas!.centerObjectV(selectedObject);
valueUpdated();
@@ -114,10 +130,14 @@
+
+
{/if}
+
\ No newline at end of file
diff --git a/src/stores.ts b/src/stores.ts
index 0a6614ef..ce9fff39 100644
--- a/src/stores.ts
+++ b/src/stores.ts
@@ -52,7 +52,10 @@ export const csvData = writablePersisted("csv_params", CsvParamsSchem
userFonts.subscribe(FileUtils.loadFonts);
-export const automation = readable(
+// src/stores.ts
+
+// 1. Change `readable` to `writable`
+export const automation = writable(
(() => {
try {
return LocalStoragePersistence.loadAutomation() ?? undefined;
@@ -63,6 +66,11 @@ export const automation = readable(
})(),
);
+// 2. Automatically save to local storage whenever it changes
+automation.subscribe((value) => {
+ LocalStoragePersistence.saveAutomation(value);
+});
+
export const refreshRfidInfo = () => {
const client = get(printerClient);
diff --git a/src/types.ts b/src/types.ts
index d7381168..469e9abb 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -100,6 +100,16 @@ export const AutomationPropsSchema = z.object({
startPrint: z.enum(["after_connect", "immediately"]).optional(),
});
+export const PdfQuickTransformSchema = z.object({
+ scaleX: z.number(),
+ scaleY: z.number(),
+ left: z.number(),
+ top: z.number(),
+ angle: z.number(),
+ originX: z.string(),
+ originY: z.string(),
+});
+
export const AppConfigSchema = z.object({
/** Keep image aspect ration when using "fit" button */
fitMode: z.enum(["stretch", "ratio_min", "ratio_max"]),
@@ -107,6 +117,15 @@ export const AppConfigSchema = z.object({
iconListMode: z.enum(["user", "pack", "both"]),
packetIntervalMs: z.number().gte(0).optional(),
gridEnabled: z.boolean().optional(),
+ pdfQuickTransform: z.object({
+ scaleX: z.number(),
+ scaleY: z.number(),
+ left: z.number(),
+ top: z.number(),
+ angle: z.number(),
+ originX: z.string(),
+ originY: z.string()
+ }).optional()
});
export const UserIconSchema = z.object({
diff --git a/standalone-apps/capacitor/android/app/src/main/AndroidManifest.xml b/standalone-apps/capacitor/android/app/src/main/AndroidManifest.xml
index 9a35dec1..f84c6254 100644
--- a/standalone-apps/capacitor/android/app/src/main/AndroidManifest.xml
+++ b/standalone-apps/capacitor/android/app/src/main/AndroidManifest.xml
@@ -14,10 +14,30 @@
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask"
android:exported="true">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
-
+
\ No newline at end of file
diff --git a/standalone-apps/capacitor/android/app/src/main/java/ru/mmote/niimblues/MainActivity.java b/standalone-apps/capacitor/android/app/src/main/java/ru/mmote/niimblues/MainActivity.java
index 181e21f6..407f68fb 100644
--- a/standalone-apps/capacitor/android/app/src/main/java/ru/mmote/niimblues/MainActivity.java
+++ b/standalone-apps/capacitor/android/app/src/main/java/ru/mmote/niimblues/MainActivity.java
@@ -1,5 +1,12 @@
package ru.mmote.niimblues;
+import android.os.Bundle;
import com.getcapacitor.BridgeActivity;
-public class MainActivity extends BridgeActivity {}
+public class MainActivity extends BridgeActivity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ registerPlugin(PdfIntentPlugin.class);
+ super.onCreate(savedInstanceState);
+ }
+}
\ No newline at end of file
diff --git a/standalone-apps/capacitor/android/app/src/main/java/ru/mmote/niimblues/PdfIntentPlugin.java b/standalone-apps/capacitor/android/app/src/main/java/ru/mmote/niimblues/PdfIntentPlugin.java
new file mode 100644
index 00000000..b10bf97f
--- /dev/null
+++ b/standalone-apps/capacitor/android/app/src/main/java/ru/mmote/niimblues/PdfIntentPlugin.java
@@ -0,0 +1,118 @@
+package ru.mmote.niimblues;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.pdf.PdfRenderer;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.util.Base64;
+import android.util.Log;
+
+import com.getcapacitor.JSObject;
+import com.getcapacitor.Plugin;
+import com.getcapacitor.PluginCall;
+import com.getcapacitor.PluginMethod;
+import com.getcapacitor.annotation.CapacitorPlugin;
+
+import java.io.ByteArrayOutputStream;
+
+@CapacitorPlugin(name = "PdfIntent")
+public class PdfIntentPlugin extends Plugin {
+
+ // Cache the image here if Android processes it before Svelte is fully loaded
+ private String cachedBase64Image = null;
+
+ @PluginMethod
+ public void checkIntent(PluginCall call) {
+ // 1. If we already caught a PDF while Svelte was booting, return it now!
+ if (cachedBase64Image != null) {
+ JSObject ret = new JSObject();
+ ret.put("image", cachedBase64Image);
+ call.resolve(ret);
+ cachedBase64Image = null; // Clear cache after delivering
+ return;
+ }
+
+ // 2. Otherwise, check the Android intent directly
+ Intent intent = getActivity().getIntent();
+ processAndResolve(intent, call, null);
+ }
+
+ @Override
+ protected void handleOnNewIntent(Intent intent) {
+ super.handleOnNewIntent(intent);
+ getActivity().setIntent(intent);
+ processAndResolve(intent, null, "onPdfReceived");
+ }
+
+ private void processAndResolve(Intent intent, PluginCall call, String eventName) {
+ if (intent == null) {
+ if (call != null) call.resolve(new JSObject());
+ return;
+ }
+
+ String action = intent.getAction();
+ Uri pdfUri = null;
+
+ if (Intent.ACTION_SEND.equals(action)) {
+ pdfUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
+ } else if (Intent.ACTION_VIEW.equals(action)) {
+ pdfUri = intent.getData();
+ }
+
+ if (pdfUri == null) {
+ if (call != null) call.resolve(new JSObject());
+ return;
+ }
+
+ Uri finalUri = pdfUri;
+
+ new Thread(() -> {
+ try {
+ ParcelFileDescriptor fd = getContext().getContentResolver().openFileDescriptor(finalUri, "r");
+ if (fd == null) throw new Exception("Android returned null FD.");
+
+ PdfRenderer renderer = new PdfRenderer(fd);
+ if (renderer.getPageCount() == 0) throw new Exception("0 pages.");
+
+ PdfRenderer.Page page = renderer.openPage(0);
+
+ int width = 800;
+ int height = (int) (width * ((float) page.getHeight() / page.getWidth()));
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+
+ bitmap.eraseColor(Color.WHITE);
+ page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_PRINT);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
+ String base64Image = "data:image/png;base64," + Base64.encodeToString(baos.toByteArray(), Base64.NO_WRAP);
+
+ page.close();
+ renderer.close();
+ fd.close();
+
+ // Clear the intent so we don't process it twice
+ getActivity().setIntent(new Intent());
+
+ JSObject ret = new JSObject();
+ ret.put("image", base64Image);
+
+ // If Javascript asked for it directly via checkIntent():
+ if (call != null) {
+ call.resolve(ret);
+ }
+ // If Android pushed it automatically via handleOnNewIntent():
+ else if (eventName != null) {
+ cachedBase64Image = base64Image; // Cache it just in case JS misses the event
+ notifyListeners(eventName, ret); // Try to notify Svelte anyway
+ }
+
+ } catch (Exception e) {
+ Log.e("PdfIntentPlugin", "PDF Error", e);
+ if (call != null) call.reject(e.getMessage() != null ? e.getMessage() : "Error");
+ }
+ }).start();
+ }
+}
\ No newline at end of file