Skip to content

Commit a8e77fe

Browse files
committed
Merge branch 'main' into 412-user-guide-and-api-documentation
2 parents 11a9b34 + 904b534 commit a8e77fe

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1737
-773
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ repos:
2525
rev: v0.6.9
2626
hooks:
2727
- id: ruff
28-
files: ^(src|tests)/
28+
files: ^skore/(src|tests)/
2929
args: [--fix]
3030
- id: ruff-format
31-
files: ^(src|tests)/
31+
files: ^skore/(src|tests)/
3232

3333
- repo: local
3434
hooks:

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
![python](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue?style=flat&logo=python)
55
[![pypi](https://img.shields.io/pypi/v/skore)](https://pypi.org/project/skore/)
66
![license](https://img.shields.io/pypi/l/skore)
7+
[![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?logo=discord&logoColor=white)](https://discord.probabl.ai/)
78

89
With `skore`, data scientists can:
910
1. Store objects of different types from their Python code: python lists, `scikit-learn` fitted pipelines, `plotly` figures, and more.

skore-ui/src/ShareApp.vue

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,60 @@
11
<script setup lang="ts">
2+
import { formatDistance } from "date-fns";
23
import Simplebar from "simplebar-vue";
34
4-
import ProjectViewCanvas from "@/components/ProjectViewCanvas.vue";
5+
import DataFrameWidget from "@/components/DataFrameWidget.vue";
6+
import HtmlSnippetWidget from "@/components/HtmlSnippetWidget.vue";
7+
import ImageWidget from "@/components/ImageWidget.vue";
8+
import MarkdownWidget from "@/components/MarkdownWidget.vue";
9+
import PlotlyWidget from "@/components/PlotlyWidget.vue";
10+
import ProjectViewCard from "@/components/ProjectViewCard.vue";
11+
import VegaWidget from "@/components/VegaWidget.vue";
512
import { useProjectStore } from "@/stores/project";
613
714
const projectStore = useProjectStore();
15+
16+
function getItemSubtitle(created_at: Date, updated_at: Date) {
17+
const now = new Date();
18+
return `Created ${formatDistance(created_at, now)} ago, updated ${formatDistance(updated_at, now)} ago`;
19+
}
820
</script>
921

1022
<template>
1123
<div class="share">
1224
<div class="share-header">
1325
<h1>{{ projectStore.currentView }}</h1>
1426
</div>
15-
<Simplebar class="canvas-wrapper">
16-
<ProjectViewCanvas :showCardActions="false" />
27+
<Simplebar class="cards">
28+
<div class="inner">
29+
<ProjectViewCard
30+
v-for="{ key, mediaType, data, createdAt, updatedAt } in projectStore.currentViewItems"
31+
:key="key"
32+
:title="key.toString()"
33+
:subtitle="getItemSubtitle(createdAt, updatedAt)"
34+
:showActions="false"
35+
>
36+
<DataFrameWidget
37+
v-if="mediaType === 'application/vnd.dataframe+json'"
38+
:columns="data.columns"
39+
:data="data.data"
40+
:index="data.index"
41+
/>
42+
<ImageWidget
43+
v-if="['image/svg+xml', 'image/png', 'image/jpeg', 'image/webp'].includes(mediaType)"
44+
:mediaType="mediaType"
45+
:base64-src="data"
46+
:alt="key.toString()"
47+
/>
48+
<MarkdownWidget v-if="mediaType === 'text/markdown'" :source="data" />
49+
<VegaWidget v-if="mediaType === 'application/vnd.vega.v5+json'" :spec="data" />
50+
<PlotlyWidget v-if="mediaType === 'application/vnd.plotly.v1+json'" :spec="data" />
51+
<HtmlSnippetWidget
52+
v-if="mediaType === 'application/vnd.sklearn.estimator+html'"
53+
:src="data"
54+
/>
55+
<HtmlSnippetWidget v-if="mediaType === 'text/html'" :src="data" />
56+
</ProjectViewCard>
57+
</div>
1758
</Simplebar>
1859
</div>
1960
</template>
@@ -56,35 +97,16 @@ const projectStore = useProjectStore();
5697
}
5798
}
5899
59-
& .drop-indicator {
60-
height: 3px;
61-
border-radius: 8px;
62-
margin: 0 10%;
63-
background-color: var(--text-color-title);
64-
opacity: 0;
65-
transition: opacity var(--transition-duration) var(--transition-easing);
66-
67-
&.visible {
68-
opacity: 1;
69-
}
70-
}
71-
72-
& .placeholder {
73-
height: 100%;
74-
padding-top: calc((100vh - var(--header-height)) * 476 / 730);
75-
background-image: var(--editor-placeholder-image);
76-
background-position: 50%;
77-
background-repeat: no-repeat;
78-
background-size: contain;
79-
color: var(--text-color-normal);
80-
font-size: var(--text-size-normal);
81-
text-align: center;
82-
}
83-
84-
& .canvas-wrapper {
100+
& .cards {
85101
height: 0;
86102
flex-grow: 1;
87103
padding: var(--spacing-padding-large);
104+
105+
& .inner {
106+
display: flex;
107+
flex-direction: column;
108+
gap: var(--spacing-gap-large);
109+
}
88110
}
89111
}
90112
</style>

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
--color-secondary: #2329ff;
5050

5151
/* typography */
52+
--text-color-low: rgb(99 99 99);
5253
--text-color-normal: #a2a2a2;
5354
--text-color-highlight: #fff;
5455

@@ -73,7 +74,7 @@
7374
--toast-border-color: rgb(255 255 255);
7475
--toast-text-color: rgb(0 0 0);
7576
--toast-dismiss-color: hsl(0deg 0% 35%);
76-
--toast-shadow-elevation: rgb(255 255 255 / 48%);
77+
--toast-shadow-elevation: rgb(0 0 0 / 48%);
7778
--toast-shadow-inset: inset 0 0 1.1px 2px rgb(0 0 0 / 13%);
7879
}
7980
}
@@ -84,6 +85,7 @@
8485
--color-secondary: #f08b30;
8586

8687
/* typography */
88+
--text-color-low: #babbbd;
8789
--text-color-normal: #616271;
8890
--text-color-highlight: #2f3037;
8991

skore-ui/src/components/DraggableList.vue

Lines changed: 71 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Interactable } from "@interactjs/types";
33
import { toPng } from "html-to-image";
44
import interact from "interactjs";
55
import Simplebar from "simplebar-core";
6-
import { onMounted, onUnmounted, ref, useTemplateRef } from "vue";
6+
import { onBeforeUnmount, onMounted, ref, useTemplateRef } from "vue";
77
88
import DynamicContentRasterizer from "@/components/DynamicContentRasterizer.vue";
99
@@ -13,6 +13,7 @@ interface Item {
1313
}
1414
1515
const items = defineModel<Item[]>("items", { required: true });
16+
const currentDropPosition = defineModel<number>("currentDropPosition");
1617
const props = defineProps<{
1718
autoScrollContainerSelector?: string;
1819
}>();
@@ -22,7 +23,7 @@ const movingItemAsPngData = ref("");
2223
const movingItemHeight = ref(0);
2324
const movingItemY = ref(0);
2425
const container = useTemplateRef("container");
25-
let interactable: Interactable;
26+
let draggable: Interactable;
2627
let direction: "up" | "down" | "none" = "none";
2728
let autoScrollContainer: HTMLElement = document.body;
2829
@@ -64,21 +65,55 @@ function capturedStyles() {
6465
};
6566
}
6667
67-
function makeModifier() {
68-
const containerBounds = container.value!.getBoundingClientRect();
69-
70-
return interact.modifiers.restrict({
71-
restriction: (x, y, { element }) => {
72-
const content = element?.parentElement?.parentElement?.querySelector(".content");
73-
const { height } = content?.getBoundingClientRect() ?? { height: 0 };
74-
return {
75-
top: containerBounds?.top - height,
76-
right: containerBounds?.right,
77-
bottom: containerBounds?.bottom + height,
78-
left: containerBounds?.left,
79-
};
80-
},
68+
function setDropIndicatorPosition(y: number) {
69+
const itemBounds = Array.from(container.value!.querySelectorAll(".item")).map((item, index) => {
70+
const { top, height } = item.getBoundingClientRect();
71+
const center = top + height / 2;
72+
return {
73+
index,
74+
distance: Math.abs(y - center),
75+
center,
76+
};
8177
});
78+
const closestItemBelow = itemBounds.reduce((closest, item) => {
79+
if (item.distance < closest.distance) {
80+
return item;
81+
}
82+
return closest;
83+
}, itemBounds[0]);
84+
85+
if (y > closestItemBelow.center) {
86+
dropIndicatorPosition.value = closestItemBelow.index;
87+
} else {
88+
dropIndicatorPosition.value = closestItemBelow.index - 1;
89+
}
90+
}
91+
92+
function onDragOver(event: DragEvent) {
93+
// scroll the container if needed
94+
const scrollBounds = autoScrollContainer.getBoundingClientRect();
95+
const distanceToTop = Math.abs(event.pageY - scrollBounds.top);
96+
const distanceToBottom = Math.abs(event.pageY - scrollBounds.bottom);
97+
const threshold = 150;
98+
const speed = 5;
99+
if (distanceToTop < threshold) {
100+
autoScrollContainer.scrollTop -= speed;
101+
} else if (distanceToBottom < threshold) {
102+
const maxScroll = autoScrollContainer.scrollHeight - scrollBounds.height;
103+
autoScrollContainer.scrollTop = Math.min(maxScroll, autoScrollContainer.scrollTop + speed);
104+
}
105+
106+
// show drop indicator to the closest item
107+
setDropIndicatorPosition(event.pageY);
108+
109+
if (dropIndicatorPosition.value !== null) {
110+
currentDropPosition.value = dropIndicatorPosition.value + 1;
111+
}
112+
}
113+
114+
function onDragLeave() {
115+
currentDropPosition.value = -1;
116+
dropIndicatorPosition.value = null;
82117
}
83118
84119
onMounted(() => {
@@ -101,14 +136,14 @@ onMounted(() => {
101136
autoScrollContainer = container.value!.parentElement ?? document.body;
102137
}
103138
104-
interactable = interact(".handle").draggable({
139+
draggable = interact(".handle").draggable({
105140
autoScroll: {
106141
enabled: true,
107142
container: autoScrollContainer,
143+
speed: 900,
108144
},
109145
startAxis: "y",
110146
lockAxis: "y",
111-
modifiers: [makeModifier()],
112147
listeners: {
113148
async start(event) {
114149
// make a rasterized copy of the moving element
@@ -134,40 +169,13 @@ onMounted(() => {
134169
event.clientY + autoScrollContainer!.scrollTop - paddingTop - containerY;
135170
136171
// set the drop indicator item index
137-
const itemBounds = Array.from(container.value!.querySelectorAll(".item")).map(
138-
(item, index) => {
139-
const { top, height } = item.getBoundingClientRect();
140-
const center = top + height / 2;
141-
return {
142-
index,
143-
distance: Math.abs(event.pageY - center),
144-
};
145-
}
146-
);
147-
const closestItemBelow = itemBounds.reduce((closest, item) => {
148-
if (item.distance < closest.distance) {
149-
return item;
150-
}
151-
return closest;
152-
}, itemBounds[0]);
153-
// if the first item is the closest we may need to move the drop indicator up
154-
if (closestItemBelow.index === 0) {
155-
// does the user want to move the item to the top?
156-
const bounds = container.value!.getBoundingClientRect();
157-
if (event.pageY < bounds.top) {
158-
dropIndicatorPosition.value = -1;
159-
} else {
160-
dropIndicatorPosition.value = 0;
161-
}
162-
} else {
163-
dropIndicatorPosition.value = closestItemBelow.index;
164-
}
172+
setDropIndicatorPosition(event.pageY);
165173
},
166174
end() {
167175
// change the model order
168176
if (items.value && movingItemIndex.value !== null && dropIndicatorPosition.value !== null) {
169177
// did user dropped the item in its previous position ?
170-
if (Math.abs(dropIndicatorPosition.value - movingItemIndex.value) > 1) {
178+
if (Math.abs(dropIndicatorPosition.value - movingItemIndex.value) >= 1) {
171179
// move the item to its new position
172180
const destinationIndex =
173181
dropIndicatorPosition.value > movingItemIndex.value
@@ -186,10 +194,17 @@ onMounted(() => {
186194
},
187195
},
188196
});
197+
198+
container.value!.addEventListener("dragover", onDragOver);
199+
container.value!.addEventListener("dragleave", onDragLeave);
200+
window.addEventListener("dragend", onDragLeave);
189201
});
190202
191-
onUnmounted(() => {
192-
interactable.unset();
203+
onBeforeUnmount(() => {
204+
container.value!.removeEventListener("dragover", onDragOver);
205+
container.value!.removeEventListener("dragleave", onDragLeave);
206+
window.removeEventListener("dragend", onDragLeave);
207+
draggable.unset();
193208
});
194209
</script>
195210

@@ -236,7 +251,7 @@ onUnmounted(() => {
236251
}
237252
238253
.draggable {
239-
--content-left-margin: 15px;
254+
--content-left-margin: 18px;
240255
241256
position: relative;
242257
display: flex;
@@ -249,18 +264,23 @@ onUnmounted(() => {
249264
250265
& .handle {
251266
position: absolute;
252-
top: 0;
267+
top: -4px;
253268
left: 0;
269+
display: flex;
270+
color: hsl(from var(--color-primary) h s calc(l * 1.5));
254271
cursor: move;
272+
font-size: calc(var(--text-size-normal) * 1.7);
255273
opacity: 0;
256274
transition: opacity var(--transition-duration) var(--transition-easing);
257275
}
258276
259277
& .content-wrapper {
278+
width: calc(100% - var(--content-left-margin));
260279
margin-left: var(--content-left-margin);
261280
}
262281
263282
& .content {
283+
width: 100%;
264284
transition: opacity var(--transition-duration) var(--transition-easing);
265285
266286
&.moving {

skore-ui/src/components/DynamicContentRasterizer.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const realContent = useTemplateRef("realContent");
1212
const contentHeight = ref(0);
1313
1414
const styles = computed(() => {
15-
const h = contentHeight.value !== 0 ? `${contentHeight.value}px` : "auto";
15+
const h = contentHeight.value !== 0 && props.isRasterized ? `${contentHeight.value}px` : "auto";
1616
return {
1717
height: h,
1818
};
@@ -64,6 +64,7 @@ onBeforeUnmount(() => {
6464
<style scoped>
6565
.dynamic-content-rasterizer {
6666
position: relative;
67+
overflow: hidden;
6768
padding: 0;
6869
margin: 0;
6970

0 commit comments

Comments
 (0)