@datawheel/bespoke 0.2.6 → 0.2.7

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.
package/dist/index.js CHANGED
@@ -16,11 +16,11 @@ import { Notifications, notifications } from '@mantine/notifications';
16
16
  import React, { forwardRef, createContext, useMemo, useState, useCallback, useContext, useEffect, memo, useRef, Fragment as Fragment$1, createElement } from 'react';
17
17
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
18
18
  import { useDispatch, useSelector } from 'react-redux';
19
- import { Stack, Text, Badge, Group, useMantineTheme, Flex, packSx, Tooltip, ActionIcon, Modal, Button, SegmentedControl, Select, MultiSelect, Center, Grid, Title, Radio, NumberInput, TextInput, Switch, Box, List, Menu, Anchor, MantineProvider, Divider, Burger, Navbar, ScrollArea, Avatar, AppShell, UnstyledButton, ThemeIcon, LoadingOverlay, Skeleton, Image, Card, Container, Loader, Alert, Collapse, Space, Code, Textarea, rem, Paper, Autocomplete, Input, Popover, Checkbox, Drawer, Overlay, SimpleGrid, Tabs, Header, px, FileInput, Accordion, HoverCard, CopyButton, Col } from '@mantine/core';
19
+ import { Stack, Text, Badge, Group, useMantineTheme, Flex, packSx, Tooltip, ActionIcon, Modal, Button, SegmentedControl, Select, MultiSelect, Center, Grid, Title, Radio, NumberInput, TextInput, Switch, Image, Accordion, Input, Box, List, Menu, Anchor, MantineProvider, Divider, Burger, Navbar, ScrollArea, Avatar, AppShell, UnstyledButton, ThemeIcon, LoadingOverlay, Skeleton, Card, Container, Loader, Alert, Collapse, Space, Code, Textarea, rem, Paper, Autocomplete, Popover, Checkbox, Drawer, Overlay, SimpleGrid, Tabs, Header, px, FileInput, HoverCard, CopyButton, Col } from '@mantine/core';
20
20
  import { dataConcat } from 'd3plus-viz';
21
21
  import * as d3plus from 'd3plus-react';
22
22
  import Router, { useRouter } from 'next/router';
23
- import { IconInfoCircle, IconRefresh, IconSearch, IconAlignLeft, IconAlignCenter, IconAlignRight, IconBoxMargin, IconTable, IconMathFunction, IconUsers, IconLogout, IconTrash, IconX, IconUserCircle, IconEdit, IconDatabase, IconServer, IconPencil, IconAlertCircle, IconCircleCheck, IconPlayerPlay, IconAlarmFilled, IconBox, IconLink, IconCircleX, IconFlag, IconCirclePlus, IconFileAnalytics, IconPlus, IconHome, IconChevronDown, IconCamera, IconShare, IconCircleDashed, IconListSearch, IconExternalLink, IconSettings, IconFileOff, IconFilesOff, IconHierarchy3, IconMenu, IconApi, IconPolaroid, IconCircleMinus, IconEyeOff, IconPhoto, IconChevronLeft, IconChevronRight, IconLogin, IconWorld, IconLock, IconVariable, IconArrowRightCircle, IconDownload, IconTemplate, IconChartBar, IconCode, IconUpload, IconCodePlus, IconClipboardCheck, IconClipboardCopy, IconPalette, IconEye, IconMinimize, IconMaximize, IconRss, IconGlobe, IconLinkOff } from '@tabler/icons-react';
23
+ import { IconInfoCircle, IconRefresh, IconPhotoFilled, IconSearch, IconAlignLeft, IconAlignCenter, IconAlignRight, IconBoxMargin, IconTable, IconMathFunction, IconUsers, IconLogout, IconTrash, IconX, IconUserCircle, IconEdit, IconDatabase, IconServer, IconPencil, IconAlertCircle, IconCircleCheck, IconPlayerPlay, IconAlarmFilled, IconBox, IconLink, IconCircleX, IconFlag, IconCirclePlus, IconFileAnalytics, IconPlus, IconHome, IconChevronDown, IconCamera, IconShare, IconCircleDashed, IconListSearch, IconExternalLink, IconSettings, IconFileOff, IconFilesOff, IconHierarchy3, IconMenu, IconApi, IconPolaroid, IconCircleMinus, IconEyeOff, IconPhoto, IconChevronLeft, IconChevronRight, IconLogin, IconWorld, IconLock, IconVariable, IconArrowRightCircle, IconDownload, IconTemplate, IconChartBar, IconCode, IconUpload, IconCodePlus, IconClipboardCheck, IconClipboardCopy, IconPalette, IconEye, IconMinimize, IconMaximize, IconRss, IconGlobe, IconLinkOff } from '@tabler/icons-react';
24
24
  import { useMediaQuery, useDisclosure, useDebouncedValue, useHotkeys, useFullscreen, getHotkeyHandler } from '@mantine/hooks';
25
25
  import dynamic from 'next/dynamic';
26
26
  import Link from 'next/link';
@@ -61,8 +61,8 @@ var init_esm_shims = __esm({
61
61
  });
62
62
 
63
63
  // api/http/lib.ts
64
- function http(axios6, config) {
65
- return axios6.request(config).then((response) => {
64
+ function http(axios8, config) {
65
+ return axios8.request(config).then((response) => {
66
66
  const { status, data } = response;
67
67
  return "error" in data ? { ok: false, status, error: data.error } : { ok: true, status, data: data.data };
68
68
  }, (err) => {
@@ -73,26 +73,26 @@ function http(axios6, config) {
73
73
  return { ok: false, status: 500, error: err.message };
74
74
  });
75
75
  }
76
- function httpGET(axios6, request, transformParams) {
76
+ function httpGET(axios8, request, transformParams) {
77
77
  const config = typeof request === "string" ? { url: request } : request;
78
- return (params) => http(axios6, {
78
+ return (params) => http(axios8, {
79
79
  ...config,
80
80
  method: "GET",
81
81
  params: transformParams ? transformParams(params) : params
82
82
  });
83
83
  }
84
- function httpPOST(axios6, request, transformPayload) {
84
+ function httpPOST(axios8, request, transformPayload) {
85
85
  const config = typeof request === "string" ? { url: request } : request;
86
- return (payload) => http(axios6, {
86
+ return (payload) => http(axios8, {
87
87
  ...config,
88
88
  method: "POST",
89
89
  data: transformPayload ? transformPayload(payload) : payload
90
90
  });
91
91
  }
92
- function httpDELETE(axios6, request, transformPayload) {
92
+ function httpDELETE(axios8, request, transformPayload) {
93
93
  const config = typeof request === "string" ? { url: request } : request;
94
94
  return (payload) => {
95
- return http(axios6, {
95
+ return http(axios8, {
96
96
  ...config,
97
97
  method: "DELETE",
98
98
  params: transformPayload ? transformPayload(payload) : payload
@@ -106,8 +106,8 @@ var init_lib = __esm({
106
106
  });
107
107
 
108
108
  // api/http/image/imageSave.ts
109
- function httpImageSaveFactory(axios6, provider) {
110
- return (params) => http(axios6, {
109
+ function httpImageSaveFactory(axios8, provider) {
110
+ return (params) => http(axios8, {
111
111
  method: "GET",
112
112
  url: `search/images/${provider}`,
113
113
  params: { prompt: params.prompt, provider }
@@ -121,8 +121,8 @@ var init_imageSave = __esm({
121
121
  });
122
122
 
123
123
  // api/http/image/imageSearch.ts
124
- function httpImageSearchFactory(axios6, provider) {
125
- return (params) => http(axios6, {
124
+ function httpImageSearchFactory(axios8, provider) {
125
+ return (params) => http(axios8, {
126
126
  method: "GET",
127
127
  url: `search/images/${provider}`,
128
128
  params: { prompt: params.prompt, provider }
@@ -134,60 +134,92 @@ var init_imageSearch = __esm({
134
134
  init_lib();
135
135
  }
136
136
  });
137
+
138
+ // api/http/icon/listIcons.ts
139
+ function httpListIconsFactory(axios8, provider) {
140
+ return () => http(axios8, {
141
+ method: "GET",
142
+ url: `list/icons/${provider}`,
143
+ params: { provider }
144
+ });
145
+ }
146
+ var init_listIcons = __esm({
147
+ "api/http/icon/listIcons.ts"() {
148
+ init_esm_shims();
149
+ init_lib();
150
+ }
151
+ });
152
+
153
+ // api/http/icon/readIcon.ts
154
+ function httpReadIconFactory(axios8, provider) {
155
+ return (params) => http(axios8, {
156
+ method: "GET",
157
+ url: `read/icons/${provider}/icon.svg`,
158
+ params: { name: params.name }
159
+ });
160
+ }
161
+ var init_readIcon = __esm({
162
+ "api/http/icon/readIcon.ts"() {
163
+ init_esm_shims();
164
+ init_lib();
165
+ }
166
+ });
137
167
  function apiFactory(baseURL) {
138
- const axios6 = axios.create({ baseURL });
168
+ const axios8 = axios.create({ baseURL });
139
169
  return {
140
- createBlock: httpPOST(axios6, "create/block"),
141
- createDimension: httpPOST(axios6, "create/dimension"),
142
- createFormatter: httpPOST(axios6, "create/formatter"),
143
- createReport: httpPOST(axios6, "create/report"),
144
- createSection: httpPOST(axios6, "create/section"),
145
- createVariant: httpPOST(axios6, "create/variant"),
146
- deleteBlock: httpDELETE(axios6, "delete/block", transformDeletePayload),
147
- deleteDimension: httpDELETE(axios6, "delete/dimension", transformDeletePayload),
148
- deleteFormatter: httpDELETE(axios6, "delete/formatter", transformDeletePayload),
149
- deleteReport: httpDELETE(axios6, "delete/report", transformDeletePayload),
150
- deleteSection: httpDELETE(axios6, "delete/section", transformDeletePayload),
151
- deleteVariant: httpDELETE(axios6, "delete/variant", transformDeletePayload),
152
- readBlock: httpGET(axios6, "read/block"),
153
- readDimension: httpGET(axios6, "read/dimension"),
154
- readFormatter: httpGET(axios6, "read/formatter"),
155
- readReport: httpGET(axios6, "read/report"),
156
- readSection: httpGET(axios6, "read/section"),
157
- readVariant: httpGET(axios6, "read/variant"),
158
- updateBlock: httpPOST(axios6, "update/block"),
159
- updateDimension: httpPOST(axios6, "update/dimension"),
160
- updateFormatter: httpPOST(axios6, "update/formatter"),
161
- updateReport: httpPOST(axios6, "update/report"),
162
- updateSection: httpPOST(axios6, "update/section"),
163
- updateVariant: httpPOST(axios6, "update/variant"),
164
- searchReport: httpGET(axios6, "search/reports"),
165
- validateVariantSlug: httpGET(axios6, "validate/variant"),
166
- readMember: httpGET(axios6, "read/members", transformReadMembers),
167
- readMemberImage: httpGET(axios6, {
170
+ createBlock: httpPOST(axios8, "create/block"),
171
+ createDimension: httpPOST(axios8, "create/dimension"),
172
+ createFormatter: httpPOST(axios8, "create/formatter"),
173
+ createReport: httpPOST(axios8, "create/report"),
174
+ createSection: httpPOST(axios8, "create/section"),
175
+ createVariant: httpPOST(axios8, "create/variant"),
176
+ deleteBlock: httpDELETE(axios8, "delete/block", transformDeletePayload),
177
+ deleteDimension: httpDELETE(axios8, "delete/dimension", transformDeletePayload),
178
+ deleteFormatter: httpDELETE(axios8, "delete/formatter", transformDeletePayload),
179
+ deleteReport: httpDELETE(axios8, "delete/report", transformDeletePayload),
180
+ deleteSection: httpDELETE(axios8, "delete/section", transformDeletePayload),
181
+ deleteVariant: httpDELETE(axios8, "delete/variant", transformDeletePayload),
182
+ readBlock: httpGET(axios8, "read/block"),
183
+ readDimension: httpGET(axios8, "read/dimension"),
184
+ readFormatter: httpGET(axios8, "read/formatter"),
185
+ readReport: httpGET(axios8, "read/report"),
186
+ readSection: httpGET(axios8, "read/section"),
187
+ readVariant: httpGET(axios8, "read/variant"),
188
+ updateBlock: httpPOST(axios8, "update/block"),
189
+ updateDimension: httpPOST(axios8, "update/dimension"),
190
+ updateFormatter: httpPOST(axios8, "update/formatter"),
191
+ updateReport: httpPOST(axios8, "update/report"),
192
+ updateSection: httpPOST(axios8, "update/section"),
193
+ updateVariant: httpPOST(axios8, "update/variant"),
194
+ searchReport: httpGET(axios8, "search/reports"),
195
+ validateVariantSlug: httpGET(axios8, "validate/variant"),
196
+ readMember: httpGET(axios8, "read/members", transformReadMembers),
197
+ readMemberImage: httpGET(axios8, {
168
198
  url: "member/image.png",
169
199
  responseType: "blob"
170
200
  }),
171
- searchMember: httpGET(axios6, "search/members"),
172
- updateMember: httpGET(axios6, "update/members"),
173
- imageLocalSearch: httpImageSearchFactory(axios6, "local"),
174
- imageLocalSave: httpImageSaveFactory(axios6, "local"),
175
- imageFlickrSearch: httpImageSearchFactory(axios6, "flickr"),
176
- imageFlickrSave: httpImageSaveFactory(axios6, "flickr"),
177
- imageUnsplashSearch: httpImageSearchFactory(axios6, "unsplash"),
178
- imageUnsplashSave: httpImageSaveFactory(axios6, "unsplash"),
179
- imageUploadSave: httpImageSaveFactory(axios6, "upload"),
180
- readMetadata: httpGET(axios6, "read/metadata"),
181
- regenerateSearch: httpPOST(axios6, "search/regenerate"),
182
- urlProxy: httpGET(axios6, "url/proxy"),
183
- searchRole: httpGET(axios6, "auth/search/roles"),
184
- searchUser: httpGET(axios6, "auth/search/users"),
185
- readUser: httpGET(axios6, "auth/read/user"),
186
- updateUser: httpPOST(axios6, "auth/update/user"),
187
- addNewReportToCurrentUser: httpPOST(axios6, "auth/update/me"),
188
- revalidateReport: httpGET(axios6, "revalidate/report"),
189
- revalidateUrl: httpGET(axios6, "revalidate/url"),
190
- readPrivateBlocks: httpPOST(axios6, "read/blocks/private")
201
+ searchMember: httpGET(axios8, "search/members"),
202
+ updateMember: httpGET(axios8, "update/members"),
203
+ imageLocalSearch: httpImageSearchFactory(axios8, "local"),
204
+ imageLocalSave: httpImageSaveFactory(axios8, "local"),
205
+ imageFlickrSearch: httpImageSearchFactory(axios8, "flickr"),
206
+ imageFlickrSave: httpImageSaveFactory(axios8, "flickr"),
207
+ imageUnsplashSearch: httpImageSearchFactory(axios8, "unsplash"),
208
+ imageUnsplashSave: httpImageSaveFactory(axios8, "unsplash"),
209
+ imageUploadSave: httpImageSaveFactory(axios8, "upload"),
210
+ readMetadata: httpGET(axios8, "read/metadata"),
211
+ regenerateSearch: httpPOST(axios8, "search/regenerate"),
212
+ urlProxy: httpGET(axios8, "url/proxy"),
213
+ searchRole: httpGET(axios8, "auth/search/roles"),
214
+ searchUser: httpGET(axios8, "auth/search/users"),
215
+ readUser: httpGET(axios8, "auth/read/user"),
216
+ updateUser: httpPOST(axios8, "auth/update/user"),
217
+ addNewReportToCurrentUser: httpPOST(axios8, "auth/update/me"),
218
+ revalidateReport: httpGET(axios8, "revalidate/report"),
219
+ revalidateUrl: httpGET(axios8, "revalidate/url"),
220
+ readPrivateBlocks: httpPOST(axios8, "read/blocks/private"),
221
+ listTablerIcons: httpListIconsFactory(axios8, "tabler"),
222
+ readTablerIcon: httpReadIconFactory(axios8, "tabler")
191
223
  };
192
224
  }
193
225
  var transformDeletePayload, transformReadMembers;
@@ -197,6 +229,8 @@ var init_http = __esm({
197
229
  init_imageSave();
198
230
  init_imageSearch();
199
231
  init_lib();
232
+ init_listIcons();
233
+ init_readIcon();
200
234
  transformDeletePayload = (id) => {
201
235
  return { id };
202
236
  };
@@ -778,6 +812,8 @@ function varSwap(sourceString, formatterFunctions, blockContext, passive = false
778
812
  const formatTitle = g1.replace(/^\w/g, (chr) => chr.toLowerCase());
779
813
  if (formatTitle in formatterFunctions) {
780
814
  formatter = formatterFunctions[formatTitle];
815
+ } else {
816
+ formatter = (d) => `${g1}${d}`;
781
817
  }
782
818
  }
783
819
  const value = Array.isArray(variables[keyMatch]) ? list(variables[keyMatch]) : variables[keyMatch];
@@ -2891,7 +2927,7 @@ var init_store = __esm({
2891
2927
  storeWrapper = createWrapper(storeFactory);
2892
2928
  }
2893
2929
  });
2894
- function withFetcher(Component, useRef8) {
2930
+ function withFetcher(Component, useRef9) {
2895
2931
  const ComponentWithFetcher = (props) => {
2896
2932
  const {
2897
2933
  id,
@@ -2899,7 +2935,7 @@ function withFetcher(Component, useRef8) {
2899
2935
  skelWidth,
2900
2936
  ...otherProps
2901
2937
  } = props;
2902
- const ref = useRef8(id);
2938
+ const ref = useRef9(id);
2903
2939
  if (ref.isUninitialized || ref.isFetching) {
2904
2940
  return /* @__PURE__ */ jsx(Skeleton, { width: skelWidth, height: skelHeight });
2905
2941
  }
@@ -3490,6 +3526,7 @@ init_esm_shims();
3490
3526
  // frontend/components/explore/ExploreFilters.tsx
3491
3527
  init_esm_shims();
3492
3528
  function ExploreFilters({ metadata, onFilter, initialReportId, initialVariantId, translations }) {
3529
+ const [showSelectors, setShowSelectors] = useState(false);
3493
3530
  const [selectors, setSelectors] = useState([]);
3494
3531
  const [filters, setFilters] = useState({ profile: void 0, variant: void 0 });
3495
3532
  const [selectedProfile, setSelectedProfile] = useState();
@@ -3532,12 +3569,20 @@ function ExploreFilters({ metadata, onFilter, initialReportId, initialVariantId,
3532
3569
  }, [filters]);
3533
3570
  useEffect(() => {
3534
3571
  if (metadata) {
3535
- const selectorStructure = metadata.filter((profile) => profile.dimensions.length > 0).map((profile) => ({
3536
- id: profile.id,
3537
- name: profile.dimensions.map((d) => d.name).join(" / "),
3538
- variants: getVariantsCombinatory(profile.dimensions.map((d) => d.variants))
3539
- }));
3572
+ let worthShowSelectors = false;
3573
+ const selectorStructure = metadata.filter((profile) => profile.dimensions.length > 0).map((profile) => {
3574
+ const combinatory = getVariantsCombinatory(profile.dimensions.map((d) => d.variants));
3575
+ if (!worthShowSelectors && combinatory && combinatory.length > 2) {
3576
+ worthShowSelectors = true;
3577
+ }
3578
+ return {
3579
+ id: profile.id,
3580
+ name: profile.dimensions.map((d) => d.name).join(" / "),
3581
+ variants: combinatory
3582
+ };
3583
+ });
3540
3584
  setSelectors([allMember, ...selectorStructure]);
3585
+ setShowSelectors(worthShowSelectors ? true : selectorStructure.length > 1);
3541
3586
  if (initialReportId || initialVariantId) {
3542
3587
  if (initialReportId) {
3543
3588
  const initialReport = selectorStructure.find((ss) => ss.id === initialReportId);
@@ -3550,8 +3595,7 @@ function ExploreFilters({ metadata, onFilter, initialReportId, initialVariantId,
3550
3595
  }
3551
3596
  }, [metadata, initialReportId, initialVariantId]);
3552
3597
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3553
- /* @__PURE__ */ jsx(Space, { h: "md" }),
3554
- selectors.length > 0 && /* @__PURE__ */ jsx(Group, { position: "center", children: selectors.map((s) => /* @__PURE__ */ jsx(
3598
+ showSelectors && /* @__PURE__ */ jsx(Group, { position: "center", className: "bespoke-explore-reports-selector", mb: "md", children: selectors.map((s) => /* @__PURE__ */ jsx(
3555
3599
  Button,
3556
3600
  {
3557
3601
  variant: s.id === selectedProfile?.id ? "outline" : "subtle",
@@ -3560,8 +3604,7 @@ function ExploreFilters({ metadata, onFilter, initialReportId, initialVariantId,
3560
3604
  },
3561
3605
  `p-${s.id}`
3562
3606
  )) }),
3563
- /* @__PURE__ */ jsx(Space, { h: "md" }),
3564
- /* @__PURE__ */ jsx(Group, { position: "center", style: { minHeight: "36px" }, children: selectedProfile && selectedProfile.variants && selectedProfile.variants.length > 2 && selectedProfile.variants.map((v) => /* @__PURE__ */ jsx(
3607
+ selectedProfile && selectedProfile.variants && selectedProfile.variants.length > 2 && /* @__PURE__ */ jsx(Group, { position: "center", style: { minHeight: "36px" }, className: "bespoke-explore-variant-selector", mb: "md", children: selectedProfile.variants.map((v) => /* @__PURE__ */ jsx(
3565
3608
  Button,
3566
3609
  {
3567
3610
  variant: v.id === selectedVariant?.id ? "outline" : "subtle",
@@ -3569,8 +3612,7 @@ function ExploreFilters({ metadata, onFilter, initialReportId, initialVariantId,
3569
3612
  children: translations?.dimension[v.name] ?? v.name
3570
3613
  },
3571
3614
  `v-${v.id}`
3572
- )) }),
3573
- /* @__PURE__ */ jsx(Space, { h: "md" })
3615
+ )) })
3574
3616
  ] });
3575
3617
  }
3576
3618
  var ExploreFilters_default = ExploreFilters;
@@ -3627,6 +3669,7 @@ function ExploreTile({ profile, profilePrefix, onSelect, reportTile }) {
3627
3669
  const url = `${localePrefix}${profilePrefix}${profile.path}`;
3628
3670
  const router = useRouter();
3629
3671
  const members = profile.members.map((m) => ({
3672
+ original_id: m.metadata.id,
3630
3673
  name: m.name,
3631
3674
  id: m.id,
3632
3675
  variant: m.metadata.variant.name,
@@ -3658,11 +3701,14 @@ function ExploreTile({ profile, profilePrefix, onSelect, reportTile }) {
3658
3701
  var ExploreTile_default = ExploreTile;
3659
3702
  function ExploreResults({ results = [], profilePrefix, onSelect = () => void 0, reportTile }) {
3660
3703
  const [scrollSize, setScrollSize] = useState(500);
3704
+ const scrollAreaRef = useRef(null);
3705
+ const offset = scrollAreaRef.current?.offsetTop || 0;
3706
+ const gap = 30;
3661
3707
  useEffect(() => {
3662
3708
  const { innerHeight } = window;
3663
- setScrollSize(innerHeight - 200);
3664
- }, []);
3665
- return /* @__PURE__ */ jsx(ScrollArea, { offsetScrollbars: true, type: "always", style: { height: scrollSize }, children: /* @__PURE__ */ jsx(Grid, { align: "stretch", children: results.map(
3709
+ setScrollSize(innerHeight - offset - gap);
3710
+ }, [offset]);
3711
+ return /* @__PURE__ */ jsx(ScrollArea, { offsetScrollbars: true, type: "always", h: scrollSize, ref: scrollAreaRef, children: /* @__PURE__ */ jsx(Grid, { align: "stretch", children: results.map(
3666
3712
  (item) => /* @__PURE__ */ jsx(
3667
3713
  ExploreTile_default,
3668
3714
  {
@@ -3764,6 +3810,7 @@ function BespokeExplore({
3764
3810
  TextInput,
3765
3811
  {
3766
3812
  placeholder: exploreTranslations["search"],
3813
+ mb: "md",
3767
3814
  size: "xl",
3768
3815
  onChange: (e) => setQuery(e.target.value),
3769
3816
  readOnly: loading,
@@ -3920,6 +3967,7 @@ function BespokeSearch({
3920
3967
  value: `VALUE: ${r.id}`,
3921
3968
  // Required for Autocomplete to generate keys.
3922
3969
  members: r.members.map((m) => ({
3970
+ original_id: m.metadata.id,
3923
3971
  name: m.name,
3924
3972
  id: m.id,
3925
3973
  variant: m.metadata.variant.name,
@@ -4129,7 +4177,54 @@ function useBespokeTranslations(key) {
4129
4177
  function TranslationsProvider({ translations, children }) {
4130
4178
  return /* @__PURE__ */ jsx(TranslationsContext.Provider, { value: translations || {}, children });
4131
4179
  }
4132
- function TitleView({ title, tooltip, slug, settings }) {
4180
+
4181
+ // components/TitleIcon.tsx
4182
+ init_esm_shims();
4183
+ function TitleIcon({
4184
+ iconType = "none",
4185
+ iconVariant = "transparent",
4186
+ icon = "",
4187
+ iconRadius = "md",
4188
+ iconSize = "md",
4189
+ iconPadding = "1"
4190
+ }) {
4191
+ const [srcSVG, setSrcSVG] = useState(void 0);
4192
+ const iconPath = iconType === "custom" ? icon : `/api/cms/read/icons/${iconType}/icon.svg?name=${icon}`;
4193
+ useEffect(() => {
4194
+ if (iconType === "custom" || !icon) {
4195
+ setSrcSVG(void 0);
4196
+ return;
4197
+ } else {
4198
+ axios.get(iconPath, { responseType: "text" }).then((resp) => resp.data).then(setSrcSVG).catch(() => void 0);
4199
+ }
4200
+ }, [icon, iconType, iconPath]);
4201
+ const ready = iconType === "custom" || srcSVG;
4202
+ const iconElement = iconType === "custom" ? /* @__PURE__ */ jsx(Image, { src: iconPath, alt: "Title Icon" }) : void 0;
4203
+ return /* @__PURE__ */ jsx(Fragment, { children: iconType !== "none" && icon && icon !== "" && ready && /* @__PURE__ */ jsx(
4204
+ ThemeIcon,
4205
+ {
4206
+ className: "bespoke-title-icon-wrapper",
4207
+ variant: iconVariant,
4208
+ radius: iconRadius,
4209
+ size: iconSize,
4210
+ p: parseInt(iconPadding),
4211
+ dangerouslySetInnerHTML: srcSVG && iconType !== "custom" ? { __html: srcSVG } : void 0,
4212
+ children: iconElement
4213
+ }
4214
+ ) });
4215
+ }
4216
+ function TitleView({
4217
+ title,
4218
+ tooltip,
4219
+ slug,
4220
+ iconType,
4221
+ iconVariant,
4222
+ icon,
4223
+ settings,
4224
+ iconRadius,
4225
+ iconSize,
4226
+ iconPadding
4227
+ }) {
4133
4228
  const titleHTML = sanitizeBlockContent_default(title) || "<span class='cr-block-placeholder'>Title</span>";
4134
4229
  const tooltipHTML = sanitizeBlockContent_default(tooltip);
4135
4230
  const intOrder = settings && settings.order ? parseInt(settings.order, 10) : 1;
@@ -4143,13 +4238,26 @@ function TitleView({ title, tooltip, slug, settings }) {
4143
4238
  variantId: state.variables.attributes.variant_id1
4144
4239
  })
4145
4240
  );
4146
- const titleElement = /* @__PURE__ */ jsx(
4147
- Title,
4148
- {
4149
- order,
4150
- dangerouslySetInnerHTML: { __html: titleHTML }
4151
- }
4152
- );
4241
+ const titleElement = /* @__PURE__ */ jsxs(Group, { children: [
4242
+ /* @__PURE__ */ jsx(
4243
+ TitleIcon,
4244
+ {
4245
+ iconType,
4246
+ iconVariant,
4247
+ iconRadius,
4248
+ iconSize,
4249
+ icon,
4250
+ iconPadding
4251
+ }
4252
+ ),
4253
+ /* @__PURE__ */ jsx(
4254
+ Title,
4255
+ {
4256
+ order,
4257
+ dangerouslySetInnerHTML: { __html: titleHTML }
4258
+ }
4259
+ )
4260
+ ] });
4153
4261
  const locale = currentLocale;
4154
4262
  const translations = useBespokeTranslations();
4155
4263
  const finalElement = useMemo(() => {
@@ -4429,6 +4537,15 @@ init_esm_shims();
4429
4537
  init_store2();
4430
4538
  init_ordering();
4431
4539
  init_cms();
4540
+ var parseIconProps = (variables, nodeId) => {
4541
+ return {
4542
+ iconType: variables[`title${nodeId}iconType`],
4543
+ icon: variables[`title${nodeId}icon`],
4544
+ iconSize: variables[`title${nodeId}iconSize`],
4545
+ iconVariant: variables[`title${nodeId}iconVariant`],
4546
+ iconRadius: variables[`title${nodeId}iconRadius`]
4547
+ };
4548
+ };
4432
4549
  function createOutline(elements, variables) {
4433
4550
  const roots = [];
4434
4551
  const nodes = {};
@@ -4442,10 +4559,13 @@ function createOutline(elements, variables) {
4442
4559
  for (const element of elements) {
4443
4560
  const { id, settings } = element;
4444
4561
  const order = parseInt(settings.order);
4562
+ const nodeVariables = variables[id] ? variables[id] : false;
4563
+ const iconProps = parseIconProps(nodeVariables, id);
4445
4564
  const node = {
4446
4565
  id,
4447
4566
  order,
4448
4567
  label: variables[id] ? variables[id][`title${id}title`] : `Title ${id}`,
4568
+ iconProps,
4449
4569
  children: []
4450
4570
  };
4451
4571
  nodes[id] = node;
@@ -4516,19 +4636,16 @@ function useContentOutline(min = 1, max = 6, headings = []) {
4516
4636
  }
4517
4637
  }, [titlesSorted, titleVariables]);
4518
4638
  }
4519
- var DesktopNav = ({ contentOutline }) => {
4639
+ var DesktopNav = ({ contentOutline, showIcons }) => {
4520
4640
  const renderMenu = (nodes) => {
4521
4641
  if (!nodes || !nodes?.length) {
4522
4642
  return null;
4523
4643
  }
4524
4644
  return /* @__PURE__ */ jsx(Fragment, { children: nodes.map((node) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
4525
- /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsx(
4526
- Anchor,
4527
- {
4528
- href: `#bespoke-title-${node.id}`,
4529
- dangerouslySetInnerHTML: { __html: node.label }
4530
- }
4531
- ) }),
4645
+ /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsx(Anchor, { href: `#bespoke-title-${node.id}`, children: /* @__PURE__ */ jsxs(Group, { spacing: "sm", children: [
4646
+ showIcons && /* @__PURE__ */ jsx(TitleIcon, { ...node.iconProps, iconSize: "sm" }),
4647
+ /* @__PURE__ */ jsx(Text, { dangerouslySetInnerHTML: { __html: node.label } })
4648
+ ] }) }) }),
4532
4649
  /* @__PURE__ */ jsx(Box, { pl: "xs", children: renderMenu(node.children) })
4533
4650
  ] }, node.id)) });
4534
4651
  };
@@ -4543,14 +4660,17 @@ var DesktopNav = ({ contentOutline }) => {
4543
4660
  }),
4544
4661
  children: contentOutline?.map(
4545
4662
  (node) => /* @__PURE__ */ jsx(List.Item, { children: /* @__PURE__ */ jsxs(Menu, { trigger: "hover", openDelay: 100, closeDelay: 400, children: [
4546
- /* @__PURE__ */ jsx(Menu.Target, { children: /* @__PURE__ */ jsx(Anchor, { href: `#bespoke-title-${node.id}`, dangerouslySetInnerHTML: { __html: node.label } }) }),
4663
+ /* @__PURE__ */ jsx(Menu.Target, { children: /* @__PURE__ */ jsx(Anchor, { href: `#bespoke-title-${node.id}`, children: /* @__PURE__ */ jsxs(Group, { spacing: "sm", children: [
4664
+ showIcons && /* @__PURE__ */ jsx(TitleIcon, { ...node.iconProps, iconSize: "sm" }),
4665
+ /* @__PURE__ */ jsx(Text, { dangerouslySetInnerHTML: { __html: node.label } })
4666
+ ] }) }) }),
4547
4667
  node?.children?.length ? /* @__PURE__ */ jsx(Menu.Dropdown, { children: renderMenu(node.children) }) : null
4548
4668
  ] }) }, node.id)
4549
4669
  )
4550
4670
  }
4551
4671
  ) });
4552
4672
  };
4553
- var MobileAccordion = ({ contentOutline, closeNav }) => {
4673
+ var MobileAccordion = ({ contentOutline, closeNav, showIcons }) => {
4554
4674
  const [value, setValue] = useState(null);
4555
4675
  return /* @__PURE__ */ jsx(Stack, { children: contentOutline.map(
4556
4676
  (node) => node.children.length ? /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -4562,7 +4682,10 @@ var MobileAccordion = ({ contentOutline, closeNav }) => {
4562
4682
  component: "a",
4563
4683
  onClick: closeNav,
4564
4684
  href: `#bespoke-title-${node.id}`,
4565
- children: /* @__PURE__ */ jsx(Text, { pl: "sm", dangerouslySetInnerHTML: { __html: node.label } })
4685
+ children: /* @__PURE__ */ jsxs(Group, { spacing: "sm", noWrap: true, children: [
4686
+ showIcons && /* @__PURE__ */ jsx(TitleIcon, { ...node.iconProps, iconSize: "sm", iconPadding: "2" }),
4687
+ /* @__PURE__ */ jsx(Text, { dangerouslySetInnerHTML: { __html: node.label } })
4688
+ ] })
4566
4689
  },
4567
4690
  node.id
4568
4691
  ),
@@ -4592,7 +4715,7 @@ var MobileAccordion = ({ contentOutline, closeNav }) => {
4592
4715
  pl: "sm",
4593
4716
  transitionTimingFunction: "linear",
4594
4717
  transitionDuration: 200,
4595
- children: /* @__PURE__ */ jsx(MobileAccordion, { contentOutline: node.children, closeNav })
4718
+ children: /* @__PURE__ */ jsx(MobileAccordion, { contentOutline: node.children, closeNav, showIcons })
4596
4719
  }
4597
4720
  )
4598
4721
  ] }) : /* @__PURE__ */ jsx(Group, { noWrap: true, children: /* @__PURE__ */ jsx(
@@ -4602,27 +4725,31 @@ var MobileAccordion = ({ contentOutline, closeNav }) => {
4602
4725
  w: "100%",
4603
4726
  component: "a",
4604
4727
  href: `#bespoke-title-${node.id}`,
4605
- children: /* @__PURE__ */ jsx(Text, { pl: "sm", dangerouslySetInnerHTML: { __html: node.label } })
4728
+ children: /* @__PURE__ */ jsxs(Group, { spacing: "sm", children: [
4729
+ showIcons && /* @__PURE__ */ jsx(TitleIcon, { ...node.iconProps, iconSize: "sm" }),
4730
+ /* @__PURE__ */ jsx(Text, { dangerouslySetInnerHTML: { __html: node.label } })
4731
+ ] })
4606
4732
  },
4607
4733
  node.id
4608
4734
  ) }, node.id)
4609
4735
  ) });
4610
4736
  };
4611
- var MobileNav = ({ contentOutline }) => {
4737
+ var MobileNav = ({ contentOutline, showIcons }) => {
4612
4738
  const [opened, { toggle }] = useDisclosure(false);
4613
4739
  const label = opened ? "Close content outline" : "Open content outline";
4614
4740
  return /* @__PURE__ */ jsxs(Box, { component: "nav", children: [
4615
4741
  /* @__PURE__ */ jsx(Group, { children: /* @__PURE__ */ jsx(Burger, { opened, onClick: toggle, "aria-label": label, size: "md" }) }),
4616
- /* @__PURE__ */ jsx(Collapse, { in: opened, mt: "md", children: /* @__PURE__ */ jsx(MobileAccordion, { contentOutline, closeNav: toggle }) })
4742
+ /* @__PURE__ */ jsx(Collapse, { in: opened, mt: "md", children: /* @__PURE__ */ jsx(MobileAccordion, { contentOutline, closeNav: toggle, showIcons }) })
4617
4743
  ] });
4618
4744
  };
4619
4745
  function NavView({ headings, settings }) {
4620
- const { type, min, max } = settings;
4746
+ const { min = "1", max = "6", icons = "yes" } = settings;
4621
4747
  const contentOutline = useContentOutline(parseInt(min, 10), parseInt(max, 10), headings);
4748
+ const showIcons = icons === "yes" ? true : false;
4622
4749
  const theme = useMantineTheme();
4623
4750
  const mq = `(max-width: ${theme.breakpoints.md})`;
4624
4751
  const smallerThanMd = useMediaQuery(mq);
4625
- return smallerThanMd ? /* @__PURE__ */ jsx(MobileNav, { contentOutline }) : /* @__PURE__ */ jsx(DesktopNav, { contentOutline });
4752
+ return smallerThanMd ? /* @__PURE__ */ jsx(MobileNav, { contentOutline, showIcons }) : /* @__PURE__ */ jsx(DesktopNav, { contentOutline, showIcons });
4626
4753
  }
4627
4754
 
4628
4755
  // frontend/components/report/blocks/Related.tsx
@@ -4646,6 +4773,7 @@ function RelatedView(props) {
4646
4773
  return /* @__PURE__ */ jsx(Grid, { children: related_reports.map((report) => {
4647
4774
  const url = `${localePrefix}${profilePrefix}/${report.variant_slug}/${report.slug}`;
4648
4775
  const members = [{
4776
+ original_id: report.id,
4649
4777
  name: report.name,
4650
4778
  id: report.content_id,
4651
4779
  variant: report.variant_name,
@@ -5230,12 +5358,249 @@ function ResetButtonUI({ id, locale, executeButton, onChange, simpleState }) {
5230
5358
  ] });
5231
5359
  }
5232
5360
 
5361
+ // components/blocks/types/simpleEditors/TitleUI.tsx
5362
+ init_esm_shims();
5363
+
5364
+ // libs/blocks/stringifyWithTemplate.ts
5365
+ init_esm_shims();
5366
+ var stringifyWithTemplate_default = (object) => {
5367
+ const regex = /([A-z0-9]*)\{\{([^}]+)\}\}/g;
5368
+ const matcher = (_, formatter, variable) => formatter ? `\${formatters.${formatter}(variables.${variable})}` : `\${variables.${variable}}`;
5369
+ const codified = Object.keys(object).reduce((acc, d) => ({
5370
+ ...acc,
5371
+ [d]: object[d].replace(regex, matcher)
5372
+ }), {});
5373
+ return Object.keys(codified).reduce((acc, d) => acc.concat(`"${d}": \`${codified[d]}\`,
5374
+ `), "return {").concat("};");
5375
+ };
5376
+ var TITLE_ICON_TYPES = {
5377
+ none: { value: "none", label: "No Icon" },
5378
+ tabler: { value: "tabler", label: "Bespoke (Tabler)", link: "https://tablericons.com", text: "Tabler Icons" },
5379
+ custom: { value: "custom", label: "Custom Icon" }
5380
+ };
5381
+ var SIZE_OPTIONS = ["xs", "sm", "md", "lg", "xl"];
5382
+ var PADDING_OPTIONS = ["0", "1", "2", "3", "4", "5"];
5383
+ function TitleUI({ onChange, simpleState }) {
5384
+ const [title, setTitle] = useState(simpleState?.title ?? "Title");
5385
+ const [tooltip, setTooltip] = useState(simpleState?.tooltip ?? "");
5386
+ const [slug, setSlug] = useState(simpleState?.slug ?? "");
5387
+ const [iconType, setIconType] = useState(simpleState?.iconType ?? "none");
5388
+ const [icon, setIcon] = useState(simpleState?.icon ?? "");
5389
+ const [iconsList, setIconsList] = useState([]);
5390
+ const [defaultIcon, setDefaultIcon] = useState("");
5391
+ const [iconVariant, setIconVariant] = useState(simpleState?.iconVariant ?? "transparent");
5392
+ const [iconSize, setIconSize] = useState(simpleState?.iconSize ?? "md");
5393
+ const [iconRadius, setIconRadius] = useState(simpleState?.iconRadius ?? "md");
5394
+ const [iconPadding, setIconPadding] = useState(simpleState?.iconPadding ?? "1");
5395
+ const isNoneOrCustom = [TITLE_ICON_TYPES.none.value, TITLE_ICON_TYPES.custom.value].includes(iconType);
5396
+ const IconItem = useMemo(() => forwardRef(
5397
+ ({ value, ...others }, ref) => {
5398
+ return /* @__PURE__ */ jsx("div", { ref, ...others, children: /* @__PURE__ */ jsxs(Group, { noWrap: true, children: [
5399
+ /* @__PURE__ */ jsx(
5400
+ Image,
5401
+ {
5402
+ width: 20,
5403
+ height: 20,
5404
+ src: `/api/cms/read/icons/${iconType}/icon.svg?name=${value}`,
5405
+ alt: `Icon preview ${value}`
5406
+ }
5407
+ ),
5408
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: value })
5409
+ ] }) });
5410
+ }
5411
+ ), [iconType]);
5412
+ useEffect(() => {
5413
+ if (!isNoneOrCustom) {
5414
+ axios.get(`/api/cms/list/icons/${iconType}`).then((response) => {
5415
+ setIconsList(response.data.data.results);
5416
+ setDefaultIcon(response.data.data.results[0]);
5417
+ });
5418
+ }
5419
+ }, [iconType]);
5420
+ useEffect(() => {
5421
+ const simpleState2 = {
5422
+ title,
5423
+ tooltip,
5424
+ slug,
5425
+ iconType,
5426
+ icon,
5427
+ iconVariant,
5428
+ iconSize,
5429
+ iconRadius,
5430
+ iconPadding
5431
+ };
5432
+ const logic = stringifyWithTemplate_default(simpleState2);
5433
+ onChange(simpleState2, logic);
5434
+ }, [title, tooltip, slug, iconType, icon, iconVariant, iconSize, iconRadius, iconPadding]);
5435
+ const onChangeIconType = (value) => {
5436
+ setIcon("");
5437
+ setIconType(value);
5438
+ };
5439
+ return /* @__PURE__ */ jsxs(Stack, { children: [
5440
+ /* @__PURE__ */ jsx(
5441
+ TextInput,
5442
+ {
5443
+ placeholder: "Title here",
5444
+ label: "Title",
5445
+ required: true,
5446
+ value: title,
5447
+ onChange: (e) => setTitle(e.currentTarget.value)
5448
+ }
5449
+ ),
5450
+ /* @__PURE__ */ jsx(
5451
+ TextInput,
5452
+ {
5453
+ placeholder: "Tooltip text here",
5454
+ label: "Tooltip",
5455
+ value: tooltip,
5456
+ onChange: (e) => setTooltip(e.currentTarget.value)
5457
+ }
5458
+ ),
5459
+ /* @__PURE__ */ jsx(
5460
+ TextInput,
5461
+ {
5462
+ placeholder: "Slug here",
5463
+ label: "Slug",
5464
+ value: slug,
5465
+ onChange: (e) => setSlug(e.currentTarget.value)
5466
+ }
5467
+ ),
5468
+ /* @__PURE__ */ jsx(Accordion, { variant: "separated", children: /* @__PURE__ */ jsxs(Accordion.Item, { value: "icon", children: [
5469
+ /* @__PURE__ */ jsx(Accordion.Control, { icon: /* @__PURE__ */ jsx(IconPhotoFilled, {}), children: /* @__PURE__ */ jsxs(Text, { children: [
5470
+ "Icon configuration: ",
5471
+ TITLE_ICON_TYPES[iconType] && /* @__PURE__ */ jsx("strong", { children: TITLE_ICON_TYPES[iconType].label })
5472
+ ] }) }),
5473
+ /* @__PURE__ */ jsx(Accordion.Panel, { children: /* @__PURE__ */ jsxs(Stack, { children: [
5474
+ /* @__PURE__ */ jsx(Input.Wrapper, { label: "Title's icon", children: /* @__PURE__ */ jsx(
5475
+ SegmentedControl,
5476
+ {
5477
+ fullWidth: true,
5478
+ value: iconType,
5479
+ onChange: onChangeIconType,
5480
+ data: Object.values(TITLE_ICON_TYPES)
5481
+ }
5482
+ ) }),
5483
+ !isNoneOrCustom && /* @__PURE__ */ jsx(
5484
+ Select,
5485
+ {
5486
+ label: "Choose an icon from the list",
5487
+ searchable: true,
5488
+ description: /* @__PURE__ */ jsxs(Fragment, { children: [
5489
+ "Powered by",
5490
+ " ",
5491
+ /* @__PURE__ */ jsx(
5492
+ "a",
5493
+ {
5494
+ style: { textDecoration: "underline" },
5495
+ href: TITLE_ICON_TYPES[iconType].link,
5496
+ target: "_blank",
5497
+ rel: "noreferrer",
5498
+ children: TITLE_ICON_TYPES[iconType].text
5499
+ }
5500
+ ),
5501
+ "."
5502
+ ] }),
5503
+ defaultValue: defaultIcon,
5504
+ value: icon,
5505
+ required: true,
5506
+ onChange: (value) => setIcon(value ? value : ""),
5507
+ data: iconsList,
5508
+ limit: 20,
5509
+ itemComponent: IconItem,
5510
+ disabled: !iconsList || iconsList.length === 0,
5511
+ icon: /* @__PURE__ */ jsx(
5512
+ Image,
5513
+ {
5514
+ p: 5,
5515
+ src: `/api/cms/read/icons/${iconType}/icon.svg?name=${icon || defaultIcon}`,
5516
+ alt: "Icon preview"
5517
+ }
5518
+ )
5519
+ }
5520
+ ),
5521
+ iconType === TITLE_ICON_TYPES.custom.value && /* @__PURE__ */ jsx(
5522
+ TextInput,
5523
+ {
5524
+ placeholder: "e.g '/image{{id1}}.png' or 'http://site/image.png'",
5525
+ label: "Enter an icon path",
5526
+ required: true,
5527
+ value: icon,
5528
+ description: "Add an absolute or a relative path url",
5529
+ onChange: (e) => setIcon(e.currentTarget.value)
5530
+ }
5531
+ ),
5532
+ iconType !== TITLE_ICON_TYPES.none.value && /* @__PURE__ */ jsxs(Fragment, { children: [
5533
+ /* @__PURE__ */ jsx(
5534
+ Select,
5535
+ {
5536
+ label: "Variant design",
5537
+ description: /* @__PURE__ */ jsxs(Fragment, { children: [
5538
+ "We are using",
5539
+ " ",
5540
+ /* @__PURE__ */ jsx(
5541
+ "a",
5542
+ {
5543
+ style: { textDecoration: "underline" },
5544
+ href: "https://mantine.dev/core/theme-icon/",
5545
+ target: "_blank",
5546
+ rel: "noreferrer",
5547
+ children: "ThemeIcon variants"
5548
+ }
5549
+ ),
5550
+ "."
5551
+ ] }),
5552
+ value: iconVariant,
5553
+ required: true,
5554
+ onChange: (value) => setIconVariant(value ? value : "transparent"),
5555
+ data: [
5556
+ { value: "transparent", label: "Transparent" },
5557
+ { value: "default", label: "Default" },
5558
+ { value: "outline", label: "Outline" },
5559
+ { value: "light", label: "Light" },
5560
+ { value: "filled", label: "Filled" }
5561
+ ]
5562
+ }
5563
+ ),
5564
+ /* @__PURE__ */ jsx(Input.Wrapper, { label: "Size", children: /* @__PURE__ */ jsx(
5565
+ SegmentedControl,
5566
+ {
5567
+ fullWidth: true,
5568
+ value: iconSize,
5569
+ onChange: setIconSize,
5570
+ data: SIZE_OPTIONS
5571
+ }
5572
+ ) }),
5573
+ /* @__PURE__ */ jsx(Input.Wrapper, { label: "Radius", children: /* @__PURE__ */ jsx(
5574
+ SegmentedControl,
5575
+ {
5576
+ fullWidth: true,
5577
+ value: iconRadius,
5578
+ onChange: setIconRadius,
5579
+ data: SIZE_OPTIONS
5580
+ }
5581
+ ) }),
5582
+ /* @__PURE__ */ jsx(Input.Wrapper, { label: "Padding", children: /* @__PURE__ */ jsx(
5583
+ SegmentedControl,
5584
+ {
5585
+ fullWidth: true,
5586
+ value: iconPadding,
5587
+ onChange: setIconPadding,
5588
+ data: PADDING_OPTIONS
5589
+ }
5590
+ ) })
5591
+ ] })
5592
+ ] }) })
5593
+ ] }) })
5594
+ ] });
5595
+ }
5596
+
5233
5597
  // components/blocks/types/simpleEditors/index.js
5234
5598
  var simpleEditors_default = {
5235
5599
  [BLOCK_TYPES.SELECTOR]: SelectorUI_default,
5236
5600
  [BLOCK_TYPES.NAV]: NavUI,
5237
5601
  [BLOCK_TYPES.RELATED]: RelatedUI,
5238
- [BLOCK_TYPES.RESET_BUTTON]: ResetButtonUI
5602
+ [BLOCK_TYPES.RESET_BUTTON]: ResetButtonUI,
5603
+ [BLOCK_TYPES.TITLE]: TitleUI
5239
5604
  };
5240
5605
 
5241
5606
  // components/blocks/types/renderers/index.tsx
@@ -5293,7 +5658,18 @@ function SubtitlePreview({ subtitle, tooltip }) {
5293
5658
  // components/blocks/types/renderers/Title.tsx
5294
5659
  init_esm_shims();
5295
5660
  init_sanitizeBlockContent();
5296
- function TitlePreview({ title, tooltip, slug, settings }) {
5661
+ function TitlePreview({
5662
+ title,
5663
+ tooltip,
5664
+ slug,
5665
+ iconType,
5666
+ iconVariant,
5667
+ icon,
5668
+ settings,
5669
+ iconRadius,
5670
+ iconSize,
5671
+ iconPadding
5672
+ }) {
5297
5673
  const titleHTML = sanitizeBlockContent_default(title) || "<span class='cr-block-placeholder'>Title</span>";
5298
5674
  const tooltipHTML = sanitizeBlockContent_default(tooltip);
5299
5675
  const intOrder = settings && settings.order ? parseInt(settings.order, 10) : 1;
@@ -5302,6 +5678,17 @@ function TitlePreview({ title, tooltip, slug, settings }) {
5302
5678
  const searchType = settings && settings.search ? settings.search : "";
5303
5679
  return /* @__PURE__ */ jsxs(Group, { spacing: "sm", children: [
5304
5680
  tooltip && /* @__PURE__ */ jsx("div", { style: { position: "absolute", right: "50px" }, children: /* @__PURE__ */ jsx(Tooltip, { label: tooltipHTML, withArrow: true, withinPortal: true, children: /* @__PURE__ */ jsx(ActionIcon, { children: /* @__PURE__ */ jsx(IconInfoCircle, {}) }) }) }, "tooltip"),
5681
+ /* @__PURE__ */ jsx(
5682
+ TitleIcon,
5683
+ {
5684
+ iconType,
5685
+ iconVariant,
5686
+ iconRadius,
5687
+ iconSize,
5688
+ icon,
5689
+ iconPadding
5690
+ }
5691
+ ),
5305
5692
  /* @__PURE__ */ jsx(
5306
5693
  Title,
5307
5694
  {
@@ -5695,52 +6082,50 @@ function stringifyPreview(member) {
5695
6082
  return `${variant}/${member.slug}`;
5696
6083
  }
5697
6084
  function NavPreview({ headings, settings }) {
5698
- const { type, min, max } = settings;
6085
+ const { min = "1", max = "6", icons = "yes" } = settings;
5699
6086
  const contentOutline = useContentOutline(parseInt(min, 10), parseInt(max, 10), headings);
6087
+ const showIcons = icons === "yes" ? true : false;
5700
6088
  const renderMenu = (nodes) => {
5701
6089
  if (!nodes || !nodes?.length) {
5702
6090
  return null;
5703
6091
  }
5704
6092
  return /* @__PURE__ */ jsx(Fragment, { children: nodes.map((node) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
5705
- /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
6093
+ /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsx(
5706
6094
  Anchor,
5707
6095
  {
5708
6096
  href: `#bespoke-title-${node.id}`,
5709
- children: [
5710
- "Title ",
5711
- node.id
5712
- ]
6097
+ children: /* @__PURE__ */ jsxs(Group, { spacing: "sm", children: [
6098
+ showIcons && /* @__PURE__ */ jsx(TitleIcon, { ...node.iconProps, iconSize: "sm" }),
6099
+ /* @__PURE__ */ jsx(Text, { dangerouslySetInnerHTML: { __html: node.label } })
6100
+ ] })
5713
6101
  }
5714
6102
  ) }, node.id),
5715
6103
  /* @__PURE__ */ jsx(Box, { pl: "xs", children: renderMenu(node.children) })
5716
6104
  ] }, node.id)) });
5717
6105
  };
5718
- return /* @__PURE__ */ jsxs(Box, { pos: "relative", children: [
5719
- /* @__PURE__ */ jsx(Tooltip, { label: `
5720
- This is a preview navbar.
5721
- The titles displayed are just placeholders.
5722
- The finished report will display the actual titles.`, children: /* @__PURE__ */ jsx(ActionIcon, { pos: "absolute", children: /* @__PURE__ */ jsx(IconInfoCircle, {}) }) }),
5723
- /* @__PURE__ */ jsx(
5724
- List,
5725
- {
5726
- sx: (theme) => ({
5727
- display: "flex",
5728
- gap: theme.spacing.md,
5729
- justifyContent: "center",
5730
- listStyleType: "none"
5731
- }),
5732
- children: contentOutline?.map(
6106
+ return /* @__PURE__ */ jsx(Box, { pos: "relative", children: /* @__PURE__ */ jsxs(
6107
+ List,
6108
+ {
6109
+ sx: (theme) => ({
6110
+ display: "flex",
6111
+ gap: theme.spacing.md,
6112
+ justifyContent: "center",
6113
+ listStyleType: "none"
6114
+ }),
6115
+ children: [
6116
+ contentOutline?.map(
5733
6117
  (node) => /* @__PURE__ */ jsx(List.Item, { children: /* @__PURE__ */ jsxs(Menu, { trigger: "hover", openDelay: 100, closeDelay: 400, children: [
5734
- /* @__PURE__ */ jsx(Menu.Target, { children: /* @__PURE__ */ jsxs(Anchor, { href: `#bespoke-title-${node.id}`, children: [
5735
- "Title ",
5736
- node.id
5737
- ] }) }),
6118
+ /* @__PURE__ */ jsx(Menu.Target, { children: /* @__PURE__ */ jsx(Anchor, { href: `#bespoke-title-${node.id}`, children: /* @__PURE__ */ jsxs(Group, { spacing: "sm", noWrap: true, children: [
6119
+ showIcons && /* @__PURE__ */ jsx(TitleIcon, { ...node.iconProps, iconSize: "sm", iconPadding: "2" }),
6120
+ /* @__PURE__ */ jsx(Text, { dangerouslySetInnerHTML: { __html: node.label } })
6121
+ ] }) }) }),
5738
6122
  node?.children?.length ? /* @__PURE__ */ jsx(Menu.Dropdown, { children: renderMenu(node.children) }) : null
5739
6123
  ] }) }, node.id)
5740
- )
5741
- }
5742
- )
5743
- ] });
6124
+ ),
6125
+ !contentOutline || contentOutline.length === 0 && /* @__PURE__ */ jsx(List.Item, { children: /* @__PURE__ */ jsx(Text, { color: "dimmed", children: "- Include some titles as inputs to see the Nav component -" }) })
6126
+ ]
6127
+ }
6128
+ ) });
5744
6129
  }
5745
6130
 
5746
6131
  // components/blocks/types/renderers/Related.tsx
@@ -5850,6 +6235,7 @@ var allBlocks = {
5850
6235
  [BLOCK_TYPES.TITLE]: {
5851
6236
  type: BLOCK_TYPES.TITLE,
5852
6237
  renderer: renderers_default[BLOCK_TYPES.TITLE],
6238
+ editor: simpleEditors_default[BLOCK_TYPES.TITLE],
5853
6239
  renderPreviewOnEdit: true,
5854
6240
  evalWhenNonActive: true
5855
6241
  },
@@ -6409,8 +6795,8 @@ function DataTab(props) {
6409
6795
  /* @__PURE__ */ jsx(
6410
6796
  Input.Wrapper,
6411
6797
  {
6412
- label: `Formatted ${selectedSource.title}
6413
- ${currentPreview.length > PREVIEW_SIZE ? `(First ${PREVIEW_SIZE} rows as preview)` : ""}:`,
6798
+ label: `${translations["formatted_src"].replace("{src}", selectedSource.title)}
6799
+ ${currentPreview.length > PREVIEW_SIZE ? translations["rows_preview"].replace("{nrows}", PREVIEW_SIZE) : ""}:`,
6414
6800
  style: { height: "300px" },
6415
6801
  children: /* @__PURE__ */ jsx(SortableTable, { data: currentPreview.slice(0, PREVIEW_SIZE) })
6416
6802
  }
@@ -6512,7 +6898,9 @@ var DEFAULT_TRANSLATIONS3 = {
6512
6898
  "choose_format": "Choose format",
6513
6899
  "transparent_bg": "Transparent background",
6514
6900
  "processing": "Processing image...",
6515
- "download": "Download {imageFormat}"
6901
+ "download": "Download {imageFormat}",
6902
+ "entire_section": "Entire section",
6903
+ "viz_only": "Visualization only"
6516
6904
  };
6517
6905
  function ImageTab(props) {
6518
6906
  const { section } = props;
@@ -6656,7 +7044,7 @@ function ImageTab(props) {
6656
7044
  },
6657
7045
  variant: imageContext === contextOptions.section ? "filled" : "default",
6658
7046
  fullWidth: true,
6659
- children: "Entire section"
7047
+ children: translations["entire_section"]
6660
7048
  }
6661
7049
  ),
6662
7050
  vizAvailable.length > 0 && /* @__PURE__ */ jsx(
@@ -6666,7 +7054,7 @@ function ImageTab(props) {
6666
7054
  onClick: () => setImageContext(contextOptions.viz),
6667
7055
  variant: imageContext === contextOptions.viz ? "filled" : "default",
6668
7056
  fullWidth: true,
6669
- children: "Visualization only"
7057
+ children: translations["viz_only"]
6670
7058
  }
6671
7059
  )
6672
7060
  ] }) }),
@@ -6896,9 +7284,9 @@ function Options(props) {
6896
7284
  };
6897
7285
  return /* @__PURE__ */ jsxs("div", { className: "cms-section-options", children: [
6898
7286
  /* @__PURE__ */ jsxs(Button.Group, { children: [
6899
- /* @__PURE__ */ jsx(Button, { variant: "default", size: "xs", compact: true, onClick: () => onClickOption("data"), children: /* @__PURE__ */ jsx(IconTable, { size: 16 }) }),
6900
- /* @__PURE__ */ jsx(Button, { variant: "default", size: "xs", compact: true, onClick: () => onClickOption("image"), children: /* @__PURE__ */ jsx(IconCamera, { size: 16 }) }),
6901
- /* @__PURE__ */ jsx(Button, { variant: "default", size: "xs", compact: true, onClick: () => onClickOption("share"), children: /* @__PURE__ */ jsx(IconShare, { size: 16 }) })
7287
+ /* @__PURE__ */ jsx(Button, { variant: "default", size: "xs", compact: true, onClick: () => onClickOption("data"), "aria-label": "Download Data", children: /* @__PURE__ */ jsx(IconTable, { size: 16 }) }),
7288
+ /* @__PURE__ */ jsx(Button, { variant: "default", size: "xs", compact: true, onClick: () => onClickOption("image"), "aria-label": "Download Chart", children: /* @__PURE__ */ jsx(IconCamera, { size: 16 }) }),
7289
+ /* @__PURE__ */ jsx(Button, { variant: "default", size: "xs", compact: true, onClick: () => onClickOption("share"), "aria-label": "Share Link", children: /* @__PURE__ */ jsx(IconShare, { size: 16 }) })
6902
7290
  ] }),
6903
7291
  sectionRef.isSuccess && /* @__PURE__ */ jsx(
6904
7292
  OptionsModal,
@@ -7329,6 +7717,14 @@ var customSettings = {
7329
7717
  { label: "5", value: "5" },
7330
7718
  { label: "6", value: "6" }
7331
7719
  ]
7720
+ },
7721
+ icons: {
7722
+ label: "Show icons",
7723
+ defaultValue: "yes",
7724
+ options: [
7725
+ { label: "Yes", value: "yes" },
7726
+ { label: "No", value: "no" }
7727
+ ]
7332
7728
  }
7333
7729
  }
7334
7730
  };
@@ -10147,19 +10543,6 @@ var deepClone = (o) => {
10147
10543
  return output;
10148
10544
  };
10149
10545
  var deepClone_default = deepClone;
10150
-
10151
- // libs/blocks/stringifyWithTemplate.ts
10152
- init_esm_shims();
10153
- var stringifyWithTemplate_default = (object) => {
10154
- const regex = /([A-z0-9]*)\{\{([^}]+)\}\}/g;
10155
- const matcher = (_, formatter, variable) => formatter ? `\${formatters.${formatter}(variables.${variable})}` : `\${variables.${variable}}`;
10156
- const codified = Object.keys(object).reduce((acc, d) => ({
10157
- ...acc,
10158
- [d]: object[d].replace(regex, matcher)
10159
- }), {});
10160
- return Object.keys(codified).reduce((acc, d) => acc.concat(`"${d}": \`${codified[d]}\`,
10161
- `), "return {").concat("};");
10162
- };
10163
10546
  function RichTextEditor({
10164
10547
  fields,
10165
10548
  locale,
package/dist/server.js CHANGED
@@ -20,6 +20,9 @@ import FlickrSDK from 'flickr-sdk';
20
20
  import sharp from 'sharp';
21
21
  import { createApi } from 'unsplash-js';
22
22
  import fs from 'fs';
23
+ import { createContext, createElement } from 'react';
24
+ import { renderToString } from 'react-dom/server';
25
+ import * as allIcons from '@tabler/icons-react';
23
26
  import NextCors from 'nextjs-cors';
24
27
  import formidable from 'formidable';
25
28
  import toposort from 'toposort';
@@ -27,7 +30,6 @@ import { schema, normalize } from 'normalizr';
27
30
  import { createSlice, configureStore } from '@reduxjs/toolkit';
28
31
  import { HYDRATE, createWrapper } from 'next-redux-wrapper';
29
32
  import { notifications } from '@mantine/notifications';
30
- import { createContext } from 'react';
31
33
  import 'react-redux';
32
34
  import 'react/jsx-runtime';
33
35
  import '@mantine/core';
@@ -3302,6 +3304,45 @@ function dbReadPrivateBlocksFactory(db) {
3302
3304
  };
3303
3305
  }
3304
3306
  }
3307
+ var iconsList = Object.keys(allIcons).filter((iconName) => iconName.indexOf("Icon") === 0);
3308
+ async function listTablerProvider() {
3309
+ return {
3310
+ meta: {
3311
+ provider: "tabler"
3312
+ },
3313
+ results: iconsList
3314
+ };
3315
+ }
3316
+ async function readTablerProvider(params) {
3317
+ const IconComponent = allIcons[params.name] ? allIcons[params.name] : () => "";
3318
+ return renderToString(createElement(IconComponent));
3319
+ }
3320
+
3321
+ // api/db/icon/listIcons.ts
3322
+ var listIconProviders = {
3323
+ tabler: listTablerProvider
3324
+ };
3325
+ function dbListIconsFactory(provider) {
3326
+ const listIconProvider = listIconProviders[provider];
3327
+ if (!listIconProvider) {
3328
+ throw new BackendError(500, `Wrong provider dbListIconsFactory.ts: ${provider}`);
3329
+ }
3330
+ return resultWrapper(() => listIconProvider());
3331
+ }
3332
+
3333
+ // api/db/icon/readIcon.ts
3334
+ var readIconProviders = {
3335
+ tabler: readTablerProvider
3336
+ };
3337
+ function dbReadIconFactory(provider) {
3338
+ return resultWrapper((params) => {
3339
+ const readIconProvider = readIconProviders[provider];
3340
+ if (!readIconProvider) {
3341
+ throw new BackendError(500, `Wrong provider dbReadIconFactory.ts: ${params.provider}`);
3342
+ }
3343
+ return readIconProvider({ name: params.name, provider });
3344
+ });
3345
+ }
3305
3346
 
3306
3347
  // api/db/index.ts
3307
3348
  function useDatabaseApi(_, __, api) {
@@ -3330,7 +3371,9 @@ function apiFactory(dbModels) {
3330
3371
  addNewReportToCurrentUser: resultWrapper(dbAddNewReportToCurrentUser()),
3331
3372
  revalidateReport: resultWrapper(dbRevalidateReportFactory(dbModels)),
3332
3373
  revalidateUrl: resultWrapper(dbRevalidateUrlFactory()),
3333
- readPrivateBlocks: resultWrapper(dbReadPrivateBlocksFactory(dbModels))
3374
+ readPrivateBlocks: resultWrapper(dbReadPrivateBlocksFactory(dbModels)),
3375
+ listTablerIcons: dbListIconsFactory("tabler"),
3376
+ readTablerIcon: dbReadIconFactory("tabler")
3334
3377
  };
3335
3378
  }
3336
3379
  async function parseBody(req) {
@@ -3657,6 +3700,27 @@ function endpointAddNewReportToCurrentUserFactory(operations) {
3657
3700
  }, [CMS_ROLES.EDITOR]);
3658
3701
  }
3659
3702
 
3703
+ // api/endpoints/icon/listIcons.ts
3704
+ function endpointListIconsFactory(operations, provider) {
3705
+ const listMethodKey = `list${capitalize(provider)}Icons`;
3706
+ const listMethod = operations[listMethodKey];
3707
+ return endpoint("GET", `list/icons/${provider}`, async () => {
3708
+ return listMethod({});
3709
+ }, [CMS_ROLES.EDITOR]);
3710
+ }
3711
+
3712
+ // api/endpoints/icon/readIcon.ts
3713
+ function endpointReadIconFactory(operations, provider) {
3714
+ const readMethodKey = `read${capitalize(provider)}Icon`;
3715
+ const readMethod = operations[readMethodKey];
3716
+ return endpoint("GET", `read/icons/${provider}/icon.svg`, async (req) => {
3717
+ return readMethod({
3718
+ provider,
3719
+ name: req.query.name
3720
+ });
3721
+ });
3722
+ }
3723
+
3660
3724
  // api/endpoints/index.ts
3661
3725
  var verbose3 = getLogging_default();
3662
3726
  function endpointKey(method, path) {
@@ -3690,7 +3754,9 @@ function getEndpointMap(db) {
3690
3754
  endpointAddNewReportToCurrentUserFactory(api),
3691
3755
  endpointRevalidateReportFactory(api),
3692
3756
  endpointRevalidateUrlFactory(),
3693
- endpointReadPrivateBlocksFactory(api)
3757
+ endpointReadPrivateBlocksFactory(api),
3758
+ endpointListIconsFactory(api, "tabler"),
3759
+ endpointReadIconFactory(api, "tabler")
3694
3760
  ].map(({ handler, method, path, roleRequired }) => [endpointKey(method, path), { handler, roleRequired }]));
3695
3761
  }
3696
3762
  var endpointMap = getDB().then(getEndpointMap);
@@ -3730,6 +3796,9 @@ async function endpointNextJsHandlerFactory(req, res) {
3730
3796
  if (result.data instanceof Buffer) {
3731
3797
  const image = result.data;
3732
3798
  res.writeHead(result.status, { "Content-Type": "image/png" }).end(image, "binary");
3799
+ } else if (req.url.indexOf("/icon.svg?name=") > 0) {
3800
+ res.setHeader("Content-Type", "image/svg+xml");
3801
+ res.status(result.status).send(result.data);
3733
3802
  } else {
3734
3803
  res.status(result.status).json({ data: result.data });
3735
3804
  }
@@ -3848,6 +3917,24 @@ function httpImageSearchFactory(axios6, provider) {
3848
3917
  });
3849
3918
  }
3850
3919
 
3920
+ // api/http/icon/listIcons.ts
3921
+ function httpListIconsFactory(axios6, provider) {
3922
+ return () => http(axios6, {
3923
+ method: "GET",
3924
+ url: `list/icons/${provider}`,
3925
+ params: { provider }
3926
+ });
3927
+ }
3928
+
3929
+ // api/http/icon/readIcon.ts
3930
+ function httpReadIconFactory(axios6, provider) {
3931
+ return (params) => http(axios6, {
3932
+ method: "GET",
3933
+ url: `read/icons/${provider}/icon.svg`,
3934
+ params: { name: params.name }
3935
+ });
3936
+ }
3937
+
3851
3938
  // api/http/index.ts
3852
3939
  var transformDeletePayload = (id) => {
3853
3940
  return { id };
@@ -3917,7 +4004,9 @@ function apiFactory2(baseURL) {
3917
4004
  addNewReportToCurrentUser: httpPOST(axios6, "auth/update/me"),
3918
4005
  revalidateReport: httpGET(axios6, "revalidate/report"),
3919
4006
  revalidateUrl: httpGET(axios6, "revalidate/url"),
3920
- readPrivateBlocks: httpPOST(axios6, "read/blocks/private")
4007
+ readPrivateBlocks: httpPOST(axios6, "read/blocks/private"),
4008
+ listTablerIcons: httpListIconsFactory(axios6, "tabler"),
4009
+ readTablerIcon: httpReadIconFactory(axios6, "tabler")
3921
4010
  };
3922
4011
  }
3923
4012
 
@@ -4000,6 +4089,8 @@ function varSwap(sourceString, formatterFunctions, blockContext, passive = false
4000
4089
  const formatTitle = g1.replace(/^\w/g, (chr) => chr.toLowerCase());
4001
4090
  if (formatTitle in formatterFunctions) {
4002
4091
  formatter = formatterFunctions[formatTitle];
4092
+ } else {
4093
+ formatter = (d) => `${g1}${d}`;
4003
4094
  }
4004
4095
  }
4005
4096
  const value = Array.isArray(variables[keyMatch]) ? list(variables[keyMatch]) : variables[keyMatch];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datawheel/bespoke",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Content management system for creating automated data reports",
5
5
  "exports": {
6
6
  ".": {