Skip to content

Commit 0f6c2d1

Browse files
authored
feat(UI): Clarify how items can be added to a view (#542)
This PR - disable double click action - add optional actions to accordion items - plug + action to the display key project's store action UI preview: https://github.com/user-attachments/assets/c6df8dd6-03ea-4fcd-a254-2eb8747d3d35
1 parent 1527a1b commit 0f6c2d1

File tree

9 files changed

+170
-60
lines changed

9 files changed

+170
-60
lines changed
100 Bytes
Binary file not shown.

skore-ui/src/assets/fonts/icomoon.svg

Lines changed: 22 additions & 21 deletions
Loading
100 Bytes
Binary file not shown.
100 Bytes
Binary file not shown.

skore-ui/src/assets/styles/_icons.css

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,23 @@
2727
text-transform: none;
2828
}
2929

30-
.icon-edit::before {
30+
.icon-more::before {
3131
content: "\e900";
3232
}
3333

3434
.icon-handle::before {
3535
content: "\e901";
3636
}
3737

38-
.icon-more::before {
38+
.icon-branch::before {
3939
content: "\e902";
4040
}
4141

42-
.icon-branch::before {
42+
.icon-trash::before {
4343
content: "\e903";
4444
}
4545

46-
.icon-trash::before {
46+
.icon-edit::before {
4747
content: "\e904";
4848
}
4949

@@ -63,66 +63,70 @@
6363
content: "\e908";
6464
}
6565

66-
.icon-plus-circle::before {
66+
.icon-plus::before {
6767
content: "\e909";
6868
}
6969

70-
.icon-warning::before {
70+
.icon-plus-circle::before {
7171
content: "\e90a";
7272
}
7373

74-
.icon-info::before {
74+
.icon-warning::before {
7575
content: "\e90b";
7676
}
7777

78-
.icon-error::before {
78+
.icon-info::before {
7979
content: "\e90c";
8080
}
8181

82-
.icon-success::before {
82+
.icon-error::before {
8383
content: "\e90d";
8484
}
8585

86-
.icon-search::before {
86+
.icon-success::before {
8787
content: "\e90e";
8888
}
8989

90-
.icon-maximize::before {
90+
.icon-search::before {
9191
content: "\e90f";
9292
}
9393

94-
.icon-folder::before {
94+
.icon-maximize::before {
9595
content: "\e910";
9696
}
9797

98-
.icon-plot::before {
98+
.icon-folder::before {
9999
content: "\e911";
100100
}
101101

102-
.icon-text::before {
102+
.icon-plot::before {
103103
content: "\e912";
104104
}
105105

106-
.icon-gift::before {
106+
.icon-text::before {
107107
content: "\e913";
108108
}
109109

110-
.icon-pie-chart::before {
110+
.icon-gift::before {
111111
content: "\e914";
112112
}
113113

114-
.icon-chevron-left::before {
114+
.icon-pie-chart::before {
115115
content: "\e915";
116116
}
117117

118-
.icon-chevron-down::before {
118+
.icon-chevron-left::before {
119119
content: "\e916";
120120
}
121121

122-
.icon-chevron-right::before {
122+
.icon-chevron-down::before {
123123
content: "\e917";
124124
}
125125

126-
.icon-chevron-up::before {
126+
.icon-chevron-right::before {
127127
content: "\e918";
128128
}
129+
130+
.icon-chevron-up::before {
131+
content: "\e919";
132+
}

skore-ui/src/components/TreeAccordion.vue

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,49 @@
11
<script lang="ts">
2+
import { ref, watch } from "vue";
3+
4+
export interface TreeAccordionItemAction {
5+
icon: string;
6+
actionName: string;
7+
}
8+
29
export interface TreeAccordionNode {
310
name: string;
411
children?: TreeAccordionNode[];
512
isRoot?: boolean;
13+
actions?: TreeAccordionItemAction[];
614
}
715
</script>
816

917
<script setup lang="ts">
1018
import TreeAccordionItem from "./TreeAccordionItem.vue";
1119
12-
const props = defineProps<{ nodes: TreeAccordionNode[] }>();
20+
const props = defineProps<{
21+
nodes: TreeAccordionNode[];
22+
}>();
23+
const emit = defineEmits<{
24+
itemAction: [action: string, itemName: string];
25+
}>();
26+
const lastItemAction = ref<string | null>(null);
27+
const lastItemName = ref<string | null>(null);
1328
14-
const emit = defineEmits<{ itemSelected: [key: string] }>();
15-
16-
function onDoubleClick(event: MouseEvent) {
17-
const target = event.target as HTMLElement;
18-
const closestNamedElement = target.closest("[data-name]") as HTMLElement;
19-
20-
if (closestNamedElement) {
21-
emit("itemSelected", closestNamedElement.dataset.name ?? "");
29+
watch([lastItemAction, lastItemName], () => {
30+
if (lastItemAction.value && lastItemName.value) {
31+
emit("itemAction", lastItemAction.value, lastItemName.value);
2232
}
23-
}
33+
});
2434
</script>
2535

2636
<template>
27-
<div class="accordion" @dblclick="onDoubleClick">
37+
<div class="accordion">
2838
<TreeAccordionItem
2939
v-for="(node, index) in props.nodes"
3040
:key="index"
3141
:name="node.name"
3242
:children="node.children"
3343
:is-root="true"
44+
:actions="node.actions"
45+
v-model:last-item-action="lastItemAction"
46+
v-model:last-item-name="lastItemName"
3447
/>
3548
</div>
3649
</template>

skore-ui/src/components/TreeAccordionItem.vue

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const props = defineProps<TreeAccordionNode>();
77
88
const isCollapsed = ref(false);
99
const isDraggable = ref(false);
10+
const lastItemAction = defineModel<string | null>("lastItemAction");
11+
const lastItemName = defineModel<string | null>("lastItemName");
1012
1113
const hasChildren = computed(() => props.children?.length);
1214
const label = computed(() => {
@@ -39,6 +41,11 @@ function onDragStart(event: DragEvent) {
3941
event.dataTransfer.setData("application/x-skore-item-name", props.name);
4042
}
4143
}
44+
45+
function onAction(action: string) {
46+
lastItemAction.value = action;
47+
lastItemName.value = props.name;
48+
}
4249
</script>
4350

4451
<template>
@@ -62,6 +69,15 @@ function onDragStart(event: DragEvent) {
6269
<span class="icon icon-pill" />
6370
<span class="text">{{ label }}</span>
6471
</div>
72+
<div class="actions">
73+
<button
74+
v-for="(action, index) in props.actions"
75+
:key="index"
76+
@click="onAction(action.actionName)"
77+
>
78+
<span :class="action.icon" />
79+
</button>
80+
</div>
6581
<button
6682
class="collapse"
6783
:class="{ collapsed: isCollapsed }"
@@ -79,6 +95,9 @@ function onDragStart(event: DragEvent) {
7995
:name="child.name"
8096
:children="child.children"
8197
:is-root="false"
98+
:actions="child.actions"
99+
v-model:last-item-action="lastItemAction"
100+
v-model:last-item-name="lastItemName"
82101
/>
83102
</div>
84103
</Transition>
@@ -147,6 +166,30 @@ function onDragStart(event: DragEvent) {
147166
}
148167
}
149168
169+
.actions {
170+
display: flex;
171+
flex-direction: row;
172+
align-items: center;
173+
gap: var(--spacing-gap-small);
174+
opacity: 0;
175+
transition: opacity var(--transition-duration) var(--transition-easing);
176+
177+
& button {
178+
padding: 0;
179+
border: none;
180+
margin: 0;
181+
background-color: transparent;
182+
color: var(--text-color-normal);
183+
cursor: pointer;
184+
font-size: var(--text-size-normal);
185+
transition: color var(--transition-duration) var(--transition-easing);
186+
187+
&:hover {
188+
color: var(--color-primary);
189+
}
190+
}
191+
}
192+
150193
.collapse {
151194
padding: 0;
152195
border: none;
@@ -160,6 +203,12 @@ function onDragStart(event: DragEvent) {
160203
transform: rotate(90deg);
161204
}
162205
}
206+
207+
&:hover {
208+
.actions {
209+
opacity: 1;
210+
}
211+
}
163212
}
164213
165214
.children {

skore-ui/src/views/ComponentsView.vue

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<script setup lang="ts">
2-
import Simplebar from "simplebar-vue";
32
import type { VisualizationSpec } from "vega-embed";
43
import { ref } from "vue";
54
@@ -11,12 +10,9 @@ import namedIndexDatatable from "@/assets/fixtures/named-index-datatable.json";
1110
import spec from "@/assets/fixtures/vega.json";
1211
1312
import DataFrameWidget from "@/components/DataFrameWidget.vue";
14-
import DraggableList from "@/components/DraggableList.vue";
1513
import DropdownButton from "@/components/DropdownButton.vue";
1614
import DropdownButtonItem from "@/components/DropdownButtonItem.vue";
17-
import DynamicContentRasterizer from "@/components/DynamicContentRasterizer.vue";
18-
import EditableList, { type EditableListItemModel } from "@/components/EditableList.vue";
19-
import FloatingTooltip from "@/components/FloatingTooltip.vue";
15+
import { type EditableListItemModel } from "@/components/EditableList.vue";
2016
import HtmlSnippetWidget from "@/components/HtmlSnippetWidget.vue";
2117
import ImageWidget from "@/components/ImageWidget.vue";
2218
import MarkdownWidget from "@/components/MarkdownWidget.vue";
@@ -99,6 +95,29 @@ const fileTreeNodes: TreeAccordionNode[] = [
9995
},
10096
];
10197
98+
const lastAction = ref<string | null>(null);
99+
const fileTreeItemWithActions: TreeAccordionNode[] = [
100+
{
101+
name: "fraud",
102+
children: [
103+
{
104+
name: "fraud/accuracy",
105+
actions: [
106+
{ icon: "icon-plus-circle", actionName: "add" },
107+
{ icon: "icon-trash", actionName: "delete" },
108+
],
109+
},
110+
{
111+
name: "fraud/accuracy3",
112+
actions: [
113+
{ icon: "icon-plus-circle", actionName: "add" },
114+
{ icon: "icon-trash", actionName: "delete" },
115+
],
116+
},
117+
],
118+
},
119+
];
120+
102121
const items = ref<EditableListItemModel[]>([
103122
{ name: "Item 1", icon: "icon-plot", id: generateRandomId() },
104123
{ name: "Item 2", id: generateRandomId() },
@@ -413,6 +432,11 @@ const isCached = ref(false);
413432
</TabsItem>
414433
<TabsItem :value="9">
415434
<TreeAccordion :nodes="fileTreeNodes" />
435+
<div style="margin-top: 20px">last item action {{ lastAction }}</div>
436+
<TreeAccordion
437+
:nodes="fileTreeItemWithActions"
438+
@item-action="(action, itemName) => (lastAction = `${action} ${itemName}`)"
439+
/>
416440
</TabsItem>
417441
<TabsItem :value="10" class="editable-list-tab">
418442
<div class="header">

skore-ui/src/views/project/ProjectItemList.vue

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,33 @@
22
import Simplebar from "simplebar-vue";
33
44
import SectionHeader from "@/components/SectionHeader.vue";
5-
import TreeAccordion from "@/components/TreeAccordion.vue";
5+
import TreeAccordion, { type TreeAccordionNode } from "@/components/TreeAccordion.vue";
66
import { useProjectStore } from "@/stores/project";
77
import { useToastsStore } from "@/stores/toasts";
8+
import { computed } from "vue";
89
910
const projectStore = useProjectStore();
1011
const toastsStore = useToastsStore();
1112
12-
function onItemSelected(key: string) {
13+
const itemsAsTree = computed(() => {
14+
const source = projectStore.keysAsTree();
15+
const tree = structuredClone(source) as unknown as TreeAccordionNode[];
16+
// add actions to the leaf nodes
17+
function addActions(node: TreeAccordionNode) {
18+
if (node.children?.length === 0) {
19+
node.actions = [{ icon: "icon-plus", actionName: "add" }];
20+
}
21+
for (const child of node.children ?? []) {
22+
addActions(child);
23+
}
24+
}
25+
for (const node of tree) {
26+
addActions(node);
27+
}
28+
return tree;
29+
});
30+
31+
function onItemAction(action: string, key: string) {
1332
if (projectStore.currentView) {
1433
projectStore.displayKey(projectStore.currentView, key);
1534
} else {
@@ -22,7 +41,7 @@ function onItemSelected(key: string) {
2241
<div class="keys-list">
2342
<SectionHeader title="Items" icon="icon-pie-chart" />
2443
<Simplebar class="scrollable">
25-
<TreeAccordion :nodes="projectStore.keysAsTree()" @item-selected="onItemSelected" />
44+
<TreeAccordion :nodes="itemsAsTree" @item-action="onItemAction" />
2645
</Simplebar>
2746
</div>
2847
</template>

0 commit comments

Comments
 (0)