Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions packages/ui/client/src/components/IdMenu.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { useClipboard, useTimeoutFn } from '@vueuse/core'
import { DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRoot, DropdownMenuTrigger } from 'reka-ui'
import { DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRoot, DropdownMenuSeparator, DropdownMenuTrigger } from 'reka-ui'
import { shallowRef } from 'vue'

import AnimatedContent from './AnimatedContent.vue'
Expand All @@ -10,7 +10,8 @@ import IconButton from './IconButton.vue'
* Overflow menu ("⋯") of a page header or a list row with explicit actions on an id, such as "Copy ID".
*
* Copying is an action with a text label and a "Copied" confirmation, instead of an unlabeled copy icon next to every
* name. The id itself is shown in the menu, so it can also be read.
* name. The id itself is shown in the menu, so it can also be read. The default slot adds the page's secondary actions
* below it (`DropdownMenuItem`s, such as "Compare attempts" on a run page).
*/
const props = defineProps<{
/** Full id to copy. */
Expand Down Expand Up @@ -47,6 +48,10 @@ async function copyId(event: Event): Promise<void> {
<span :class="copied ? 'i-mingcute-check-line text-green-600 dark:text-green-400' : 'i-mingcute-copy-2-line'" aria-hidden="true" />
{{ copied ? 'Copied' : 'Copy ID' }}
</DropdownMenuItem>
<template v-if="$slots.default">
<DropdownMenuSeparator :class="['my-1 h-px bg-neutral-200 dark:bg-neutral-800']" />
<slot />
</template>
</AnimatedContent>
</DropdownMenuContent>
</DropdownMenuPortal>
Expand Down
14 changes: 12 additions & 2 deletions packages/ui/client/src/components/PageHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ const props = defineProps<{
subtitle?: string
/** Name of the page: the heading (unless the `title` slot replaces it) and the document title. */
title: string
/**
* `sm` sets the title in body size, for a page whose header leads with something else (the run's score).
* @default 'lg'
*/
titleSize?: 'lg' | 'sm'
}>()
// The browser tab follows the page; a page still loading keeps the plain "Vieval" of the title template.
useHead({ title: () => (props.loading ? undefined : props.title) })
Expand Down Expand Up @@ -62,7 +67,7 @@ const siblings = computed(() => {
title would get narrower than 24rem. -->
<div :class="['flex items-start gap-x-3 gap-y-2 flex-wrap']">
<div :class="['min-w-0 flex-1 md:basis-96']">
<h1 :class="['flex items-start gap-2 text-lg font-semibold [overflow-wrap:anywhere] leading-7']">
<h1 :class="['flex items-start gap-2 font-semibold [overflow-wrap:anywhere]', props.titleSize === 'sm' ? 'text-base leading-6' : 'text-lg leading-7']">
<span v-if="props.level" :class="[levelIcons[props.level], 'shrink-0 mt-1 muted']" role="img" :aria-label="levelLabels[props.level]" :title="levelLabels[props.level]" />
<span v-if="props.loading" :class="['skeleton h-6 my-0.5 w-56 max-w-full']" data-testid="page-header-skeleton" />
<slot v-else name="title">
Expand All @@ -83,7 +88,12 @@ const siblings = computed(() => {
</div>
<!-- IdMenu renders no element of its own; the wrapper carries the order and the alignment. -->
<span v-if="props.id && props.level" :class="['flex items-center min-h-7 max-md:order-1']">
<IdMenu :id="props.id" :kind="levelLabels[props.level].toLowerCase()" />
<!-- The page's secondary actions join the menu only when the page has some, so the menu adds no empty group. -->
<IdMenu :id="props.id" :kind="levelLabels[props.level].toLowerCase()">
<template v-if="$slots.menu" #default>
<slot name="menu" />
</template>
</IdMenu>
</span>
</div>
</header>
Expand Down
27 changes: 16 additions & 11 deletions packages/ui/client/src/pages/RunPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { LogLine, RunListItem } from '../../../src/shared/contract'
import type { ViewTab } from '../components/ViewTabs.vue'
import type { Route } from '../router'

import { DropdownMenuItem } from 'reka-ui'
import { computed, toRef, useTemplateRef } from 'vue'

import CountsBar from '../components/CountsBar.vue'
Expand All @@ -28,7 +29,7 @@ import { useRunLive } from '../composables/useRunLive'
import { useStatusFavicon } from '../composables/useStatusFavicon'
import { formatDateTime } from '../format'
import { levelIcons } from '../hierarchy'
import { attemptLabel, runLabel } from '../labels'
import { runLabel } from '../labels'
import { href, navigate } from '../router'

/**
Expand Down Expand Up @@ -101,7 +102,7 @@ const errorCaseHref = (target: { caseId: string, taskId: string }) => href({ ...
</script>

<template>
<PageHeader :id="props.route.runKey" :loading="!live.snapshot.value && !live.error.value" level="run" :position="position" :title="documentTitle">
<PageHeader :id="props.route.runKey" :loading="!live.snapshot.value && !live.error.value" level="run" :position="position" :title="documentTitle" title-size="sm">
<template #title>
<IdLabel :id="props.route.runKey" :label="label" />
<!-- RunDetails renders no element of its own; the wrapper centers the button on the title's first line. -->
Expand All @@ -111,9 +112,8 @@ const errorCaseHref = (target: { caseId: string, taskId: string }) => href({ ...
<span v-if="position.experiment" :class="['inline-flex flex-wrap items-center gap-x-1.5 gap-y-0.5 mr-1 align-middle']">
<span :class="[levelIcons.experiment]" aria-hidden="true" />
<ExperimentName :experiment-id="position.experiment" :matrix="experimentMatrix" :href="position.workspace && position.project ? href({ experiment: position.experiment, name: 'scope', project: position.project, workspace: position.workspace }) : undefined" />
<template v-if="position.attempt">
· {{ attemptLabel(position.attempt) }}
</template>
<!-- The attempt's time was printed here; it is the run's start (the title) to the second, and the breadcrumb
names the attempt, so it only repeated the title. -->
</span>
<!-- The start is the title already; only the end (or the interruption) adds a fact. -->
<template v-if="live.snapshot.value?.endedAt || live.snapshot.value?.interruptSignal">
Expand All @@ -128,14 +128,19 @@ const errorCaseHref = (target: { caseId: string, taskId: string }) => href({ ...
<template #actions>
<!-- The run's score at a glance, on the title's line: the headline with its completion rate, and the case bar
(counts in its tooltip). A score over part of the cases is not the score of the run, so the completion rate
always stands next to it. Phones give the bar the full width and put the score below it. -->
always stands next to it. The score leads the header (larger than the title); phones put the bar below it
at the full width. -->
<span v-if="counts && live.snapshot.value" :class="['flex items-center gap-3 mr-1', 'max-md:(w-full mr-0 flex-col items-stretch gap-2)']" data-testid="run-score">
<HeadlineScore :result="live.snapshot.value.headline" :definitions="live.snapshot.value.scoreDefinitions" :task-label="taskLabel" @errors="showErrors" />
<CountsBar :class="['w-32 max-md:(order-first w-full)']" :counts="counts" />
<HeadlineScore :result="live.snapshot.value.headline" :definitions="live.snapshot.value.scoreDefinitions" :task-label="taskLabel" size="lg" @errors="showErrors" />
<CountsBar :class="['w-32 max-md:w-full']" :counts="counts" />
</span>
<a v-if="position.workspace && position.project && position.experiment" :class="['btn']" :href="href({ name: 'compare', query: { experiment: position.experiment, project: position.project, view: 'attempts', workspace: position.workspace } })">
<span class="i-mingcute-git-compare-line" aria-hidden="true" />Compare attempts
</a>
</template>
<template v-if="position.workspace && position.project && position.experiment" #menu>
<DropdownMenuItem as-child :class="['menu-item']">
<a :href="href({ name: 'compare', query: { experiment: position.experiment, project: position.project, view: 'attempts', workspace: position.workspace } })">
<span class="i-mingcute-git-compare-line" aria-hidden="true" />Compare attempts
</a>
</DropdownMenuItem>
</template>
</PageHeader>

Expand Down
Loading