Skip to content

Source Toolbar

Source Toolbar

Cette page est générée automatiquement à partir du dépôt local au moment de la génération de la documentation.

Fichiers inclus

packages/common/src/lib/widgets/toolbar/toolbar.declaration.ts

packages/common/src/lib/widgets/toolbar/toolbar.declaration.ts
import { widgetFactorySvelte, type WidgetProps } from '$lib/api/managers/widget';
import { type ToolbarConfig, toolbarConfigSchema } from './toolbar.config';
import type { WidgetDeclaration } from '$lib/api/managers/widget/widget-declaration';
export const declaration = {
factory: () => import('./Toolbar.svelte').then((Toolbar) => widgetFactorySvelte(Toolbar)),
schema: () => toolbarConfigSchema,
} satisfies WidgetDeclaration;
export type ToolbarProps = WidgetProps<ToolbarConfig>;

packages/common/src/lib/widgets/toolbar/toolbar.config.ts

packages/common/src/lib/widgets/toolbar/toolbar.config.ts
import { defineWidgetConfig } from '$lib/api/managers/configuration/models/widget/widget-configuration.schema';
import {
DEFAULT_TOOLBAR_ID,
inToolbarSchemaFrom,
} from '$lib/api/managers/configuration/models/widget/widget-in-toolbar.schema';
import { mapIconSchema } from '$lib/components/icon/map-icon';
import { z } from 'zod';
import { styleRecordSchema } from '$lib/api/utils/style/style.schema';
import { i18nSchemaFrom } from '$lib/api/managers/i18n';
import { toolbarI18n } from '$lib/widgets/toolbar/toolbar.i18n';
export const sideEnumSchema = z.enum([
'top',
// 'top-start',
// 'top-end',
'bottom',
// 'bottom-start',
// 'bottom-end',
'left',
// 'left-start',
// 'left-end',
'right',
// 'right-start',
// 'right-end',
]);
export const toolbarConfigSchema = defineWidgetConfig({
active: true,
inToolbar: inToolbarSchemaFrom(false),
i18n: i18nSchemaFrom(toolbarI18n),
config: z
.object({
elements: z.array(z.string()).optional(),
iconStyle: mapIconSchema.optional(),
display: z.enum(['full', 'light']).default('light'),
orientation: z.enum(['right', 'left', 'up', 'down']).default('right'),
tooltipSide: sideEnumSchema.default('bottom'),
toolbarId: z.string().default(DEFAULT_TOOLBAR_ID),
style: styleRecordSchema.optional(),
})
.prefault({}),
});
export type ToolbarConfig = z.infer<typeof toolbarConfigSchema>;
export type ToolbarDisplay = ToolbarConfig['config']['display'];
export type ToolbarI18n = ToolbarConfig['i18n'];

packages/common/src/lib/widgets/toolbar/toolbar.i18n.ts

packages/common/src/lib/widgets/toolbar/toolbar.i18n.ts
import type { I18nRegistry } from '$lib/api/managers/i18n';
export const toolbarI18n = {} satisfies I18nRegistry;

packages/common/src/lib/widgets/toolbar/Toolbar.svelte

packages/common/src/lib/widgets/toolbar/Toolbar.svelte
<script lang="ts">
import type { WidgetConfig } from '$lib/api/managers/configuration';
import { getConfigurationManager } from '$lib/api/managers/configuration/configuration.manager.svelte';
import { getWidgetManager } from '$lib/api/managers/widget/widget.manager.svelte';
import type { ContainerConfig, ContainerWidgetConfig } from '$lib/components/containers';
import { ToolbarTabs } from '$lib/components/containers/toolbar-tabs';
import type { ToolbarTabsConfig } from '$lib/components/containers/toolbar-tabs/toolbar-tabs.schema';
import { cn } from '$lib/components/shadcn/utils';
import ToolbarItem from './ToolbarItem.svelte';
import { recordToCssStyle } from '$lib/api/utils/style/style.utils';
import type { ToolbarProps } from './toolbar.declaration';
const { fullConfig }: ToolbarProps = $props();
type ToolbarItem = WidgetConfig | ToolbarTabsConfig;
const config = $derived(fullConfig.config);
const configurationManager = getConfigurationManager();
const widgetManager = getWidgetManager();
const sections = $derived.by(() => {
return config.elements && config.elements.length > 0
? getWidgetsFromIds(config.elements)
: getWidgetsFromThereConfigs();
});
function getWidgetsFromThereConfigs(): ToolbarItem[][] {
const inToolbar = configurationManager.widgetsConfiguration!.filter(
(w) => w.inToolbar && w.inToolbar.toolbarId === config.toolbarId,
) as WidgetConfig[];
const toolbarTabs = configurationManager.viewerConfiguration!.containers.filter(
(c) => isToolbarTabConfig(c) && c.toolbarId === config.toolbarId,
) as ToolbarTabsConfig[];
return [
[...inToolbar, ...toolbarTabs].sort((a, b) => {
const aOrder = getOrder(a);
const bOrder = getOrder(b);
return aOrder - bOrder;
}),
];
}
function getWidgetsFromIds(ids: string[]) {
const sections = [] as ToolbarItem[][];
let current = [] as ToolbarItem[];
sections.push(current);
for (const id of ids) {
if (id === '---') {
current = [];
sections.push(current);
continue;
}
const item =
(configurationManager.viewerConfiguration!.containers.find(
(c) => isToolbarTabConfig(c) && c.id === id,
) as ToolbarTabsConfig) ??
configurationManager.getWidgetConfigById(id) ??
configurationManager.getWidgetConfigByClass(id);
if (!item) {
console.warn('No widgets found for id', id);
continue;
}
current.push(item);
}
return sections;
}
function getOrder(item: ToolbarItem): number {
if (isToolbarTabs(item)) {
return item.order ?? 0;
} else {
return item.inToolbar?.order ?? 0;
}
}
function getKey(item: ToolbarItem): string {
if (isToolbarTabs(item)) {
return `toolbar-tab-${item.id}`;
} else {
return item.widgetId;
}
}
function isToolbarTabs(item: ToolbarItem): item is ToolbarTabsConfig {
return 'type' in item && item.type === 'toolbar-tabs';
}
function isToolbarTabConfig(
container: ContainerConfig | ContainerWidgetConfig | string,
): container is ToolbarTabsConfig {
return !!container && typeof container === 'object' && 'type' in container && container.type === 'toolbar-tabs';
}
async function toggleWidget(widgetId: string) {
const reference = widgetManager.getReference(widgetId);
reference.toggle({ openContainer: true });
}
const elementsPaddingClass = $derived(
cn({
'not-first:gv-px-2xs': config.orientation === 'left' || config.orientation === 'right',
'not-first:gv-py-2xs': config.orientation === 'up' || config.orientation === 'down',
}),
);
</script>
{#if sections.length > 0}
<div
class={cn('gv-flex gv-gap-md', {
'gv-flex-row-reverse': config.orientation === 'left',
'gv-flex-row': config.orientation === 'right',
'gv-flex-col-reverse': config.orientation === 'up',
'gv-flex-col': config.orientation === 'down',
})}
>
{#each sections as section}
<div
class={cn(
'toolbar gv-bg-background gv-flex gv-p-2xs gv-rounded-full gv-overflow-hidden gv-text-primary gv-border-background',
{
'gv-flex-row-reverse gv-divide-x gv-divide-x-reverse': config.orientation === 'left',
'gv-flex-row gv-divide-x': config.orientation === 'right',
'gv-flex-col-reverse gv-divide-y gv-divide-y-reverse': config.orientation === 'up',
'gv-flex-col gv-divide-y ': config.orientation === 'down',
},
)}
style={recordToCssStyle(fullConfig.config.style)}
>
{#each section as widget (getKey(widget))}
{#if isToolbarTabs(widget)}
<ToolbarTabs config={widget} class={elementsPaddingClass} />
{:else}
<ToolbarItem
widgetConfig={widget}
i18nConfig={fullConfig.i18n}
display={config.display}
tooltipSide={config.tooltipSide}
onclick={() => toggleWidget(widget.widgetId)}
style={recordToCssStyle(widget.inToolbar?.style)}
class={elementsPaddingClass}
/>
{/if}
{/each}
</div>
{/each}
</div>
{/if}
<style>
.toolbar {
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.25);
}
</style>

packages/common/src/lib/widgets/toolbar/ToolbarItem.svelte

packages/common/src/lib/widgets/toolbar/ToolbarItem.svelte
<script lang="ts">
import type { WidgetConfig } from '$lib/api/managers/configuration';
import { isActiveIcon } from '$lib/api/managers/configuration/models/widget/widget-in-toolbar.schema';
import { getI18n } from '$lib/api/managers/i18n';
import { getWidgetManager } from '$lib/api/managers/widget';
import { type TooltipSide } from '$lib/components/api-simple-tooltip';
import WidgetContainer from '$lib/components/containers/WidgetContainer.svelte';
import Icon from '$lib/components/icon/Icon.svelte';
import { mapIconButtonVariants, type MapIconSize, type MapIconVariant } from '$lib/components/icon/map-icon';
import { cn } from '$lib/components/shadcn/utils';
import type { ToolbarDisplay, ToolbarI18n } from './toolbar.config';
import type { HTMLAttributes } from 'svelte/elements';
import ToolbarTooltip from '$lib/widgets/toolbar/ToolbarTooltip.svelte';
interface Props extends HTMLAttributes<HTMLButtonElement> {
widgetConfig: WidgetConfig;
i18nConfig: ToolbarI18n;
display: ToolbarDisplay;
variant?: MapIconVariant;
tooltipSide: TooltipSide;
size?: MapIconSize;
onclick?: (e: MouseEvent) => void;
class?: string;
}
let {
widgetConfig,
i18nConfig,
display,
variant = 'default',
size = 'default',
tooltipSide,
onclick,
class: className,
...restProps
}: Props = $props();
const widgetManager = getWidgetManager();
const i18n = $derived(getI18n(i18nConfig));
const toolbarConfig = $derived(widgetConfig.inToolbar!);
const title = $derived(
toolbarConfig.type === 'button' ? i18n.translate(toolbarConfig.label ?? widgetConfig.title) : undefined,
);
// FIXME: remove this when WOM features are fully released (xd)
const tooltipText = $derived(
toolbarConfig.type === 'button' && toolbarConfig.disabled
? i18n('common.incoming-tool', { tool: title })
: title,
);
const icon = $derived(toolbarConfig.type === 'button' ? (toolbarConfig.icon ?? widgetConfig.icon) : undefined);
const reference = $derived(widgetManager.getReference(widgetConfig.widgetId));
</script>
{#if toolbarConfig.type === 'button'}
<ToolbarTooltip text={tooltipText} side={tooltipSide} class={className}>
<button
{onclick}
disabled={toolbarConfig.disabled}
data-test-id="ToolbarItem-{widgetConfig.widgetId}"
class={cn(mapIconButtonVariants({ variant, size }))}
data-state={reference.active ? 'active' : ''}
{...restProps}
>
{#if display === 'full'}
<span class="gv-text-[11px]">{title}</span>
{/if}
{#if isActiveIcon(icon)}
<Icon icon={reference.active ? icon.active : icon.inactive} />
{:else}
<Icon {icon} />
{/if}
</button>
</ToolbarTooltip>
{:else if toolbarConfig.type === 'integrated'}
{#if reference.active}
<WidgetContainer {reference} class={className} />
{/if}
{/if}

packages/common/src/lib/widgets/toolbar/ToolbarTooltip.svelte

packages/common/src/lib/widgets/toolbar/ToolbarTooltip.svelte
<script lang="ts">
import type { PropsWithChildren } from '$lib/api/utils/index.ts';
import { type ContentProps, Tooltip, TooltipContent, TooltipTrigger } from '$lib/components/shadcn/ui/tooltip';
import { cn } from '$lib/components/shadcn/utils';
interface Props {
text: string | null | undefined;
side: ContentProps['side'];
class?: string;
}
let { text, side, class: className, children }: PropsWithChildren<Props> = $props();
</script>
<Tooltip openDelay={0}>
<TooltipTrigger class={cn('gv-size-fit', className)}>
{@render children?.()}
</TooltipTrigger>
{#if text}
<TooltipContent class="gv-text-base gv-px-lb gv-py-[10px] gv-font-bold gv-rounded-5xl" {side} sideOffset={15}>
<p>{text}</p>
</TooltipContent>
{/if}
</Tooltip>

Aller plus loin