diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx index be5e581418f..36966864095 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-panel.tsx @@ -1,6 +1,17 @@ 'use client' -import { createContext, memo, useContext, useEffect, useMemo, useRef, useState } from 'react' +import { + Children, + cloneElement, + createContext, + isValidElement, + memo, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from 'react' import matter from 'gray-matter' import { useRouter } from 'next/navigation' import rehypeSlug from 'rehype-slug' @@ -10,7 +21,7 @@ import { Streamdown } from 'streamdown' import 'streamdown/styles.css' import { toError } from '@sim/utils/errors' import { generateShortId } from '@sim/utils/id' -import { Checkbox, CopyCodeButton, highlight, languages, Skeleton } from '@/components/emcn' +import { Checkbox, highlight, languages, Skeleton } from '@/components/emcn' import '@/components/emcn/components/code/code.css' import 'prismjs/components/prism-bash' import 'prismjs/components/prism-css' @@ -473,7 +484,15 @@ function resolveSimFileUrl(src: string | undefined): string | undefined { } const STATIC_MARKDOWN_COMPONENTS = { - pre: ({ children }: { children?: React.ReactNode }) => <>{children}, + pre: ({ children }: { children?: React.ReactNode }) => ( + <> + {Children.map(children, (child) => + isValidElement>(child) + ? cloneElement(child, { 'data-block': 'true' }) + : child + ) ?? children} + + ), 'mermaid-diagram': ({ definition }: { definition?: string }) => { const isStreaming = useContext(MermaidStreamingCtx) return @@ -531,20 +550,11 @@ const STATIC_MARKDOWN_COMPONENTS = { {children} ), - inlineCode: ({ children }: { children?: React.ReactNode }) => { - if (typeof children === 'string' && children.includes('\n')) { - return ( - - {children} - - ) - } - return ( - - {children} - - ) - }, + inlineCode: ({ children }: { children?: React.ReactNode }) => ( + + {children} + + ), code: ({ children, className }: { children?: React.ReactNode; className?: string }) => { const langMatch = className?.match(/language-(\w+)/) const langRaw = langMatch?.[1] ?? '' @@ -564,22 +574,18 @@ const STATIC_MARKDOWN_COMPONENTS = { return (
-
+
{langRaw || 'code'} -
{html ? (
           ) : (
-            
-              {codeString.trimEnd()}
+            
+              {codeString.trimEnd()}
             
)}
@@ -634,7 +640,7 @@ const STATIC_MARKDOWN_COMPONENTS = { {children} ), th: ({ children }: { children?: React.ReactNode }) => ( - + {children} ), @@ -684,20 +690,36 @@ function LiRenderer({ const isTaskItem = typeof className === 'string' && className.includes('task-list-item') if (isTaskItem) { + const [checkboxChild, ...contentChildren] = Children.toArray(children) + const content = {contentChildren} + if (ctx) { const offset = node?.position?.start?.offset if (offset === undefined) { - return
  • {children}
  • + return ( +
  • + {checkboxChild} + {content} +
  • + ) } const before = ctx.contentRef.current.slice(0, offset) const prior = before.match(/^(\s*(?:[-*+]|\d+[.)]) +)\[([ xX])\]/gm) return ( -
  • {children}
  • +
  • + {checkboxChild} + {content} +
  • ) } - return
  • {children}
  • + return ( +
  • + {checkboxChild} + {content} +
  • + ) } return
  • {children}
  • diff --git a/apps/sim/app/workspace/[workspaceId]/files/files.tsx b/apps/sim/app/workspace/[workspaceId]/files/files.tsx index e259a7dad2a..45e7fb9dd37 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/files.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/files.tsx @@ -622,7 +622,7 @@ export function Files() { const mimeType = getMimeTypeFromExtension('md') const blob = new Blob([''], { type: mimeType }) const file = new File([blob], name, { type: mimeType }) - const result = await uploadFile.mutateAsync({ workspaceId, file }) + const result = await uploadFile.mutateAsync({ workspaceId, file, skipToast: true }) const fileId = result.file?.id if (fileId) { justCreatedFileIdRef.current = fileId diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-sidebar/column-sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-sidebar/column-sidebar.tsx index 69fc07bcf02..73017fc25ec 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-sidebar/column-sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-sidebar/column-sidebar.tsx @@ -63,6 +63,7 @@ import { } from '@/hooks/queries/tables' import { useWorkflowState, workflowKeys } from '@/hooks/queries/workflows' import type { WorkflowMetadata } from '@/stores/workflows/registry/types' +import { COLUMN_SIDEBAR_WIDTH_CSS } from '../table/constants' import { COLUMN_TYPE_OPTIONS, type SidebarColumnType } from './column-types' export type ColumnConfigState = @@ -1017,9 +1018,10 @@ export function ColumnSidebar({ role='dialog' aria-label='Configure column' className={cn( - 'absolute top-0 right-0 bottom-0 z-[var(--z-modal)] flex w-[400px] flex-col overflow-hidden border-[var(--border)] border-l bg-[var(--bg)] shadow-overlay transition-transform duration-200 ease-out', + 'absolute top-0 right-0 bottom-0 z-[var(--z-modal)] flex flex-col overflow-hidden border-[var(--border)] border-l bg-[var(--bg)] shadow-md transition-transform duration-200 ease-out', open ? 'translate-x-0' : 'translate-x-full' )} + style={{ width: COLUMN_SIDEBAR_WIDTH_CSS }} >
    diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table/cells/cell-content.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table/cells/cell-content.tsx index 57457056af0..5224fd80a24 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table/cells/cell-content.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table/cells/cell-content.tsx @@ -119,7 +119,9 @@ export function CellContent({
    - + + +
    ) } else if (!isNull && column.type === 'json') { diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table/constants.ts b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table/constants.ts index cc2c314ded8..28aead32657 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table/constants.ts +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table/constants.ts @@ -4,3 +4,6 @@ export const SELECTION_TINT_BG = 'bg-[rgba(37,99,235,0.06)]' /** Default column width in pixels. Used as a fallback when a column hasn't * been measured yet and as the initial width for newly-added columns. */ export const COL_WIDTH = 160 + +/** Column config sidebar width: roomy by default, bounded on narrow screens. */ +export const COLUMN_SIDEBAR_WIDTH_CSS = 'min(480px, calc(100vw - 48px))' diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table/table.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table/table.tsx index 35b9c6a9fb1..1a1993aec65 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table/table.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table/table.tsx @@ -66,7 +66,7 @@ import { RowModal } from '../row-modal' import { TableFilter } from '../table-filter' import { CellContent } from './cells/cell-content' import { ExpandedCellPopover } from './cells/expanded-cell-popover' -import { COL_WIDTH, SELECTION_TINT_BG } from './constants' +import { COL_WIDTH, COLUMN_SIDEBAR_WIDTH_CSS, SELECTION_TINT_BG } from './constants' import { ColumnHeaderMenu } from './headers/column-header-menu' import { COLUMN_TYPE_ICONS } from './headers/column-type-icon' import { WorkflowGroupMetaCell } from './headers/workflow-group-meta-cell' @@ -94,8 +94,6 @@ const COL_WIDTH_AUTO_FIT_MAX = 1000 // row-by-row. const CHECKBOX_COL_WIDTH = 56 const ADD_COL_WIDTH = 120 -/** Width of the column-config slideout (matches `column-sidebar.tsx`'s `w-[400px]`). */ -const COLUMN_SIDEBAR_WIDTH = 400 const SKELETON_COL_COUNT = 4 const SKELETON_ROW_COUNT = 10 const ROW_HEIGHT_ESTIMATE = 35 @@ -1218,22 +1216,25 @@ export function Table({ return () => cancelAnimationFrame(rafId) }, [selectionAnchor, selectionFocus, isColumnSelection]) - const handleCellClick = useCallback((rowId: string, columnName: string) => { - const column = columnsRef.current.find((c) => c.name === columnName) - if (column?.type === 'boolean') { - if (!canEditRef.current) return - const row = rowsRef.current.find((r) => r.id === rowId) - if (row) { - toggleBooleanCell(rowId, columnName, row.data[columnName]) + const handleCellClick = useCallback( + (rowId: string, columnName: string, options?: { toggleBoolean?: boolean }) => { + const column = columnsRef.current.find((c) => c.name === columnName) + if (column?.type === 'boolean') { + if (!options?.toggleBoolean || !canEditRef.current) return + const row = rowsRef.current.find((r) => r.id === rowId) + if (row) { + toggleBooleanCell(rowId, columnName, row.data[columnName]) + } + return } - return - } - const current = editingCellRef.current - if (current && current.rowId === rowId && current.columnName === columnName) return - setEditingCell(null) - setInitialCharacter(null) - }, []) + const current = editingCellRef.current + if (current && current.rowId === rowId && current.columnName === columnName) return + setEditingCell(null) + setInitialCharacter(null) + }, + [] + ) // The cell has `select-none` which suppresses programmatic selection, so we // override `user-select` on the inner element until the next click. The popover @@ -1241,6 +1242,9 @@ export function Table({ // (workflow cells nest text inside a span with its own `overflow-clip`). const handleCellDoubleClick = useCallback( (rowId: string, columnName: string, columnKey: string) => { + const column = columnsRef.current.find((c) => c.key === columnKey) + if (column?.type === 'boolean') return + setSelectionFocus(null) setIsColumnSelection(false) @@ -2080,11 +2084,11 @@ export function Table({ * The two panels are mutually exclusive (each opener closes the other). */ const logPanelWidth = useLogDetailsUIStore((state) => state.panelWidth) - const sidebarReservedPx = configState - ? COLUMN_SIDEBAR_WIDTH + const sidebarReservedWidth = configState + ? COLUMN_SIDEBAR_WIDTH_CSS : executionDetailsId - ? logPanelWidth - : 0 + ? `${logPanelWidth}px` + : '0px' const handleConfigureColumn = useCallback((columnName: string) => { setExecutionDetailsId(null) @@ -2578,8 +2582,8 @@ export function Table({
    | null normalizedSelection: NormalizedSelection | null - onClick: (rowId: string, columnName: string) => void + onClick: (rowId: string, columnName: string, options?: { toggleBoolean?: boolean }) => void onDoubleClick: (rowId: string, columnName: string, columnKey: string) => void onSave: (rowId: string, columnName: string, value: unknown, reason: SaveReason) => void onCancel: () => void @@ -3115,15 +3119,15 @@ const DataRow = React.memo(function DataRow({ return ( onContextMenu(e, row)}> - ) diff --git a/apps/sim/app/workspace/[workspaceId]/tables/components/import-csv-dialog/import-csv-dialog.tsx b/apps/sim/app/workspace/[workspaceId]/tables/components/import-csv-dialog/import-csv-dialog.tsx index 6ae39b07e75..9da13375a22 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/components/import-csv-dialog/import-csv-dialog.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/components/import-csv-dialog/import-csv-dialog.tsx @@ -345,7 +345,7 @@ export function ImportCsvDialog({ {!parsed ? (
    - +
    + { + if (e.button !== 0) return + onRowToggle(rowIndex, e.shiftKey) + }} + >
    -
    { - if (e.button !== 0) return - onRowToggle(rowIndex, e.shiftKey) - }} - > +
    0 ? `Stop ${runningCount} running` : 'Run row'} title={runningCount > 0 ? `Stop ${runningCount} running` : 'Run row'} className='ml-auto flex h-[20px] w-[20px] shrink-0 items-center justify-center rounded text-[var(--text-primary)] transition-colors hover-hover:bg-[var(--surface-2)]' + onMouseDown={(e) => { + e.stopPropagation() + }} onClick={() => { if (runningCount > 0) { onStopRow(row.id) @@ -3192,7 +3199,13 @@ const DataRow = React.memo(function DataRow({ onCellMouseDown(rowIndex, colIndex, e.shiftKey) }} onMouseEnter={() => onCellMouseEnter(rowIndex, colIndex)} - onClick={() => onClick(row.id, column.name)} + onClick={(e) => + onClick(row.id, column.name, { + toggleBoolean: Boolean( + (e.target as HTMLElement).closest('[data-boolean-cell-toggle]') + ), + }) + } onDoubleClick={() => onDoubleClick(row.id, column.name, column.key)} > {isHighlighted && (isMultiCell || isRowChecked) && ( @@ -3307,9 +3320,23 @@ const SelectAllCheckbox = React.memo(function SelectAllCheckbox({ onCheckedChange: () => void }) { return ( -
    + { + if (e.button !== 0) return + onCheckedChange() + }} + onKeyDown={(e) => { + if (e.key !== ' ' && e.key !== 'Enter') return + e.preventDefault() + onCheckedChange() + }} + >
    - +