Source Share
Source Share
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/export/share/share.declaration.tspackages/common/src/lib/widgets/export/share/share.config.tspackages/common/src/lib/widgets/export/share/CopyText.sveltepackages/common/src/lib/widgets/export/share/share.models.tspackages/common/src/lib/widgets/export/share/Share.svelte
packages/common/src/lib/widgets/export/share/share.declaration.ts
import { widgetFactorySvelte, type WidgetProps } from '$lib/api/managers/widget';import { type ShareConfig, shareConfigSchema } from './share.config';import type { WidgetDeclaration } from '$lib/api/managers/widget/widget-declaration';
export const declaration = { factory: () => import('./Share.svelte').then((Share) => widgetFactorySvelte(Share)), schema: () => shareConfigSchema,} satisfies WidgetDeclaration;
export type ShareProps = WidgetProps<ShareConfig>;packages/common/src/lib/widgets/export/share/share.config.ts
import { defineWidgetConfig, inToolbarSchemaFrom } from '$lib/api/managers/configuration';import { z } from 'zod';import { i18nDataSchema, type I18nRegistry, i18nSchemaFrom } from '$lib/api/managers/i18n';import { iconSchema } from '$lib/api/icons';
export const shareTranslations = { 'embedded-copied': { fr: 'Le code a été copié dans le presse papier', nl: 'NL - Le code a été copié dans le presse papier', }, 'embedded-height': { fr: 'Hauteur', nl: 'NL - Hauteur', }, 'embedded-width': { fr: 'Largeur', nl: 'NL - Largeur', }, 'integrate-map': { fr: 'Intégrer la carte', nl: 'NL - Intégrer la carte', }, 'link-copied': { fr: 'Le lien a été copié dans le presse papier', nl: 'NL - Le lien a été copié dans le presse papier', }, 'link-copy-error': { fr: 'Une erreur est survenue lors de la copie du lien', nl: 'NL - Une erreur est survenue lors de la copie du lien', },} satisfies I18nRegistry;
export const shareOptionSchema = z.object({ icon: iconSchema, label: i18nDataSchema, type: z.enum(['facebook', 'mail', 'x', 'linkedin', 'whatsapp', 'embedded', 'clipboard']),});export type ShareOption = z.infer<typeof shareOptionSchema>;
const defaultShareOptions: ShareOption[] = [ { type: 'clipboard', icon: { lucide: 'Copy' }, label: { fr: 'Presse-Papier', nl: 'NL - Presse-Papier', }, }, { type: 'embedded', icon: { lucide: 'CodeXml' }, label: { fr: 'Intégré', nl: 'NL - Intégré', }, }, { type: 'mail', icon: { geoviewer: 'mail' }, label: { fr: 'Mail', nl: 'NL - Mail', }, }, { type: 'facebook', icon: { geoviewer: 'facebook' }, label: { fr: 'Facebook', nl: 'NL - Facebook', }, }, { type: 'linkedin', icon: { geoviewer: 'linkedin' }, label: { fr: 'LinkedIn', nl: 'NL - LinkedIn', }, }, { type: 'whatsapp', icon: { geoviewer: 'whatsapp' }, label: { fr: 'WhatsApp', nl: 'NL - WhatsApp', }, },];
export const shareConfigSchema = defineWidgetConfig({ title: { fr: 'Partager', nl: 'NL - Partager', }, i18n: i18nSchemaFrom(shareTranslations), icon: { lucide: 'Share2', }, inToolbar: inToolbarSchemaFrom(false), active: false, config: z .object({ shareOptions: z.array(shareOptionSchema).default(defaultShareOptions), iframeBaseUrl: z.string().default('https://geoportail.wallonie.be/files/GeoViewer/WOMLIGHT-5/index.html'), }) .prefault({}),});
export type ShareConfig = z.infer<typeof shareConfigSchema>;packages/common/src/lib/widgets/export/share/CopyText.svelte
<script lang="ts"> import { Button } from '$lib/components/shadcn/ui/button'; import { getI18n } from '$lib/api/managers/i18n';
interface Props { onclick?: () => void; text: string; }
let { onclick, text }: Props = $props();
const i18n = getI18n();</script>
<div class="gv-flex gv-gap-2xs gv-items-center group gv-border gv-border-input gv-rounded-xs gv-px-md"> <span class="gv-flex-1 gv-text-grey-600 gv-truncate gv-border-none gv-select-none"> {text} </span> <Button {onclick} variant="link"> {i18n('common.copy')} </Button></div>packages/common/src/lib/widgets/export/share/share.models.ts
import type { ShareOption } from '$lib/widgets/export/share/share.config';
export function buildIntegrationLink(shareLink: ShareOption, url: string): string { switch (shareLink.type) { case 'mail': { const urlParams = new URLSearchParams({ body: url, }); return `mailto:?${urlParams}`; } case 'facebook': { const urlParams = new URLSearchParams({ u: url, }); return `https://www.facebook.com/sharer/sharer.php?${urlParams}`; } case 'linkedin': { const urlParams = new URLSearchParams({ url: url, mini: 'true', }); return `https://www.linkedin.com/shareArticle?${urlParams}`; } case 'whatsapp': { const urlParams = new URLSearchParams({ text: url, }); return `https://wa.me/?${urlParams}`; } default: return url; }}
export function buildEmbeddedHtml(url: string, width: number, height: number) { return `<iframe src="${url}" width="${width}" height="${height}"></iframe>`;}
export function buildShareUrl(baseUrl: string, token: string) { return `${baseUrl}#SHARE=${token}`;}packages/common/src/lib/widgets/export/share/Share.svelte
<script lang="ts"> import { Button } from '$lib/components/shadcn/ui/button'; import { getI18n } from '$lib/api/managers/i18n'; import { getMapManager } from '$lib/api/map'; import type { ShareProps } from '$lib/widgets/export/share/share.declaration'; import { Icon } from '$lib/components/icon'; import { Input } from '$lib/components/shadcn/ui/input'; import { Label } from '$lib/components/shadcn/ui/label'; import { mutationState } from '$lib/api/utils'; import Loader from '$lib/components/common/Loader.svelte'; import { copyToClipboard } from '$lib/api/utils/clipboard.utils'; import { showToast } from '$lib/components/toast/toast.utils'; import type { ShareOption } from '$lib/widgets/export/share/share.config'; import { buildEmbeddedHtml, buildIntegrationLink, buildShareUrl } from '$lib/widgets/export/share/share.models'; import { ShareService } from '$lib/api/services/share/share.service'; import { getViewerService } from '$lib/api/viewer.service'; import { cn } from '$lib/components/shadcn/utils'; import type { ApiExtent } from '$lib/api/domain'; import CopyText from './CopyText.svelte';
let { fullConfig }: ShareProps = $props();
const { config } = fullConfig; const i18n = getI18n(fullConfig.i18n); const mapManager = getMapManager(); const viewerService = getViewerService(); const shareService = new ShareService(viewerService.info);
const generateShareToken = mutationState({ mutateFn: () => shareService.createShare(mapManager), });
let selected = $derived(config.shareOptions[0]);
let isEmbedded = $derived(selected?.type === 'embedded'); let embeddedWidth = $state(500); let embeddedHeight = $state(500); const embeddedPreviewText = $derived( buildEmbeddedHtml( buildShareUrl(config.iframeBaseUrl, generateShareToken.value ?? ''), embeddedWidth, embeddedHeight, ), ); const previewText = $derived(buildShareUrl(getBaseUrl(), generateShareToken.value ?? ''));
// Reactive extent signal, debounced by 500 let extent = $state<ApiExtent | null>(null); $effect(() => { let id: any; return mapManager.tools.events.watch('EXTENT', (v) => { if (id) { clearTimeout(id); } id = setTimeout(() => { extent = v; }, 500); }); });
// Create a new token on layer list, basemap or extent change $effect(() => { mapManager.layerList.list.length; mapManager.basemapList.selected; extent; generateShareToken.clear(); });
function getBaseUrl(): string { let currentWindow = window; while (currentWindow !== currentWindow.parent) { currentWindow = currentWindow.parent as never; } return currentWindow.location.href.replace(/#SHARE=[^&]*/, '').replace(/#$/, ''); }
function copy(toastMessage: string, link: string) { copyToClipboard(link, { onSuccess: () => showToast({ level: 'success', message: toastMessage }), onError: () => showToast({ level: 'error', message: i18n('link-copy-error') }), }); }
async function selectOption(option: ShareOption) { if (option.type === 'embedded') { selected = option; isEmbedded = true; return; } isEmbedded = false;
if (option.type === 'clipboard') { selected = option; return; }
// Generate a token and open the link const token = await getOrCreateToken(); const linkWithShare = `${getBaseUrl()}#SHARE=${token}`; const shareLink = buildIntegrationLink(option, linkWithShare); window.open(shareLink, '_blank'); selected = config.shareOptions.find((o) => o.type === 'clipboard')!; }
async function copyEmbeddedHtml() { const token = await getOrCreateToken(); const html = buildEmbeddedHtml(buildShareUrl(config.iframeBaseUrl, token), embeddedWidth, embeddedHeight); copy(i18n('embedded-copied'), html); }
async function copyClipboard() { const token = await getOrCreateToken(); const url = buildShareUrl(getBaseUrl(), token); copy(i18n('link-copied'), url); }
async function getOrCreateToken() { let token = generateShareToken.value; if (!token) { token = await generateShareToken.mutate(null!); } return token; }</script>
<div class="gv-space-y-md"> <div class="gv-flex gv-justify-start gv-gap-2"> {#each config.shareOptions as shareOption (shareOption)} {@const isSelected = selected?.type === shareOption.type} <Button title={i18n.translate(shareOption.label)} class="gv-size-8 gv-p-1 gv-border-transparent" size="icon" onclick={() => selectOption(shareOption)} variant={isSelected ? 'default' : 'outline'} > <Icon icon={shareOption.icon} class={cn(!isSelected && 'gv-text-primary')} /> </Button> {/each} {#if generateShareToken.loading} <div class="gv-flex gv-justify-center gv-items-center"> <Loader /> </div> {/if} </div>
{#if isEmbedded} <div class="gv-grid gv-grid-cols-2 gv-gap-md"> <div class="gv-grid gv-gap-sm"> <Label class="gv-font-bold">{i18n('embedded-height')}</Label> <Input bind:value={embeddedHeight} type="number" /> </div> <div class="gv-grid gv-gap-sm"> <Label class="gv-font-bold">{i18n('embedded-width')}</Label> <Input bind:value={embeddedWidth} type="number" /> </div> </div> <CopyText onclick={copyEmbeddedHtml} text={embeddedPreviewText} /> {:else} <CopyText onclick={copyClipboard} text={previewText} /> {/if}</div>