Skip to content

Commit

Permalink
feat: Allow docking floating panels in either sidebar in any order
Browse files Browse the repository at this point in the history
  • Loading branch information
annehaley committed Feb 25, 2025
1 parent f346a2c commit bb00946
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 64 deletions.
15 changes: 12 additions & 3 deletions web/src/components/FloatingPanel.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import { panelArrangement } from "@/store";
import ChartsPanel from "./ChartsPanel.vue";
import AnalyticsPanel from "./AnalyticsPanel.vue";
import DatasetsPanel from "./DatasetsPanel.vue";
import LayersPanel from "./LayersPanel.vue";
import { panelArrangement, availableDatasets } from "@/store";
import { startDrag } from "@/drag";
const props = defineProps<{
Expand All @@ -24,6 +29,7 @@ function getPanelContainerClass() {
function getPanelContainerStyle() {
let styleObj: Record<string, string> = {};
styleObj.order = panel.value?.order.toString() || '0';
if (!panel.value?.position) {
if (panel.value?.height && !panel.value.collapsed) {
styleObj.height = panel.value?.height + "px";
Expand All @@ -36,7 +42,7 @@ function getPanelContainerStyle() {
function getPanelStyle() {
let styleObj: Record<string, string> = {};
if (panel.value?.position) {
styleObj["z-index"] = "10000"; // above vuetify navigation drawer
styleObj["z-index"] = "2"; // above vuetify navigation drawer
styleObj.visibility = "visible"; // prevent hiding when sidebar closes
styleObj.position = "absolute";
styleObj.top = panel.value.position.y + "px";
Expand Down Expand Up @@ -113,7 +119,10 @@ function panelUpdated() {
</div>
<v-card-text class="pa-2">{{ panel.label }}</v-card-text>
<v-card-text v-if="!panel.collapsed" class="pa-2 panel-content">
<slot></slot>
<DatasetsPanel v-if="props.id === 'datasets'" :datasets="availableDatasets"/>
<LayersPanel v-else-if="props.id === 'layers'"/>
<ChartsPanel v-else-if="props.id === 'charts'"/>
<AnalyticsPanel v-else-if="props.id === 'analytics'"/>
<v-icon
v-if="panel.position"
icon="mdi-resize-bottom-right"
Expand Down
32 changes: 13 additions & 19 deletions web/src/components/SideBars.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,11 @@
import { ref, Ref, watch } from "vue";
import { useTheme } from "vuetify/lib/framework.mjs";
import { availableDatasets, currentUser, openSidebars, panelArrangement, theme } from "@/store";
import { currentUser, openSidebars, panelArrangement, theme } from "@/store";
import { logout } from "@/api/auth";
import { FloatingPanelConfig } from "@/types";
import FloatingPanel from "./FloatingPanel.vue";
import ProjectConfig from "./ProjectConfig.vue";
import ChartsPanel from "./ChartsPanel.vue";
import AnalyticsPanel from "./AnalyticsPanel.vue";
import DatasetsPanel from "./DatasetsPanel.vue";
import LayersPanel from "./LayersPanel.vue";
const version = process.env.VUE_APP_VERSION;
const hash = process.env.VUE_APP_HASH;
Expand Down Expand Up @@ -106,12 +101,11 @@ watch(darkMode, () => {
</v-toolbar>
<ProjectConfig />
<div class="panel-set">
<FloatingPanel id="datasets">
<DatasetsPanel :datasets="availableDatasets"/>
</FloatingPanel>
<FloatingPanel id="layers" bottom>
<LayersPanel />
</FloatingPanel>
<FloatingPanel
v-for="panel, index in panelArrangement.filter((p) => p.dock == 'left')"
:id="panel.id"
:bottom="index == panelArrangement.filter((p) => p.dock == 'right').length -1"
/>
</div>
</v-navigation-drawer>

Expand Down Expand Up @@ -191,7 +185,7 @@ watch(darkMode, () => {
></v-icon>
</template>
<v-list
:items="panelArrangement.filter((p) => p.right)"
:items="panelArrangement.filter((p) => p.closeable)"
item-title="label"
item-value="id"
selectable
Expand All @@ -212,12 +206,11 @@ watch(darkMode, () => {
</v-menu>
</div>
<div class="panel-set">
<FloatingPanel id="charts">
<ChartsPanel/>
</FloatingPanel>
<FloatingPanel id="analytics" bottom>
<AnalyticsPanel/>
</FloatingPanel>
<FloatingPanel
v-for="panel, index in panelArrangement.filter((p) => p.dock == 'right')"
:id="panel.id"
:bottom="index == panelArrangement.filter((p) => p.dock == 'right').length -1"
/>
</div>
</v-navigation-drawer>
</div>
Expand All @@ -229,6 +222,7 @@ watch(darkMode, () => {
border-radius: 6px;
width: 350px !important;
max-height: calc(100% - 15px);
z-index: 1 !important;
}
.sidebar > .v-navigation-drawer__content {
height: 100%;
Expand Down
81 changes: 44 additions & 37 deletions web/src/drag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,51 +19,62 @@ export function startDrag(
export function dragPanel(event: MouseEvent) {
let offsetX = -5;
const offsetY = 30;
const snapToleranceX = 200;
const snapToleranceY = 300;
const minHeight = 100;
const minWidth = 150;

const panel = panelArrangement.value.find(
(p) => p.id === draggingPanel.value
);
if (!panel) return undefined;
if (draggingFrom.value) {
const from: { x: number; y: number } = { ...draggingFrom.value };

if (panel.right) offsetX += document.body.clientWidth - 390;
const position = {
x: event.clientX - offsetX - (panel.element?.clientWidth || 0),
y: event.clientY - offsetY,
};
if (dragModes.value?.includes("position")) {
const sidebarOpen = (
(panel.right && openSidebars.value.includes("right")) ||
(!panel.right && openSidebars.value.includes("left"))
)
if (
sidebarOpen &&
panel.initialPosition &&
Math.abs(position.x - panel.initialPosition.x) < snapToleranceX &&
Math.abs(position.y - panel.initialPosition.y) < snapToleranceY
) {
// snap to sidebar
panel.position = undefined;
} else {
if (!panel.initialPosition) {
// convert to floating
panel.width = 300;
panel.height = 200;
panel.initialPosition = position;
if (panel.dock == 'right') offsetX += document.body.clientWidth - 390;
const position = {
x: event.clientX - offsetX - (panel.element?.clientWidth || 0),
y: event.clientY - offsetY,
};
if (dragModes.value?.includes("position")) {
const allowDock = (
Math.abs(from.x - event.clientX) > 10 &&
Math.abs(from.y - event.clientY) > 10
)
if (
allowDock &&
openSidebars.value.includes("left") &&
event.clientX < 350
) {
// dock left
panel.dock = 'left';
panel.position = undefined;
panel.width = undefined;
panel.height = undefined;
// determine order
const currentDocked = panelArrangement.value.filter((p) => p.dock === 'left' && !p.position)
panel.order = Math.ceil(event.clientY / (document.body.clientHeight / currentDocked.length))
} else if (
panel.width && position.x + panel.width < document.body.clientWidth &&
panel.height && position.y + panel.height < document.body.clientHeight
allowDock &&
openSidebars.value.includes("right") &&
event.clientX > document.body.clientWidth - 350
) {
// dock right
panel.dock = 'right';
panel.position = undefined;
panel.width = undefined;
panel.height = undefined;
// determine order
const currentDocked = panelArrangement.value.filter((p) => p.dock === 'right' && !p.position)
panel.order = Math.ceil(event.clientY / (document.body.clientHeight / currentDocked.length))
} else if (!panel.position) {
// float
panel.width = 300;
panel.height = 200;
panel.position = position;
} else {
panel.position = position;
}
}
}
if (draggingFrom.value) {
const from: { x: number; y: number } = { ...draggingFrom.value };
if (dragModes.value?.includes("height") && draggingFrom.value) {
if (dragModes.value?.includes("height")) {
if(!panel.height) {
panel.height = panel.element?.clientHeight
}
Expand All @@ -75,11 +86,7 @@ export function dragPanel(event: MouseEvent) {
}
}
}
if (
dragModes.value?.includes("width") &&
draggingFrom.value &&
panel.width
) {
if (dragModes.value?.includes("width") && panel.width) {
const widthDelta = event.clientX - draggingFrom.value.x;
if (panel.width + widthDelta > minWidth) {
panel.width = panel.width + widthDelta;
Expand Down
16 changes: 13 additions & 3 deletions web/src/storeFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,26 +43,36 @@ export function clearState() {
currentError.value = undefined;
polls.value = {};
panelArrangement.value = [
{ id: "datasets", label: "Datasets", visible: true, closeable: false },
{ id: "datasets",
label: "Datasets",
visible: true,
closeable: false,
dock: 'left',
order: 1
},
{
id: "layers",
label: "Selected Layers",
visible: true,
closeable: false,
dock: 'left',
order: 2,
},
{
id: "charts",
label: "Charts",
visible: true,
closeable: true,
right: true,
dock: 'right',
order: 1,
},
{
id: "analytics",
label: "Analytics",
visible: true,
closeable: true,
right: true,
dock: 'right',
order: 2,
},
];
draggingPanel.value = undefined;
Expand Down
4 changes: 2 additions & 2 deletions web/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,8 @@ export interface FloatingPanelConfig {
visible: boolean;
closeable: boolean;
collapsed?: boolean;
right?: boolean;
initialPosition?: { x: number; y: number } | undefined;
dock: 'left' | 'right';
order: number;
position?: { x: number; y: number } | undefined;
width?: number | undefined;
height?: number | undefined;
Expand Down

0 comments on commit bb00946

Please sign in to comment.