@datawheel/bespoke 0.6.0-rc.7 → 0.6.0-rc.9

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 (2) hide show
  1. package/dist/index.js +333 -295
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -22,10 +22,11 @@ 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, IconCode, IconPalette, IconBold, IconItalic, IconUnderline, IconAlignCenter, IconAlignRight, IconAlignJustified, IconArrowBackUp, IconArrowForwardUp, IconLanguageOff, IconTriangleInvertedFilled, 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, IconPalette, IconBold, IconItalic, IconUnderline, IconAlignCenter, IconAlignRight, IconAlignJustified, IconArrowBackUp, IconArrowForwardUp, IconLanguageOff, IconTriangleInvertedFilled, IconCode, 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';
29
30
  import { MantineReactTable } from 'mantine-react-table';
30
31
  import { dataConcat, dataLoad } from 'd3plus-viz';
31
32
  import * as d3plus from 'd3plus-react';
@@ -50,8 +51,9 @@ import { Prism } from '@mantine/prism';
50
51
  import slugifyFn from 'slugify';
51
52
  import JSZip from 'jszip';
52
53
  import { saveAs } from 'file-saver';
53
- import { select } from 'd3-selection';
54
54
  import { FacebookShareButton, FacebookIcon, TwitterShareButton, TwitterIcon, TelegramShareButton, TelegramIcon, WhatsappShareButton, WhatsappIcon, LinkedinShareButton, LinkedinIcon, RedditShareButton, RedditIcon, EmailShareButton, EmailIcon } from 'react-share';
55
+ import { select } from 'd3-selection';
56
+ import { toPng } from 'html-to-image';
55
57
  import Head from 'next/head';
56
58
  import Editor, { useMonaco } from '@monaco-editor/react';
57
59
  import { format } from 'pretty-format';
@@ -263,8 +265,8 @@ var init_ExploreFilters = __esm({
263
265
  });
264
266
 
265
267
  // api/http/lib.ts
266
- function http(axios11, config) {
267
- return axios11.request(config).then((response) => {
268
+ function http(axios10, config) {
269
+ return axios10.request(config).then((response) => {
268
270
  const { status, data } = response;
269
271
  return "error" in data ? { ok: false, status, error: data.error } : { ok: true, status, data: data.data };
270
272
  }, (err) => {
@@ -275,26 +277,26 @@ function http(axios11, config) {
275
277
  return { ok: false, status: 500, error: err.message };
276
278
  });
277
279
  }
278
- function httpGET(axios11, request, transformParams) {
280
+ function httpGET(axios10, request, transformParams) {
279
281
  const config = typeof request === "string" ? { url: request } : request;
280
- return (params) => http(axios11, {
282
+ return (params) => http(axios10, {
281
283
  ...config,
282
284
  method: "GET",
283
285
  params: transformParams ? transformParams(params) : params
284
286
  });
285
287
  }
286
- function httpPOST(axios11, request, transformPayload) {
288
+ function httpPOST(axios10, request, transformPayload) {
287
289
  const config = typeof request === "string" ? { url: request } : request;
288
- return (payload) => http(axios11, {
290
+ return (payload) => http(axios10, {
289
291
  ...config,
290
292
  method: "POST",
291
293
  data: transformPayload ? transformPayload(payload) : payload
292
294
  });
293
295
  }
294
- function httpDELETE(axios11, request, transformPayload) {
296
+ function httpDELETE(axios10, request, transformPayload) {
295
297
  const config = typeof request === "string" ? { url: request } : request;
296
298
  return (payload) => {
297
- return http(axios11, {
299
+ return http(axios10, {
298
300
  ...config,
299
301
  method: "DELETE",
300
302
  params: transformPayload ? transformPayload(payload) : payload
@@ -308,8 +310,8 @@ var init_lib = __esm({
308
310
  });
309
311
 
310
312
  // api/http/image/imageSave.ts
311
- function httpImageSaveFactory(axios11, provider) {
312
- return (params) => http(axios11, {
313
+ function httpImageSaveFactory(axios10, provider) {
314
+ return (params) => http(axios10, {
313
315
  method: "POST",
314
316
  url: `images/save/${provider}`,
315
317
  params: { prompt: params.prompt, provider }
@@ -323,8 +325,8 @@ var init_imageSave = __esm({
323
325
  });
324
326
 
325
327
  // api/http/image/imageSearch.ts
326
- function httpImageSearchFactory(axios11, provider) {
327
- return (params) => http(axios11, {
328
+ function httpImageSearchFactory(axios10, provider) {
329
+ return (params) => http(axios10, {
328
330
  method: "GET",
329
331
  url: `images/search/${provider}`,
330
332
  params: { prompt: params.prompt, page: params.page, provider }
@@ -338,8 +340,8 @@ var init_imageSearch = __esm({
338
340
  });
339
341
 
340
342
  // api/http/icon/listIcons.ts
341
- function httpListIconsFactory(axios11, provider) {
342
- return () => http(axios11, {
343
+ function httpListIconsFactory(axios10, provider) {
344
+ return () => http(axios10, {
343
345
  method: "GET",
344
346
  url: `list/icons/${provider}`,
345
347
  params: { provider }
@@ -353,8 +355,8 @@ var init_listIcons = __esm({
353
355
  });
354
356
 
355
357
  // api/http/icon/readIcon.ts
356
- function httpReadIconFactory(axios11, provider) {
357
- return (params) => http(axios11, {
358
+ function httpReadIconFactory(axios10, provider) {
359
+ return (params) => http(axios10, {
358
360
  method: "GET",
359
361
  url: `read/icons/${provider}/icon.svg`,
360
362
  params: { name: params.name }
@@ -367,65 +369,65 @@ var init_readIcon = __esm({
367
369
  }
368
370
  });
369
371
  function apiFactory(baseURL) {
370
- const axios11 = axios.create({ baseURL });
372
+ const axios10 = axios.create({ baseURL });
371
373
  return {
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, {
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, {
402
404
  url: "member/image",
403
405
  responseType: "blob"
404
406
  }),
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")
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")
429
431
  };
430
432
  }
431
433
  var transformDeletePayload, transformReadMembers;
@@ -4093,7 +4095,7 @@ var init_store = __esm({
4093
4095
  storeWrapper = createWrapper(storeFactory);
4094
4096
  }
4095
4097
  });
4096
- function withFetcher(Component, useRef14) {
4098
+ function withFetcher(Component, useRef16) {
4097
4099
  const ComponentWithFetcher = (props) => {
4098
4100
  const {
4099
4101
  id,
@@ -4101,7 +4103,7 @@ function withFetcher(Component, useRef14) {
4101
4103
  skelWidth,
4102
4104
  ...otherProps
4103
4105
  } = props;
4104
- const ref = useRef14(id);
4106
+ const ref = useRef16(id);
4105
4107
  if (ref.isUninitialized || ref.isFetching) {
4106
4108
  return /* @__PURE__ */ jsx(Skeleton, { width: skelWidth, height: skelHeight });
4107
4109
  }
@@ -5725,6 +5727,41 @@ var init_useScrollToAnchor = __esm({
5725
5727
  init_esm_shims();
5726
5728
  }
5727
5729
  });
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
+ });
5728
5765
  function createOutline(elements, variables) {
5729
5766
  const roots = [];
5730
5767
  const nodes = {};
@@ -5842,6 +5879,7 @@ var init_hooks2 = __esm({
5842
5879
  init_useInitialState();
5843
5880
  init_useOnChangeSelector();
5844
5881
  init_useScrollToAnchor();
5882
+ init_useEmbed();
5845
5883
  init_useContentOutline();
5846
5884
  }
5847
5885
  });
@@ -15951,12 +15989,169 @@ var init_useLocalePrefix = __esm({
15951
15989
  init_esm_shims();
15952
15990
  }
15953
15991
  });
15992
+ function CopyInput(props) {
15993
+ const { title, url, disabled = false } = props;
15994
+ const Icon = disabled ? IconCode : IconLink;
15995
+ return /* @__PURE__ */ jsx(Input.Wrapper, { label: title, children: /* @__PURE__ */ jsx(
15996
+ Input,
15997
+ {
15998
+ icon: /* @__PURE__ */ jsx(Icon, {}),
15999
+ value: url,
16000
+ disabled,
16001
+ readOnly: true,
16002
+ 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 }) }) }) })
16003
+ }
16004
+ ) });
16005
+ }
16006
+ var init_CopyInput = __esm({
16007
+ "components/options/CopyInput.tsx"() {
16008
+ init_esm_shims();
16009
+ }
16010
+ });
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
+ }
15954
16148
  function ImageTab(props) {
15955
16149
  const { section } = props;
15956
16150
  const blocksIds = section.blocks || [];
15957
- const { asPath, query, locale } = useRouter();
15958
- const prefixLocale = useLocalePrefix();
15959
- const { pathSegment } = useBespoke();
16151
+ const framer = useRef();
16152
+ useRouter();
16153
+ useLocalePrefix();
16154
+ useBespoke();
15960
16155
  const vizAvailable = useAppSelector((state) => blocksIds.map((blockId) => state.records.entities.block[blockId]).filter((block) => block.type === "visualization" && state.variables.status[block.id].allowed));
15961
16156
  const [imageContext, setImageContext] = useState(contextOptions.section);
15962
16157
  const [imageFormat, setImageFormat] = useState(formatOptions2.png);
@@ -15966,62 +16161,13 @@ function ImageTab(props) {
15966
16161
  const [transparentBackground, setTransparentBackground] = useState(false);
15967
16162
  const [loadingPreviews, setLoadingPreviews] = useState(false);
15968
16163
  const optionsTranslations = useBespokeTranslations("options");
15969
- const translations = { ...DEFAULT_TRANSLATIONS3, ...optionsTranslations["image_tab"] };
16164
+ const translations = { ...DEFAULT_TRANSLATIONS4, ...optionsTranslations["image_tab"] };
16165
+ const search = Object.fromEntries(new URLSearchParams(location.search));
16166
+ const selectors = Object.fromEntries(
16167
+ Object.entries(search).filter(([key]) => key.match(/selector\d+id/))
16168
+ );
15970
16169
  const sectionId = slugify_default(section?.settings?.name) || `section-${section.id}`;
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
- };
16170
+ const embedPath = useEmbedPath(section.id, imageContext === contextOptions.viz ? vizSelected : void 0, selectors);
16025
16171
  const generatePreviews = async () => {
16026
16172
  const { toCanvas } = await import('html-to-image');
16027
16173
  Promise.all(
@@ -16047,6 +16193,51 @@ function ImageTab(props) {
16047
16193
  const onSelectViz = (vizId) => () => {
16048
16194
  setVizSelected(vizId);
16049
16195
  };
16196
+ const onDownloadIframe = () => {
16197
+ setImageProcessing(true);
16198
+ const container = document.getElementById("iframe-preview");
16199
+ framer.current = new Framer(container, { src: embedPath });
16200
+ };
16201
+ useEffect(() => {
16202
+ const onIframeLoad = (e) => {
16203
+ if (e.data === "iframe-loaded") {
16204
+ console.log("Downloading image");
16205
+ const node = document.getElementById("iframe-preview");
16206
+ if (imageFormat === formatOptions2.png) {
16207
+ toPng(
16208
+ node,
16209
+ {
16210
+ backgroundColor: transparentBackground ? "transparent" : "white",
16211
+ cacheBust: true
16212
+ }
16213
+ ).then(function(dataUrl) {
16214
+ const link = document.createElement("a");
16215
+ link.href = dataUrl;
16216
+ link.setAttribute("download", "image.png");
16217
+ document.body.appendChild(link);
16218
+ link.click();
16219
+ document.body.removeChild(link);
16220
+ setImageProcessing(false);
16221
+ if (typeof framer.current?.remove === "function") {
16222
+ framer.current.remove();
16223
+ }
16224
+ }).catch(function(error) {
16225
+ console.error("oops, something went wrong!", error);
16226
+ setImageProcessing(false);
16227
+ });
16228
+ } else {
16229
+ const iframe = document.querySelector("#iframe-preview > iframe");
16230
+ const doc = iframe.contentDocument;
16231
+ const svg = doc?.querySelector("svg.d3plus-viz");
16232
+ console.log(svg.innerHtml);
16233
+ if (svg)
16234
+ exportSVG(svg);
16235
+ }
16236
+ }
16237
+ };
16238
+ window.addEventListener("message", onIframeLoad);
16239
+ return () => window.removeEventListener("message", onIframeLoad);
16240
+ }, [transparentBackground]);
16050
16241
  return /* @__PURE__ */ jsxs(Stack, { className: "cms-section-options-image", children: [
16051
16242
  /* @__PURE__ */ jsx(Space, { h: "xs" }),
16052
16243
  vizAvailable.length > 0 ? /* @__PURE__ */ jsx(Input.Wrapper, { label: `${translations["choose_area"]}:`, children: /* @__PURE__ */ jsx(
@@ -16093,28 +16284,7 @@ function ImageTab(props) {
16093
16284
  ]
16094
16285
  }
16095
16286
  ),
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
- ] }) }),
16287
+ /* @__PURE__ */ jsx("div", { style: { height: 0, overflow: "hidden" }, children: /* @__PURE__ */ jsx("div", { id: "iframe-preview", "data-frame-src": embedPath, style: { width: 1e3 } }) }),
16118
16288
  imageFormat !== formatOptions2.svg && /* @__PURE__ */ jsx(
16119
16289
  Checkbox,
16120
16290
  {
@@ -16129,14 +16299,14 @@ function ImageTab(props) {
16129
16299
  {
16130
16300
  leftIcon: /* @__PURE__ */ jsx(IconDownload, { size: 16 }),
16131
16301
  loading: imageProcessing,
16132
- onClick: imageContext === contextOptions.section ? onSaveClickSection : onSaveClickViz,
16302
+ onClick: onDownloadIframe,
16133
16303
  fullWidth: true,
16134
16304
  children: /* @__PURE__ */ jsx("span", { children: imageProcessing ? translations["processing"] : translations["download"].replace("{imageFormat}", imageFormat) })
16135
16305
  }
16136
16306
  )
16137
16307
  ] });
16138
16308
  }
16139
- var contextOptions, formatOptions2, VizPreview2, DEFAULT_TRANSLATIONS3, getSectionNode, getVizNode;
16309
+ var contextOptions, formatOptions2, VizPreview2, DEFAULT_TRANSLATIONS4, getSectionNode, getVizNode;
16140
16310
  var init_ImageTab = __esm({
16141
16311
  "components/options/tabs/ImageTab.tsx"() {
16142
16312
  init_esm_shims();
@@ -16149,6 +16319,7 @@ var init_ImageTab = __esm({
16149
16319
  init_varSwapRecursive();
16150
16320
  init_getBlockContent();
16151
16321
  init_hooks();
16322
+ init_ShareTab();
16152
16323
  contextOptions = {
16153
16324
  viz: "viz",
16154
16325
  section: "section"
@@ -16202,7 +16373,7 @@ var init_ImageTab = __esm({
16202
16373
  )
16203
16374
  ] });
16204
16375
  };
16205
- DEFAULT_TRANSLATIONS3 = {
16376
+ DEFAULT_TRANSLATIONS4 = {
16206
16377
  "wrong_node": "Wrong node in export",
16207
16378
  "choose_area": "Choose image area",
16208
16379
  "choose_viz": "Choose visualization",
@@ -16225,142 +16396,6 @@ var init_ImageTab = __esm({
16225
16396
  };
16226
16397
  }
16227
16398
  });
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
- });
16364
16399
  function OptionsModal(props) {
16365
16400
  const {
16366
16401
  section,
@@ -17694,6 +17729,9 @@ var LoginButton_default = BespokeLoginBtn;
17694
17729
  init_esm_shims();
17695
17730
  var UserProvider_default = UserProvider;
17696
17731
 
17732
+ // frontend/index.ts
17733
+ init_hooks2();
17734
+
17697
17735
  // frontend/components/auth/withPageRoleAuthRequired.tsx
17698
17736
  init_esm_shims();
17699
17737
  function withPageRoleAuthRequired(params) {
@@ -25818,4 +25856,4 @@ function BespokeRenderer({
25818
25856
  ] }) });
25819
25857
  }
25820
25858
 
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 };
25859
+ 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datawheel/bespoke",
3
- "version": "0.6.0-rc.7",
3
+ "version": "0.6.0-rc.9",
4
4
  "description": "Content management system for creating automated data reports",
5
5
  "exports": {
6
6
  ".": {
@@ -52,6 +52,7 @@
52
52
  "@mantine/notifications": "^6.0.19",
53
53
  "@mantine/prism": "^6.0.19",
54
54
  "@monaco-editor/react": "^4.4.5",
55
+ "@newswire/frames": "^1.0.0",
55
56
  "@reduxjs/toolkit": "^1.8.4",
56
57
  "@tabler/icons-react": "^2.15.0",
57
58
  "@tiptap/extension-hard-break": "^2.2.2",