Skip to content

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.ts

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

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

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

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

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>

Aller plus loin