@datawheel/bespoke 0.6.0-rc.14 → 0.6.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +300 -335
  2. package/dist/server.js +239 -1
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -22,11 +22,10 @@ import { Notifications, notifications } from '@mantine/notifications';
22
22
  import { useDispatch, useSelector } from 'react-redux';
23
23
  import Router, { useRouter } from 'next/router';
24
24
  import { useClickOutside, useDisclosure, useDebouncedValue, useSetState, useUncontrolled, useHotkeys, useListState, randomId, getHotkeyHandler, useMediaQuery, useMergedRef, useFullscreen } from '@mantine/hooks';
25
- import { IconDice, IconBoxMultiple, IconEyeOff, IconChevronDown, IconBallpen, IconDatabase, IconMathFunction, IconUsers, IconLogout, IconHeading, IconApi, IconPercentage, IconChartBar, IconAlignLeft, IconSelector, IconPhoto, IconTable, IconUserCircle, IconEdit, IconServer, IconPencil, IconAlertCircle, IconCircleCheck, IconPlayerPlay, IconTrash, IconCircleX, IconPlus, IconFileAnalytics, IconHome, IconSearch, IconX, IconRefresh, IconDownload, IconCircleDashed, IconLanguage, IconSettingsFilled, IconEye, IconWorldUpload, IconBraces, IconClockHour2, IconAugmentedReality, IconGitMerge, IconGripHorizontal, IconChevronLeft, IconChevronRight, IconListCheck, IconPolaroid, IconCircleMinus, IconInfoCircle, IconGripVertical, IconCamera, IconShare, IconQuestionMark, IconCirclePlus, IconLogin, IconWorld, IconLock, IconCopy, IconBinaryTree, IconVariable, IconArrowRightCircle, IconPhotoFilled, IconFileUpload, IconIndentIncrease, IconCodeDots, IconUpload, IconCheck, IconCodePlus, IconLink, IconSparkles, IconClipboardCheck, IconClipboardCopy, IconExternalLink, IconFileTypeCsv, IconFileTypeJs, IconFileTypeXls, IconTemplate, IconPalette, IconBold, IconItalic, IconUnderline, IconAlignCenter, IconAlignRight, IconAlignJustified, IconArrowBackUp, IconArrowForwardUp, IconLanguageOff, IconTriangleInvertedFilled, IconCode, IconDeviceFloppy, IconSettings, IconMinimize, IconMaximize, IconGlobe, IconLinkOff } from '@tabler/icons-react';
25
+ import { IconDice, IconBoxMultiple, IconEyeOff, IconChevronDown, IconBallpen, IconDatabase, IconMathFunction, IconUsers, IconLogout, IconHeading, IconApi, IconPercentage, IconChartBar, IconAlignLeft, IconSelector, IconPhoto, IconTable, IconUserCircle, IconEdit, IconServer, IconPencil, IconAlertCircle, IconCircleCheck, IconPlayerPlay, IconTrash, IconCircleX, IconPlus, IconFileAnalytics, IconHome, IconSearch, IconX, IconRefresh, IconDownload, IconCircleDashed, IconLanguage, IconSettingsFilled, IconEye, IconWorldUpload, IconBraces, IconClockHour2, IconAugmentedReality, IconGitMerge, IconGripHorizontal, IconChevronLeft, IconChevronRight, IconListCheck, IconPolaroid, IconCircleMinus, IconInfoCircle, IconGripVertical, IconCamera, IconShare, IconQuestionMark, IconCirclePlus, IconLogin, IconWorld, IconLock, IconCopy, IconBinaryTree, IconVariable, IconArrowRightCircle, IconPhotoFilled, IconFileUpload, IconIndentIncrease, IconCodeDots, IconUpload, IconCheck, IconCodePlus, IconLink, IconSparkles, IconClipboardCheck, IconClipboardCopy, IconExternalLink, IconFileTypeCsv, IconFileTypeJs, IconFileTypeXls, IconTemplate, IconCode, IconPalette, IconBold, IconItalic, IconUnderline, IconAlignCenter, IconAlignRight, IconAlignJustified, IconArrowBackUp, IconArrowForwardUp, IconLanguageOff, IconTriangleInvertedFilled, IconDeviceFloppy, IconSettings, IconMinimize, IconMaximize, IconGlobe, IconLinkOff } from '@tabler/icons-react';
26
26
  import Link from 'next/link';
27
27
  import parse2, { Element as Element$1, domToReact, Text as Text$1 } from 'html-react-parser';
28
28
  import { UserProvider, withPageAuthRequired, useUser } from '@auth0/nextjs-auth0/client';
29
- import { initFrameAndPoll, Framer } from '@newswire/frames';
30
29
  import { MantineReactTable } from 'mantine-react-table';
31
30
  import { dataConcat, dataLoad } from 'd3plus-viz';
32
31
  import * as d3plus from 'd3plus-react';
@@ -51,9 +50,8 @@ import { Prism } from '@mantine/prism';
51
50
  import slugifyFn from 'slugify';
52
51
  import JSZip from 'jszip';
53
52
  import { saveAs } from 'file-saver';
54
- import { FacebookShareButton, FacebookIcon, TwitterShareButton, TwitterIcon, TelegramShareButton, TelegramIcon, WhatsappShareButton, WhatsappIcon, LinkedinShareButton, LinkedinIcon, RedditShareButton, RedditIcon, EmailShareButton, EmailIcon } from 'react-share';
55
53
  import { select } from 'd3-selection';
56
- import { getFontEmbedCSS, toPng } from 'html-to-image';
54
+ import { FacebookShareButton, FacebookIcon, TwitterShareButton, TwitterIcon, TelegramShareButton, TelegramIcon, WhatsappShareButton, WhatsappIcon, LinkedinShareButton, LinkedinIcon, RedditShareButton, RedditIcon, EmailShareButton, EmailIcon } from 'react-share';
57
55
  import Head from 'next/head';
58
56
  import Editor, { useMonaco } from '@monaco-editor/react';
59
57
  import { format } from 'pretty-format';
@@ -265,8 +263,8 @@ var init_ExploreFilters = __esm({
265
263
  });
266
264
 
267
265
  // api/http/lib.ts
268
- function http(axios10, config) {
269
- return axios10.request(config).then((response) => {
266
+ function http(axios11, config) {
267
+ return axios11.request(config).then((response) => {
270
268
  const { status, data } = response;
271
269
  return "error" in data ? { ok: false, status, error: data.error } : { ok: true, status, data: data.data };
272
270
  }, (err) => {
@@ -277,26 +275,26 @@ function http(axios10, config) {
277
275
  return { ok: false, status: 500, error: err.message };
278
276
  });
279
277
  }
280
- function httpGET(axios10, request, transformParams) {
278
+ function httpGET(axios11, request, transformParams) {
281
279
  const config = typeof request === "string" ? { url: request } : request;
282
- return (params) => http(axios10, {
280
+ return (params) => http(axios11, {
283
281
  ...config,
284
282
  method: "GET",
285
283
  params: transformParams ? transformParams(params) : params
286
284
  });
287
285
  }
288
- function httpPOST(axios10, request, transformPayload) {
286
+ function httpPOST(axios11, request, transformPayload) {
289
287
  const config = typeof request === "string" ? { url: request } : request;
290
- return (payload) => http(axios10, {
288
+ return (payload) => http(axios11, {
291
289
  ...config,
292
290
  method: "POST",
293
291
  data: transformPayload ? transformPayload(payload) : payload
294
292
  });
295
293
  }
296
- function httpDELETE(axios10, request, transformPayload) {
294
+ function httpDELETE(axios11, request, transformPayload) {
297
295
  const config = typeof request === "string" ? { url: request } : request;
298
296
  return (payload) => {
299
- return http(axios10, {
297
+ return http(axios11, {
300
298
  ...config,
301
299
  method: "DELETE",
302
300
  params: transformPayload ? transformPayload(payload) : payload
@@ -310,8 +308,8 @@ var init_lib = __esm({
310
308
  });
311
309
 
312
310
  // api/http/image/imageSave.ts
313
- function httpImageSaveFactory(axios10, provider) {
314
- return (params) => http(axios10, {
311
+ function httpImageSaveFactory(axios11, provider) {
312
+ return (params) => http(axios11, {
315
313
  method: "POST",
316
314
  url: `images/save/${provider}`,
317
315
  params: { prompt: params.prompt, provider }
@@ -325,8 +323,8 @@ var init_imageSave = __esm({
325
323
  });
326
324
 
327
325
  // api/http/image/imageSearch.ts
328
- function httpImageSearchFactory(axios10, provider) {
329
- return (params) => http(axios10, {
326
+ function httpImageSearchFactory(axios11, provider) {
327
+ return (params) => http(axios11, {
330
328
  method: "GET",
331
329
  url: `images/search/${provider}`,
332
330
  params: { prompt: params.prompt, page: params.page, provider }
@@ -340,8 +338,8 @@ var init_imageSearch = __esm({
340
338
  });
341
339
 
342
340
  // api/http/icon/listIcons.ts
343
- function httpListIconsFactory(axios10, provider) {
344
- return () => http(axios10, {
341
+ function httpListIconsFactory(axios11, provider) {
342
+ return () => http(axios11, {
345
343
  method: "GET",
346
344
  url: `list/icons/${provider}`,
347
345
  params: { provider }
@@ -355,8 +353,8 @@ var init_listIcons = __esm({
355
353
  });
356
354
 
357
355
  // api/http/icon/readIcon.ts
358
- function httpReadIconFactory(axios10, provider) {
359
- return (params) => http(axios10, {
356
+ function httpReadIconFactory(axios11, provider) {
357
+ return (params) => http(axios11, {
360
358
  method: "GET",
361
359
  url: `read/icons/${provider}/icon.svg`,
362
360
  params: { name: params.name }
@@ -369,65 +367,65 @@ var init_readIcon = __esm({
369
367
  }
370
368
  });
371
369
  function apiFactory(baseURL) {
372
- const axios10 = axios.create({ baseURL });
370
+ const axios11 = axios.create({ baseURL });
373
371
  return {
374
- createBulkBlock: httpPOST(axios10, "create/bulk/block"),
375
- createBlock: httpPOST(axios10, "create/block"),
376
- createDimension: httpPOST(axios10, "create/dimension"),
377
- createFormatter: httpPOST(axios10, "create/formatter"),
378
- createReport: httpPOST(axios10, "create/report"),
379
- createSection: httpPOST(axios10, "create/section"),
380
- createVariant: httpPOST(axios10, "create/variant"),
381
- deleteBlock: httpDELETE(axios10, "delete/block", transformDeletePayload),
382
- deleteDimension: httpDELETE(axios10, "delete/dimension", transformDeletePayload),
383
- deleteFormatter: httpDELETE(axios10, "delete/formatter", transformDeletePayload),
384
- deleteReport: httpDELETE(axios10, "delete/report", transformDeletePayload),
385
- deleteSection: httpDELETE(axios10, "delete/section", transformDeletePayload),
386
- deleteVariant: httpDELETE(axios10, "delete/variant", transformDeletePayload),
387
- readBlock: httpGET(axios10, "read/block"),
388
- readDimension: httpGET(axios10, "read/dimension"),
389
- readFormatter: httpGET(axios10, "read/formatter"),
390
- readReport: httpGET(axios10, "read/report"),
391
- readSection: httpGET(axios10, "read/section"),
392
- readVariant: httpGET(axios10, "read/variant"),
393
- updateBulkBlock: httpPOST(axios10, "update/bulk/block"),
394
- updateBlock: httpPOST(axios10, "update/block"),
395
- updateDimension: httpPOST(axios10, "update/dimension"),
396
- updateFormatter: httpPOST(axios10, "update/formatter"),
397
- updateReport: httpPOST(axios10, "update/report"),
398
- updateSection: httpPOST(axios10, "update/section"),
399
- updateVariant: httpPOST(axios10, "update/variant"),
400
- searchReport: httpGET(axios10, "search/reports"),
401
- validateVariantSlug: httpGET(axios10, "validate/variant"),
402
- readMember: httpGET(axios10, "read/members", transformReadMembers),
403
- readMemberImage: httpGET(axios10, {
372
+ createBulkBlock: httpPOST(axios11, "create/bulk/block"),
373
+ createBlock: httpPOST(axios11, "create/block"),
374
+ createDimension: httpPOST(axios11, "create/dimension"),
375
+ createFormatter: httpPOST(axios11, "create/formatter"),
376
+ createReport: httpPOST(axios11, "create/report"),
377
+ createSection: httpPOST(axios11, "create/section"),
378
+ createVariant: httpPOST(axios11, "create/variant"),
379
+ deleteBlock: httpDELETE(axios11, "delete/block", transformDeletePayload),
380
+ deleteDimension: httpDELETE(axios11, "delete/dimension", transformDeletePayload),
381
+ deleteFormatter: httpDELETE(axios11, "delete/formatter", transformDeletePayload),
382
+ deleteReport: httpDELETE(axios11, "delete/report", transformDeletePayload),
383
+ deleteSection: httpDELETE(axios11, "delete/section", transformDeletePayload),
384
+ deleteVariant: httpDELETE(axios11, "delete/variant", transformDeletePayload),
385
+ readBlock: httpGET(axios11, "read/block"),
386
+ readDimension: httpGET(axios11, "read/dimension"),
387
+ readFormatter: httpGET(axios11, "read/formatter"),
388
+ readReport: httpGET(axios11, "read/report"),
389
+ readSection: httpGET(axios11, "read/section"),
390
+ readVariant: httpGET(axios11, "read/variant"),
391
+ updateBulkBlock: httpPOST(axios11, "update/bulk/block"),
392
+ updateBlock: httpPOST(axios11, "update/block"),
393
+ updateDimension: httpPOST(axios11, "update/dimension"),
394
+ updateFormatter: httpPOST(axios11, "update/formatter"),
395
+ updateReport: httpPOST(axios11, "update/report"),
396
+ updateSection: httpPOST(axios11, "update/section"),
397
+ updateVariant: httpPOST(axios11, "update/variant"),
398
+ searchReport: httpGET(axios11, "search/reports"),
399
+ validateVariantSlug: httpGET(axios11, "validate/variant"),
400
+ readMember: httpGET(axios11, "read/members", transformReadMembers),
401
+ readMemberImage: httpGET(axios11, {
404
402
  url: "member/image",
405
403
  responseType: "blob"
406
404
  }),
407
- searchMember: httpGET(axios10, "search/members"),
408
- updateMember: httpGET(axios10, "update/members"),
409
- imageLocalSearch: httpImageSearchFactory(axios10, "local"),
410
- imageLocalSave: httpImageSaveFactory(axios10, "local"),
411
- imageFlickrSearch: httpImageSearchFactory(axios10, "flickr"),
412
- imageFlickrSave: httpImageSaveFactory(axios10, "flickr"),
413
- imageUnsplashSearch: httpImageSearchFactory(axios10, "unsplash"),
414
- imageUnsplashSave: httpImageSaveFactory(axios10, "unsplash"),
415
- imageUploadSave: httpImageSaveFactory(axios10, "upload"),
416
- imageAdobeSearch: httpImageSearchFactory(axios10, "adobe"),
417
- imageAdobeSave: httpImageSaveFactory(axios10, "adobe"),
418
- readMetadata: httpGET(axios10, "read/metadata"),
419
- regenerateSearch: httpPOST(axios10, "search/regenerate"),
420
- urlProxy: httpGET(axios10, "url/proxy"),
421
- searchRole: httpGET(axios10, "auth/search/roles"),
422
- searchUser: httpGET(axios10, "auth/search/users"),
423
- readUser: httpGET(axios10, "auth/read/user"),
424
- updateUser: httpPOST(axios10, "auth/update/user"),
425
- updateMyData: httpPOST(axios10, "auth/update/me"),
426
- revalidateReport: httpGET(axios10, "revalidate/report"),
427
- revalidateUrl: httpGET(axios10, "revalidate/url"),
428
- readPrivateBlocks: httpPOST(axios10, "read/blocks/private"),
429
- listTablerIcons: httpListIconsFactory(axios10, "tabler"),
430
- readTablerIcon: httpReadIconFactory(axios10, "tabler")
405
+ searchMember: httpGET(axios11, "search/members"),
406
+ updateMember: httpGET(axios11, "update/members"),
407
+ imageLocalSearch: httpImageSearchFactory(axios11, "local"),
408
+ imageLocalSave: httpImageSaveFactory(axios11, "local"),
409
+ imageFlickrSearch: httpImageSearchFactory(axios11, "flickr"),
410
+ imageFlickrSave: httpImageSaveFactory(axios11, "flickr"),
411
+ imageUnsplashSearch: httpImageSearchFactory(axios11, "unsplash"),
412
+ imageUnsplashSave: httpImageSaveFactory(axios11, "unsplash"),
413
+ imageUploadSave: httpImageSaveFactory(axios11, "upload"),
414
+ imageAdobeSearch: httpImageSearchFactory(axios11, "adobe"),
415
+ imageAdobeSave: httpImageSaveFactory(axios11, "adobe"),
416
+ readMetadata: httpGET(axios11, "read/metadata"),
417
+ regenerateSearch: httpPOST(axios11, "search/regenerate"),
418
+ urlProxy: httpGET(axios11, "url/proxy"),
419
+ searchRole: httpGET(axios11, "auth/search/roles"),
420
+ searchUser: httpGET(axios11, "auth/search/users"),
421
+ readUser: httpGET(axios11, "auth/read/user"),
422
+ updateUser: httpPOST(axios11, "auth/update/user"),
423
+ updateMyData: httpPOST(axios11, "auth/update/me"),
424
+ revalidateReport: httpGET(axios11, "revalidate/report"),
425
+ revalidateUrl: httpGET(axios11, "revalidate/url"),
426
+ readPrivateBlocks: httpPOST(axios11, "read/blocks/private"),
427
+ listTablerIcons: httpListIconsFactory(axios11, "tabler"),
428
+ readTablerIcon: httpReadIconFactory(axios11, "tabler")
431
429
  };
432
430
  }
433
431
  var transformDeletePayload, transformReadMembers;
@@ -4095,7 +4093,7 @@ var init_store = __esm({
4095
4093
  storeWrapper = createWrapper(storeFactory);
4096
4094
  }
4097
4095
  });
4098
- function withFetcher(Component, useRef16) {
4096
+ function withFetcher(Component, useRef14) {
4099
4097
  const ComponentWithFetcher = (props) => {
4100
4098
  const {
4101
4099
  id,
@@ -4103,7 +4101,7 @@ function withFetcher(Component, useRef16) {
4103
4101
  skelWidth,
4104
4102
  ...otherProps
4105
4103
  } = props;
4106
- const ref = useRef16(id);
4104
+ const ref = useRef14(id);
4107
4105
  if (ref.isUninitialized || ref.isFetching) {
4108
4106
  return /* @__PURE__ */ jsx(Skeleton, { width: skelWidth, height: skelHeight });
4109
4107
  }
@@ -5727,41 +5725,6 @@ var init_useScrollToAnchor = __esm({
5727
5725
  init_esm_shims();
5728
5726
  }
5729
5727
  });
5730
- function useEmbed() {
5731
- const loaded = useRef(false);
5732
- useEffect(() => {
5733
- let prevRequests = 0;
5734
- let thresholdCount = 0;
5735
- const interval = setInterval(() => {
5736
- const resources = performance.getEntriesByType("resource");
5737
- const allRequestsComplete = resources.every((resource) => resource.responseEnd > 0);
5738
- if (prevRequests === resources.length && allRequestsComplete && thresholdCount > 3) {
5739
- clearInterval(interval);
5740
- if (!loaded.current) {
5741
- setTimeout(() => {
5742
- window.parent.postMessage("iframe-loaded", "*");
5743
- }, 500);
5744
- loaded.current = true;
5745
- }
5746
- } else {
5747
- if (resources.length > prevRequests) {
5748
- thresholdCount = 0;
5749
- } else {
5750
- thresholdCount++;
5751
- }
5752
- prevRequests = resources.length;
5753
- }
5754
- }, 300);
5755
- if (typeof document !== "undefined") {
5756
- initFrameAndPoll();
5757
- }
5758
- }, []);
5759
- }
5760
- var init_useEmbed = __esm({
5761
- "frontend/hooks/useEmbed.ts"() {
5762
- init_esm_shims();
5763
- }
5764
- });
5765
5728
  function createOutline(elements, variables) {
5766
5729
  const roots = [];
5767
5730
  const nodes = {};
@@ -5879,7 +5842,6 @@ var init_hooks2 = __esm({
5879
5842
  init_useInitialState();
5880
5843
  init_useOnChangeSelector();
5881
5844
  init_useScrollToAnchor();
5882
- init_useEmbed();
5883
5845
  init_useContentOutline();
5884
5846
  }
5885
5847
  });
@@ -15979,25 +15941,6 @@ var init_DataTab = __esm({
15979
15941
  };
15980
15942
  }
15981
15943
  });
15982
- function CopyInput(props) {
15983
- const { title, url, disabled = false } = props;
15984
- const Icon = disabled ? IconCode : IconLink;
15985
- return /* @__PURE__ */ jsx(Input.Wrapper, { label: title, children: /* @__PURE__ */ jsx(
15986
- Input,
15987
- {
15988
- icon: /* @__PURE__ */ jsx(Icon, {}),
15989
- value: url,
15990
- disabled,
15991
- readOnly: true,
15992
- rightSection: /* @__PURE__ */ jsx(CopyButton, { value: url, timeout: 2e3, children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, { label: copied ? "Copied" : "Copy", withArrow: true, position: "right", children: /* @__PURE__ */ jsx(ActionIcon, { variant: copied ? "filled" : "subtle", onClick: copy, disabled, children: copied ? /* @__PURE__ */ jsx(IconClipboardCheck, { size: 16 }) : /* @__PURE__ */ jsx(IconClipboardCopy, { size: 16 }) }) }) })
15993
- }
15994
- ) });
15995
- }
15996
- var init_CopyInput = __esm({
15997
- "components/options/CopyInput.tsx"() {
15998
- init_esm_shims();
15999
- }
16000
- });
16001
15944
  function useLocalePrefix() {
16002
15945
  const { locales: locales4 } = useRouter();
16003
15946
  const locale = location && location.pathname.split("/").filter((segment) => segment.length > 0)[0];
@@ -16008,147 +15951,12 @@ var init_useLocalePrefix = __esm({
16008
15951
  init_esm_shims();
16009
15952
  }
16010
15953
  });
16011
- function useEmbedPath(sectionId, vizId = void 0, queryParams = {}) {
16012
- const { query, locale } = useRouter();
16013
- const prefixLocale = useLocalePrefix();
16014
- const { pathSegment } = useBespoke();
16015
- const slugs = query[pathSegment];
16016
- if (slugs && Array.isArray(slugs)) {
16017
- const search = new URLSearchParams({ section: sectionId, ...queryParams });
16018
- if (vizId) {
16019
- search.set("viz", vizId);
16020
- }
16021
- const embed = `/embed/${slugs.join("/")}`;
16022
- const url = new URL(
16023
- prefixLocale ? `/${locale}${embed}` : `${embed}`,
16024
- location.href
16025
- );
16026
- url.search = search.toString();
16027
- return url.href;
16028
- }
16029
- return void 0;
16030
- }
16031
- function ShareTab(props) {
16032
- const { section } = props;
16033
- const [shareUrl, setShareUrl] = useState("");
16034
- const [title, setTitle] = useState("");
16035
- const [includeSection, setIncludeSection] = useState(true);
16036
- const optionsTranslations = useBespokeTranslations("options");
16037
- const embedPath = useEmbedPath(section.id);
16038
- const iframeStr = `<iframe width="100%" height="500px" frameborder="0" src="${embedPath}"></iframe>`;
16039
- const translations = { ...DEFAULT_TRANSLATIONS3, ...optionsTranslations["share_tab"] };
16040
- useEffect(() => {
16041
- setTitle(document.title);
16042
- const { origin, pathname } = window.location;
16043
- const url = `${origin}${pathname}`;
16044
- const sectionAnchor = includeSection ? `#section-${section.id}` : "";
16045
- setShareUrl(`${url}${sectionAnchor}`);
16046
- }, [includeSection]);
16047
- return /* @__PURE__ */ jsxs(Stack, { className: "cms-section-options-share", children: [
16048
- /* @__PURE__ */ jsx(Space, { h: "xs" }),
16049
- /* @__PURE__ */ jsx(CopyInput, { title: `${translations["title"]}: ${title}`, url: shareUrl }),
16050
- /* @__PURE__ */ jsx(
16051
- Checkbox,
16052
- {
16053
- checked: includeSection,
16054
- label: translations["include_section"],
16055
- onChange: () => setIncludeSection(!includeSection),
16056
- radius: "xl"
16057
- }
16058
- ),
16059
- /* @__PURE__ */ jsx(CopyInput, { title: translations["embed"], url: iframeStr }),
16060
- /* @__PURE__ */ jsxs(Group, { position: "center", children: [
16061
- /* @__PURE__ */ jsx(
16062
- FacebookShareButton,
16063
- {
16064
- url: shareUrl,
16065
- quote: title,
16066
- children: /* @__PURE__ */ jsx(FacebookIcon, { size: 32, round: true })
16067
- }
16068
- ),
16069
- /* @__PURE__ */ jsx(
16070
- TwitterShareButton,
16071
- {
16072
- url: shareUrl,
16073
- title,
16074
- children: /* @__PURE__ */ jsx(TwitterIcon, { size: 32, round: true })
16075
- }
16076
- ),
16077
- /* @__PURE__ */ jsx(
16078
- TelegramShareButton,
16079
- {
16080
- url: shareUrl,
16081
- title,
16082
- children: /* @__PURE__ */ jsx(TelegramIcon, { size: 32, round: true })
16083
- }
16084
- ),
16085
- /* @__PURE__ */ jsx(
16086
- WhatsappShareButton,
16087
- {
16088
- url: shareUrl,
16089
- title,
16090
- separator: ":: ",
16091
- children: /* @__PURE__ */ jsx(WhatsappIcon, { size: 32, round: true })
16092
- }
16093
- ),
16094
- /* @__PURE__ */ jsx(LinkedinShareButton, { url: shareUrl, children: /* @__PURE__ */ jsx(LinkedinIcon, { size: 32, round: true }) }),
16095
- /* @__PURE__ */ jsx(
16096
- RedditShareButton,
16097
- {
16098
- url: shareUrl,
16099
- title,
16100
- windowWidth: 660,
16101
- windowHeight: 460,
16102
- children: /* @__PURE__ */ jsx(RedditIcon, { size: 32, round: true })
16103
- }
16104
- ),
16105
- /* @__PURE__ */ jsx(
16106
- EmailShareButton,
16107
- {
16108
- url: shareUrl,
16109
- subject: title,
16110
- children: /* @__PURE__ */ jsx(EmailIcon, { size: 32, round: true })
16111
- }
16112
- )
16113
- ] })
16114
- ] });
16115
- }
16116
- var DEFAULT_TRANSLATIONS3;
16117
- var init_ShareTab = __esm({
16118
- "components/options/tabs/ShareTab.tsx"() {
16119
- init_esm_shims();
16120
- init_CopyInput();
16121
- init_TranslationsProvider();
16122
- init_useLocalePrefix();
16123
- init_ResourceProvider();
16124
- DEFAULT_TRANSLATIONS3 = {
16125
- "title": "Title",
16126
- "include_section": "Include Section",
16127
- "embed": "Embed"
16128
- };
16129
- }
16130
- });
16131
- function exportSVG(svg) {
16132
- const serializer = new XMLSerializer();
16133
- let source = serializer.serializeToString(svg);
16134
- if (!source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)) {
16135
- source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
16136
- }
16137
- if (!source.match(/^<svg[^>]+"http\:\/\/www\.w3\.org\/1999\/xlink"/)) {
16138
- source = source.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
16139
- }
16140
- source = '<?xml version="1.0" standalone="no"?>\r\n' + source;
16141
- const url = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(source);
16142
- const a2 = document.createElement("a");
16143
- const e = new MouseEvent("click");
16144
- a2.download = "image.svg";
16145
- a2.href = url;
16146
- a2.dispatchEvent(e);
16147
- }
16148
15954
  function ImageTab(props) {
16149
15955
  const { section } = props;
16150
15956
  const blocksIds = section.blocks || [];
16151
- const framer = useRef();
15957
+ const { asPath, query, locale } = useRouter();
15958
+ const prefixLocale = useLocalePrefix();
15959
+ const { pathSegment } = useBespoke();
16152
15960
  const vizAvailable = useAppSelector((state) => blocksIds.map((blockId) => state.records.entities.block[blockId]).filter((block) => block.type === "visualization" && state.variables.status[block.id].allowed));
16153
15961
  const [imageContext, setImageContext] = useState(contextOptions.section);
16154
15962
  const [imageFormat, setImageFormat] = useState(formatOptions2.png);
@@ -16158,13 +15966,62 @@ function ImageTab(props) {
16158
15966
  const [transparentBackground, setTransparentBackground] = useState(false);
16159
15967
  const [loadingPreviews, setLoadingPreviews] = useState(false);
16160
15968
  const optionsTranslations = useBespokeTranslations("options");
16161
- const translations = { ...DEFAULT_TRANSLATIONS4, ...optionsTranslations["image_tab"] };
16162
- const search = Object.fromEntries(new URLSearchParams(location.search));
16163
- const selectors = Object.fromEntries(
16164
- Object.entries(search).filter(([key]) => key.match(/selector\d+id/))
16165
- );
15969
+ const translations = { ...DEFAULT_TRANSLATIONS3, ...optionsTranslations["image_tab"] };
16166
15970
  const sectionId = slugify_default(section?.settings?.name) || `section-${section.id}`;
16167
- const embedPath = useEmbedPath(section.id, imageContext === contextOptions.viz ? vizSelected : void 0, selectors);
15971
+ const onSaveClickSection = async () => {
15972
+ const search = Object.fromEntries(new URLSearchParams(location.search));
15973
+ const selectors = Object.fromEntries(
15974
+ Object.entries(search).filter(([key]) => key.match(/selector\d+id/))
15975
+ );
15976
+ const body = {
15977
+ path: asPath,
15978
+ slugs: JSON.stringify(query[pathSegment]),
15979
+ section: section.id,
15980
+ query: selectors,
15981
+ locale,
15982
+ prefixLocale,
15983
+ transparent: transparentBackground
15984
+ };
15985
+ setImageProcessing(true);
15986
+ axios.post("/api/cms/img", body, { responseType: "blob" }).then((res) => {
15987
+ const url = window.URL.createObjectURL(new Blob([res.data]));
15988
+ const link = document.createElement("a");
15989
+ link.href = url;
15990
+ link.setAttribute("download", "image.png");
15991
+ document.body.appendChild(link);
15992
+ link.click();
15993
+ document.body.removeChild(link);
15994
+ setImageProcessing(false);
15995
+ });
15996
+ };
15997
+ const onSaveClickViz = async () => {
15998
+ const search = Object.fromEntries(new URLSearchParams(location.search));
15999
+ const selectors = Object.fromEntries(
16000
+ Object.entries(search).filter(([key]) => key.match(/selector\d+id/))
16001
+ );
16002
+ const body = {
16003
+ path: asPath,
16004
+ slugs: JSON.stringify(query[pathSegment]),
16005
+ section: section.id,
16006
+ viz: vizSelected,
16007
+ query: selectors,
16008
+ locale,
16009
+ prefixLocale,
16010
+ transparent: transparentBackground,
16011
+ svg: imageFormat === formatOptions2.svg
16012
+ };
16013
+ setImageProcessing(true);
16014
+ axios.post("/api/cms/img", body, { responseType: "blob" }).then((res) => {
16015
+ const url = window.URL.createObjectURL(new Blob([res.data]));
16016
+ const link = document.createElement("a");
16017
+ link.href = url;
16018
+ link.setAttribute("download", `image.${imageFormat.toLowerCase()}`);
16019
+ document.body.appendChild(link);
16020
+ link.click();
16021
+ document.body.removeChild(link);
16022
+ setImageProcessing(false);
16023
+ });
16024
+ };
16168
16025
  const generatePreviews = async () => {
16169
16026
  const { toCanvas } = await import('html-to-image');
16170
16027
  Promise.all(
@@ -16190,54 +16047,6 @@ function ImageTab(props) {
16190
16047
  const onSelectViz = (vizId) => () => {
16191
16048
  setVizSelected(vizId);
16192
16049
  };
16193
- const onDownloadIframe = () => {
16194
- setImageProcessing(true);
16195
- const container = document.getElementById("iframe-preview");
16196
- framer.current = new Framer(container, { src: embedPath });
16197
- };
16198
- useEffect(() => {
16199
- const onIframeLoad = async (e) => {
16200
- if (e.data === "iframe-loaded") {
16201
- if (imageFormat === formatOptions2.png) {
16202
- const iframe = document.querySelector("#iframe-preview > iframe");
16203
- const doc = iframe.contentDocument;
16204
- const node = doc?.querySelector(".bespoke-Section-root");
16205
- const fontEmbedCSS = await getFontEmbedCSS(node);
16206
- toPng(
16207
- node,
16208
- {
16209
- backgroundColor: transparentBackground ? "transparent" : "white",
16210
- cacheBust: true,
16211
- skipFonts: true,
16212
- fontEmbedCSS
16213
- }
16214
- ).then(function(dataUrl) {
16215
- const link = document.createElement("a");
16216
- link.href = dataUrl;
16217
- link.setAttribute("download", `${sectionId}.png`);
16218
- document.body.appendChild(link);
16219
- link.click();
16220
- document.body.removeChild(link);
16221
- setImageProcessing(false);
16222
- if (typeof framer.current?.remove === "function") {
16223
- framer.current.remove();
16224
- }
16225
- }).catch(function(error) {
16226
- console.error("oops, something went wrong!", error);
16227
- setImageProcessing(false);
16228
- });
16229
- } else {
16230
- const iframe = document.querySelector("#iframe-preview > iframe");
16231
- const doc = iframe.contentDocument;
16232
- const svg = doc?.querySelector("svg.d3plus-viz");
16233
- if (svg)
16234
- exportSVG(svg);
16235
- }
16236
- }
16237
- };
16238
- window.addEventListener("message", onIframeLoad);
16239
- return () => window.removeEventListener("message", onIframeLoad);
16240
- }, [transparentBackground]);
16241
16050
  return /* @__PURE__ */ jsxs(Stack, { className: "cms-section-options-image", children: [
16242
16051
  /* @__PURE__ */ jsx(Space, { h: "xs" }),
16243
16052
  vizAvailable.length > 0 ? /* @__PURE__ */ jsx(Input.Wrapper, { label: `${translations["choose_area"]}:`, children: /* @__PURE__ */ jsx(
@@ -16284,7 +16093,28 @@ function ImageTab(props) {
16284
16093
  ]
16285
16094
  }
16286
16095
  ),
16287
- /* @__PURE__ */ jsx("div", { style: { height: 0, overflow: "scroll" }, children: /* @__PURE__ */ jsx("div", { id: "iframe-preview", "data-frame-src": embedPath, style: { width: 1366 } }) }),
16096
+ imageContext !== contextOptions.section && /* @__PURE__ */ jsx(Input.Wrapper, { label: `${translations["choose_format"]}:`, children: /* @__PURE__ */ jsxs(Button.Group, { children: [
16097
+ /* @__PURE__ */ jsx(
16098
+ Button,
16099
+ {
16100
+ leftIcon: /* @__PURE__ */ jsx(IconPolaroid, { size: 16 }),
16101
+ onClick: () => setImageFormat(formatOptions2.png),
16102
+ variant: imageFormat === formatOptions2.png ? "filled" : "default",
16103
+ fullWidth: true,
16104
+ children: formatOptions2.png
16105
+ }
16106
+ ),
16107
+ /* @__PURE__ */ jsx(
16108
+ Button,
16109
+ {
16110
+ leftIcon: /* @__PURE__ */ jsx(IconCode, { size: 16 }),
16111
+ onClick: () => setImageFormat(formatOptions2.svg),
16112
+ variant: imageFormat === formatOptions2.svg ? "filled" : "default",
16113
+ fullWidth: true,
16114
+ children: formatOptions2.svg
16115
+ }
16116
+ )
16117
+ ] }) }),
16288
16118
  imageFormat !== formatOptions2.svg && /* @__PURE__ */ jsx(
16289
16119
  Checkbox,
16290
16120
  {
@@ -16299,25 +16129,26 @@ function ImageTab(props) {
16299
16129
  {
16300
16130
  leftIcon: /* @__PURE__ */ jsx(IconDownload, { size: 16 }),
16301
16131
  loading: imageProcessing,
16302
- onClick: onDownloadIframe,
16132
+ onClick: imageContext === contextOptions.section ? onSaveClickSection : onSaveClickViz,
16303
16133
  fullWidth: true,
16304
16134
  children: /* @__PURE__ */ jsx("span", { children: imageProcessing ? translations["processing"] : translations["download"].replace("{imageFormat}", imageFormat) })
16305
16135
  }
16306
16136
  )
16307
16137
  ] });
16308
16138
  }
16309
- var contextOptions, formatOptions2, VizPreview2, DEFAULT_TRANSLATIONS4, getSectionNode, getVizNode;
16139
+ var contextOptions, formatOptions2, VizPreview2, DEFAULT_TRANSLATIONS3, getSectionNode, getVizNode;
16310
16140
  var init_ImageTab = __esm({
16311
16141
  "components/options/tabs/ImageTab.tsx"() {
16312
16142
  init_esm_shims();
16313
16143
  init_slugify();
16314
16144
  init_store2();
16315
16145
  init_TranslationsProvider();
16146
+ init_ResourceProvider();
16147
+ init_useLocalePrefix();
16316
16148
  init_d3plusPropify();
16317
16149
  init_varSwapRecursive();
16318
16150
  init_getBlockContent();
16319
16151
  init_hooks();
16320
- init_ShareTab();
16321
16152
  contextOptions = {
16322
16153
  viz: "viz",
16323
16154
  section: "section"
@@ -16371,7 +16202,7 @@ var init_ImageTab = __esm({
16371
16202
  )
16372
16203
  ] });
16373
16204
  };
16374
- DEFAULT_TRANSLATIONS4 = {
16205
+ DEFAULT_TRANSLATIONS3 = {
16375
16206
  "wrong_node": "Wrong node in export",
16376
16207
  "choose_area": "Choose image area",
16377
16208
  "choose_viz": "Choose visualization",
@@ -16384,6 +16215,7 @@ var init_ImageTab = __esm({
16384
16215
  "source": "Source: {src}"
16385
16216
  };
16386
16217
  getSectionNode = (sectionId) => {
16218
+ console.log({ sectionId });
16387
16219
  const sectionNode = document.getElementById(sectionId);
16388
16220
  return select(sectionNode).select(".bespoke-section-content").node();
16389
16221
  };
@@ -16393,6 +16225,142 @@ var init_ImageTab = __esm({
16393
16225
  };
16394
16226
  }
16395
16227
  });
16228
+ function CopyInput(props) {
16229
+ const { title, url, disabled = false } = props;
16230
+ const Icon = disabled ? IconCode : IconLink;
16231
+ return /* @__PURE__ */ jsx(Input.Wrapper, { label: title, children: /* @__PURE__ */ jsx(
16232
+ Input,
16233
+ {
16234
+ icon: /* @__PURE__ */ jsx(Icon, {}),
16235
+ value: url,
16236
+ disabled,
16237
+ readOnly: true,
16238
+ rightSection: /* @__PURE__ */ jsx(CopyButton, { value: url, timeout: 2e3, children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, { label: copied ? "Copied" : "Copy", withArrow: true, position: "right", children: /* @__PURE__ */ jsx(ActionIcon, { variant: copied ? "filled" : "subtle", onClick: copy, disabled, children: copied ? /* @__PURE__ */ jsx(IconClipboardCheck, { size: 16 }) : /* @__PURE__ */ jsx(IconClipboardCopy, { size: 16 }) }) }) })
16239
+ }
16240
+ ) });
16241
+ }
16242
+ var init_CopyInput = __esm({
16243
+ "components/options/CopyInput.tsx"() {
16244
+ init_esm_shims();
16245
+ }
16246
+ });
16247
+ function useEmbedPath(sectionId) {
16248
+ const { query, locale } = useRouter();
16249
+ const prefixLocale = useLocalePrefix();
16250
+ const { pathSegment } = useBespoke();
16251
+ const slugs = query[pathSegment];
16252
+ if (slugs && Array.isArray(slugs)) {
16253
+ const search = new URLSearchParams({ section: sectionId });
16254
+ const embed = `/embed/${slugs.join("/")}`;
16255
+ const url = new URL(
16256
+ prefixLocale ? `/${locale}${embed}` : `${embed}`,
16257
+ location.href
16258
+ );
16259
+ url.search = search.toString();
16260
+ return url.href;
16261
+ }
16262
+ return void 0;
16263
+ }
16264
+ function ShareTab(props) {
16265
+ const { section } = props;
16266
+ const [shareUrl, setShareUrl] = useState("");
16267
+ const [title, setTitle] = useState("");
16268
+ const [includeSection, setIncludeSection] = useState(true);
16269
+ const optionsTranslations = useBespokeTranslations("options");
16270
+ const embedPath = useEmbedPath(section.id);
16271
+ const iframeStr = `<iframe width="100%" height="500px" frameborder="0" src="${embedPath}"></iframe>`;
16272
+ const translations = { ...DEFAULT_TRANSLATIONS4, ...optionsTranslations["share_tab"] };
16273
+ useEffect(() => {
16274
+ setTitle(document.title);
16275
+ const { origin, pathname } = window.location;
16276
+ const url = `${origin}${pathname}`;
16277
+ const sectionAnchor = includeSection ? `#section-${section.id}` : "";
16278
+ setShareUrl(`${url}${sectionAnchor}`);
16279
+ }, [includeSection]);
16280
+ return /* @__PURE__ */ jsxs(Stack, { className: "cms-section-options-share", children: [
16281
+ /* @__PURE__ */ jsx(Space, { h: "xs" }),
16282
+ /* @__PURE__ */ jsx(CopyInput, { title: `${translations["title"]}: ${title}`, url: shareUrl }),
16283
+ /* @__PURE__ */ jsx(
16284
+ Checkbox,
16285
+ {
16286
+ checked: includeSection,
16287
+ label: translations["include_section"],
16288
+ onChange: () => setIncludeSection(!includeSection),
16289
+ radius: "xl"
16290
+ }
16291
+ ),
16292
+ /* @__PURE__ */ jsx(CopyInput, { title: translations["embed"], url: iframeStr }),
16293
+ /* @__PURE__ */ jsxs(Group, { position: "center", children: [
16294
+ /* @__PURE__ */ jsx(
16295
+ FacebookShareButton,
16296
+ {
16297
+ url: shareUrl,
16298
+ quote: title,
16299
+ children: /* @__PURE__ */ jsx(FacebookIcon, { size: 32, round: true })
16300
+ }
16301
+ ),
16302
+ /* @__PURE__ */ jsx(
16303
+ TwitterShareButton,
16304
+ {
16305
+ url: shareUrl,
16306
+ title,
16307
+ children: /* @__PURE__ */ jsx(TwitterIcon, { size: 32, round: true })
16308
+ }
16309
+ ),
16310
+ /* @__PURE__ */ jsx(
16311
+ TelegramShareButton,
16312
+ {
16313
+ url: shareUrl,
16314
+ title,
16315
+ children: /* @__PURE__ */ jsx(TelegramIcon, { size: 32, round: true })
16316
+ }
16317
+ ),
16318
+ /* @__PURE__ */ jsx(
16319
+ WhatsappShareButton,
16320
+ {
16321
+ url: shareUrl,
16322
+ title,
16323
+ separator: ":: ",
16324
+ children: /* @__PURE__ */ jsx(WhatsappIcon, { size: 32, round: true })
16325
+ }
16326
+ ),
16327
+ /* @__PURE__ */ jsx(LinkedinShareButton, { url: shareUrl, children: /* @__PURE__ */ jsx(LinkedinIcon, { size: 32, round: true }) }),
16328
+ /* @__PURE__ */ jsx(
16329
+ RedditShareButton,
16330
+ {
16331
+ url: shareUrl,
16332
+ title,
16333
+ windowWidth: 660,
16334
+ windowHeight: 460,
16335
+ children: /* @__PURE__ */ jsx(RedditIcon, { size: 32, round: true })
16336
+ }
16337
+ ),
16338
+ /* @__PURE__ */ jsx(
16339
+ EmailShareButton,
16340
+ {
16341
+ url: shareUrl,
16342
+ subject: title,
16343
+ children: /* @__PURE__ */ jsx(EmailIcon, { size: 32, round: true })
16344
+ }
16345
+ )
16346
+ ] })
16347
+ ] });
16348
+ }
16349
+ var DEFAULT_TRANSLATIONS4;
16350
+ var init_ShareTab = __esm({
16351
+ "components/options/tabs/ShareTab.tsx"() {
16352
+ init_esm_shims();
16353
+ init_CopyInput();
16354
+ init_TranslationsProvider();
16355
+ init_useLocalePrefix();
16356
+ init_ResourceProvider();
16357
+ DEFAULT_TRANSLATIONS4 = {
16358
+ "title": "Title",
16359
+ "include_section": "Include Section",
16360
+ "embed": "Embed"
16361
+ };
16362
+ }
16363
+ });
16396
16364
  function OptionsModal(props) {
16397
16365
  const {
16398
16366
  section,
@@ -17522,8 +17490,8 @@ var init_Section = __esm({
17522
17490
  const slugs = Array.isArray(querySlugs) ? querySlugs?.join("/") : [];
17523
17491
  const prefixLocale = useLocalePrefix();
17524
17492
  let path = prefixLocale ? `/${locale}` : "";
17525
- path += `${profilePrefix}/${slugs}/`;
17526
- const link = new URL(path, window.location.href).href;
17493
+ path += `/${profilePrefix}/${slugs}/`;
17494
+ const link = new URL(path, window?.location.href).href;
17527
17495
  return /* @__PURE__ */ jsxs(Group, { position: "apart", noWrap: true, sx: { flexGrow: 1 }, children: [
17528
17496
  /* @__PURE__ */ jsx(Anchor, { href: link, target: "_blank", children: link }),
17529
17497
  /* @__PURE__ */ jsx(
@@ -17726,9 +17694,6 @@ var LoginButton_default = BespokeLoginBtn;
17726
17694
  init_esm_shims();
17727
17695
  var UserProvider_default = UserProvider;
17728
17696
 
17729
- // frontend/index.ts
17730
- init_hooks2();
17731
-
17732
17697
  // frontend/components/auth/withPageRoleAuthRequired.tsx
17733
17698
  init_esm_shims();
17734
17699
  function withPageRoleAuthRequired(params) {
@@ -25853,4 +25818,4 @@ function BespokeRenderer({
25853
25818
  ] }) });
25854
25819
  }
25855
25820
 
25856
- export { Explore_default as BespokeExplore, ExploreModal_default as BespokeExploreModal, LoginButton_default as BespokeLoginBtn, BespokeManager, BespokeRenderer, Report_default as BespokeReport, Search_default as BespokeSearch, UserProvider_default as BespokeUserProvider, withPageRoleAuthRequired as BespokeWithPageRoleAuthRequired, CMS_ROLES, DialogProvider, actions_exports as actions, storeWrapper, useAppDispatch as useBespokeDispatch, useAppSelector as useBespokeSelector, useUser_default as useBespokeUser, useDialog, useEmbed };
25821
+ export { Explore_default as BespokeExplore, ExploreModal_default as BespokeExploreModal, LoginButton_default as BespokeLoginBtn, BespokeManager, BespokeRenderer, Report_default as BespokeReport, Search_default as BespokeSearch, UserProvider_default as BespokeUserProvider, withPageRoleAuthRequired as BespokeWithPageRoleAuthRequired, CMS_ROLES, DialogProvider, actions_exports as actions, storeWrapper, useAppDispatch as useBespokeDispatch, useAppSelector as useBespokeSelector, useUser_default as useBespokeUser, useDialog };
package/dist/server.js CHANGED
@@ -26,6 +26,7 @@ import * as allIcons from '@tabler/icons-react';
26
26
  import NextCors from 'nextjs-cors';
27
27
  import imageType from 'image-type';
28
28
  import formidable from 'formidable';
29
+ import puppeteer from 'puppeteer';
29
30
  import DomParser from 'dom-parser';
30
31
  import path from 'path';
31
32
  import axiosRetry from 'axios-retry';
@@ -4219,6 +4220,242 @@ function endpointUpdateMyDataFactory(operations) {
4219
4220
  }
4220
4221
  }, []);
4221
4222
  }
4223
+ async function generatePDF(path2) {
4224
+ const width = 1920;
4225
+ const height = 1080;
4226
+ const maxTimeoutPerPage = 0;
4227
+ try {
4228
+ const browser = await puppeteer.launch({
4229
+ args: [
4230
+ `--window-size=${width},${height}`,
4231
+ "--start-fullscreen"
4232
+ ],
4233
+ defaultViewport: { width, height },
4234
+ headless: "new",
4235
+ ignoreHTTPSErrors: true
4236
+ });
4237
+ const url = new URL(path2);
4238
+ const searchParams = new URLSearchParams(url.search);
4239
+ searchParams.set("print", "true");
4240
+ url.search = searchParams.toString();
4241
+ const page = await browser.newPage();
4242
+ await page.goto(url.toString(), { waitUntil: "networkidle0", timeout: maxTimeoutPerPage });
4243
+ await page.waitForSelector("#bespoke-report", { timeout: maxTimeoutPerPage });
4244
+ await page.evaluate(async () => {
4245
+ await new Promise((resolve) => {
4246
+ const scrollInterval = setInterval(() => {
4247
+ window.scrollBy(0, window.innerHeight);
4248
+ }, 100);
4249
+ const checkNetworkActivity = () => {
4250
+ const activeRequests = performance.getEntriesByType("resource").filter((entry) => entry.duration === 0);
4251
+ return activeRequests.length === 0;
4252
+ };
4253
+ const checkInterval = setInterval(() => {
4254
+ if (checkNetworkActivity()) {
4255
+ clearInterval(scrollInterval);
4256
+ clearInterval(checkInterval);
4257
+ resolve();
4258
+ }
4259
+ }, 1e3);
4260
+ });
4261
+ });
4262
+ const pdf = await page.pdf({
4263
+ displayHeaderFooter: true,
4264
+ footerTemplate: `
4265
+ <div
4266
+ style="
4267
+ color: lightgray; border-top: solid lightgray 1px; font-size: 10px;
4268
+ padding-top: 5px; text-align: center; width: 100%;
4269
+ "
4270
+ >
4271
+ <p>Page <span class="pageNumber" /> of <span class="totalPages" /></p>
4272
+ </div>`,
4273
+ format: "A4",
4274
+ // Letter,Legal,Tabloid,Ledger,A0,A1,A2,A3,A4,A5,A6
4275
+ headerTemplate: `
4276
+ <div
4277
+ style="
4278
+ color: lightgray; border-bottom: solid lightgray 1px; font-size: 10px;
4279
+ padding-bottom: 5px; text-align: center; width: 100%;
4280
+ "
4281
+ >
4282
+ <p class="url" />
4283
+ </div>`,
4284
+ // height: `${height}px`,
4285
+ landscape: false,
4286
+ margin: {
4287
+ bottom: 70,
4288
+ // minimum required for footer msg to display
4289
+ left: 20,
4290
+ right: 20,
4291
+ top: 70
4292
+ // minimum required for header msg to display
4293
+ },
4294
+ omitBackground: true,
4295
+ pageRanges: "",
4296
+ // All
4297
+ // path: "./test.pdf" // Path to save the file
4298
+ preferCSSPageSize: false,
4299
+ // Give any CSS @page size declared in the page priority over what is declared in the width or height or format option.
4300
+ printBackground: true,
4301
+ scale: 1,
4302
+ // From 0.1 to 2
4303
+ tagged: false,
4304
+ // Generate tagged (accessible) PDF.
4305
+ timeout: 0
4306
+ // width: `${width}px`,
4307
+ });
4308
+ await browser.close();
4309
+ return pdf.toString("base64");
4310
+ } catch (error) {
4311
+ console.error("Error generating PDF:", error);
4312
+ throw error;
4313
+ }
4314
+ }
4315
+ function endpointDownloadReport() {
4316
+ return endpoint("POST", "pdf", async (req, res) => {
4317
+ const { slugs, path: path2 } = req.body;
4318
+ if (!slugs || !path2) {
4319
+ throw new BackendError(400, "Missing path parameter in request.");
4320
+ } else {
4321
+ const url = new URL(path2, req.headers["origin"]);
4322
+ const pdf = await generatePDF(url);
4323
+ return {
4324
+ ok: true,
4325
+ status: 200,
4326
+ data: pdf
4327
+ };
4328
+ }
4329
+ });
4330
+ }
4331
+ var minimal_args = [
4332
+ "--autoplay-policy=user-gesture-required",
4333
+ "--disable-background-networking",
4334
+ "--disable-background-timer-throttling",
4335
+ "--disable-backgrounding-occluded-windows",
4336
+ "--disable-breakpad",
4337
+ "--disable-client-side-phishing-detection",
4338
+ "--disable-component-update",
4339
+ "--disable-default-apps",
4340
+ "--disable-dev-shm-usage",
4341
+ "--disable-domain-reliability",
4342
+ "--disable-extensions",
4343
+ "--disable-features=AudioServiceOutOfProcess",
4344
+ "--disable-hang-monitor",
4345
+ "--disable-ipc-flooding-protection",
4346
+ "--disable-notifications",
4347
+ "--disable-offer-store-unmasked-wallet-cards",
4348
+ "--disable-popup-blocking",
4349
+ "--disable-print-preview",
4350
+ "--disable-prompt-on-repost",
4351
+ "--disable-renderer-backgrounding",
4352
+ "--disable-setuid-sandbox",
4353
+ "--disable-speech-api",
4354
+ "--disable-sync",
4355
+ "--hide-scrollbars",
4356
+ "--ignore-gpu-blacklist",
4357
+ "--metrics-recording-only",
4358
+ "--mute-audio",
4359
+ "--no-default-browser-check",
4360
+ "--no-first-run",
4361
+ "--no-pings",
4362
+ "--no-zygote",
4363
+ "--password-store=basic",
4364
+ "--use-gl=swiftshader",
4365
+ "--use-mock-keychain",
4366
+ "--start-fullscreen",
4367
+ "--disable-dev-shm-usage",
4368
+ "--no-sandbox"
4369
+ ];
4370
+ var blocked_domains = [
4371
+ "google-analytics.com",
4372
+ "googletagmanager.com"
4373
+ ];
4374
+ async function generateImage(path2, section, format = "png", transparent = false) {
4375
+ const width = 1366;
4376
+ const height = 768;
4377
+ const maxTimeoutPerPage = 0;
4378
+ try {
4379
+ const browser = await puppeteer.launch({
4380
+ args: [
4381
+ `--window-size=${width},${height}`,
4382
+ ...minimal_args
4383
+ ],
4384
+ defaultViewport: { width, height, deviceScaleFactor: 2 },
4385
+ headless: "new",
4386
+ ignoreHTTPSErrors: true
4387
+ });
4388
+ const url = new URL(path2);
4389
+ const searchParams = new URLSearchParams(url.search);
4390
+ url.search = searchParams.toString();
4391
+ const page = await browser.newPage();
4392
+ await page.addStyleTag({ content: ".bespoke-Section-container{ padding: 1rem !important }" });
4393
+ await page.setRequestInterception(true);
4394
+ page.on("request", (request) => {
4395
+ const url2 = request.url();
4396
+ if (blocked_domains.some((domain) => url2.includes(domain))) {
4397
+ request.abort();
4398
+ } else {
4399
+ request.continue();
4400
+ }
4401
+ });
4402
+ await page.goto(url.toString(), { waitUntil: "networkidle0", timeout: maxTimeoutPerPage });
4403
+ await page.waitForSelector("#bespoke-report", { timeout: maxTimeoutPerPage });
4404
+ if (transparent) {
4405
+ await page.$eval(`.bespoke-Section-${section}`, (el) => el["style"].background = "transparent");
4406
+ await page.evaluate(() => document.body.style.background = "transparent");
4407
+ }
4408
+ if (format === "svg") {
4409
+ const element = await page.$(".d3plus-viz");
4410
+ const svg = await element?.evaluate((el) => el.outerHTML);
4411
+ return svg?.replace(/ {4}|[\t\n\r]/gm, "");
4412
+ } else {
4413
+ const element = await page.$(".bespoke-Section-container");
4414
+ const screenshot = await element?.screenshot({
4415
+ type: "png",
4416
+ omitBackground: true
4417
+ });
4418
+ await browser.close();
4419
+ return screenshot;
4420
+ }
4421
+ } catch (error) {
4422
+ console.error("Error generating Image:", error);
4423
+ throw error;
4424
+ }
4425
+ }
4426
+ function endpointExportImage() {
4427
+ return endpoint("POST", "img", async (req, res) => {
4428
+ const { slugs, path: path2, section, viz, query: selectors, locale, prefixLocale, transparent = false, svg = false } = req.body;
4429
+ if (!slugs || !path2 || !section) {
4430
+ throw new BackendError(400, "Missing parameter in request.");
4431
+ } else {
4432
+ try {
4433
+ let path3 = prefixLocale ? `/${locale}/` : "/";
4434
+ path3 += `embed/${JSON.parse(slugs).join("/")}`;
4435
+ const url = new URL(path3, req.headers["origin"]);
4436
+ const query = new URLSearchParams(Object.entries(selectors || {}));
4437
+ query.set("section", section);
4438
+ if (viz) {
4439
+ query.set("viz", viz);
4440
+ }
4441
+ url.search = query.toString();
4442
+ const format = yn2(svg) ? "svg" : "png";
4443
+ const img = await generateImage(url, section, format, yn2(transparent));
4444
+ return {
4445
+ ok: true,
4446
+ status: 200,
4447
+ data: img
4448
+ };
4449
+ } catch (e) {
4450
+ console.log(e);
4451
+ return {
4452
+ ok: false,
4453
+ status: 500
4454
+ };
4455
+ }
4456
+ }
4457
+ });
4458
+ }
4222
4459
 
4223
4460
  // api/endpoints/icon/listIcons.ts
4224
4461
  function endpointListIconsFactory(operations, provider) {
@@ -4308,7 +4545,8 @@ function getEndpointMap(db) {
4308
4545
  endpointRevalidateReportFactory(api),
4309
4546
  endpointRevalidateUrlFactory(),
4310
4547
  endpointReadPrivateBlocksFactory(api),
4311
- // endpointDownloadReport(),
4548
+ endpointDownloadReport(),
4549
+ endpointExportImage(),
4312
4550
  endpointListIconsFactory(api, "tabler"),
4313
4551
  endpointReadIconFactory(api, "tabler"),
4314
4552
  endpointReportVariables()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datawheel/bespoke",
3
- "version": "0.6.0-rc.14",
3
+ "version": "0.6.0-rc.2",
4
4
  "description": "Content management system for creating automated data reports",
5
5
  "exports": {
6
6
  ".": {
@@ -51,7 +51,6 @@
51
51
  "@mantine/notifications": "^6.0.19",
52
52
  "@mantine/prism": "^6.0.19",
53
53
  "@monaco-editor/react": "^4.4.5",
54
- "@newswire/frames": "^1.0.0",
55
54
  "@reduxjs/toolkit": "^1.8.4",
56
55
  "@tabler/icons-react": "^2.15.0",
57
56
  "@tiptap/extension-hard-break": "^2.2.2",
@@ -105,6 +104,7 @@
105
104
  "normalizr": "^3.6.2",
106
105
  "pg": "^8.7.3",
107
106
  "pretty-format": "^28.1.1",
107
+ "puppeteer": "^21.5.2",
108
108
  "react": "^18.2.0",
109
109
  "react-clipboard.js": "^2.0.16",
110
110
  "react-dom": "^18.2.0",