simvyn 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -471,6 +471,10 @@
471
471
  z-index: 50;
472
472
  }
473
473
 
474
+ .z-\[100\] {
475
+ z-index: 100;
476
+ }
477
+
474
478
  .col-span-2 {
475
479
  grid-column: span 2 / span 2;
476
480
  }
@@ -776,8 +780,8 @@
776
780
  width: calc(var(--spacing) * 48);
777
781
  }
778
782
 
779
- .w-72 {
780
- width: calc(var(--spacing) * 72);
783
+ .w-84 {
784
+ width: calc(var(--spacing) * 84);
781
785
  }
782
786
 
783
787
  .w-\[30\%\] {
@@ -1314,6 +1318,10 @@
1314
1318
  background-color: #1e1e2d99 !important;
1315
1319
  }
1316
1320
 
1321
+ .\!bg-\[rgba\(30\,30\,45\,0\.85\)\] {
1322
+ background-color: #1e1e2dd9 !important;
1323
+ }
1324
+
1317
1325
  .bg-accent-blue {
1318
1326
  background-color: var(--color-accent-blue);
1319
1327
  }
@@ -1542,6 +1550,10 @@
1542
1550
  }
1543
1551
  }
1544
1552
 
1553
+ .fill-amber-400 {
1554
+ fill: var(--color-amber-400);
1555
+ }
1556
+
1545
1557
  .object-cover {
1546
1558
  object-fit: cover;
1547
1559
  }
@@ -1865,6 +1877,16 @@
1865
1877
  color: var(--color-text-muted);
1866
1878
  }
1867
1879
 
1880
+ .text-text-muted\/40 {
1881
+ color: #54555866;
1882
+ }
1883
+
1884
+ @supports (color: color-mix(in lab, red, red)) {
1885
+ .text-text-muted\/40 {
1886
+ color: color-mix(in oklab, var(--color-text-muted) 40%, transparent);
1887
+ }
1888
+ }
1889
+
1868
1890
  .text-text-muted\/50 {
1869
1891
  color: #54555880;
1870
1892
  }
@@ -1944,6 +1966,16 @@
1944
1966
  }
1945
1967
  }
1946
1968
 
1969
+ .shadow-black\/40 {
1970
+ --tw-shadow-color: #0006;
1971
+ }
1972
+
1973
+ @supports (color: color-mix(in lab, red, red)) {
1974
+ .shadow-black\/40 {
1975
+ --tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-black) 40%, transparent) var(--tw-shadow-alpha), transparent);
1976
+ }
1977
+ }
1978
+
1947
1979
  .ring-green-500\/20 {
1948
1980
  --tw-ring-color: #00c75833;
1949
1981
  }
@@ -2103,6 +2135,16 @@
2103
2135
  }
2104
2136
  }
2105
2137
 
2138
+ .hover\:text-amber-400\/60:hover {
2139
+ color: #fcbb0099;
2140
+ }
2141
+
2142
+ @supports (color: color-mix(in lab, red, red)) {
2143
+ .hover\:text-amber-400\/60:hover {
2144
+ color: color-mix(in oklab, var(--color-amber-400) 60%, transparent);
2145
+ }
2146
+ }
2147
+
2106
2148
  .hover\:text-red-200:hover {
2107
2149
  color: var(--color-red-200);
2108
2150
  }
@@ -30173,6 +30173,46 @@ function useWsListener(channel, type, handler) {
30173
30173
  return addListener(channel, type, handler);
30174
30174
  }, [addListener, channel, type, handler]);
30175
30175
  }
30176
+ const useFavouriteStore = create((set, get2) => ({
30177
+ favouriteIds: /* @__PURE__ */ new Set(),
30178
+ loaded: false,
30179
+ load: async () => {
30180
+ try {
30181
+ const res = await fetch("/api/modules/devices/favourites");
30182
+ if (res.ok) {
30183
+ const data = await res.json();
30184
+ const ids = data.favourites ?? [];
30185
+ set({ favouriteIds: new Set(ids), loaded: true });
30186
+ }
30187
+ } catch {
30188
+ }
30189
+ },
30190
+ toggle: async (id2) => {
30191
+ const { favouriteIds } = get2();
30192
+ const wasFavourite = favouriteIds.has(id2);
30193
+ const next = new Set(favouriteIds);
30194
+ if (wasFavourite) {
30195
+ next.delete(id2);
30196
+ } else {
30197
+ next.add(id2);
30198
+ }
30199
+ set({ favouriteIds: next });
30200
+ try {
30201
+ const res = await fetch("/api/modules/devices/favourites", {
30202
+ method: wasFavourite ? "DELETE" : "POST",
30203
+ headers: { "Content-Type": "application/json" },
30204
+ body: JSON.stringify({ deviceId: id2 })
30205
+ });
30206
+ if (!res.ok) {
30207
+ set({ favouriteIds });
30208
+ }
30209
+ } catch {
30210
+ set({ favouriteIds });
30211
+ }
30212
+ },
30213
+ isFavourite: (id2) => get2().favouriteIds.has(id2)
30214
+ }));
30215
+ useFavouriteStore.getState().load();
30176
30216
  const MULTI_SELECT_MODULES = /* @__PURE__ */ new Set(["location"]);
30177
30217
  function groupDevices(devices) {
30178
30218
  const groups = {};
@@ -30193,22 +30233,52 @@ function groupDevices(devices) {
30193
30233
  }
30194
30234
  return ordered;
30195
30235
  }
30236
+ function StarIcon({
30237
+ filled,
30238
+ onClick
30239
+ }) {
30240
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
30241
+ "svg",
30242
+ {
30243
+ className: `h-3.5 w-3.5 shrink-0 cursor-pointer transition-colors ${filled ? "text-amber-400" : "text-text-muted/40 hover:text-amber-400/60"}`,
30244
+ viewBox: "0 0 24 24",
30245
+ fill: filled ? "currentColor" : "none",
30246
+ stroke: "currentColor",
30247
+ strokeWidth: 2,
30248
+ onClick,
30249
+ children: [
30250
+ /* @__PURE__ */ jsxRuntimeExports.jsx("title", { children: filled ? "Remove from Favourites" : "Add to Favourites" }),
30251
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
30252
+ "path",
30253
+ {
30254
+ strokeLinecap: "round",
30255
+ strokeLinejoin: "round",
30256
+ d: "M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z"
30257
+ }
30258
+ )
30259
+ ]
30260
+ }
30261
+ );
30262
+ }
30196
30263
  function StateIndicator({ state }) {
30197
30264
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
30198
30265
  "span",
30199
30266
  {
30200
- className: `inline-block h-2 w-2 rounded-full ${state === "booted" ? "bg-green-500 ring-2 ring-green-500/20" : "bg-text-muted/50"}`
30267
+ className: `inline-block h-2 w-2 shrink-0 rounded-full ${state === "booted" ? "bg-green-500 ring-2 ring-green-500/20" : "bg-text-muted/50"}`
30201
30268
  }
30202
30269
  );
30203
30270
  }
30204
30271
  function DeviceSelector() {
30205
30272
  const [open, setOpen] = reactExports.useState(false);
30273
+ const [contextMenu, setContextMenu] = reactExports.useState(null);
30206
30274
  const ref = reactExports.useRef(null);
30207
30275
  const devices = useDeviceStore((s) => s.devices);
30208
30276
  const selectedDeviceIds = useDeviceStore((s) => s.selectedDeviceIds);
30209
30277
  const selectDevice = useDeviceStore((s) => s.selectDevice);
30210
30278
  const toggleDevice = useDeviceStore((s) => s.toggleDevice);
30211
30279
  const truncateToFirst = useDeviceStore((s) => s.truncateToFirst);
30280
+ const toggleFavourite = useFavouriteStore((s) => s.toggle);
30281
+ const isFavourite = useFavouriteStore((s) => s.isFavourite);
30212
30282
  const activeModule = useModuleStore((s) => s.activeModule);
30213
30283
  const isMultiSelect = MULTI_SELECT_MODULES.has(activeModule ?? "");
30214
30284
  const handleDeviceDisconnected = reactExports.useCallback((payload) => {
@@ -30226,11 +30296,24 @@ function DeviceSelector() {
30226
30296
  function handleClick(e) {
30227
30297
  if (ref.current && !ref.current.contains(e.target)) {
30228
30298
  setOpen(false);
30299
+ setContextMenu(null);
30229
30300
  }
30230
30301
  }
30231
30302
  document.addEventListener("mousedown", handleClick);
30232
30303
  return () => document.removeEventListener("mousedown", handleClick);
30233
30304
  }, []);
30305
+ reactExports.useEffect(() => {
30306
+ if (!contextMenu) return;
30307
+ function handleClick() {
30308
+ setContextMenu(null);
30309
+ }
30310
+ document.addEventListener("mousedown", handleClick);
30311
+ return () => document.removeEventListener("mousedown", handleClick);
30312
+ }, [contextMenu]);
30313
+ const handleContextMenu = (e, deviceId) => {
30314
+ e.preventDefault();
30315
+ setContextMenu({ x: e.clientX, y: e.clientY, deviceId });
30316
+ };
30234
30317
  const selectedSet = new Set(selectedDeviceIds);
30235
30318
  const firstDevice = devices.find((d) => d.id === selectedDeviceIds[0]);
30236
30319
  let label;
@@ -30266,7 +30349,7 @@ function DeviceSelector() {
30266
30349
  ]
30267
30350
  }
30268
30351
  ),
30269
- open && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "glass-panel absolute top-full right-0 z-50 mt-2 w-72 max-h-96 overflow-y-auto p-1 shadow-xl shadow-black/30 !backdrop-blur-2xl !bg-[rgba(30,30,45,0.6)]", children: [
30352
+ open && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "glass-panel absolute top-full right-0 z-50 mt-2 w-84 max-h-96 overflow-y-auto p-1 shadow-xl shadow-black/30 !backdrop-blur-2xl !bg-[rgba(30,30,45,0.6)]", children: [
30270
30353
  Object.entries(groups).map(([platform, devs]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
30271
30354
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-1 text-[10px] font-semibold uppercase tracking-wider text-text-muted", children: platform }),
30272
30355
  devs.map((d) => {
@@ -30283,24 +30366,32 @@ function DeviceSelector() {
30283
30366
  setOpen(false);
30284
30367
  }
30285
30368
  },
30369
+ onContextMenu: (e) => handleContextMenu(e, d.id),
30286
30370
  className: `flex w-full items-center gap-2 rounded-[var(--radius-button)] px-3 py-2 text-left text-sm transition-colors ${isSelected ? "bg-accent-blue/20 text-accent-blue" : "text-text-primary hover:bg-[rgba(255,255,255,0.08)]"}`,
30287
30371
  children: [
30288
30372
  isMultiSelect ? /* @__PURE__ */ jsxRuntimeExports.jsx(
30289
30373
  "span",
30290
30374
  {
30291
- className: `inline-flex h-3.5 w-3.5 items-center justify-center rounded-full border ${isSelected ? "border-accent-blue bg-accent-blue" : "border-text-muted/50 bg-transparent"}`,
30375
+ className: `inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-full border ${isSelected ? "border-accent-blue bg-accent-blue" : "border-text-muted/50 bg-transparent"}`,
30292
30376
  children: isSelected && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-white" })
30293
30377
  }
30294
30378
  ) : /* @__PURE__ */ jsxRuntimeExports.jsx(StateIndicator, { state: d.state }),
30295
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 truncate", children: d.name }),
30296
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs text-text-muted", children: [
30297
- d.osVersion,
30298
- d.deviceType !== "Physical" && d.deviceType !== "Emulator" && d.deviceType !== "Unknown" && (d.id.startsWith("physical:") || d.deviceType !== d.name) ? ` · ${d.deviceType}` : ""
30299
- ] }),
30379
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 truncate", title: d.name, children: d.name }),
30380
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-xs text-text-muted", children: d.osVersion }),
30381
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
30382
+ StarIcon,
30383
+ {
30384
+ filled: isFavourite(d.id),
30385
+ onClick: (e) => {
30386
+ e.stopPropagation();
30387
+ toggleFavourite(d.id);
30388
+ }
30389
+ }
30390
+ ),
30300
30391
  !isMultiSelect && isSelected && /* @__PURE__ */ jsxRuntimeExports.jsxs(
30301
30392
  "svg",
30302
30393
  {
30303
- className: "h-4 w-4 text-accent-blue",
30394
+ className: "h-4 w-4 shrink-0 text-accent-blue",
30304
30395
  fill: "none",
30305
30396
  viewBox: "0 0 24 24",
30306
30397
  stroke: "currentColor",
@@ -30325,7 +30416,27 @@ function DeviceSelector() {
30325
30416
  })
30326
30417
  ] }, platform)),
30327
30418
  devices.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-4 text-center text-sm text-text-muted", children: "No devices detected" })
30328
- ] })
30419
+ ] }),
30420
+ contextMenu && /* @__PURE__ */ jsxRuntimeExports.jsx(
30421
+ "div",
30422
+ {
30423
+ className: "glass-panel fixed p-1 shadow-xl shadow-black/40 z-[100] !backdrop-blur-2xl !bg-[rgba(30,30,45,0.85)]",
30424
+ style: { left: contextMenu.x, top: contextMenu.y },
30425
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
30426
+ "button",
30427
+ {
30428
+ type: "button",
30429
+ className: "flex w-full items-center gap-2 rounded-[var(--radius-button)] px-3 py-1.5 text-left text-sm text-text-primary hover:bg-[rgba(255,255,255,0.08)] whitespace-nowrap",
30430
+ onMouseDown: (e) => {
30431
+ e.stopPropagation();
30432
+ toggleFavourite(contextMenu.deviceId);
30433
+ setContextMenu(null);
30434
+ },
30435
+ children: isFavourite(contextMenu.deviceId) ? "Remove from Favourites" : "Add to Favourites"
30436
+ }
30437
+ )
30438
+ }
30439
+ )
30329
30440
  ] });
30330
30441
  }
30331
30442
  function isMac() {
@@ -30485,6 +30596,8 @@ function DevicePanel() {
30485
30596
  } catch {
30486
30597
  }
30487
30598
  }
30599
+ const favouriteIds = useFavouriteStore((s) => s.favouriteIds);
30600
+ const favouriteDevices = devices.filter((d) => favouriteIds.has(d.id));
30488
30601
  const iosDevices = devices.filter((d) => d.platform === "ios");
30489
30602
  const androidDevices = devices.filter((d) => d.platform === "android");
30490
30603
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-6 space-y-6", children: [
@@ -30508,6 +30621,17 @@ function DevicePanel() {
30508
30621
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-base font-medium text-text-primary mb-2", children: "No Devices Detected" }),
30509
30622
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-text-secondary", children: "Make sure Xcode Simulator or Android Emulator tools are installed." })
30510
30623
  ] }),
30624
+ favouriteDevices.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
30625
+ FavouritesSection,
30626
+ {
30627
+ devices: favouriteDevices,
30628
+ actionInFlight,
30629
+ onAction: doAction,
30630
+ onClone: handleClone,
30631
+ onRename: handleRename,
30632
+ onDelete: handleDelete
30633
+ }
30634
+ ),
30511
30635
  iosDevices.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
30512
30636
  IosDeviceSection,
30513
30637
  {
@@ -30629,6 +30753,35 @@ function CreateSimulatorForm({ onCreated }) {
30629
30753
  ] })
30630
30754
  ] });
30631
30755
  }
30756
+ function FavouritesSection({
30757
+ devices,
30758
+ actionInFlight,
30759
+ onAction,
30760
+ onClone,
30761
+ onRename,
30762
+ onDelete
30763
+ }) {
30764
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
30765
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mb-3", children: [
30766
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Star, { size: 14, className: "text-amber-400", fill: "currentColor" }),
30767
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-sm font-medium text-text-muted uppercase tracking-wider", children: "Favourites" })
30768
+ ] }),
30769
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid gap-3 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", children: devices.map((device) => /* @__PURE__ */ jsxRuntimeExports.jsx(
30770
+ DeviceCard,
30771
+ {
30772
+ device,
30773
+ isLoading: actionInFlight?.deviceId === device.id,
30774
+ loadingAction: actionInFlight?.deviceId === device.id ? actionInFlight.action : void 0,
30775
+ onAction,
30776
+ onClone: device.platform === "ios" ? () => onClone(device) : void 0,
30777
+ onRename: device.platform === "ios" ? () => onRename(device) : void 0,
30778
+ onDelete: device.platform === "ios" ? () => onDelete(device) : void 0,
30779
+ showLifecycleActions: device.platform === "ios"
30780
+ },
30781
+ device.id
30782
+ )) })
30783
+ ] });
30784
+ }
30632
30785
  function IosDeviceSection({
30633
30786
  devices,
30634
30787
  actionInFlight,
@@ -30872,13 +31025,32 @@ function DeviceCard({
30872
31025
  onDelete,
30873
31026
  showLifecycleActions
30874
31027
  }) {
31028
+ const isFav = useFavouriteStore((s) => s.isFavourite)(device.id);
31029
+ const toggleFavourite = useFavouriteStore((s) => s.toggle);
30875
31030
  const truncatedId = device.id.length > 16 ? `${device.id.slice(0, 8)}…${device.id.slice(-6)}` : device.id;
30876
31031
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "glass-panel p-4 flex flex-col gap-3 hover:border-glass-border-hover transition-all duration-150", children: [
30877
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between", children: [
31032
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
30878
31033
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0 flex-1", children: [
30879
31034
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium text-text-primary truncate", children: device.name }),
30880
31035
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-text-muted mt-0.5 font-mono", children: truncatedId })
30881
31036
  ] }),
31037
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
31038
+ "button",
31039
+ {
31040
+ type: "button",
31041
+ onClick: () => toggleFavourite(device.id),
31042
+ className: "shrink-0 mt-0.5",
31043
+ title: isFav ? "Remove from Favourites" : "Add to Favourites",
31044
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
31045
+ Star,
31046
+ {
31047
+ size: 16,
31048
+ className: `transition-colors cursor-pointer ${isFav ? "text-amber-400 fill-amber-400" : "text-text-muted/40 hover:text-amber-400/60"}`,
31049
+ fill: isFav ? "currentColor" : "none"
31050
+ }
31051
+ )
31052
+ }
31053
+ ),
30882
31054
  /* @__PURE__ */ jsxRuntimeExports.jsx(StateBadge, { state: device.state })
30883
31055
  ] }),
30884
31056
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 text-xs text-text-secondary", children: [
@@ -51520,7 +51692,7 @@ function ToolSettingsPanel() {
51520
51692
  const [copied, setCopied] = reactExports.useState(false);
51521
51693
  const devices = useDeviceStore((s) => s.devices);
51522
51694
  const modules = useModuleStore((s) => s.modules);
51523
- const version = "2.0.0";
51695
+ const version = "2.2.0";
51524
51696
  const fetchStorage = reactExports.useCallback(() => {
51525
51697
  fetch("/api/tool-settings/storage").then((r) => r.json()).then((data) => setStorage(data)).catch(() => {
51526
51698
  });
@@ -52868,4 +53040,4 @@ function App() {
52868
53040
  clientExports.createRoot(document.getElementById("root")).render(
52869
53041
  /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.StrictMode, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(App, {}) })
52870
53042
  );
52871
- //# sourceMappingURL=index-SgS9nwZ7.js.map
53043
+ //# sourceMappingURL=index-B_mApJSj.js.map