Skip to content

Commit 478fc0f

Browse files
antfubotantfu
andauthored
feat(messages): split MessagesView, move search/actions to nav, reuse @antfu/design (#135)
Co-authored-by: Anthony Fu <github@antfu.me>
1 parent c359ac1 commit 478fc0f

30 files changed

Lines changed: 724 additions & 576 deletions

‎alias.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ export const alias = {
101101
'@devframes/plugin-messages/client': p('messages/src/client/index.ts'),
102102
'@devframes/plugin-messages/node': p('messages/src/node/index.ts'),
103103
'@devframes/plugin-messages/constants': p('messages/src/constants.ts'),
104-
'@devframes/plugin-messages/types': p('messages/src/types.ts'),
105104
'@devframes/plugin-messages/rpc': p('messages/src/rpc/index.ts'),
106105
'@devframes/plugin-messages/cli': p('messages/src/cli.ts'),
107106
'@devframes/plugin-messages/vite': p('messages/src/vite.ts'),

‎plugins/messages/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
"./constants": "./dist/constants.mjs",
2828
"./node": "./dist/node/index.mjs",
2929
"./rpc": "./dist/rpc/index.mjs",
30-
"./types": "./dist/types.mjs",
3130
"./vite": "./dist/vite.mjs",
3231
"./package.json": "./package.json"
3332
},
@@ -75,6 +74,7 @@
7574
"@vueuse/core": "catalog:frontend",
7675
"colorjs.io": "catalog:frontend",
7776
"devframe": "workspace:*",
77+
"floating-vue": "catalog:frontend",
7878
"get-port-please": "catalog:deps",
7979
"h3": "catalog:deps",
8080
"storybook": "catalog:storybook",

‎plugins/messages/src/client/App.vue‎

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<script setup lang="ts">
2+
import type { DevframeMessageEntry } from '@devframes/hub/types'
23
import type { DevframeConnectionStatus, DevframeRpcClient } from 'devframe/client'
3-
import type { DevframeMessageEntry } from '../types'
4+
import DisplayBadge from '@antfu/design/components/Display/DisplayBadge.vue'
5+
import FormSearchField from '@antfu/design/components/Form/FormSearchField.vue'
46
import LayoutToolbar from '@antfu/design/components/Layout/LayoutToolbar.vue'
57
import { computed, onBeforeUnmount, ref } from 'vue'
68
import {
@@ -13,6 +15,8 @@ import {
1315
connectionTitle,
1416
} from '../../../../design/design'
1517
import MessagesView from './components/MessagesView.vue'
18+
import MessageToolbarActions from './components/MessageToolbarActions.vue'
19+
import { useMessageFilters } from './composables/useMessageFilters'
1620
import { useMessages } from './state/messages'
1721
1822
const props = defineProps<{
@@ -21,6 +25,10 @@ const props = defineProps<{
2125
2226
const state = useMessages(props.rpc)
2327
28+
// Search / sort / filter state lives here (not in the view) so the nav bar's
29+
// search field + actions and the view's filter bar share one source of truth.
30+
const filters = useMessageFilters(() => state.entries)
31+
2432
// The live feed rides on shared-state over the socket, so a dropped socket or
2533
// refused auth is surfaced instead of silently freezing the list. The client
2634
// doesn't auto-reconnect; a reload re-runs the whole handshake.
@@ -50,9 +58,9 @@ async function onDismiss(id: string): Promise<void> {
5058
await props.rpc.call('devframes:plugin:messages:remove', id)
5159
}
5260
53-
async function onDismissMany(ids: string[]): Promise<void> {
54-
for (const id of ids)
55-
await props.rpc.call('devframes:plugin:messages:remove', id)
61+
async function onDismissFiltered(): Promise<void> {
62+
for (const entry of filters.filteredEntries)
63+
await props.rpc.call('devframes:plugin:messages:remove', entry.id)
5664
}
5765
5866
async function onClear(): Promise<void> {
@@ -85,17 +93,36 @@ async function onOpenFile(entry: DevframeMessageEntry): Promise<void> {
8593
</div>
8694

8795
<template #search>
88-
<div />
96+
<div class="flex gap-2 items-center">
97+
<FormSearchField
98+
v-if="!connState"
99+
v-model="filters.search"
100+
size="sm"
101+
placeholder="Search messages…"
102+
class="max-w-64"
103+
/>
104+
<DisplayBadge v-if="filters.totalCount > 0" :color="false" class="text-xs font-mono">
105+
<template v-if="filters.filteredCount !== filters.totalCount">
106+
{{ filters.filteredCount }}/{{ filters.totalCount }}
107+
</template>
108+
<template v-else>
109+
{{ filters.totalCount }}
110+
</template>
111+
</DisplayBadge>
112+
</div>
89113
</template>
90114

91115
<template #end>
116+
<MessageToolbarActions
117+
v-if="!connState"
118+
:filters
119+
@dismiss-filtered="onDismissFiltered"
120+
@clear="onClear"
121+
/>
92122
<span v-if="conn" :class="conn.class">
93123
<span :class="conn.dot" />
94124
{{ conn.label }}
95125
</span>
96-
<span v-if="state.entries.length > 0" class="badge-muted font-mono">
97-
{{ state.entries.length }}
98-
</span>
99126
</template>
100127
</LayoutToolbar>
101128

@@ -122,11 +149,9 @@ async function onOpenFile(entry: DevframeMessageEntry): Promise<void> {
122149
</div>
123150
<MessagesView
124151
v-else
125-
:entries="state.entries"
152+
:filters
126153
:can-open-file="canOpenFile"
127154
@dismiss="onDismiss"
128-
@dismiss-many="onDismissMany"
129-
@clear="onClear"
130155
@persist="onPersist"
131156
@open-file="onOpenFile"
132157
/>

‎plugins/messages/src/client/components/FilterToggles.stories.ts‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Meta, StoryObj } from '@storybook/vue3-vite'
22
import FilterToggles from './FilterToggles.vue'
3-
import { getHashColorFromString, levels } from './MessageItemConstants'
3+
import { levels } from './message-styles'
44

55
const meta = {
66
title: 'Messages/FilterToggles',
@@ -32,11 +32,20 @@ export const LevelsFiltered: Story = {
3232
},
3333
}
3434

35-
export const HashColored: Story = {
35+
export const CategoryTags: Story = {
3636
args: {
3737
label: 'Category',
3838
items: ['a11y', 'lint', 'runtime', 'build'],
3939
active: new Set<string>(),
40-
hashColor: getHashColorFromString,
40+
tag: 'category',
41+
},
42+
}
43+
44+
export const LabelTags: Story = {
45+
args: {
46+
label: 'Labels',
47+
items: ['axe', 'eslint', 'vite', 'hmr'],
48+
active: new Set<string>(),
49+
tag: 'label',
4150
},
4251
}
Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,47 @@
11
<script setup lang="ts">
2+
import MessageTag from './MessageTag.vue'
3+
24
defineProps<{
35
label: string
46
items: string[]
57
active: Set<string>
6-
/** Map item key → { icon, color, label } for styled items */
8+
/** Map item key → { icon, color, label } for styled (level/source) items. */
79
styles?: Record<string, { icon?: string, color?: string, label?: string }>
8-
/** Compute inline color via hash for items without predefined styles */
9-
hashColor?: (item: string, opacity: number) => string
10+
/** Render items as hash-colored `MessageTag` chips of this kind (category/label). */
11+
tag?: 'category' | 'label'
1012
}>()
1113
1214
defineEmits<{
1315
toggle: [item: string]
1416
}>()
17+
18+
function isDimmed(active: Set<string>, item: string): boolean {
19+
return active.size > 0 && !active.has(item)
20+
}
1521
</script>
1622

1723
<template>
18-
<span class="text-xs op40">{{ label }}</span>
24+
<span class="text-xs op-fade">{{ label }}</span>
1925
<div class="flex flex-wrap items-center gap-0.5">
2026
<button
2127
v-for="item of items"
2228
:key="item"
23-
class="px-1.5 py-0.5 rounded text-xs flex items-center gap-0.5 hover:bg-active transition"
29+
type="button"
30+
class="rounded flex items-center transition"
2431
:class="[
25-
active.size === 0 || active.has(item)
26-
? (styles?.[item]?.color || '')
27-
: 'op30',
32+
tag ? 'p-0.5' : 'px-1.5 py-0.5 gap-0.5 text-xs',
33+
!tag && !isDimmed(active, item) ? (styles?.[item]?.color || '') : '',
34+
isDimmed(active, item) ? 'op50 saturate-10 hover:op85 hover:saturate-50' : 'op85 hover:op100',
2835
]"
29-
:style="!styles?.[item]?.color && hashColor ? {
30-
color: active.size === 0 || active.has(item) ? hashColor(item, 1) : undefined,
31-
backgroundColor: active.size === 0 || active.has(item) ? hashColor(item, 0.1) : undefined,
32-
} : undefined"
3336
@click="$emit('toggle', item)"
3437
>
35-
<div v-if="styles?.[item]?.icon" :class="styles[item]!.icon" class="w-3.5 h-3.5" />
36-
<span>{{ styles?.[item]?.label || item }}</span>
38+
<template v-if="tag">
39+
<MessageTag :text="item" :kind="tag" />
40+
</template>
41+
<template v-else>
42+
<div v-if="styles?.[item]?.icon" :class="styles[item]!.icon" class="w-3.5 h-3.5" />
43+
<span>{{ styles?.[item]?.label || item }}</span>
44+
</template>
3745
</button>
3846
</div>
3947
</template>

‎plugins/messages/src/client/components/HashBadge.vue‎

Lines changed: 0 additions & 17 deletions
This file was deleted.

0 commit comments

Comments
 (0)