diff --git a/apps/electron-app/package.json b/apps/electron-app/package.json index 410fd932..8638c22e 100644 --- a/apps/electron-app/package.json +++ b/apps/electron-app/package.json @@ -6,7 +6,7 @@ "type": "git", "url": "https://github.com/xiduzo/microflow" }, - "version": "0.8.4", + "version": "0.8.5", "description": "An application which allows you to create flow-based logic for microcontrollers", "author": { "name": "Sander Boer", diff --git a/apps/electron-app/src/app.tsx b/apps/electron-app/src/app.tsx index 94440ffb..023ae4ae 100644 --- a/apps/electron-app/src/app.tsx +++ b/apps/electron-app/src/app.tsx @@ -4,7 +4,7 @@ import { useFigmaStore, useMqttStore, } from '@microflow/mqtt-provider/client'; -import { Toaster } from '@microflow/ui'; +import { Toaster, TooltipProvider } from '@microflow/ui'; import { ReactFlowProvider } from '@xyflow/react'; import { createRoot } from 'react-dom/client'; import { useDarkMode, useLocalStorage } from 'usehooks-ts'; @@ -17,28 +17,28 @@ import { CelebrationParticles } from './render/components/CelebrationParticles'; import { NewNodeCommandDialog } from './render/providers/NewNodeProvider'; import { StrictMode, useEffect, useMemo } from 'react'; import { useAppStore } from './render/stores/app'; -import { MqttSettingsForm } from './render/components/forms/MqttSettingsForm'; -import { AdvancedSettingsForm } from './render/components/forms/AdvancedSettingsForm'; -import { UserSettingsForm } from './render/components/forms/UserSettingsForm'; +import { Settings } from './render/components/Settings'; import logger from 'electron-log/renderer'; export function App() { return ( -
- - - - - - - - - - - - - -
+ +
+ + + + + + + + + + + + + +
+
); } @@ -127,15 +127,3 @@ function DarkMode() { return null; } - -function Settings() { - const { settingsOpen } = useAppStore(); - - return ( - <> - - - - - ); -} diff --git a/apps/electron-app/src/main/menu.ts b/apps/electron-app/src/main/menu.ts index 7578c391..53b3122a 100644 --- a/apps/electron-app/src/main/menu.ts +++ b/apps/electron-app/src/main/menu.ts @@ -34,7 +34,7 @@ export function createMenu(mainWindow: BrowserWindow, createWindow: () => Promis label: 'Flow', submenu: [ { - label: 'Insert node', + label: 'Add node', accelerator: isMac ? 'Cmd+K' : 'Ctrl+K', click: () => sendMessage(mainWindow, 'add-node'), }, @@ -143,13 +143,17 @@ export function createMenu(mainWindow: BrowserWindow, createWindow: () => Promis label: 'Settings', submenu: [ { - label: 'Microcontroller settings', - click: () => sendMessage(mainWindow, 'board-settings'), + label: 'User settings', + click: () => sendMessage(mainWindow, 'user-settings'), }, { label: 'MQTT settings', click: () => sendMessage(mainWindow, 'mqtt-settings'), }, + { + label: 'Microcontroller settings', + click: () => sendMessage(mainWindow, 'board-settings'), + }, ], }, // Development-only menu diff --git a/apps/electron-app/src/render/components/Settings.tsx b/apps/electron-app/src/render/components/Settings.tsx new file mode 100644 index 00000000..4c4be012 --- /dev/null +++ b/apps/electron-app/src/render/components/Settings.tsx @@ -0,0 +1,96 @@ +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, + Icons, +} from '@microflow/ui'; +import { useAppStore } from '../stores/app'; +import { UserSettingsForm } from './forms/UserSettingsForm'; +import { MqttSettingsForm } from './forms/MqttSettingsForm'; +import { AdvancedSettingsForm } from './forms/AdvancedSettingsForm'; +import { useEffect, useState } from 'react'; + +export function Settings() { + const { settingsOpen, setSettingsOpen } = useAppStore(); + const [accordionValue, setAccordionValue] = useState(''); + + // Map settingsOpen to accordion value + useEffect(() => { + if (settingsOpen === 'user-settings') { + setAccordionValue('user-settings'); + } else if (settingsOpen === 'mqtt-settings') { + setAccordionValue('mqtt-settings'); + } else if (settingsOpen === 'board-settings') { + setAccordionValue('board-settings'); + } + }, [settingsOpen]); + + const isOpen = !!settingsOpen; + + function handleOpenChange(open: boolean) { + if (!open) { + setSettingsOpen(undefined); + setAccordionValue(''); + } + } + + return ( + + + + + + Settings + + +
+ + + +
+ + User settings +
+
+ + + +
+ + +
+ + MQTT settings +
+
+ + + +
+ + +
+ + Microcontroller settings +
+
+ + + +
+
+
+
+
+ ); +} diff --git a/apps/electron-app/src/render/components/forms/AdvancedSettingsForm.tsx b/apps/electron-app/src/render/components/forms/AdvancedSettingsForm.tsx index cb1c7049..66c4e29f 100644 --- a/apps/electron-app/src/render/components/forms/AdvancedSettingsForm.tsx +++ b/apps/electron-app/src/render/components/forms/AdvancedSettingsForm.tsx @@ -1,9 +1,4 @@ import { - Sheet, - SheetContent, - SheetDescription, - SheetHeader, - SheetTitle, useForm, zodResolver, Zod, @@ -15,18 +10,15 @@ import { FormLabel, FormMessage, Input, - SheetClose, - SheetFooter, FormDescription, Icons, Alert, AlertTitle, AlertDescription, - Icon, - TooltipProvider, TooltipTrigger, TooltipContent, Tooltip, + toast, } from '@microflow/ui'; import { useLocalStorage } from 'usehooks-ts'; import { useAppStore } from '../../stores/app'; @@ -42,11 +34,10 @@ type Schema = Zod.infer; export type AdvancedConfig = Schema; -export function AdvancedSettingsForm(props: Props) { +export function AdvancedSettingsForm() { const [config, setConfig] = useLocalStorage('advanced-config', { ip: undefined, }); - const { setSettingsOpen } = useAppStore(); const form = useForm({ resolver: zodResolver(schema), @@ -55,12 +46,10 @@ export function AdvancedSettingsForm(props: Props) { function onSubmit(data: Schema) { setConfig(data); - closeForm(); - } - - function closeForm() { - setSettingsOpen(undefined); - props.onClose?.(); + form.reset(data); + toast.success('Microcontroller settings saved', { + description: 'Your microcontroller settings have been updated successfully.', + }); } function openUrl(url: string) { @@ -68,79 +57,67 @@ export function AdvancedSettingsForm(props: Props) { } return ( - { - if (opened) return; - closeForm(); - }} - > - - - - - Microcontroller settings - - - These settings will apply to any connected microcontroller. - - -
- - ( - - - IP-address - - - - - - - The IP-address of your microcontroller running StandardFirmataWifi. - - - - - - - - - Leave blank if you want to connect via USB. - - )} - /> - - - - - - - - - - - openUrl('https://github.com/firmata/arduino/tree/main/examples/StandardFirmataWiFi') - } - > - - StandardFirmataWifi - - When connecting over WiFi, you will need to flash and configure this library on your - microcontroller. - - -
-
+
+ + openUrl('https://github.com/firmata/arduino/tree/main/examples/StandardFirmataWiFi') + } + > + + StandardFirmataWifi + + + + When connecting over WiFi, you will need to flash and configure this library on your + microcontroller. + + + openUrl('https://github.com/ajfisher/node-pixel/tree/master/firmware')} + > + + LED strip control + + + + If you need to control a LED strip over WiFi, add the node-pixel firmare to{' '} + StandardFirmataWifi + + +
+ + ( + + + IP-address + + + + + + The IP-address of your microcontroller running StandardFirmataWifi. + + + + + + + + Leave blank if you want to connect via USB. + + )} + /> +
+ +
+ + +
); } - -type Props = { - open: boolean; - onClose?: () => void; -}; diff --git a/apps/electron-app/src/render/components/forms/MqttSettingsForm.tsx b/apps/electron-app/src/render/components/forms/MqttSettingsForm.tsx index bdc2719c..16e5b566 100644 --- a/apps/electron-app/src/render/components/forms/MqttSettingsForm.tsx +++ b/apps/electron-app/src/render/components/forms/MqttSettingsForm.tsx @@ -11,24 +11,18 @@ import { FormMessage, Icons, Input, - Sheet, - SheetClose, - SheetContent, - SheetDescription, - SheetFooter, - SheetHeader, - SheetTitle, Switch, useForm, Zod, zodResolver, + toast, } from '@microflow/ui'; import { useAppStore } from '../../stores/app'; import { MqttConfig } from '@microflow/mqtt-provider/client'; const schema = Zod.object({ - host: Zod.string().optional(), - port: Zod.number().optional(), + host: Zod.url().or(Zod.ipv4()), + port: Zod.number().min(0), username: Zod.string().optional(), password: Zod.string().optional(), protocol: Zod.enum(['ws', 'wss']).default('wss'), @@ -36,11 +30,13 @@ const schema = Zod.object({ type Schema = Zod.infer; -export function MqttSettingsForm(props: Props) { - const { user, mqttConfig, setMqttConfig } = useAppStore(); +export function MqttSettingsForm() { + const { user, mqttConfig, setMqttConfig, setSettingsOpen } = useAppStore(); const form = useForm({ resolver: zodResolver(schema), + mode: 'onChange', + reValidateMode: 'onChange', defaultValues: { host: mqttConfig?.host, port: mqttConfig?.port, @@ -50,156 +46,120 @@ export function MqttSettingsForm(props: Props) { }, }); - const { setSettingsOpen } = useAppStore(); - function onSubmit(data: Schema) { setMqttConfig(data); - setSettingsOpen(undefined); - closeForm(); - } - - function closeForm() { - setSettingsOpen(undefined); - props.onClose?.(); + form.reset(data); + toast.success('MQTT settings saved', { + description: 'Your MQTT broker settings have been updated successfully.', + }); } return ( - { - if (opened) return; - closeForm(); - }} - > - - - - - Broker settings - - - When using Figma nodes, make sure to configure the same MQTT broker in the{' '} - - Figma plugin - - . - - -
- - - Identifier - - - This is configured in your{' '} - { - event.preventDefault(); - setSettingsOpen('user-settings'); - }} - > - user settings - - - - ( - - Host - - - - - - )} - /> - ( - - Port - - { - const value = e.target.value; - field.onChange(value === '' ? undefined : Number(value)); - }} - /> - - - - )} - /> - ( - - Username - - - - - - )} - /> - ( - - Password - - - - - - )} - /> - ( - - Encrypted (wss) - - { - form.setValue('protocol', checked ? 'wss' : 'ws'); - }} - defaultChecked={form.getValues('protocol') === 'wss'} - /> - - - - )} - /> - - - - - - - - -
-
+
+

+ When using Figma nodes, make sure to configure the same MQTT broker in the{' '} + + Figma plugin + + . +

+
+ + + Identifier + + This is configured in your user settings + + ( + + Host + + + + + + )} + /> + ( + + Port + + { + const value = e.target.value; + field.onChange(value === '' ? undefined : Number(value)); + }} + /> + + + + )} + /> + ( + + Username + + + + + + )} + /> + ( + + Password + + + + + + )} + /> + ( + + Encrypted (wss) + + { + form.setValue('protocol', checked ? 'wss' : 'ws'); + }} + defaultChecked={form.getValues('protocol') === 'wss'} + /> + + + + )} + /> +
+ +
+ + +
); } - -type Props = { - open: boolean; - onClose?: () => void; -}; diff --git a/apps/electron-app/src/render/components/forms/UserSettingsForm.tsx b/apps/electron-app/src/render/components/forms/UserSettingsForm.tsx index 43b3480b..36190566 100644 --- a/apps/electron-app/src/render/components/forms/UserSettingsForm.tsx +++ b/apps/electron-app/src/render/components/forms/UserSettingsForm.tsx @@ -1,8 +1,4 @@ import { - Sheet, - SheetContent, - SheetHeader, - SheetTitle, useForm, zodResolver, Zod, @@ -15,8 +11,7 @@ import { FormMessage, Input, Icons, - SheetFooter, - SheetClose, + toast, } from '@microflow/ui'; import { useAppStore } from '../../stores/app'; import { getRandomUniqueUserName } from '../../../common/unique'; @@ -36,98 +31,78 @@ const schema = Zod.object({ type Schema = Zod.infer; -export function UserSettingsForm(props: Props) { - const { user, setUser, setSettingsOpen } = useAppStore(); +export function UserSettingsForm() { + const { user, setUser } = useAppStore(); const form = useForm({ resolver: zodResolver(schema), + mode: 'onChange', + reValidateMode: 'onChange', defaultValues: { name: user?.name ?? '', color: user?.color ?? '#ffcc00', }, - mode: 'onChange', }); - function setRandomUniqueName() { - const newName = getRandomUniqueUserName(); - form.clearErrors('name'); - form.setValue('name', newName); - } - const submit = (data: Schema) => { setUser(data); - closeForm(); + form.reset(data); + toast.success('User settings saved', { + description: 'Your user settings have been updated successfully.', + }); }; - function closeForm() { - setSettingsOpen(undefined); - props.onClose?.(); - } - return ( - { - closeForm(); - }} - > - - - - - User settings - - -
- - ( - - Your identifier -
- - - - -
- -
- )} - /> - ( - - Your cursor color - field.onChange(color)} - /> - - )} - /> - - - - - - - - -
-
+
+ + ( + + Your identifier +
+ + + + +
+ +
+ )} + /> + ( + + Your cursor color + field.onChange(color)} + /> + + )} + /> +
+ +
+ + ); } - -type Props = { - open: boolean; - onClose?: () => void; -}; diff --git a/apps/electron-app/src/render/components/react-flow/Handle.tsx b/apps/electron-app/src/render/components/react-flow/Handle.tsx index 59e451a4..528cb7de 100644 --- a/apps/electron-app/src/render/components/react-flow/Handle.tsx +++ b/apps/electron-app/src/render/components/react-flow/Handle.tsx @@ -1,4 +1,4 @@ -import { cva, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@microflow/ui'; +import { cva, Tooltip, TooltipContent, TooltipTrigger } from '@microflow/ui'; import { HandleProps, Position, @@ -82,52 +82,50 @@ export function Handle(props: Props) { }, [props.id, getZoom]); return ( - - - - { - if (props.isValidConnection) props.isValidConnection(edges, edge); - - // Can not connect to self - if (edge.source === edge.target) return false; - return true; - }} - className={handle({ + + + { + if (props.isValidConnection) props.isValidConnection(edges, edge); + + // Can not connect to self + if (edge.source === edge.target) return false; + return true; + }} + className={handle({ + position: props.position, + className: props.className, + isHandleSelectedViaEdge: isHandleSelectedViaEdge, + })} + style={{ + width: HANDLE_SIZE, + height: HANDLE_SIZE, + marginLeft: [Position.Top, Position.Bottom].includes(props.position) + ? HANDLE_SPACING * 2 * (props.offset ?? 0) + : 0, + marginTop: [Position.Left, Position.Right].includes(props.position) + ? HANDLE_SPACING * (props.offset ?? 0) + HANDLE_SPACING_OFFSET + : 0, + translate, + ...props.style, + }} + > + - - {String(props.title ?? props.id).toLowerCase()} - - - - {props.hint && {props.hint}} - - + {String(props.title ?? props.id).toLowerCase()} + + + + {props.hint && {props.hint}} + ); } diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Figma.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Figma.tsx index 8b1594dc..a50ea158 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/Figma.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/Figma.tsx @@ -5,14 +5,7 @@ import { useFigmaVariables, } from '@microflow/mqtt-provider/src/stores/figma'; import { useMqttStore } from '@microflow/mqtt-provider/src/stores/mqtt'; -import { - Icons, - Switch, - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from '@microflow/ui'; +import { Icons, Switch, Tooltip, TooltipContent, TooltipTrigger } from '@microflow/ui'; import { Position, useUpdateNodeInternals } from '@xyflow/react'; import { useEffect, useMemo, useRef } from 'react'; import { Handle } from '../Handle'; @@ -233,16 +226,14 @@ function Value() { case 'STRING': return (
- - - -
- {String(value)} -
-
- {String(value)} -
-
+ + +
+ {String(value)} +
+
+ {String(value)} +
{variable?.name}
); diff --git a/apps/electron-app/src/render/components/react-flow/nodes/Node.tsx b/apps/electron-app/src/render/components/react-flow/nodes/Node.tsx index 1a3027df..21448a49 100644 --- a/apps/electron-app/src/render/components/react-flow/nodes/Node.tsx +++ b/apps/electron-app/src/render/components/react-flow/nodes/Node.tsx @@ -10,7 +10,6 @@ import { Icons, Tooltip, TooltipContent, - TooltipProvider, TooltipTrigger, } from '@microflow/ui'; import { LevaPanel, useControls, useCreateStore } from 'leva'; @@ -41,14 +40,12 @@ function NodeHeader(props: { error?: string }) { {props.error && ( - - - - - - {props.error} - - + + + + + {props.error} + )} diff --git a/apps/electron-app/src/render/components/react-flow/panels/DockPanel.tsx b/apps/electron-app/src/render/components/react-flow/panels/DockPanel.tsx index d7b8fc6e..fa8e631b 100644 --- a/apps/electron-app/src/render/components/react-flow/panels/DockPanel.tsx +++ b/apps/electron-app/src/render/components/react-flow/panels/DockPanel.tsx @@ -8,8 +8,13 @@ import { DropdownMenuTrigger, Icon, Icons, + Kbd, + KbdGroup, Separator, toast, + Tooltip, + TooltipContent, + TooltipTrigger, } from '@microflow/ui'; import { useCollaborationActions, useCollaborationState } from '../../../stores/yjs'; import { useReactFlow } from '@xyflow/react'; @@ -18,9 +23,9 @@ import { useShallow } from 'zustand/shallow'; import { useNewNodeStore } from '../../../stores/new-node'; import { useState } from 'react'; import { JoinCollaborationDialog } from './JoinCollaborationDialog'; -import { useCopyToClipboard } from 'usehooks-ts'; import { formatOTP, generateOTP } from '../../../../common/otp'; import { useCopyCollaborationCode } from '../../../hooks/useCopyCollaborationCode'; +import { KbdAccelerator } from '../../KeyboardShortcut'; export function DockPanel() { const { undo, redo, canUndo, canRedo } = useCollaborationActions(); @@ -32,42 +37,108 @@ export function DockPanel() { - - + + + + + + Undo + + {KbdAccelerator()} + Z + + + - + + + + + + Redo + + Shift + {KbdAccelerator()} + Z + + + - + + + + + + Add node + + {KbdAccelerator()} + K + + + - + + + + + + Zoom in + + {KbdAccelerator()} + + + + + - + + + + + + Zoom out + + {KbdAccelerator()} + - + + + - + + + + + + Fit view + + {KbdAccelerator()} + O + + + ); @@ -85,17 +156,17 @@ function Settings() { - setSettingsOpen('board-settings')}> - - Microcontroller settings + setSettingsOpen('user-settings')}> + + User settings setSettingsOpen('mqtt-settings')}> MQTT settings - setSettingsOpen('user-settings')}> - - User settings + setSettingsOpen('board-settings')}> + + Microcontroller settings diff --git a/apps/electron-app/src/render/stores/yjs.ts b/apps/electron-app/src/render/stores/yjs.ts index 186d6ba6..df1fc79e 100644 --- a/apps/electron-app/src/render/stores/yjs.ts +++ b/apps/electron-app/src/render/stores/yjs.ts @@ -303,11 +303,7 @@ export const useYjsStore = create()((set, get) => { try { provider = new WebrtcProvider(normalizedRoomName, ydoc, { - signaling: [ - 'wss://microflow-signaling.xiduzo.com', - // 'ws://localhost:4444', - // 'wss://signaling.yjs.dev', - ], + signaling: ['wss://microflow-signaling.xiduzo.com'], password: undefined, maxConns: 20, ...options, diff --git a/apps/figma-plugin/package.json b/apps/figma-plugin/package.json index 33dff4d1..f64d34d2 100644 --- a/apps/figma-plugin/package.json +++ b/apps/figma-plugin/package.json @@ -2,7 +2,7 @@ "name": "microflow-hardware-bridge", "productName": "microflow-hardware-bridge", "private": true, - "version": "0.8.4", + "version": "0.8.5", "scripts": { "dev": "concurrently --names \"UI,PLUGIN\" --prefix-colors \"blue,green\" \"yarn watch:ui\" \"yarn watch:plugin\"", "dev:ui-only": "vite -c ./vite.config.ui.ts", diff --git a/apps/nextjs-app/components/DownloadApp.tsx b/apps/nextjs-app/components/DownloadApp.tsx index 52a1ee23..6bb89c97 100644 --- a/apps/nextjs-app/components/DownloadApp.tsx +++ b/apps/nextjs-app/components/DownloadApp.tsx @@ -23,7 +23,7 @@ export function DownloadApp() { const [os, setOs] = useState(); function downloadApp() { - const version = '0.8.4'; + const version = '0.8.5'; const baseUrl = `https://github.com/xiduzo/microflow/releases/download/v${version}`; switch (os) { diff --git a/apps/nextjs-app/package.json b/apps/nextjs-app/package.json index adec74a1..cb2a5bc8 100644 --- a/apps/nextjs-app/package.json +++ b/apps/nextjs-app/package.json @@ -1,6 +1,6 @@ { "name": "nextjs-app", - "version": "0.8.4", + "version": "0.8.5", "private": true, "scripts": { "dev": "next dev", diff --git a/packages/ui/components/ui/dock.tsx b/packages/ui/components/ui/dock.tsx index 41ab098b..3cb74bc4 100644 --- a/packages/ui/components/ui/dock.tsx +++ b/packages/ui/components/ui/dock.tsx @@ -128,7 +128,7 @@ const DockIcon = ({ ref={ref} style={{ width: scaleSize, height: scaleSize, padding }} className={cn( - 'flex aspect-square cursor-pointer items-center justify-center rounded-full', + 'flex aspect-square cursor-pointer items-center justify-center rounded-full hover:bg-muted-foreground', disableMagnification && 'hover:bg-muted-foreground transition-colors', className )}