Skip to content

Commit

Permalink
feat(UI & API): Manage views in the frontend (#414)
Browse files Browse the repository at this point in the history
## This PR allows user to create/duplicate/edit & delete views.

### Backend

All project endpoints are now below the same prefix.

- `GET /api/project/items` lists all items of a project.
- `POST /api/project/views/share/{key}` returns a static shareable HTML
file named `{view name}-{yyyy-MM-dd-HH-mm}.html`.
- `PUT /api/project/views/{key}` save a view in the project.
- `DELETE /api/project/views/{key}` delete a view from the project.

### Frontend

- A new `EditableList` component as been implemented to manage views. 
- A lot of renaming to get rid of the concept of "report".
- Plug everything to the new layout.
- Rework view card layout and add an action dropdown to it.
- Rework dropdowns using floating UI to make them fixed.


https://github.com/user-attachments/assets/3f94e483-ecea-4d5a-8433-50f9150fc6e6


Co-authored with @augustebaum.
Fixes #336, #407, #377, #332

---------

Co-authored-by: Auguste Baum <[email protected]>
Co-authored-by: Auguste Baum <[email protected]>
  • Loading branch information
3 people authored Oct 3, 2024
1 parent 9880d1e commit 274b6f0
Show file tree
Hide file tree
Showing 37 changed files with 1,066 additions and 576 deletions.
63 changes: 63 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"format": "prettier --write src/"
},
"dependencies": {
"@floating-ui/vue": "^1.1.5",
"@vscode/markdown-it-katex": "^1.1.0",
"date-fns": "^3.6.0",
"markdown-it": "^14.1.0",
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/ShareApp.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
<script setup lang="ts">
import Simplebar from "simplebar-vue";
import ReportCanvas from "@/components/ReportCanvas.vue";
import ProjectViewCanvas from "@/components/ProjectViewCanvas.vue";
import { useProjectStore } from "@/stores/project";
const projectStore = useProjectStore();
</script>

<template>
<div class="share">
<div class="share-header">
<h1>Report</h1>
<h1>{{ projectStore.currentView }}</h1>
</div>
<Simplebar class="canvas-wrapper">
<ReportCanvas :showCardButtons="false" />
<ProjectViewCanvas :showCardActions="false" />
</Simplebar>
</div>
</template>
Expand Down
Binary file modified frontend/src/assets/fonts/icomoon.eot
Binary file not shown.
51 changes: 24 additions & 27 deletions frontend/src/assets/fonts/icomoon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/src/assets/fonts/icomoon.ttf
Binary file not shown.
Binary file modified frontend/src/assets/fonts/icomoon.woff
Binary file not shown.
58 changes: 23 additions & 35 deletions frontend/src/assets/styles/_icons.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,109 +28,97 @@
}

.icon-branch::before {
content: "\e91a";
}

.icon-plus::before {
content: "\e900";
}

.icon-trash::before {
.icon-more::before {
content: "\e901";
}

.icon-rename::before {
.icon-trash::before {
content: "\e902";
}

.icon-duplicate::before {
.icon-edit::before {
content: "\e903";
}

.icon-pill::before {
.icon-copy::before {
content: "\e904";
}

.icon-new-file::before {
.icon-pill::before {
content: "\e905";
}

.icon-recent-file::before {
.icon-new-document::before {
content: "\e906";
}

.icon-warning::before {
.icon-recent-document::before {
content: "\e907";
}

.icon-info::before {
.icon-plus-circle::before {
content: "\e908";
}

.icon-error::before {
.icon-warning::before {
content: "\e909";
}

.icon-success::before {
.icon-info::before {
content: "\e90a";
}

.icon-grid-layout-large::before {
.icon-error::before {
content: "\e90b";
}

.icon-grid-layout-medium::before {
.icon-success::before {
content: "\e90c";
}

.icon-grid-layout-small::before {
.icon-search::before {
content: "\e90d";
}

.icon-magnifying-glass::before {
.icon-maximize::before {
content: "\e90e";
}

.icon-focus::before {
.icon-folder::before {
content: "\e90f";
}

.icon-folder::before {
.icon-plot::before {
content: "\e910";
}

.icon-plot::before {
.icon-text::before {
content: "\e911";
}

.icon-text-size::before {
content: "\e912";
}

.icon-gift::before {
content: "\e913";
content: "\e912";
}

.icon-pie-chart::before {
content: "\e914";
}

.icon-equal::before {
content: "\e915";
content: "\e913";
}

.icon-chevron-left::before {
content: "\e916";
content: "\e914";
}

.icon-chevron-down::before {
content: "\e917";
content: "\e915";
}

.icon-chevron-right::before {
content: "\e918";
content: "\e916";
}

.icon-chevron-up::before {
content: "\e919";
content: "\e917";
}
1 change: 1 addition & 0 deletions frontend/src/assets/styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@import url("./_typography.css");
@import url("./_animations.css");
@import url("./_icons.css");
@import url("simplebar-vue/dist/simplebar.min.css");
@import url("highlight.js/styles/atom-one-dark.css") screen and (prefers-color-scheme: dark);
@import url("highlight.js/styles/atom-one-light.css") screen and (prefers-color-scheme: light);

Expand Down
7 changes: 1 addition & 6 deletions frontend/src/components/DataFrameWidget.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,7 @@ watch([() => toValue(props.data), () => toValue(props.columns)], () => {

<template>
<div class="data-frame-widget">
<TextInput
v-model="search"
icon="icon-magnifying-glass"
placeholder="Search"
class="search-input"
/>
<TextInput v-model="search" icon="icon-search" placeholder="Search" class="search-input" />
<Simplebar>
<table>
<thead>
Expand Down
76 changes: 57 additions & 19 deletions frontend/src/components/DropdownButton.vue
Original file line number Diff line number Diff line change
@@ -1,31 +1,77 @@
<script setup lang="ts">
import SimpleButton from "@/components/SimpleButton.vue";
import { autoUpdate, useFloating } from "@floating-ui/vue";
import { onBeforeUnmount, onMounted, ref } from "vue";
import SimpleButton from "@/components/SimpleButton.vue";
interface DropdownProps {
label?: string;
icon?: string;
isPrimary?: boolean;
align?: "left" | "right";
isInline?: boolean;
}
const props = withDefaults(defineProps<DropdownProps>(), { isPrimary: false, align: "left" });
const isOpen = ref(false);
const el = ref<HTMLDivElement>();
const reference = ref<HTMLElement>();
const floating = ref<HTMLDivElement>();
const { floatingStyles } = useFloating(reference, floating, {
placement: props.align === "right" ? "bottom-end" : "bottom-start",
strategy: "fixed",
whileElementsMounted: autoUpdate,
});
function onClick(e: Event) {
if (el.value && floating.value) {
// is it a click outside or a click on an item ?
if (!el.value.contains(e.target as Node) || floating.value.contains(e.target as Node)) {
isOpen.value = false;
}
}
}
function closeDropdown(e: Event) {
if (el.value && !el.value.contains(e.target as Node)) {
// Mouse move listener to close the dropdown when the mouse moves
function onMouseMove() {
if (
el.value &&
!el.value.checkVisibility({
opacityProperty: true,
contentVisibilityAuto: true,
visibilityProperty: true,
})
) {
isOpen.value = false;
}
}
// Intersection observer to close the dropdown when it is not visible
const intersectionObserver = new IntersectionObserver(
([entry]) => {
if (!entry.isIntersecting) {
isOpen.value = false;
}
},
{
root: document.body,
threshold: 0,
}
);
onMounted(() => {
document.addEventListener("click", closeDropdown);
document.addEventListener("click", onClick);
document.addEventListener("mousemove", onMouseMove);
if (el.value) {
intersectionObserver.observe(el.value);
}
});
onBeforeUnmount(() => {
document.removeEventListener("click", closeDropdown);
document.removeEventListener("click", onClick);
document.removeEventListener("mousemove", onMouseMove);
intersectionObserver.disconnect();
});
</script>

Expand All @@ -35,10 +81,12 @@ onBeforeUnmount(() => {
:is-primary="props.isPrimary"
:label="props.label"
:icon="props.icon"
:is-inline="props.isInline"
@click="isOpen = !isOpen"
ref="reference"
/>
<Transition name="fade">
<div class="items" v-if="isOpen">
<div class="items" v-if="isOpen" ref="floating" :style="floatingStyles">
<slot></slot>
</div>
</Transition>
Expand All @@ -51,26 +99,16 @@ onBeforeUnmount(() => {
display: inline-block;
& .items {
position: absolute;
z-index: 1000;
top: 100%;
left: 0;
position: fixed;
z-index: 9999;
display: flex;
overflow: visible;
width: max-content;
flex-direction: column;
padding: var(--spacing-padding-small);
border: solid 1px var(--border-color-normal);
border-radius: var(--border-radius);
background-color: var(--background-color-normal);
box-shadow: 4px 10px 20px var(--background-color-selected);
gap: var(--spacing-padding-small);
}
&.align-right {
& .items {
right: 0;
left: unset;
}
}
}
</style>
Loading

0 comments on commit 274b6f0

Please sign in to comment.