@@ -12,7 +12,6 @@ import {
1212 AlertCircle ,
1313 Check ,
1414 ChevronDown ,
15- FolderOpen ,
1615 FolderSearch ,
1716 Loader2 ,
1817} from 'lucide-react' ;
@@ -24,14 +23,11 @@ import {
2423import {
2524 deleteWorkspaceAppearance ,
2625 upsertWorkspaceAppearance ,
27- uploadWorkspaceIcon ,
2826} from '@/lib/api/workspaceAppearances' ;
2927import { isStarterFeatureSet , type FeatureSet } from '@/lib/api/featureSets' ;
3028import { createMachine , getHostname , type Machine } from '@/lib/api/machines' ;
3129import type { Space } from '@/lib/api/spaces' ;
32- import { ServerIcon } from '@/components/ServerIcon' ;
3330import { MachineProfileEditor } from '@/components/machine-profile-editor' ;
34- import { EmojiPickerButton } from '@/components/emoji-picker-button.component' ;
3531
3632export type SaveStatus =
3733 | { kind : 'idle' }
@@ -161,12 +157,9 @@ export function BindingForm({
161157 featureSets,
162158 machines,
163159 localMachineId,
164- initial,
160+ initial : _initial ,
165161 initialUnmappedIcon,
166- label,
167- setLabel,
168162 icon,
169- setIcon,
170163 spaceId,
171164 setSpaceId,
172165 fsIds,
@@ -181,8 +174,6 @@ export function BindingForm({
181174 onFormSubmit,
182175 onCancel,
183176 onError,
184- onPersistIcon,
185- onIconChange,
186177 t,
187178} : {
188179 mode : 'create' | 'edit' | 'create-from-live' ;
@@ -192,10 +183,7 @@ export function BindingForm({
192183 localMachineId : string | null ;
193184 initial ?: WorkspaceBinding | null ;
194185 initialUnmappedIcon ?: string | null ;
195- label : string ;
196- setLabel : ( value : string ) => void ;
197186 icon : string ;
198- setIcon : ( value : string ) => void ;
199187 spaceId : string ;
200188 setSpaceId : ( value : string ) => void ;
201189 fsIds : string [ ] ;
@@ -210,8 +198,6 @@ export function BindingForm({
210198 onFormSubmit : ( machineTargets : ( string | null ) [ ] ) => Promise < void > ;
211199 onCancel : ( ) => void ;
212200 onError : ( message : string ) => void ;
213- onPersistIcon ?: ( nextIcon : string ) => Promise < void > ;
214- onIconChange ?: ( icon : string | null ) => void ;
215201 t : TFunction < [ 'workspaces' , 'common' ] > ;
216202} ) {
217203 const rootRef = useRef < HTMLInputElement | null > ( null ) ;
@@ -225,7 +211,6 @@ export function BindingForm({
225211 const [ newMachineIcon , setNewMachineIcon ] = useState ( '' ) ;
226212 const [ newMachineHostname , setNewMachineHostname ] = useState ( '' ) ;
227213 const [ creatingMachine , setCreatingMachine ] = useState ( false ) ;
228- const [ iconFilePath , setIconFilePath ] = useState ( '' ) ;
229214 const isEdit = mode === 'edit' ;
230215
231216 const rootEditable = mode !== 'create-from-live' ;
@@ -326,31 +311,6 @@ export function BindingForm({
326311 mode === 'create-from-live' ? normalizeIcon ( initialUnmappedIcon ) : null
327312 ) ;
328313
329- /** Persist icon immediately after upload so the card updates without waiting for autosave. */
330- const persistIconNow = async ( nextIcon : string ) => {
331- const workspaceRoot = root . trim ( ) ;
332- if ( ! workspaceRoot ) return ;
333-
334- if ( mode === 'edit' && initial && canSubmit && onPersistIcon ) {
335- await onPersistIcon ( nextIcon ) ;
336- return ;
337- }
338-
339- if ( mode === 'create-from-live' ) {
340- const normalizedIcon = normalizeIcon ( nextIcon ) ;
341- if ( normalizedIcon ) {
342- await upsertWorkspaceAppearance ( {
343- workspace_root : workspaceRoot ,
344- icon : normalizedIcon ,
345- } ) ;
346- lastSavedAppearanceRef . current = normalizedIcon ;
347- } else {
348- await deleteWorkspaceAppearance ( workspaceRoot ) ;
349- lastSavedAppearanceRef . current = null ;
350- }
351- }
352- } ;
353-
354314 const submitLabel =
355315 mode === 'create-from-live' ? t ( 'form.saveBinding' ) : t ( 'form.createBinding' ) ;
356316
@@ -384,134 +344,6 @@ export function BindingForm({
384344
385345 return (
386346 < div className = "space-y-5" >
387- < FormField label = { t ( 'form.label' ) } hint = { t ( 'form.labelHint' ) } >
388- < input
389- type = "text"
390- value = { label }
391- onChange = { ( e ) => setLabel ( e . target . value ) }
392- placeholder = { t ( 'form.labelPlaceholder' ) }
393- className = "w-full px-3 py-2 rounded-lg text-sm bg-[rgb(var(--background))] border border-[rgb(var(--border))] focus:outline-none focus:ring-2 focus:ring-primary-500"
394- data-testid = "workspace-binding-label-input"
395- />
396- </ FormField >
397-
398- < FormField label = { t ( 'form.icon' ) } hint = { t ( 'form.iconHint' ) } >
399- < div className = "space-y-2.5" >
400- < div className = "flex items-start gap-3" >
401- < div className = "w-14 h-14 rounded-xl border border-[rgb(var(--border-subtle))] bg-[rgb(var(--background))] flex items-center justify-center flex-shrink-0" >
402- { icon . trim ( ) ? (
403- < ServerIcon icon = { icon . trim ( ) } className = "h-9 w-9 object-contain" fallback = "📁" />
404- ) : (
405- < FolderOpen className = "h-6 w-6 text-[rgb(var(--muted))]" />
406- ) }
407- </ div >
408- < div className = "flex-1 min-w-0 space-y-2" >
409- < div className = "flex items-center gap-2" >
410- < EmojiPickerButton
411- value = { icon . trim ( ) . length <= 2 ? icon . trim ( ) : '' }
412- onChange = { ( emoji ) => {
413- setIcon ( emoji ) ;
414- onIconChange ?.( emoji ) ;
415- } }
416- />
417- < input
418- type = "text"
419- value = { icon }
420- onChange = { ( e ) => {
421- const next = e . target . value ;
422- setIcon ( next ) ;
423- onIconChange ?.( normalizeIcon ( next ) ) ;
424- } }
425- placeholder = { t ( 'form.iconPlaceholder' ) }
426- className = "min-w-0 flex-1 h-10 px-3 rounded-lg text-sm bg-[rgb(var(--background))] border border-[rgb(var(--border))] focus:outline-none focus:ring-2 focus:ring-primary-500"
427- data-testid = "workspace-binding-icon-input"
428- />
429- </ div >
430- < div className = "flex items-center gap-2 flex-wrap" >
431- { isTauri ( ) ? (
432- < Button
433- variant = "secondary"
434- size = "sm"
435- onClick = { async ( ) => {
436- try {
437- const picked = await pickPath ( {
438- directory : false ,
439- multiple : false ,
440- title : t ( 'form.pickIconTitle' ) ,
441- filters : [
442- {
443- name : t ( 'form.imagesFilter' ) ,
444- extensions : [ 'png' , 'jpg' , 'jpeg' , 'webp' , 'gif' ] ,
445- } ,
446- ] ,
447- } ) ;
448- if ( typeof picked !== 'string' || picked . length === 0 ) return ;
449- const localRef = await uploadWorkspaceIcon ( picked ) ;
450- setIcon ( localRef ) ;
451- onIconChange ?.( localRef ) ;
452- await persistIconNow ( localRef ) ;
453- } catch ( e ) {
454- onError ( e instanceof Error ? e . message : String ( e ) ) ;
455- }
456- } }
457- data-testid = "workspace-binding-icon-upload"
458- >
459- { t ( 'form.upload' ) }
460- </ Button >
461- ) : (
462- < >
463- < input
464- type = "text"
465- value = { iconFilePath }
466- onChange = { ( e ) => setIconFilePath ( e . target . value ) }
467- placeholder = "Enter absolute path"
468- className = "min-w-0 flex-1 px-3 py-2 rounded-lg text-sm bg-[rgb(var(--background))] border border-[rgb(var(--border))] focus:outline-none focus:ring-2 focus:ring-primary-500"
469- data-testid = "workspace-binding-icon-path-input"
470- />
471- < Button
472- variant = "secondary"
473- size = "sm"
474- disabled = { ! iconFilePath . trim ( ) }
475- onClick = { async ( ) => {
476- const picked = iconFilePath . trim ( ) ;
477- if ( ! picked ) return ;
478- try {
479- const localRef = await uploadWorkspaceIcon ( picked ) ;
480- setIcon ( localRef ) ;
481- onIconChange ?.( localRef ) ;
482- await persistIconNow ( localRef ) ;
483- setIconFilePath ( '' ) ;
484- } catch ( e ) {
485- onError ( e instanceof Error ? e . message : String ( e ) ) ;
486- }
487- } }
488- data-testid = "workspace-binding-icon-upload"
489- >
490- { t ( 'form.upload' ) }
491- </ Button >
492- </ >
493- ) }
494- < Button
495- variant = "ghost"
496- size = "sm"
497- onClick = { ( ) => {
498- setIcon ( '' ) ;
499- onIconChange ?.( null ) ;
500- void persistIconNow ( '' ) . catch ( ( e ) =>
501- onError ( e instanceof Error ? e . message : String ( e ) )
502- ) ;
503- } }
504- disabled = { ! icon . trim ( ) }
505- data-testid = "workspace-binding-icon-clear"
506- >
507- { t ( 'form.clear' ) }
508- </ Button >
509- </ div >
510- </ div >
511- </ div >
512- </ div >
513- </ FormField >
514-
515347 < FormField label = { t ( 'form.machine' ) } hint = { t ( 'form.machineHint' ) } >
516348 { isEdit ? (
517349 < div className = "space-y-2" >
0 commit comments