Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
<Sidebar :show-toolbar="props.showToolbar" />
<div ref="threeContainer" class="three-container"></div>
<ThemeIndicator />
<ObjectInfo />
<RightSidebar />
</div>
</template>

<script setup lang="ts">
import { onMounted, ref } from "vue";
import ObjectInfo from "./components/layout/ObjectInfo.vue";
import RightSidebar from "./components/layout/RightSidebar.vue";
import Sidebar from "@/components/layout/Sidebar.vue";
import ThemeIndicator from "@/components/layout/ThemeIndicator.vue";
import type { ViewerRuntime } from "@/viewer/viewer_runtime";
Expand Down
126 changes: 126 additions & 0 deletions src/components/layout/ObjectActions.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<template>
<div
class="theme object-actions"
:class="{ 'is-empty': !objectActionsState.length }"
id="actions-panel"
ref="actionsPanel"
>
<h1
class="text-lg font-bold section-title"
:class="{ dark: theme.value === 'dark' }"
>
FUNCTIONS
</h1>
<div
v-for="action in objectActionsState"
:key="action.guid"
class="single_data"
>
<Button
v-if="action.type === 'button'"
variant="outline"
@click="handleObjectAction(action)"
class="w-full"
>
{{ action.text }}
</Button>

<Select
v-else-if="action.type === 'select'"
:model-value="
typeof action.defaultValue === 'string' ? action.defaultValue : ''
"
@update:model-value="
(value) => {
action.defaultValue = typeof value === 'string' ? value : '';
handleObjectAction(action, value);
}
"
>
<SelectTrigger class="w-full">
<SelectValue
:placeholder="action.placeholder ?? 'Select an option'"
/>
</SelectTrigger>
<SelectContent class="z-[4000]">
<SelectItem
v-for="option in action.options"
:key="option"
:value="option"
>
{{ option }}
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</template>

<script setup lang="ts">
import { Button } from "@/components/ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useHover } from "@/composables/useHover";
import { ref, watchEffect } from "vue";
import { useViewerRuntime } from "@/viewer/viewer_context";
import type { ObjectAction } from "@/viewer/viewer_store";

const runtime = useViewerRuntime();
const { objectActionsState, blockPicker, theme } = runtime.store;
const handleObjectAction = (action: ObjectAction, value?: unknown) =>
runtime.handleObjectAction({ ...action }, value);
const actionsPanel = ref<HTMLElement | null>(null);

const { isHovered } = useHover(actionsPanel);

watchEffect(() => {
blockPicker.value = isHovered.value;
});
</script>

<style scoped>
div.object-actions {
z-index: 1000; /* Ensure it appears above other content */
padding: 20px;
max-width: 400px;
border-radius: 10px;
margin: 0px;
flex: 0 1 auto; /* Size to content within RightSidebar's column, but shrink if it doesn't fit */
max-height: 100%;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 30px;
color: var(--foreground);
pointer-events: auto;
}

div.object-actions.is-empty {
/* Stay mounted (rather than v-if) so ordinary content refreshes — e.g.
reselecting a different object — don't trigger RightSidebar's
TransitionGroup enter/leave transition, which is reserved for the
deliberate ObjectInfo open/close toggle. */
display: none;
}

h1.section-title {
margin-bottom: 10px;
color: var(--foreground);
background: color-mix(in oklab, var(--background) 25%, transparent);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
padding: 5px;
padding-left: 10px;
border-radius: 10px;
box-shadow:
1px 1px 3px 0px color-mix(in oklab, var(--foreground) 35%, transparent)
inset,
-1px -1px 3px 0px color-mix(in oklab, var(--background) 70%, transparent)
inset;
}
</style>
158 changes: 18 additions & 140 deletions src/components/layout/ObjectInfo.vue
Original file line number Diff line number Diff line change
@@ -1,175 +1,69 @@
<template>
<div class="right-bar">
<div
class="theme object-info"
:class="{ 'is-hidden': !objectBarData.isVisible }"
id="info-panel"
ref="infoPanel"
>
<div id="data-container">
<div class="metadata item">
<h1
class="text-lg font-bold section-title"
:class="{ dark: theme.value === 'dark' }"
>
METADATA
</h1>
<div
v-for="(value, key) in objectBarData.data"
:key="key"
class="data-entry"
>
<p>
<strong> {{ key }}:</strong> {{ value }}
</p>
</div>
</div>
</div>

<div class="data-container">
<div class="theme object-info" id="info-panel" ref="infoPanel">
<div id="data-container">
<div class="metadata item">
<h1
class="text-lg font-bold section-title"
:class="{ dark: theme.value === 'dark' }"
>
FUNCTIONS
METADATA
Comment thread
ericgozzi marked this conversation as resolved.
</h1>
<div
v-for="action in objectActionsState"
:key="action.guid"
class="single_data"
v-for="(value, key) in objectBarData.data"
:key="key"
class="data-entry"
>
<Button
v-if="action.type === 'button'"
variant="outline"
@click="handleObjectAction(action)"
class="w-full"
>
{{ action.text }}
</Button>

<Select
v-else-if="action.type === 'select'"
:model-value="
typeof action.defaultValue === 'string' ? action.defaultValue : ''
"
@update:model-value="
(value) => {
action.defaultValue = typeof value === 'string' ? value : '';
handleObjectAction(action, value);
}
"
>
<SelectTrigger class="w-full">
<SelectValue
:placeholder="action.placeholder ?? 'Select an option'"
/>
</SelectTrigger>
<SelectContent class="z-[4000]">
<SelectItem
v-for="option in action.options"
:key="option"
:value="option"
>
{{ option }}
</SelectItem>
</SelectContent>
</Select>
<p>
<strong> {{ key }}:</strong> {{ value }}
</p>
</div>
</div>

<Button
variant="secondary"
size="icon"
id="closeObjectBar"
@click="toggleObjectBar()"
>
<ArrowBigRightDash />
</Button>
</div>

<Button
variant="secondary"
size="icon"
id="openObjectBar"
:class="{ 'is-hidden': !objectBarData.isVisible }"
@click="toggleObjectBar()"
id="closeObjectBar"
@click="objectBarData.isVisible = false"
>
<ArrowBigLeftDash />
<ArrowBigRightDash />
</Button>
</div>
</template>

<script setup lang="ts">
import { Button } from "@/components/ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useHover } from "@/composables/useHover";
import { ref, watchEffect } from "vue";
import { ArrowBigLeftDash, ArrowBigRightDash } from "lucide-vue-next";
import { ArrowBigRightDash } from "lucide-vue-next";
import { useViewerRuntime } from "@/viewer/viewer_context";
import type { ObjectAction } from "@/viewer/viewer_store";

const runtime = useViewerRuntime();
const { objectActionsState, objectBarData, blockPicker, theme } = runtime.store;
const handleObjectAction = (action: ObjectAction, value?: unknown) =>
runtime.handleObjectAction({ ...action }, value);
const { objectBarData, blockPicker, theme } = runtime.store;
const infoPanel = ref<HTMLElement | null>(null);

const { isHovered } = useHover(infoPanel);

watchEffect(() => {
blockPicker.value = isHovered.value && objectBarData.isVisible;
blockPicker.value = isHovered.value;
});

const toggleObjectBar = () => {
objectBarData.isVisible = !objectBarData.isVisible;
};
</script>

<style scoped>
div.right-bar {
position: absolute;
top: 0;
right: 0;
height: 100%;
display: flex;
flex-direction: column;
padding: 20px; /* Padding around the content */

/* WIDTH */
width: 30vw;
max-width: 300px;
min-width: 250px;
pointer-events: none;
}

/* 'scoped' means these styles only apply to this module */
div.object-info {
z-index: 1000; /* Ensure it appears above other content */
padding: 20px;
max-width: 400px;
border-radius: 10px;
height: 100%;
max-height: 100%;
margin: 0px;
right: 0%;
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
flex: 0 1 auto; /* Size to content within RightSidebar's column, but shrink if it doesn't fit */
display: flex;
flex-direction: column;
color: var(--foreground);
pointer-events: auto;
}

div.is-hidden {
transform: translateX(+150%);
/*display: none;*/
pointer-events: none;
}

div#data-container {
position: relative;
display: flex;
Expand Down Expand Up @@ -210,20 +104,4 @@ Button#closeObjectBar {
align-self: flex-end;
margin-top: auto;
}

Button#openObjectBar {
position: absolute;
bottom: 40px;
right: 40px;
z-index: 1;
display: flex;
visibility: hidden;
transition: visibility 1s;
pointer-events: auto;
}

Button#openObjectBar.is-hidden {
opacity: 1;
visibility: visible;
}
</style>
Loading
Loading