Skip to content

Commit 5f4386f

Browse files
committed
improvement(org): collapse repeated recents and give every chip a mark
(cherry picked from commit 33fd602)
1 parent b57f66d commit 5f4386f

16 files changed

Lines changed: 257 additions & 66 deletions

File tree

‎apps/sim/hooks/queries/search-history.ts‎

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,27 +45,31 @@ export function useClearSearchHistory(organizationId: string, userId: string) {
4545
})
4646
}
4747

48-
/** Takes the source off the list at once and restores it if the server refuses. */
48+
/** Takes the sources off the list at once and restores them if the server refuses. */
4949
export function useRemoveSearchHistorySource(organizationId: string, userId: string | undefined) {
5050
const queryClient = useQueryClient()
5151
const queryKey = searchHistoryKeys.list(organizationId, userId)
5252
return useMutation({
53-
mutationFn: (url: string) =>
54-
requestJson(removeSearchHistorySourceContract, {
55-
params: { id: organizationId },
56-
body: { url },
57-
}),
58-
onMutate: async (url) => {
53+
mutationFn: (urls: readonly string[]) =>
54+
Promise.all(
55+
urls.map((url) =>
56+
requestJson(removeSearchHistorySourceContract, {
57+
params: { id: organizationId },
58+
body: { url },
59+
})
60+
)
61+
),
62+
onMutate: async (urls) => {
5963
await queryClient.cancelQueries({ queryKey })
6064
const previous = queryClient.getQueryData<SearchHistoryResponse>(queryKey)
6165
if (previous)
6266
queryClient.setQueryData<SearchHistoryResponse>(queryKey, {
6367
...previous,
64-
sources: previous.sources.filter((source) => source.url !== url),
68+
sources: previous.sources.filter((source) => !urls.includes(source.url)),
6569
})
6670
return { previous }
6771
},
68-
onError: (_error, _url, context) => {
72+
onError: (_error, _urls, context) => {
6973
if (context?.previous) queryClient.setQueryData(queryKey, context.previous)
7074
},
7175
onSettled: () => queryClient.invalidateQueries({ queryKey }),

‎apps/sim/platform/design-system/components/icon/provider-marks.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ const ASSET_MARKS = new Set([
2727
'zoom',
2828
])
2929

30+
/** What a provider without its own mark gets. */
31+
export const FALLBACK_PROVIDER_MARK: IconName = 'I070'
32+
3033
export function providerMark(providerId: string): IconName | { href: string } {
3134
const shared = SHARED_MARKS.get(providerId)
3235
if (shared) return shared
33-
if (!ASSET_MARKS.has(providerId.replaceAll('-', '_'))) return 'I070'
36+
if (!ASSET_MARKS.has(providerId.replaceAll('-', '_'))) return FALLBACK_PROVIDER_MARK
3437
return { href: publicAssetPath(`/provider-marks/${providerId.replaceAll('_', '-')}.svg#mark`) }
3538
}

‎apps/sim/platform/design-system/components/recent-items/recent-items.module.css‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@
1919
}
2020
.recentLane > li {
2121
flex: none;
22+
max-inline-size: var(--container-216);
2223
}

‎apps/sim/platform/design-system/components/recent-items/recent-items.tsx‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { ReactNode } from 'react'
12
import Link from 'next/link'
23
import type { IconName } from '@/platform/design-system/catalog/icon-geometry'
34
import { IconButton } from '@/platform/design-system/components/button/button'
@@ -9,6 +10,8 @@ export interface RecentItem {
910
id: string
1011
label: string
1112
icon?: IconName
13+
/** A mark the caller draws, for one no icon name can express. */
14+
leading?: ReactNode
1215
/** An external link opens independently of the current workspace. */
1316
href?: string
1417
}
@@ -34,7 +37,8 @@ export function RecentItems({ label, title = label, items, onSelect, onRemove }:
3437
<ControlRow
3538
label={item.label}
3639
icon={item.icon}
37-
actionSide={item.icon ? 'start' : 'end'}
40+
leading={item.leading}
41+
actionSide={item.icon || item.leading ? 'start' : 'end'}
3842
render={
3943
item.href ? (
4044
<Link href={item.href} target='_blank' rel='noopener noreferrer' />
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
.mark {
2+
display: grid;
3+
flex: none;
4+
place-items: center;
5+
inline-size: var(--spacing-16);
6+
block-size: var(--spacing-16);
7+
}
8+
.mark > * {
9+
grid-area: 1 / 1;
10+
}
11+
.favicon {
12+
flex: none;
13+
inline-size: var(--spacing-16);
14+
block-size: var(--spacing-16);
15+
border-radius: var(--radius-2);
16+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
import { Favicon } from '@/components/favicon'
5+
import { Icon } from '@/platform/design-system/components/icon/icon'
6+
import styles from '@/platform/embedded/app/(shell)/_components/site-mark/site-mark.module.css'
7+
8+
interface SiteMarkProps {
9+
host: string | null
10+
}
11+
12+
/** A site's favicon, held by the globe until it paints and replaced by it when there is none, so a row never shows an empty slot. */
13+
export function SiteMark({ host }: SiteMarkProps) {
14+
const [loaded, setLoaded] = useState(false)
15+
return (
16+
<span className={styles.mark}>
17+
{!loaded && <Icon name='I041' />}
18+
<Favicon
19+
host={host}
20+
fallback={null}
21+
className={styles.favicon}
22+
onLoad={() => setLoaded(true)}
23+
/>
24+
</span>
25+
)
26+
}

‎apps/sim/platform/embedded/app/(shell)/home/_components/home-dashboard/home-dashboard.tsx‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import type { ChatRequestMode } from '@/app/workspace/[workspaceId]/home/types'
55
import { FailureNotice } from '@/platform/design-system/components'
66
import { Icon } from '@/platform/design-system/components/icon/icon'
77
import { ActionMenu } from '@/platform/design-system/components/menu/action-menu'
8-
import { RecentItems } from '@/platform/design-system/components/recent-items/recent-items'
98
import { Surface } from '@/platform/design-system/components/surface/surface'
109
import { HomeActivityChart } from '@/platform/embedded/app/(shell)/home/_components/activity-chart/activity-chart'
1110
import {
@@ -16,6 +15,7 @@ import { CalendarDay } from '@/platform/embedded/app/(shell)/home/_components/ca
1615
import { ConnectedWidget } from '@/platform/embedded/app/(shell)/home/_components/connected-widget/connected-widget'
1716
import { GetStarted } from '@/platform/embedded/app/(shell)/home/_components/get-started/get-started'
1817
import styles from '@/platform/embedded/app/(shell)/home/_components/home-dashboard/home-dashboard.module.css'
18+
import { HomeRecents } from '@/platform/embedded/app/(shell)/home/_components/home-recents/home-recents'
1919
import { HomeWorkspaces } from '@/platform/embedded/app/(shell)/home/_components/home-workspaces/home-workspaces'
2020
import { SkeletonLane } from '@/platform/embedded/app/(shell)/home/_components/widget-skeleton/widget-skeleton'
2121
import type { HomeActions, HomeView } from '@/platform/embedded/lib/data/home-view'
@@ -112,11 +112,7 @@ export function HomeDashboard({ view, actions, requestMode, composer }: HomeDash
112112
)}
113113
{view.recents.status === 'ready' && view.recents.items.length > 0 && (
114114
<div className={styles.recents}>
115-
<RecentItems
116-
label='Recents'
117-
items={view.recents.items}
118-
onRemove={actions.removeRecent}
119-
/>
115+
<HomeRecents items={view.recents.items} onRemove={actions.removeRecent} />
120116
</div>
121117
)}
122118
{view.getStarted.length > 0 && <GetStarted steps={view.getStarted} />}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use client'
2+
3+
import { RecentItems } from '@/platform/design-system/components/recent-items/recent-items'
4+
import { SiteMark } from '@/platform/embedded/app/(shell)/_components/site-mark/site-mark'
5+
import type { HomeRecentItem } from '@/platform/embedded/lib/data/home-view'
6+
7+
interface HomeRecentsProps {
8+
items: readonly HomeRecentItem[]
9+
onRemove: (id: string) => void
10+
}
11+
12+
export function HomeRecents({ items, onRemove }: HomeRecentsProps) {
13+
return (
14+
<RecentItems
15+
label='Recents'
16+
items={items.map((item) => ({
17+
...item,
18+
leading: item.icon ? undefined : <SiteMark key={item.host} host={item.host} />,
19+
}))}
20+
onRemove={onRemove}
21+
/>
22+
)
23+
}

‎apps/sim/platform/embedded/app/(shell)/links/_components/links-page/links-page.module.css‎

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,7 @@
2929
min-inline-size: 0;
3030
}
3131
.nameLeading {
32-
display: grid;
3332
flex: none;
34-
place-items: center;
35-
inline-size: var(--spacing-16);
36-
block-size: var(--spacing-16);
37-
}
38-
.nameLeading > * {
39-
grid-area: 1 / 1;
4033
}
4134
.nameCell {
4235
display: grid;
@@ -69,12 +62,6 @@
6962
display: grid;
7063
}
7164
}
72-
.favicon {
73-
flex: none;
74-
inline-size: var(--spacing-16);
75-
block-size: var(--spacing-16);
76-
border-radius: var(--radius-2);
77-
}
7865
.learnMore {
7966
margin-inline-start: calc(-1 * var(--spacing-8));
8067
}

‎apps/sim/platform/embedded/app/(shell)/links/_components/links-page/links-page.tsx‎

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use client'
22

33
import { type MouseEvent, memo, type ReactNode, type TouchEvent, useMemo, useState } from 'react'
4-
import { Favicon } from '@/components/favicon'
54
import type { SimLink } from '@/lib/api/contracts/sim-links'
65
import { simLinkOrigin } from '@/lib/sim-links/validation'
76
import { QueryBoundary } from '@/platform/design-system/components'
@@ -18,6 +17,7 @@ import { ActionMenu, ContextActionMenu } from '@/platform/design-system/componen
1817
import { ConfirmDialog } from '@/platform/design-system/components/modal/confirm-dialog'
1918
import { OverflowText } from '@/platform/design-system/components/overflow-text/overflow-text'
2019
import type { LoadMoreState } from '@/platform/design-system/components/scroll-area/use-load-more'
20+
import { SiteMark } from '@/platform/embedded/app/(shell)/_components/site-mark/site-mark'
2121
import { LinkEditor } from '@/platform/embedded/app/(shell)/links/_components/links-page/link-editor'
2222
import { LinksFrame } from '@/platform/embedded/app/(shell)/links/_components/links-page/links-frame'
2323
import styles from '@/platform/embedded/app/(shell)/links/_components/links-page/links-page.module.css'
@@ -105,22 +105,6 @@ function LinkRowContextMenu({ links, children, ...actions }: LinkRowContextMenuP
105105
)
106106
}
107107

108-
/** Holds the favicon's box with the globe until the image paints, so a row never shows a gap. */
109-
function LinkMark({ host }: { host: string | null }) {
110-
const [loaded, setLoaded] = useState(false)
111-
return (
112-
<span className={styles.nameLeading}>
113-
{!loaded && <Icon name='I041' />}
114-
<Favicon
115-
host={host}
116-
fallback={null}
117-
className={styles.favicon}
118-
onLoad={() => setLoaded(true)}
119-
/>
120-
</span>
121-
)
122-
}
123-
124108
interface LinkSummaryProps {
125109
link: SimLink
126110
updated: string
@@ -173,7 +157,11 @@ const LinksTable = memo(function LinksTable({
173157
cells: {
174158
name: (
175159
<TableLabel
176-
leading={<LinkMark key={origin} host={origin ? new URL(origin).hostname : null} />}
160+
leading={
161+
<span className={styles.nameLeading}>
162+
<SiteMark key={origin} host={origin ? new URL(origin).hostname : null} />
163+
</span>
164+
}
177165
>
178166
<span className={styles.nameCell}>
179167
<OverflowText text={link.name}>{link.name}</OverflowText>

0 commit comments

Comments
 (0)