Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow for pinning scenarios/datasets in model-check report #551

Merged
merged 21 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ce99e95
build: add svelte-dnd-action dependency
chrispcampbell Sep 23, 2024
17a52a3
build: silence some Svelte warnings
chrispcampbell Sep 23, 2024
af3bc03
feat: checkpoint work on pinned rows in summary view
chrispcampbell Sep 23, 2024
3110de8
fix: reimplement navigation to detail view
chrispcampbell Sep 24, 2024
884670c
fix: show pinned items at top of detail view
chrispcampbell Sep 24, 2024
e42c2f5
Merge branch 'main' into chris/537-pinned-rows
chrispcampbell Oct 8, 2024
0855b91
fix: use readable store for pinned items in detail view
chrispcampbell Oct 9, 2024
ceb67d9
fix: use context menu for managing pinned state for detail boxes
chrispcampbell Oct 10, 2024
0f20d27
fix: simplify repetitive code
chrispcampbell Oct 10, 2024
1b578de
fix: clarify toggle-context event name
chrispcampbell Oct 10, 2024
c5811fd
fix: remove unused separator case
chrispcampbell Oct 10, 2024
8d8ec3b
fix: define ContextMenuItem type
chrispcampbell Oct 10, 2024
58605a2
fix: add missing parameter
chrispcampbell Oct 10, 2024
c060639
fix: remove pin button from summary items + remove pinned section in …
chrispcampbell Oct 10, 2024
4dd1f91
fix: show item kind in context menu
chrispcampbell Oct 10, 2024
3a5f4e9
fix: allow for pinning a whole row of scenarios
chrispcampbell Oct 10, 2024
091905d
fix: implement "Move Item to Top" action
chrispcampbell Oct 10, 2024
7c68236
fix: improve styling of section headers in detail view
chrispcampbell Oct 10, 2024
9ffbf1e
fix: remove logs
chrispcampbell Oct 11, 2024
207dad9
fix: comment out unused code
chrispcampbell Oct 11, 2024
e19a0d4
fix: update copyright year
chrispcampbell Oct 11, 2024
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
3 changes: 2 additions & 1 deletion packages/check-ui-shell/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"assert-never": "^1.2.1",
"chart.js": "^2.9.4",
"copy-text-to-clipboard": "^3.2.0",
"fontfaceobserver": "^2.3.0"
"fontfaceobserver": "^2.3.0",
"svelte-dnd-action": "^0.9.50"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.1.2",
Expand Down
5 changes: 3 additions & 2 deletions packages/check-ui-shell/src/_shared/stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { Writable } from 'svelte/store'
import { writable } from 'svelte/store'

/**
* Return a Svelte writable store that is backed by local storage.
* Return a Svelte writable number store that is backed by local storage.
*/
export function localStorageWritableNumber(key: string, defaultValue: number): Writable<number> {
const initialStringValue = localStorage.getItem(key)
Expand Down Expand Up @@ -37,9 +37,10 @@ export function localStorageWritableNumber(key: string, defaultValue: number): W
}

/**
* Return a Svelte writable store that is backed by local storage.
* Return a Svelte writable boolean store that is backed by local storage.
*/
export function localStorageWritableBoolean(key: string, defaultValue: boolean): Writable<boolean> {
// TODO: Update callers to use `localStore` and then remove this variant
const initialStringValue = localStorage.getItem(key)
let initialValue: boolean
if (initialStringValue !== null) {
Expand Down
75 changes: 50 additions & 25 deletions packages/check-ui-shell/src/app-vm.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund

import assertNever from 'assert-never'

import type { Readable, Writable } from 'svelte/store'
import { get, writable } from 'svelte/store'

Expand All @@ -12,17 +14,18 @@ import type { UserPrefs } from './_shared/user-prefs'
import type { AppModel } from './model/app-model'

import type { ComparisonGroupingKind } from './components/compare/_shared/comparison-grouping-kind'
import type { PinnedItemState, PinnedItemStates } from './components/compare/_shared/pinned-item-state'
import { createPinnedItemStates } from './components/compare/_shared/pinned-item-state'
import type { CompareDetailViewModel } from './components/compare/detail/compare-detail-vm'
import { createCompareDetailViewModel } from './components/compare/detail/compare-detail-vm'
import type { ComparisonSummaryRowViewModel } from './components/compare/summary/comparison-summary-row-vm'
import type { ComparisonSummaryViewModel } from './components/compare/summary/comparison-summary-vm'
import type { HeaderViewModel } from './components/header/header-vm'
import { createHeaderViewModel } from './components/header/header-vm'
import type { PerfViewModel } from './components/perf/perf-vm'
import { createPerfViewModel } from './components/perf/perf-vm'
import type { SummaryViewModel } from './components/summary/summary-vm'
import { createSummaryViewModel } from './components/summary/summary-vm'
import assertNever from 'assert-never'
import type { ComparisonSummaryViewModel } from './components/compare/summary/comparison-summary-vm'

export interface RunSuiteCallbacks {
onProgress?: (pct: number) => void
Expand All @@ -36,6 +39,7 @@ export class AppViewModel {
public readonly progress: Readable<string>
public readonly userPrefs: UserPrefs
public readonly headerViewModel: HeaderViewModel
private readonly pinnedItemStates: PinnedItemStates
public summaryViewModel: SummaryViewModel
private cancelRunSuite: () => void

Expand Down Expand Up @@ -69,6 +73,9 @@ export class AppViewModel {

// Create the header view model
this.headerViewModel = createHeaderViewModel(appModel.config.comparison, simplifyScenarios, zoom, consistentYRange)

// Create the object that manages pinned items states
this.pinnedItemStates = createPinnedItemStates()
}

runTestSuite(): void {
Expand All @@ -94,7 +101,8 @@ export class AppViewModel {
this.appModel.checkDataCoordinator,
checkReport,
comparisonConfig,
comparisonSummary
comparisonSummary,
this.pinnedItemStates
)
this.writableChecksInProgress.set(false)
} else {
Expand All @@ -121,7 +129,8 @@ export class AppViewModel {
this.appModel.checkDataCoordinator,
checkReport,
comparisonConfig,
comparisonSummary
comparisonSummary,
this.pinnedItemStates
)
this.writableChecksInProgress.set(false)
},
Expand All @@ -140,46 +149,62 @@ export class AppViewModel {
createCompareDetailViewModelForSummaryRow(
summaryRowViewModel: ComparisonSummaryRowViewModel
): CompareDetailViewModel {
const comparisonSummaryViewModel = this.getComparisonSummaryViewModel(summaryRowViewModel.kind)
const groupSummary = summaryRowViewModel.groupSummary
const groupKey = summaryRowViewModel.groupKey

const viewGroup = summaryRowViewModel.viewMetadata?.viewGroup
const view = summaryRowViewModel.viewMetadata?.view

// Determine which rows precede and follow the selected row
let previousRowIndex: number
let nextRowIndex: number
const rowCount = comparisonSummaryViewModel.allRows.length
const rowIndex = comparisonSummaryViewModel.allRows.findIndex(row => row.groupKey === groupKey)
if (rowIndex >= 0) {
if (rowIndex > 0) {
previousRowIndex = rowIndex - 1
}
if (rowIndex < rowCount - 1) {
nextRowIndex = rowIndex + 1
}
let pinnedItemState: PinnedItemState
if (groupSummary.group.kind === 'by-dataset') {
// Show pinned scenarios at the top of the detail view
pinnedItemState = this.pinnedItemStates.pinnedScenarios
} else {
// Show pinned datasets at the top of the detail view
pinnedItemState = this.pinnedItemStates.pinnedDatasets
}

return createCompareDetailViewModel(
summaryRowViewModel.key,
this.appModel.config.comparison,
this.appModel.comparisonDataCoordinator,
this.userPrefs,
groupSummary,
viewGroup,
view,
previousRowIndex,
nextRowIndex
pinnedItemState
)
}

createCompareDetailViewModelForSummaryRowIndex(
createCompareDetailViewModelForFirstSummaryRow(kind: ComparisonGroupingKind): CompareDetailViewModel | undefined {
// Get the index of the associated row in the context of the summary view
const comparisonSummaryViewModel = this.getComparisonSummaryViewModel(kind)
const allRows = get(comparisonSummaryViewModel.allRows)
if (allRows.length > 0) {
// Create a detail view for the first row
const firstRow = allRows[0]
return this.createCompareDetailViewModelForSummaryRow(firstRow)
} else {
return undefined
}
}

createCompareDetailViewModelForSummaryRowWithDelta(
kind: ComparisonGroupingKind,
rowIndex: number
): CompareDetailViewModel {
summaryRowKey: string,
delta: -1 | 1
): CompareDetailViewModel | undefined {
// Get the index of the associated row in the context of the summary view
const comparisonSummaryViewModel = this.getComparisonSummaryViewModel(kind)
const rowViewModel = comparisonSummaryViewModel.allRows[rowIndex]
return this.createCompareDetailViewModelForSummaryRow(rowViewModel)
const allRows = get(comparisonSummaryViewModel.allRows)
const rowIndex = allRows.findIndex(row => row.key === summaryRowKey)
const adjRowIndex = rowIndex + delta
if (adjRowIndex >= 0 && adjRowIndex < allRows.length) {
// Create a detail view for the adjacent row
const prevRow = allRows[adjRowIndex]
return this.createCompareDetailViewModelForSummaryRow(prevRow)
} else {
return undefined
}
}

private getComparisonSummaryViewModel(kind: ComparisonGroupingKind): ComparisonSummaryViewModel {
Expand Down
19 changes: 14 additions & 5 deletions packages/check-ui-shell/src/app.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,27 @@ function onCommand(event: CustomEvent) {
default:
return
}
compareDetailViewModel = viewModel.createCompareDetailViewModelForSummaryRowIndex(kind, 0)
viewMode = 'comparison-detail'
const first = viewModel.createCompareDetailViewModelForFirstSummaryRow(kind)
if (first) {
compareDetailViewModel = first
viewMode = 'comparison-detail'
}
}
break
case 'show-comparison-detail':
compareDetailViewModel = viewModel.createCompareDetailViewModelForSummaryRow(cmdObj.summaryRow)
viewMode = 'comparison-detail'
break
case 'show-comparison-detail-at-index':
compareDetailViewModel = viewModel.createCompareDetailViewModelForSummaryRowIndex(cmdObj.kind, cmdObj.index)
viewMode = 'comparison-detail'
case 'show-comparison-detail-for-previous':
case 'show-comparison-detail-for-next': {
const delta = cmd === 'show-comparison-detail-for-previous' ? -1 : +1
const adjacent = viewModel.createCompareDetailViewModelForSummaryRowWithDelta(cmdObj.kind, cmdObj.summaryRowKey, delta)
if (adjacent) {
compareDetailViewModel = adjacent
viewMode = 'comparison-detail'
}
break
}
case 'show-perf':
if (!perfViewModel) {
perfViewModel = viewModel.createPerfViewModel()
Expand Down
22 changes: 22 additions & 0 deletions packages/check-ui-shell/src/components/_shared/click-outside.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) 2024 Climate Interactive / New Venture Fund. All rights reserved.

/** Dispatch an event when click event occurs outside of the given element. */
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function clickOutside(element: HTMLElement) {
// This implementation is based on:
// https://svelte.dev/repl/0ace7a508bd843b798ae599940a91783?version=3.16.7

const handleClick = (event: Event) => {
if (element && !element.contains(event.target as HTMLElement) && !event.defaultPrevented) {
element.dispatchEvent(new CustomEvent('clickout'))
}
}

document.addEventListener('click', handleClick, true)

return {
destroy() {
document.removeEventListener('click', handleClick, true)
}
}
}
125 changes: 125 additions & 0 deletions packages/check-ui-shell/src/components/_shared/context-menu.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<!-- Copyright (c) 2024 Climate Interactive / New Venture Fund -->

<!--
Based on example code by @dukenmarga from:
https://svelte.dev/repl/6fb90919e24942b2b47d9ad154386b0c?version=3.49.0
-->

<script context="module" lang="ts">

export interface ContextMenuItem {
key: string
displayText: string
iconClass?: string
disabled?: boolean
}

</script>

<script lang="ts">

import { createEventDispatcher } from 'svelte'

import { clickOutside } from './click-outside'

export let items: ContextMenuItem[]
export let parentElem: HTMLElement
export let initialEvent: MouseEvent

const dispatch = createEventDispatcher()

// Cursor position of the initial event
let pos = { x: 0, y: 0 }

// Whether to show or hide the menu
let showMenu = false

$: if (initialEvent) {
const bounds = parentElem.getBoundingClientRect()
pos = {
x: initialEvent.clientX - bounds.left,
y: initialEvent.clientY - bounds.top
}
showMenu = true
} else {
showMenu = false
}

function onItemSelected(cmd: string) {
dispatch('item-selected', cmd)
}

</script>

{#if showMenu}
<nav use:clickOutside on:clickout style="position: absolute; top:{pos.y}px; left:{pos.x}px">
<div class="navbar" id="navbar">
<ul>
{#each items as item}
{#if item.key == "hr"}
<hr>
{:else}
<li><button class:disabled={item.disabled === true} on:click={() => onItemSelected(item.key)}><i class={item.iconClass}></i>{item.displayText}</button></li>
{/if}
{/each}
</ul>
</div>
</nav>
{/if}

<style lang="sass">

*
padding: 0
margin: 0

nav
z-index: 200

.navbar
display: inline-flex
flex-direction: column
width: 170px
background-color: #fff
border-radius: .4rem
box-shadow: 0 .2rem .4rem rgba(0,0,0,.8)
overflow: hidden

.navbar ul
margin: 6px

ul li
display: block
list-style-type: none
width: 1fr

ul li button
font-family: Roboto, sans-serif
font-weight: 700
font-size: 1rem
color: #222
width: 100%
height: 30px
text-align: left
border: 0px
background-color: #fff

ul li button.disabled
color: #888
pointer-events: none

ul li button:not(.disabled):hover
color: #000
text-align: left
border-radius: .3rem
background-color: #eee

ul li button i
padding: 0px 15px 0px 10px

hr
border: none
border-bottom: 1px solid #ccc
margin: 5px 0px

</style>
Loading