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
145 changes: 145 additions & 0 deletions examples/embedded_tab_title.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Configurable tab title</title>
<link rel="stylesheet" href="../dist-lib/style.css" />
<style>
html,
body {
width: 100%;
height: 100%;
margin: 0;
}

body {
display: flex;
flex-direction: column;
font-family: system-ui, sans-serif;
}

#controls {
flex: 0 0 auto;
display: flex;
align-items: center;
gap: 10px;
padding: 10px 16px;
background: #1e1e1e;
color: #eee;
}

#controls input {
padding: 6px 8px;
border-radius: 6px;
border: none;
min-width: 220px;
}

#controls button {
padding: 6px 12px;
border-radius: 6px;
border: none;
cursor: pointer;
}

#viewer {
flex: 1;
min-height: 0;
position: relative;
}
</style>
<script type="importmap">
{
"imports": {
"vue": "../node_modules/vue/dist/vue.esm-browser.js",
"three": "../node_modules/three/build/three.module.js",
"three/": "../node_modules/three/",
"@gramaziokohler/compas-pb-ts": "../node_modules/@gramaziokohler/compas-pb-ts/dist/index.js",
"@bufbuild/protobuf/wire": "../node_modules/@bufbuild/protobuf/dist/esm/wire/index.js"
}
}
</script>
</head>

<body>
<!--
This bar is just this demo page's own controls, outside the viewer container -
it's not part of the library. It lets you retype the title to see the effect
live; in a real app you'd normally pass `title` once, up front.
-->
<div id="controls">
<label for="titleInput">Title</label>
<input id="titleInput" type="text" value="My Custom Viewer" />
<button id="applyButton">Apply</button>
<span style="opacity: 0.7">
Sets both the browser tab title and the toolbar heading (normally
"COMPAS ThreeJs").
</span>
</div>

<div id="viewer" aria-label="3D viewer"></div>

<script type="module">
import { createViewer } from "../dist-lib/index.js";
import { Box, pbDumpBytes } from "@gramaziokohler/compas-pb-ts";

const container = document.querySelector("#viewer");
if (!(container instanceof HTMLElement)) {
throw new Error("Viewer container was not found");
}

const titleInput = document.querySelector("#titleInput");
const applyButton = document.querySelector("#applyButton");

let viewer = null;

function mountViewer() {
viewer?.dispose();

// `title` is a creation-time option - like toolbarPlacement, it's read once
// when the viewer mounts, so applying a new value here recreates the viewer
// rather than updating it live. It sets `document.title` and, when the
// toolbar is shown, replaces its hardcoded "COMPAS ThreeJs" heading.
viewer = createViewer(container, {
mode: "embedded",
defaultLighting: true,
showToolbar: true,
title: titleInput.value || undefined,
onError(error) {
console.error(error.code, error.message, error.details);
},
});

viewer.dispatch(
pbDumpBytes(
new Box({
data: {
guid: crypto.randomUUID(),
name: "Box",
frame: {
point: { x: 0, y: 0, z: 0 },
xaxis: { x: 1, y: 0, z: 0 },
yaxis: { x: 0, y: 1, z: 0 },
},
xsize: 3,
ysize: 3,
zsize: 1,
},
}),
),
);
}

applyButton.addEventListener("click", mountViewer);
titleInput.addEventListener("keydown", (event) => {
if (event.key === "Enter") mountViewer();
});

mountViewer();
window.addEventListener("pagehide", () => viewer?.dispose(), {
once: true,
});
</script>
</body>
</html>
4 changes: 3 additions & 1 deletion src/components/layout/Toolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
id="toolbar"
>
<h1 class="text-lg font-bold" :class="{ dark: theme.value === 'dark' }">
COMPAS ThreeJs
{{ toolbarTitle }}
</h1>
<TransformGroup />
<AddObjectGroup />
Expand All @@ -29,6 +29,7 @@ import DisplayGroup from "@/components/tools/display/DisplayGroup.vue";
import {
isHorizontalPlacement,
useToolbarPlacement,
useToolbarTitle,
useViewerRuntime,
} from "@/viewer/viewer_context";
import { useHover } from "@/composables/useHover";
Expand All @@ -43,6 +44,7 @@ const { isHovered } = useHover(toolbarElement);
const { theme, blockPicker } = useViewerRuntime().store;
const placement = useToolbarPlacement();
const isHorizontal = isHorizontalPlacement(placement);
const toolbarTitle = useToolbarTitle();

watchEffect(() => {
blockPicker.value = isHovered.value;
Expand Down
8 changes: 8 additions & 0 deletions src/library/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
objectActionsPlacementKey,
openbarPlacementKey,
toolbarPlacementKey,
toolbarTitleKey,
useViewerRuntime,
viewerRuntimeKey,
} from "../viewer/viewer_context";
Expand Down Expand Up @@ -48,6 +49,10 @@ export function createViewer(
);
}

if (options.title) {
document.title = options.title;
}

const runtime = markRaw(new ViewerRuntime(container, options));
const app = createApp(App, {
runtime,
Expand All @@ -58,6 +63,9 @@ export function createViewer(
app.provide(toolbarPlacementKey, options.toolbarPlacement ?? "left");
app.provide(openbarPlacementKey, options.openbarPlacement ?? "left");
app.provide(metadataPlacementKey, options.metadataPlacement ?? "right");
if (options.title) {
app.provide(toolbarTitleKey, options.title);
}
app.provide(
objectActionsPlacementKey,
options.objectActionsPlacement ?? "right",
Expand Down
2 changes: 2 additions & 0 deletions src/library/public.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export interface CompasViewerOptions {
websocket?: ViewerWebSocketOptions;
defaultLighting?: boolean;
showToolbar?: boolean;
/** Browser tab title to apply for this viewer instance. Leaves `document.title` untouched if omitted. */
title?: string;
/**
* Extra toolbar modules mounted after the built-in groups - e.g. a component
* installed from an npm package. Each module owns its own icons/behavior/ids the
Expand Down
2 changes: 2 additions & 0 deletions src/library/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export interface CompasViewerOptions {
websocket?: ViewerWebSocketOptions;
defaultLighting?: boolean;
showToolbar?: boolean;
/** Browser tab title to apply for this viewer instance. Leaves `document.title` untouched if omitted. */
title?: string;
/**
* Extra toolbar modules to mount after the built-in groups - e.g. a `.vue` group
* installed from an npm package. Each module owns its own icons/behavior/ids the
Expand Down
4 changes: 4 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { createViewer } from "./library";
const container = document.querySelector<HTMLElement>("#app");
if (!container) throw new Error("Standalone viewer requires an #app container");

const title =
new URLSearchParams(window.location.search).get("title") ?? undefined;

createViewer(container, {
mode: "websocket",
showToolbar: true,
title,
});
8 changes: 8 additions & 0 deletions src/viewer/viewer_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ export const viewerRuntimeKey: InjectionKey<ViewerRuntime> = Symbol(
"compas-viewer-runtime",
);

export const toolbarTitleKey: InjectionKey<string> = Symbol(
"compas-viewer-toolbar-title",
);

export function useViewerRuntime(): ViewerRuntime {
const runtime = inject(viewerRuntimeKey);
if (!runtime) {
Expand All @@ -15,6 +19,10 @@ export function useViewerRuntime(): ViewerRuntime {
return runtime;
}

export function useToolbarTitle(): string {
return inject(toolbarTitleKey, "COMPAS ThreeJs");
}

/**
* Which edge of the viewer a panel docks to. Panels sharing an edge stack along it, in
* a fixed priority order (toolbar, openbar, metadata, objectActions) - see App.vue.
Expand Down
Loading