diff --git a/src/lib/auditors/hashes.ts b/src/lib/auditors/hashes.ts new file mode 100644 index 0000000000000000000000000000000000000000..4aaebf79dc96f563de3c0969856cced8d822a7a2 --- /dev/null +++ b/src/lib/auditors/hashes.ts @@ -0,0 +1,54 @@ +import { + auditChain, + auditTrimString, + type Audit, + type Auditor, + auditArray, + auditTest, + auditFunction, + auditSetNullish, +} from "@auditors/core" + +export function auditHashSingleton(...auditors: Auditor[]) { + return auditChain( + auditArray(), + auditTest( + (values) => values.length <= 1, + "Parameter must be present only once in hash", + ), + auditFunction((value) => value[0]), + ...auditors, + ) +} + +export function auditSimulationHash( + audit: Audit, + hash: unknown, +): [unknown, unknown] { + if (typeof hash !== "string") { + return audit.unexpectedType(hash, "string") + } + const query = new URLSearchParams((hash as string).replace(/^#/, "")) + + const data: { [key: string]: unknown } = {} + for (const [key, value] of query.entries()) { + let values = data[key] as string[] | undefined + if (values === undefined) { + values = data[key] = [] + } + values.push(value) + } + const errors: { [key: string]: unknown } = {} + const remainingKeys = new Set(Object.keys(data)) + + audit.attribute( + data, + "parameterHash", + true, + errors, + remainingKeys, + auditHashSingleton(auditTrimString), + ) + + return audit.reduceRemaining(data, errors, remainingKeys, auditSetNullish({})) +} diff --git a/src/lib/components/NavBar.svelte b/src/lib/components/NavBar.svelte index e9663554cfad87dfc22550d56d5678030c3a6b8a..22dc3c4963f656d7bad2e8b97b1dc07f57947643 100644 --- a/src/lib/components/NavBar.svelte +++ b/src/lib/components/NavBar.svelte @@ -97,6 +97,7 @@ const testCases = getContext("testCases") as Writable<Situation[]> async function shareLink(): Promise<void> { + delete $displayModeWritable?.parameterHash const urlString = "/simulations" const res = await fetch(urlString, { body: JSON.stringify({ diff --git a/src/lib/components/test_cases/TestCaseView.svelte b/src/lib/components/test_cases/TestCaseView.svelte index 3b089cb666f48b4b03aaf974b0be1a5d219825d1..025d1c67244b1c5291a2921d7dee558ccd9c8d17 100644 --- a/src/lib/components/test_cases/TestCaseView.svelte +++ b/src/lib/components/test_cases/TestCaseView.svelte @@ -294,22 +294,20 @@ <!--TICKET DE CARBURANT--> {#if displayMode.parametersVariableName === "taxes_tous_carburants" || (displayMode.parametersVariableName !== undefined && oilTypes.some( (oilType) => displayMode.parametersVariableName?.includes(oilType), ))} - <div class="w-1/2"> - {#each oilSpendings as { depenseTtcVariableName, nombreLitresVariableName, prixTtcLitreVariableName, ticpeVariableName, tvaVariableName }} - <OilSpendingBill - {depenseTtcVariableName} - {nombreLitresVariableName} - {prixTtcLitreVariableName} - on:changeTestCaseToEditIndex - {situation} - {situationIndex} - {ticpeVariableName} - {tvaVariableName} - {valuesByCalculationNameByVariableName} - {year} - /> - {/each} - </div> + {#each oilSpendings as { depenseTtcVariableName, nombreLitresVariableName, prixTtcLitreVariableName, ticpeVariableName, tvaVariableName }} + <OilSpendingBill + {depenseTtcVariableName} + {nombreLitresVariableName} + {prixTtcLitreVariableName} + on:changeTestCaseToEditIndex + {situation} + {situationIndex} + {ticpeVariableName} + {tvaVariableName} + {valuesByCalculationNameByVariableName} + {year} + /> + {/each} {/if} </div> diff --git a/src/lib/components/variables/NonVariableReferredParameter.svelte b/src/lib/components/variables/NonVariableReferredParameter.svelte index bed19c2977ea0f6637596ec0520a3ea1d9f19802..e54b49e24ab0738615ca866eec8267a4bc9061ef 100644 --- a/src/lib/components/variables/NonVariableReferredParameter.svelte +++ b/src/lib/components/variables/NonVariableReferredParameter.svelte @@ -11,6 +11,7 @@ rootParameter, rootParameterByReformName, } from "$lib/parameters" + import type { DisplayMode } from "$lib/displays" import VariableReferredNodeParameter from "./VariableReferredNodeParameter.svelte" import VariableReferredScaleParameter from "./VariableReferredScaleParameter.svelte" @@ -18,8 +19,10 @@ export let budget: boolean | undefined export let date: string + export let displayMode: DisplayMode /// Parameter name export let name: string + export let onCopyLink: (parameterName: string) => void const billName = getContext("billName") as Writable<string | undefined> @@ -41,7 +44,9 @@ {budget} {date} depth={0} + {displayMode} lawParameter={asNodeParameter(lawParameter)} + {onCopyLink} /> {:else if billParameter.class === ParameterClass.Value} <VariableReferredValueParameter @@ -49,7 +54,9 @@ {budget} {date} depth={0} + {displayMode} lawParameter={asValueParameter(lawParameter)} + {onCopyLink} /> {:else if billParameter.class === ParameterClass.Scale} <VariableReferredScaleParameter @@ -57,7 +64,9 @@ {budget} {date} depth={0} + {displayMode} lawParameter={asScaleParameter(lawParameter)} + {onCopyLink} /> {/if} </section> diff --git a/src/lib/components/variables/VariableHeader.svelte b/src/lib/components/variables/VariableHeader.svelte index d473c8f1894d1baa0eac3a3df6da67d84bdbaa98..db92d6f9180aa441451fecaaf1c525ee2ef2a2ec 100644 --- a/src/lib/components/variables/VariableHeader.svelte +++ b/src/lib/components/variables/VariableHeader.svelte @@ -77,21 +77,19 @@ </span> </Tooltip> {:else} - <div class="flex"> - <a - id="dispositif-{variableName}-a-jour-voir-formule" - class="flex items-center font-bold italic text-black hover:text-le-bleu" - href="/variables/{variableName}" - > - <iconify-icon - class="mr-1 text-[#13CC03]" - icon="material-symbols:new-releases" - width="28" - height="28" - /> - f - </a> - </div> + <a + id="dispositif-{variableName}-a-jour-voir-formule" + class="flex items-center font-bold italic text-black hover:text-le-bleu" + href="/variables/{variableName}" + > + <iconify-icon + class="mx-1 text-[#13CC03]" + icon="material-symbols:new-releases" + width="28" + height="28" + /> + f + </a> <Tooltip class="z-40 w-60 bg-gray-100 px-3 py-1 text-left text-xs text-black" style="custom" diff --git a/src/lib/components/variables/VariableReferredNodeParameter.svelte b/src/lib/components/variables/VariableReferredNodeParameter.svelte index 19634ca7d4ea700fa590d3294869106cda23d42d..94139424b995e2eb3c295127cf51d6e1be6486ae 100644 --- a/src/lib/components/variables/VariableReferredNodeParameter.svelte +++ b/src/lib/components/variables/VariableReferredNodeParameter.svelte @@ -6,6 +6,7 @@ } from "@openfisca/json-model" import { asScaleParameter, asValueParameter } from "$lib/parameters" + import type { DisplayMode } from "$lib/displays" import VariableReferredParameterHeader from "./VariableReferredParameterHeader.svelte" import VariableReferredScaleParameter from "./VariableReferredScaleParameter.svelte" @@ -16,9 +17,11 @@ export let budget: boolean | undefined export let date: string export let depth: number + export let displayMode: DisplayMode export let hideNull = false export let lawParameter: NodeParameter | undefined export let name: string | undefined = undefined + export let onCopyLink: (parameterName: string) => void function compareUsingOrder( order: string[], @@ -64,7 +67,12 @@ </script> <section> - <VariableReferredParameterHeader {depth} parameter={billParameter} /> + <VariableReferredParameterHeader + {depth} + {displayMode} + {onCopyLink} + parameter={billParameter} + /> {#if billParameter.children !== undefined} <ul> @@ -77,9 +85,11 @@ {budget} {date} depth={depth + 1} + {displayMode} {hideNull} lawParameter={lawChild} {name} + {onCopyLink} /> {:else if billChild.class === ParameterClass.Value} <VariableReferredValueParameter @@ -87,9 +97,11 @@ {budget} {date} depth={depth + 1} + {displayMode} {hideNull} lawParameter={asValueParameter(lawChild)} {name} + {onCopyLink} /> {:else if billChild.class === ParameterClass.Scale} <VariableReferredScaleParameter @@ -97,8 +109,10 @@ {budget} {date} depth={depth + 1} + {displayMode} lawParameter={asScaleParameter(lawChild)} {name} + {onCopyLink} /> {/if} </li> diff --git a/src/lib/components/variables/VariableReferredParameterHeader.svelte b/src/lib/components/variables/VariableReferredParameterHeader.svelte index 8973c737b11bde66900d128cea2a70d1d5bd4978..3da2f2396206a4b3b169247976ad22268b717f0d 100644 --- a/src/lib/components/variables/VariableReferredParameterHeader.svelte +++ b/src/lib/components/variables/VariableReferredParameterHeader.svelte @@ -4,20 +4,59 @@ import { getContext } from "svelte" import type { SelfTargetAProps } from "$lib/urls" + import type { DisplayMode } from "$lib/displays" + import viewport from "$lib/viewport" + import { goto } from "$app/navigation" + import { newSimulationUrl } from "$lib/urls" export let depth: number + export let displayMode: DisplayMode + export let onCopyLink: (parameterName: string) => void export let parameter: Parameter const newSelfTargetAProps = getContext("newSelfTargetAProps") as ( url: string, ) => SelfTargetAProps + + let isCopiedSuccessfully = false + function copyLink() { + if (parameter.name !== undefined) { + onCopyLink(parameter.name) + isCopiedSuccessfully = true + setTimeout(() => (isCopiedSuccessfully = false), 2500) + } + } + + $: isParameterSelected = displayMode.parameterHash === parameter.name + let htmlElement + let isElementInViewport = false + $: if (htmlElement !== undefined && isParameterSelected) + requestAnimationFrame(() => + htmlElement.scrollIntoView({ behavior: "smooth" }), + ) + + let hasAnimationEnded = false + $: if (isElementInViewport) + setTimeout(() => { + hasAnimationEnded = true + goto( + newSimulationUrl({ + ...displayMode, + parameterHash: undefined, + }), + ) + }, 5000) </script> +<!--scroll-mt-60--> {#if depth === 0 || parameter.title !== parameter.parent?.title} <h1 - class="mr-4 font-serif text-le-gris-dispositif-dark {parameter.class !== + bind:this={htmlElement} + use:viewport + on:enterViewport={() => (isElementInViewport = true)} + class="mr-4 inline-flex scroll-mt-48 font-serif text-le-gris-dispositif-dark {parameter.class !== ParameterClass.Node - ? 'text-base italic text-le-gris-dispositif-dark' + ? '-ml-5 text-base italic text-le-gris-dispositif-dark' : depth === 0 ? 'my-1 inline-flex text-base font-bold' : depth === 1 @@ -39,7 +78,25 @@ {/each} {/if} {#if parameter.title !== parameter.parent?.title} - {parameter.title} : + <button + title="Copier le lien" + class="flex shrink-0 items-center justify-center self-start px-0.5 py-1 text-le-gris-dispositif-light" + class:hover:text-le-gris-dispositif={!isCopiedSuccessfully} + class:active:text-le-gris-dispositif-dark={!isCopiedSuccessfully} + class:!text-le-gris-dispositif-dark={isCopiedSuccessfully} + disabled={isCopiedSuccessfully} + on:click={copyLink} + ><iconify-icon + icon={!isCopiedSuccessfully ? "ri-link" : "ri-check-line"} + /></button + > + <span + class="transition-all duration-1000 ease-out" + class:px-2={isParameterSelected} + class:bg-le-bleu-light={isParameterSelected} + class:animate-blink={isParameterSelected && isElementInViewport} + style="animation-delay: 500ms">{parameter.title} :</span + > {/if} </h1> {/if} diff --git a/src/lib/components/variables/VariableReferredParameters.svelte b/src/lib/components/variables/VariableReferredParameters.svelte index b30b62de02c77bea175391e65aff0cef21bc0e6f..e9047e944e999f158d8224bf584fa1ebb8d4b595 100644 --- a/src/lib/components/variables/VariableReferredParameters.svelte +++ b/src/lib/components/variables/VariableReferredParameters.svelte @@ -37,6 +37,7 @@ export let date: string export let displayMode: DisplayMode export let name: string + export let onCopyLink: (parameterName: string) => void const billName = getContext("billName") as Writable<string | undefined> let openDirectParameters = true @@ -171,9 +172,11 @@ budget={displayMode.budget} {date} depth={0} + {displayMode} hideNull lawParameter={asNodeParameter(lawParameter)} {name} + {onCopyLink} /> {:else if billParameter.class === ParameterClass.Value} <VariableReferredValueParameter @@ -181,9 +184,11 @@ budget={displayMode.budget} {date} depth={0} + {displayMode} hideNull lawParameter={asValueParameter(lawParameter)} {name} + {onCopyLink} /> {:else if billParameter.class === ParameterClass.Scale} <VariableReferredScaleParameter @@ -191,6 +196,7 @@ budget={displayMode.budget} {date} depth={0} + {displayMode} lawParameter={asScaleParameter(lawParameter)} {name} /> @@ -234,7 +240,9 @@ budget={displayMode.budget} {date} depth={0} + {displayMode} lawParameter={asNodeParameter(lawParameter)} + {onCopyLink} /> {:else if billParameter.class === ParameterClass.Value} <VariableReferredValueParameter @@ -242,7 +250,9 @@ budget={displayMode.budget} {date} depth={0} + {displayMode} lawParameter={asValueParameter(lawParameter)} + {onCopyLink} /> {:else if billParameter.class === ParameterClass.Scale} <VariableReferredScaleParameter @@ -250,7 +260,9 @@ budget={displayMode.budget} {date} depth={0} + {displayMode} lawParameter={asScaleParameter(lawParameter)} + {onCopyLink} /> {/if} </li> diff --git a/src/lib/components/variables/VariableReferredScaleParameter.svelte b/src/lib/components/variables/VariableReferredScaleParameter.svelte index 96ace8a8deea25381a08632bde23e448f9480d3b..90e3e57c54cf8e90e434e121af0d6cf7c768cdf8 100644 --- a/src/lib/components/variables/VariableReferredScaleParameter.svelte +++ b/src/lib/components/variables/VariableReferredScaleParameter.svelte @@ -15,6 +15,7 @@ requestTestCasesCalculation, type RequestedCalculations, } from "$lib/calculations" + import type { DisplayMode } from "$lib/displays" import ArticleModal from "$lib/components/parameters/ArticleModal.svelte" import VariableReferredParameterHeader from "$lib/components/variables/VariableReferredParameterHeader.svelte" import VariableReferredScaleAtInstant from "$lib/components/variables/VariableReferredScaleAtInstant.svelte" @@ -32,8 +33,10 @@ export let budget: boolean | undefined export let date: string export let depth: number + export let displayMode: DisplayMode export let lawParameter: ScaleParameter | undefined export let name: string | undefined = undefined + export let onCopyLink: (parameterName: string) => void const dateFormatter = new Intl.DateTimeFormat("fr-FR", { dateStyle: "full" }) .format @@ -141,8 +144,13 @@ } </script> -<section class="ml-5 border-l-2 border-le-gris-dispositif-light pb-4 pl-4"> - <VariableReferredParameterHeader {depth} parameter={billParameter} /> +<section class="ml-10 border-l-2 border-le-gris-dispositif-light pb-4 pl-6"> + <VariableReferredParameterHeader + {depth} + {displayMode} + parameter={billParameter} + {onCopyLink} + /> <div class="mt-1 flex rounded-t bg-gray-100 pl-1 pt-2"> <VariableReferredScaleAtInstant diff --git a/src/lib/components/variables/VariableReferredValueParameter.svelte b/src/lib/components/variables/VariableReferredValueParameter.svelte index 791dd7fc45eeb42bc6549beec196df1fdfc983ce..07ed794f1491cc479be940fdef95a822bc98f142 100644 --- a/src/lib/components/variables/VariableReferredValueParameter.svelte +++ b/src/lib/components/variables/VariableReferredValueParameter.svelte @@ -14,6 +14,7 @@ requestTestCasesCalculation, type RequestedCalculations, } from "$lib/calculations" + import type { DisplayMode } from "$lib/displays" import ArticleModal from "$lib/components/parameters/ArticleModal.svelte" import VariableReferredParameterHeader from "$lib/components/variables/VariableReferredParameterHeader.svelte" import VariableReferredValueEdit from "$lib/components/variables/VariableReferredValueEdit.svelte" @@ -33,9 +34,11 @@ export let budget: boolean | undefined export let date: string export let depth: number + export let displayMode: DisplayMode export let hideNull = false export let lawParameter: ValueParameter | undefined export let name: string | undefined = undefined + export let onCopyLink: (parameterName: string) => void let billLatestInstantValueCouplesArray: [string, ValueAtInstant][] const dateFormatter = new Intl.DateTimeFormat("fr-FR", { dateStyle: "full" }) @@ -149,8 +152,13 @@ </script> {#if !hideNull || value !== null} - <section class="ml-5 border-l-2 border-le-gris-dispositif-light pb-6 pl-4"> - <VariableReferredParameterHeader {depth} parameter={billParameter} /> + <section class="ml-10 border-l-2 border-le-gris-dispositif-light pb-6 pl-6"> + <VariableReferredParameterHeader + {depth} + {displayMode} + parameter={billParameter} + {onCopyLink} + /> <div class="flex flex-wrap items-center justify-between gap-x-4 gap-y-2 rounded bg-gray-100 p-2 pb-2" diff --git a/src/lib/displays.ts b/src/lib/displays.ts index 027a49046b5d9b69823ce422708779789243f00d..a2da3bd84dc72c8d29d720274c4e987ca752f704 100644 --- a/src/lib/displays.ts +++ b/src/lib/displays.ts @@ -9,4 +9,5 @@ export interface DisplayMode { variableName?: string waterfallName: string tab: string + parameterHash?: string } diff --git a/src/lib/urls.ts b/src/lib/urls.ts index 695c78b0db94f5938fc32facf29e1e36762d847d..ff7937b9d40eb466565a2bb720b0190dd5d40e89 100644 --- a/src/lib/urls.ts +++ b/src/lib/urls.ts @@ -13,41 +13,50 @@ export function newSelfTargetAProps(url: string): SelfTargetAProps { } export function newSimulationUrl(displayMode: DisplayMode): string { - const query: URLSearchParams = new URLSearchParams() + const parametersQuery: URLSearchParams = new URLSearchParams() if (displayMode.budget) { - query.append("budget", "true") + parametersQuery.append("budget", "true") } if (displayMode.edit !== undefined) { - query.append("edit", displayMode.edit.toString()) + parametersQuery.append("edit", displayMode.edit.toString()) } if (displayMode.mobileLaw) { - query.append("law", "true") + parametersQuery.append("law", "true") } if ( displayMode.testCasesIndex.length !== 1 || displayMode.testCasesIndex[0] !== 0 ) { for (const testCaseIndex of displayMode.testCasesIndex) { - query.append("test_case", testCaseIndex.toString()) + parametersQuery.append("test_case", testCaseIndex.toString()) } } if (displayMode.variableName !== undefined) { - query.append("variable", displayMode.variableName) + parametersQuery.append("variable", displayMode.variableName) } if (displayMode.parameterName !== undefined) { - query.append("parameter", displayMode.parameterName) + parametersQuery.append("parameter", displayMode.parameterName) } if (displayMode.parametersVariableName !== undefined) { - query.append("parameters", displayMode.parametersVariableName) + parametersQuery.append("parameters", displayMode.parametersVariableName) } if (displayMode.waterfallName !== waterfalls[0].name) { - query.append("waterfall", displayMode.waterfallName) + parametersQuery.append("waterfall", displayMode.waterfallName) } if (displayMode.tab !== undefined) { - query.append("tab", displayMode.tab) + parametersQuery.append("tab", displayMode.tab) } - const queryString = query.toString() - return `/${queryString ? "?" + queryString : ""}` + const parametersQueryString = parametersQuery.toString() + + const hashesQuery: URLSearchParams = new URLSearchParams() + if (displayMode.parameterHash !== undefined) { + hashesQuery.append("parameterHash", displayMode.parameterHash) + } + const hashesQueryString = hashesQuery.toString() + + return `/${parametersQueryString ? "?" + parametersQueryString : ""}${ + hashesQueryString ? "#" + hashesQueryString : "" + }` } export function stringifyQuery(queryParameters?: { diff --git a/src/lib/viewport.ts b/src/lib/viewport.ts new file mode 100644 index 0000000000000000000000000000000000000000..c1e9ea4420b041012f67a25ccc25979c4b7e3451 --- /dev/null +++ b/src/lib/viewport.ts @@ -0,0 +1,24 @@ +let intersectionObserver: IntersectionObserver + +function ensureIntersectionObserver() { + if (intersectionObserver) return + + intersectionObserver = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + const eventName = entry.isIntersecting ? "enterViewport" : "exitViewport" + entry.target.dispatchEvent(new CustomEvent(eventName)) + }) + }) +} + +export default function viewport(element: HTMLElement) { + ensureIntersectionObserver() + + intersectionObserver.observe(element) + + return { + destroy() { + intersectionObserver.unobserve(element) + }, + } +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 43bad027dd25165ff725d7336f76eb9efc36cf67..798cd3918025f79046c1dd936032184fcf944f29 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -21,6 +21,7 @@ import { goto } from "$app/navigation" import { page } from "$app/stores" import { auditQueryArray, auditQuerySingleton } from "$lib/auditors/queries" + import { auditSimulationHash } from "$lib/auditors/hashes" import type { BudgetSimulation } from "$lib/budgets" import { requestAllBudgetCalculations, @@ -69,6 +70,7 @@ } from "$lib/variables" import type { PageData } from "./$types" + import CopyClipboard from "$lib/components/CopyClipboard.svelte" export let data: PageData @@ -123,7 +125,7 @@ $: ({ user } = data) - $: ensureValidDisplayMode($page.url.searchParams) + $: ensureValidDisplayMode($page.url.searchParams, $page.url.hash) $: if ( browser && @@ -517,25 +519,46 @@ ) } - function ensureValidDisplayMode(query: URLSearchParams): void { - let [validDisplayMode, queryError] = auditSimulationQuery( + function ensureValidDisplayMode(query: URLSearchParams, hash: string): void { + let [validQueryDisplayMode, queryError] = auditSimulationQuery( cleanAudit, query, ) as [DisplayMode, unknown] + if (queryError !== null) { console.warn( `Query error at ${$page.url.pathname}: ${JSON.stringify( queryError, null, 2, - )}\n\n${JSON.stringify(validDisplayMode, null, 2)}`, + )}\n\n${JSON.stringify(validQueryDisplayMode, null, 2)}`, ) - validDisplayMode = { + validQueryDisplayMode = { testCasesIndex: [0], waterfallName: waterfalls[0].name, } } - displayMode = validDisplayMode + + let [validHashDisplayMode, hashError] = auditSimulationHash( + cleanAudit, + hash, + ) as [DisplayMode, unknown] + + if (hashError !== null) { + console.warn( + `Hash error at ${$page.url.pathname}: ${JSON.stringify( + hashError, + null, + 2, + )}\n\n${JSON.stringify(validHashDisplayMode, null, 2)}`, + ) + validHashDisplayMode = {} + } + + displayMode = { + ...validQueryDisplayMode, + ...validHashDisplayMode, + } $testCasesIndex = displayMode.testCasesIndex $waterfall = waterfalls.find( ({ name }) => name === displayMode.waterfallName, @@ -741,6 +764,53 @@ { noScroll: true }, ) } + + let clipboardElement: HTMLElement + async function onCopyParameterLink(parameterName: string) { + const urlString = "/simulations" + const res = await fetch(urlString, { + body: JSON.stringify({ + displayMode: { + ...displayMode, + parameterHash: parameterName, + mobileLaw: true, + }, + inputInstantsByVariableNameArray: $inputInstantsByVariableNameArray.map( + (inputInstantsByVariableName) => + Object.fromEntries( + Object.entries(inputInstantsByVariableName).map( + ([variableName, inputInstants]) => [ + variableName, + [...inputInstants], + ], + ), + ), + ), + parametricReform: $parametricReform, + testCases: $testCases, + }), + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + method: "POST", + }) + if (!res.ok) { + console.error( + `Error ${ + res.status + } while creating a share link at ${urlString}\n\n${await res.text()}`, + ) + return + } + const { token } = await res.json() + const url = new URL(`/simulations/${token}`, $page.data.baseUrl).toString() + const copyClipboard = new CopyClipboard({ + target: clipboardElement, + props: { value: url }, + }) + copyClipboard.$destroy() + } </script> <svelte:window bind:innerWidth={windowInnerWidth} on:keydown={onKeyDown} /> @@ -748,6 +818,8 @@ <title>Calculs | {data.title}</title> </svelte:head> +<div bind:this={clipboardElement} /> + <main class="fond flex h-full flex-1 overflow-x-clip after:absolute after:inset-0 after:z-10 after:bg-[rgba(0,0,0,.3)] after:transition-all md:overflow-hidden" class:after:content-none={!$isSearchActive} @@ -994,20 +1066,20 @@ {/each} </div> <!-- Vue modification de la loi --> - {:else if displayMode.parametersVariableName !== undefined && displayMode.parameterName === undefined} - <div class="mb-5 flex flex-col gap-2"> - <div class="flex justify-end"> - <button - class="text-sm uppercase text-gray-600 hover:text-black" - on:click={closeParametersEditionPane} - >Autres dispositifs<iconify-icon - class="ml-1 align-[-0.23rem] text-lg" - icon="ri-arrow-up-line" - /> - </button> - </div> + {:else} + <div class="flex justify-end"> + <button + class="text-sm uppercase text-gray-600 hover:text-black" + on:click={closeParametersEditionPane} + >Autres dispositifs<iconify-icon + class="ml-1 align-[-0.23rem] text-lg" + icon="ri-arrow-up-line" + /> + </button> + </div> + {#if displayMode.parametersVariableName !== undefined && displayMode.parameterName === undefined} {#if displayMode.parametersVariableName === "csg_deductible_salaire" || displayMode.parametersVariableName === "csg_imposable_salaire"} - <div class="flex justify-end"> + <div class="mt-2 flex justify-end"> <a class="flex items-center text-sm uppercase text-gray-600 hover:text-black" href={newSimulationUrl({ @@ -1033,53 +1105,29 @@ </a> </div> {/if} - </div> - <div class="flex-1 overflow-y-auto bg-white"> - <VariableReferredParameters - date={$date} - {displayMode} - name={displayMode.parametersVariableName} - /> - </div> - <!-- Vue modification d'un paramètre --> - {:else} - <!-- Bouton de fermeture du dispositif en cours - DESKTOP --> - <div class="mx-5 mb-2 hidden justify-end md:flex"> - <button - class="text-sm uppercase text-gray-600 hover:text-black" - on:click={closeParametersEditionPane} - >Autres dispositifs<iconify-icon - class="ml-1 align-[-0.2rem] text-lg" - icon="ri-arrow-up-line" - /></button - > - </div> - <div class="md:[calc(100vh-13rem)] overflow-y-auto"> - <!-- Bouton de fermeture du dispositif en cours - MOBILE --> - <div class="mx-5 mb-2 flex justify-end md:hidden"> - <button - class="text-sm uppercase text-gray-600 hover:text-black" - on:click={closeParametersEditionPane} - >Autres dispositifs<iconify-icon - class="ml-1 align-[-0.2rem] text-lg" - icon="ri-arrow-up-line" - /></button - > + <!-- <div class="mb-5 flex flex-col gap-2">--> + + <div class="mt-5 flex-1 overflow-y-auto bg-white"> + <VariableReferredParameters + date={$date} + {displayMode} + name={displayMode.parametersVariableName} + onCopyLink={onCopyParameterLink} + /> </div> - <h1 - class="mb-5 ml-5 flex border-b border-black pb-3 pt-1 text-xl font-bold text-black md:hidden" - > - Modifier le droit en vigueur - </h1> - <div class="ml-5 bg-white"> + {:else} + <div class="ml-5 mt-5 bg-white"> <NonVariableReferredParameter budget={displayMode.budget} date={$date} + {displayMode} name={displayMode.parameterName} + onCopyLink={onCopyParameterLink} /> </div> - </div> + {/if} + <!-- Vue modification d'un paramètre --> {/if} </div> </div> diff --git a/src/routes/simulations/+server.ts b/src/routes/simulations/+server.ts index 9a9cd15be8767f6609dba565360c06ffe081c5e7..7faefcd57af767b55bf050307e8485f0c6963d66 100644 --- a/src/routes/simulations/+server.ts +++ b/src/routes/simulations/+server.ts @@ -82,7 +82,13 @@ export const POST: RequestHandler = async ({ request, url }) => { throw error(400, `Invalid body: ${JSON.stringify(bodyError, null, 2)}`) } const bodyJson = JSON.stringify(body, null, 2) - const digest = XXH64(Buffer.from(bodyJson)).toString(16) + + // Sometimes, the magnitude of the BigInt value generated by XXH64() + // is not large enough to require 16 characters when represented in base 16. + // As a result, the base 16 representation has leading zeros that are not necessary, + // and JavaScript omits them when converting the BigInt to a string. To fix this, + // we add zeros in the beginning until we reach a length of 16. + const digest = XXH64(Buffer.from(bodyJson)).toString(16).padStart(16, "0") const simulationDir = path.join(simulationsDir, digest.substring(0, 2)) const simulationFilePath = path.join(simulationDir, `${digest}.json`) diff --git a/tailwind.config.cjs b/tailwind.config.cjs index 71a02805ef14bc2c75a4bce0ed0caa832454c00c..56fa69a1c5115613f6a79a4ffebdefd46d75c259 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -28,6 +28,9 @@ const config = { ], theme: { extend: { + animation: { + blink: "blinker 300ms ease-in 2", + }, blur: { xs: "1.2px", xxs: "0.8px", @@ -48,8 +51,10 @@ const config = { "le-vert-validation": "#13CC03", "le-vert-validation-dark": "#377330", }, - zIndex: { - 25: "25", + keyframes: { + blinker: { + "50%": { opacity: "60%" }, + }, }, transitionTimingFunction: { "in-quad": "cubic-bezier(.55, .085, .68, .53)", @@ -76,6 +81,9 @@ const config = { "in-out-circ": "cubic-bezier(.785, .135, .15, .86)", "in-out-back": "cubic-bezier(.68, -.6, .32, 1.6)", }, + zIndex: { + 25: "25", + }, }, fontFamily: { sans: ["Lato", "sans-serif"],