) => { state.isDeleting = true; },\n fontDeleteSuccess: (state) => { state.deleteSuccess = true; state.isDeleting = false; },\n fontDeleteFailed: (state) => { state.deleteSuccess = false; state.isDeleting = false; },\n resetDeleteSuccess: (state) => { state.deleteSuccess = false; },\n },\n});\n\nexport const { reducer } = slice;\n\nexport default slice;\n","import React from 'react';\nimport { Link } from 'react-router-dom';\n\ninterface Props {\n repoId: string;\n fontFamilyName: string;\n repoName: string;\n existingStyles?: string[];\n style?: string;\n}\n\nexport function DetailsSection({ repoId, fontFamilyName, repoName, existingStyles, style }: Props) {\n return (\n \n
\n {fontFamilyName} \n
\n {\n style && (\n
\n {style} \n
\n )\n }\n
\n {repoName} \n
\n {existingStyles && (\n
\n {existingStyles.length} font styles\n
\n )}\n
\n );\n}\n","import React, { useState } from 'react';\nimport Pako from 'pako';\nimport VisibilitySensor from 'react-visibility-sensor';\n\nimport { shapes } from '@cimpress/react-components';\nimport { FONT_BASE_URL, Links } from '../../../clients/fontClient';\nimport { Status } from '../FontCard';\n\ninterface FontPreviewProps {\n content: string;\n size: number;\n fontStyle: string;\n fontFamily: string;\n color?: string;\n canDownloadFontFile: boolean;\n links: Links | undefined;\n repoId: string;\n status: Status;\n}\n\ninterface FontRangeSize {\n minSize: number;\n maxSize: number;\n height: number;\n}\n\ninterface FontPreviewStyle {\n height: string;\n fontFamily?: string;\n fontSize?: string;\n background?: string;\n}\n\nconst { Spinner } = shapes;\n\n// Data list, first item is fontSize and 2nd is height\nconst maxSizeToHeightLevelsArr = [\n [20, 30],\n [25, 50],\n [36, 80],\n [100, 100],\n [200, 120],\n [400, 240],\n];\n\n// Initializing size and height ranges\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst fontSizeToHeightRange = ((maxSizeToHeightLevels) => {\n const SIZE = 0; const\n HEIGHT = 1;\n\n return maxSizeToHeightLevels.map((sizeHeight: number[], i: number) => {\n // from 2nd range onwards, minSize of the range is taken from\n // the maxSize of the previous range\n const prevRangeIndex = i - 1;\n return {\n minSize: i === 0 ? 0 : (maxSizeToHeightLevels[prevRangeIndex][SIZE]) + 1,\n maxSize: sizeHeight[SIZE],\n height: sizeHeight[HEIGHT],\n };\n });\n})(maxSizeToHeightLevelsArr);\n\n// This function will give the image height for a particular size\nconst getSizeRange = (size: number, rangeSizes: FontRangeSize[]) => {\n const range = rangeSizes.find((rangeSize, i) => size <= rangeSize.maxSize && size > rangeSize.minSize);\n return range ? range.height : undefined;\n};\n\nconst getPreviewImageHeight = (size: number) => {\n const height = 150;\n return getSizeRange(size, fontSizeToHeightRange) || height;\n};\n\nconst cimDocFontStyleMap = new Map([\n ['Normal', 'Normal'],\n ['Bold', 'Bold'],\n ['Italic', 'Italic'],\n ['BoldItalic', 'Bold, Italic'],\n]);\n\nfunction getTransientUdsDocument({ content, size, fontFamily, fontStyle, color, repoId, status }: FontPreviewProps) {\n const width = 100;\n const height = getPreviewImageHeight(size);\n const fontSize = `${size}pt`;\n\n const heightInMM = `${height}mm`;\n const widthInMM = `${width}mm`;\n const txtAreaWidthInMM = `${width - 8}mm`;\n\n const docContent = {\n version: 2,\n document: {\n surfaces: [\n {\n name: 'front',\n width: widthInMM,\n height: heightInMM,\n textAreas: [\n {\n position: {\n x: '8mm',\n y: '0mm',\n width: txtAreaWidthInMM,\n height: heightInMM,\n },\n horizontalAlignment: 'left',\n verticalAlignment: 'top',\n blockFlowDirection: 'horizontal-tb',\n textFields: [\n {\n fontSize,\n fontStyle: cimDocFontStyleMap.get(fontStyle),\n fontFamily,\n content,\n color: color || 'rgb(0,0,0)',\n },\n ],\n },\n ],\n },\n ],\n },\n fontRepositoryUrl: `${FONT_BASE_URL}/${repoId}/draft`,\n };\n\n const deflatedContent = Pako.deflateRaw(JSON.stringify(docContent), { to: 'string' });\n const encodedContent = encodeURIComponent(btoa(deflatedContent));\n return `https://uds.documents.cimpress.io/v2/transient/deflate?document=${encodedContent}&type=preview`;\n}\n\nfunction getFontPreview(fontProps: FontPreviewProps) {\n const width = 340;\n return `http://rendering.documents.cimpress.io/v1/cse/preview?format=png&width=${width}&instructions_uri=${encodeURIComponent(getTransientUdsDocument(fontProps))}`;\n}\n\nconst getRenderingAssetId = (links?: Links) => {\n if (links) {\n if (links.draft && links.draft['*']) {\n return links.draft['*'].renderingAssetId;\n }\n if (links.published && links.published['*']) {\n return links.published['*'].renderingAssetId;\n }\n }\n\n return '';\n};\n\nconst createPreviewStyle = (props: FontPreviewProps, fontURL: string, isVisible: boolean) => {\n const style: FontPreviewStyle = {\n height: `${getPreviewImageHeight(props.size)}mm`,\n };\n\n if (Status.NoAsset !== props.status && props.canDownloadFontFile && isVisible) {\n style.fontFamily = `'${props.fontFamily}-${getRenderingAssetId(props.links)}'`;\n style.fontSize = `${props.size}pt`;\n } else {\n style.background = `url(${fontURL}) no-repeat center ${Status.NoAsset === props.status ? '/ 100px' : ''}`;\n }\n\n return style;\n};\n\nconst renderPreview = (props: FontPreviewProps, fontURL: string, isVisible: boolean) => (\n \n {Status.NoAsset !== props.status && props.canDownloadFontFile ? props.content : ''}\n
\n);\n\nconst getFontFile = (repositoryId: string, links?: Links) => `${FONT_BASE_URL}/${repositoryId}/assets/${getRenderingAssetId(links)}`;\n\n// Using data URL for default image: 1px transparent binary gif\nconst dummyDataImage = 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==';\nconst deleteIconURL = 'https://static.ux.cimpress.io/assets/icons/bin-1-l.svg';\n\nexport function PreviewSection(fontPreviewProps: FontPreviewProps) {\n const [imgURL, setImgURL] = useState(dummyDataImage);\n const fontImageURL = fontPreviewProps.canDownloadFontFile ? '' : getFontPreview(fontPreviewProps);\n const [isLoading, setIsLoading] = useState(!fontPreviewProps.canDownloadFontFile);\n\n return (\n \n {isLoading &&
}\n
setIsLoading(false)} />\n {fontPreviewProps.canDownloadFontFile\n && (\n // eslint-disable-next-line react/no-danger\n \n )}\n
\n {({ isVisible }) => {\n if (isVisible) {\n if (fontImageURL !== imgURL && !fontPreviewProps.canDownloadFontFile) {\n setIsLoading(true);\n setImgURL(fontImageURL);\n }\n\n if (Status.NoAsset === fontPreviewProps.status) {\n setImgURL(deleteIconURL);\n }\n }\n return renderPreview(fontPreviewProps, imgURL, isVisible);\n }}\n \n
\n );\n}\n","import React from 'react';\nimport { Label } from '@cimpress/react-components';\nimport { Status } from '../FontCard';\n\ninterface Props {\n status: Status;\n}\n\nexport function FooterSection({ status }: Props) {\n return (\n \n {\n status === Status.Published\n && \n }\n {\n status === Status.Draft\n && \n }\n {\n status === Status.NoAsset\n && \n }\n
\n );\n}\n","import React from 'react';\n\nimport { DetailsSection } from './section/DetailsSection';\nimport { PreviewSection } from './section/PreviewSection';\nimport { FooterSection } from './section/FooterSection';\nimport { Font } from '../../clients/fontClient';\n\nimport './fontCard.scss';\nimport { Actions, Action } from './section/Actions';\n\ninterface Props {\n repoId: string;\n textContent: string;\n font: Font;\n size: number;\n fontRepoName: string;\n isDetailView: boolean;\n isLoading?: boolean;\n actions: Action[];\n}\n\nexport enum Status {\n Draft= 'Draft',\n Published= 'Published',\n NoAsset= 'No Assets',\n}\n\nconst getStatus = (font: Font) => {\n if (font.links && font.links.draft) {\n return Status.Draft;\n } if (font.links && font.links.published) {\n return Status.Published;\n }\n return Status.NoAsset;\n};\n\nexport function FontCard({ repoId, textContent, font, fontRepoName, size, isDetailView, actions }: Props) {\n const deletedFontCssClass = isDetailView ? 'deleted-background-details' : 'deleted-background';\n return (\n \n {isDetailView && !(Status.NoAsset === getStatus(font)) &&
}\n
\n
\n {isDetailView && (\n
\n )\n }\n
\n );\n}\n","import React from 'react';\nimport { Button, Icon, Tooltip } from '@cimpress/react-components';\n\nexport interface Action {\n title: string;\n icon: string;\n onClick(): void;\n}\n\ninterface Props {\n actions: Action[];\n}\n\nexport function Actions({ actions }: Props) {\n return (\n \n {actions.map(action => (\n \n \n \n \n \n ))}\n
\n );\n}\n","import { FontCard } from './FontCard';\n\nexport default FontCard;\n","import React, { useEffect, useState } from 'react';\nimport { connect } from 'react-redux';\nimport { Snackbar, Modal, Button } from '@cimpress/react-components';\nimport { RootState } from '../../../state/rootReducer';\nimport slice from '../../../fonts/slice';\nimport AddFontPopup, { defaultAlert } from '../addFontPopup/AddFontPopup';\nimport FontCard from '../../../components/fontCard';\nimport { Font, FontRepo } from '../../../clients/fontClient';\nimport './fontList.scss';\nimport { Action } from '../../../components/fontCard/section/Actions';\n\ninterface Props {\n size: number;\n textContent: string;\n familyName: string;\n searchText: string;\n fonts: Font[];\n showSnackbar: boolean;\n showDeletedFonts: boolean;\n isPublishing: boolean;\n repoList: FontRepo[];\n publishSuccess: boolean;\n isDeleteSuccess: boolean;\n currentFontRepo: FontRepo;\n inheritSuccess: boolean;\n fontsLoaded: boolean;\n onQueryFonts(): void;\n onPublishFont(familyName: string): void;\n onDeleteFont(fontId: string): void;\n onLoadFonts(): void;\n hideSnackbar(): void;\n resetDeleteSuccess(): void;\n}\n\nenum FontTech {\n Print ='Print',\n Embroidery = 'Embroidery',\n}\n\nexport enum Status {\n Draft= 'Draft',\n Published= 'Published',\n NoAsset= 'No Assets',\n}\n\nconst getStatus = (font: Font) => {\n if (font.links && font.links.draft) {\n return Status.Draft;\n } if (font.links && font.links.published) {\n return Status.Published;\n }\n return Status.NoAsset;\n};\n\nconst techListOrdered = [FontTech.Print.valueOf(), FontTech.Embroidery.valueOf()];\n\nfunction FontList({ size,\n textContent,\n showSnackbar,\n showDeletedFonts,\n familyName,\n searchText,\n fonts,\n repoList,\n onQueryFonts,\n hideSnackbar,\n onLoadFonts,\n onPublishFont,\n onDeleteFont,\n currentFontRepo,\n inheritSuccess,\n publishSuccess,\n isDeleteSuccess,\n resetDeleteSuccess,\n fontsLoaded }: Props) {\n const [alert, setAlert] = useState(defaultAlert);\n const [showDeleteModal, setShowDeleteModal] = useState(false);\n const [fontId, setFontId] = useState('');\n const [fontToUpdate, setFontToUpdate] = useState();\n const [showUpdateModal, setShowUpdateModal] = useState(false);\n\n const onDelete = (id: string) => {\n onDeleteFont(id);\n setShowDeleteModal(false);\n };\n\n const onCloseClick = () => {\n setFontId('');\n setShowDeleteModal(false);\n };\n\n const showFontDeleteModal = (id: string) => {\n setFontId(id);\n setShowDeleteModal(true);\n };\n\n const getNoFontsMessage = () => {\n let emptyMessage = '';\n if (fontsLoaded) {\n if (searchText) {\n emptyMessage = 'No fonts available for the selected search criteria.';\n } else if (familyName) {\n emptyMessage = `No fonts available of ${familyName}. Please navigate back to the repository catalog to browse fonts`;\n } else if (!fonts.length) {\n emptyMessage = 'No fonts in the repository.';\n }\n }\n return emptyMessage;\n };\n\n useEffect(() => {\n if (showSnackbar) {\n setAlert({ isShown: true, message: 'Something went wrong, please try again.', style: 'danger', autoHide: false });\n }\n }, [showSnackbar]);\n\n const onHideSnackbar = () => hideSnackbar();\n\n useEffect(() => {\n onLoadFonts();\n onQueryFonts();\n }, [onLoadFonts, onQueryFonts, currentFontRepo.id, publishSuccess, inheritSuccess]);\n\n const onPublish = (id: string) => {\n onPublishFont(id);\n };\n\n const deleteModalFooter = () => (\n \n onDelete(fontId)}>Delete \n Cancel \n
\n );\n\n const allFonts = (showDeleted: boolean) => (showDeleted ? fonts : fonts.filter(font => (font.links && (font.links.draft || font.links.published))));\n const hasDraft = (font: Font) => font.links && font.links.draft;\n const isPublished = (font: Font) => font.links && !font.links.draft && font.links.published;\n\n const onPublishClick = (font: Font) => {\n onPublish(font.id);\n };\n\n const onUpdateClick = (font: Font) => {\n setFontToUpdate(font);\n setShowUpdateModal(true);\n };\n\n const onUpdateCancel = () => setShowUpdateModal(false);\n\n const getCardActions = (font: Font): Action[] => {\n const cardActions = {\n Delete: {\n title: 'Delete font',\n icon: 'bin-1-l',\n onClick: () => showFontDeleteModal(font.id),\n },\n Publish: {\n title: 'Publish',\n icon: 'box-air-delivery-l',\n onClick: () => onPublishClick(font),\n },\n Update: {\n title: 'Update asset',\n icon: 'file-new-2-l',\n onClick: () => onUpdateClick(font),\n },\n };\n const actions = [];\n if (font.repositoryId === currentFontRepo.id && repoList.some(repo => repo.id === font.repositoryId)) {\n if (hasDraft(font)) {\n actions.push(cardActions.Delete);\n }\n if (getStatus(font) === Status.Draft) {\n actions.push(cardActions.Publish);\n }\n if (isPublished(font)) {\n actions.push(cardActions.Update);\n }\n }\n return actions;\n };\n\n const techsAvailable = allFonts(showDeletedFonts).reduce((acc: string[], font: Font) => (acc.includes(font.tech) ? acc : acc.concat(font.tech)), []);\n const techs = techsAvailable.sort((a: string, b: string) => (techListOrdered.indexOf(a) - techListOrdered.indexOf(b)));\n\n const renderFontCards = (fontList: Font[]) => fontList.map((font: Font) => (\n \n ));\n\n const renderFontDetails = () => (\n <>\n {showUpdateModal && (\n \n )}\n\n {techs && techs.length > 0 && techs.map(tech => (\n \n
{tech}
\n
\n {renderFontCards(allFonts(showDeletedFonts).filter((font: Font) => tech === font.tech))}\n
\n
\n ))\n }\n >\n );\n\n return (\n <>\n \n {alert.message}\n \n\n \n Successfully deleted font.\n \n\n {showDeleteModal && (\n \n Do you want to delete this font? This action cannot be undone.\n \n )}\n\n {!familyName && !!fonts.length && (\n \n {renderFontCards(allFonts(showDeletedFonts))}\n
\n )}\n\n {familyName && renderFontDetails()}\n\n { !fonts.length && (\n \n {getNoFontsMessage()}\n
\n )}\n >\n );\n}\nexport default connect(\n (state: RootState) => ({\n isPublishing: state.font.isPublishing,\n showSnackbar: state.font.showSnackbar,\n publishSuccess: state.font.publishSuccess,\n isDeleteSuccess: state.font.deleteSuccess,\n fontsLoaded: state.font.loaded,\n inheritSuccess: state.repository.inheritSuccess,\n repoList: state.repository.repoList,\n }),\n {\n onLoadFonts: slice.actions.onLoadFonts,\n onPublishFont: slice.actions.onPublishFont,\n onDeleteFont: slice.actions.onDeleteFont,\n onQueryFonts: slice.actions.onQueryFonts,\n hideSnackbar: slice.actions.hideSnackbar,\n resetDeleteSuccess: slice.actions.resetDeleteSuccess,\n },\n)(FontList);\n","import React, { useState, useEffect } from 'react';\nimport { connect } from 'react-redux';\nimport { Card, Snackbar, shapes, Toggle } from '@cimpress/react-components';\n\nimport AddFont from './addFont/AddFont';\nimport FontText, { DEFAULT_FONT_PREVIEW_TEXT } from './fontText/FontText';\nimport SearchFont from './searchFont/SearchFont';\nimport FontSizeSelector from '../../components/fontSizeSelector';\nimport Repository from '../repository/Repository';\nimport RepositoryActions from '../repository/RepositoryActions';\nimport FontList from './fontList/FontList';\nimport { RootState } from '../../state/rootReducer';\nimport { GroupedFont } from '../../fonts/saga';\nimport { FontSizeOption } from '../../components/fontSizeSelector/FontSizeSelector';\nimport './catalog.scss';\nimport { getQueryVariable } from '../../urlUtil';\nimport { validFontStyles } from './addFontPopup/AddFontPopup';\nimport { Font, FontRepo } from '../../clients/fontClient';\nimport { getFontList } from './fontList/getFontList';\nimport { Tenant } from '../../components/tenant';\n\nexport enum SearchType {\n fontStyle = 'fontStyle',\n fontFamily = 'fontFamily'\n}\n\ninterface Match {\n params: {\n id: string;\n };\n}\n\ninterface Props {\n match: Match;\n location: {\n search: string;\n };\n availableTenants: Tenant[];\n tenantsLoaded: boolean;\n fontsLoading: boolean;\n fontsLoaded: boolean;\n fontDeleting: boolean;\n repoLoading: boolean;\n groupedFonts: GroupedFont[];\n currentFontRepo: FontRepo;\n repoList: FontRepo[];\n currentTenant: Tenant | undefined;\n isRepoFetchCompleted: boolean;\n}\n\nconst { Spinner } = shapes;\n\nconst FONT_SIZES = [8, 12, 14, 20, 24, 32, 40, 64, 96, 120, 184, 280, 400];\nconst DEFAULT_FONT_SIZE = FONT_SIZES[4];\n\n// Util function to populate a select options array\nconst sizes: FontSizeOption[] = ((fontSizes: number[]) => fontSizes.map(fSize => ({\n value: fSize,\n label: `${fSize}pt`,\n})))(FONT_SIZES);\n\nconst getSearchType = (text: string) => (validFontStyles.map((s => s.toLocaleLowerCase())).includes(text.toLocaleLowerCase()) ? SearchType.fontStyle : SearchType.fontFamily);\n\nconst hasManageFontPermission = (currentTenant: Tenant) => currentTenant && currentTenant.permissions && currentTenant.permissions.fonts && currentTenant.permissions.fonts.canManage;\n\nfunction Catalog({ currentFontRepo, groupedFonts, match, location, availableTenants, tenantsLoaded, fontsLoading, repoLoading, fontDeleting, repoList, currentTenant, fontsLoaded }: Props) {\n const familyName = getQueryVariable('familyName', location.search) || '';\n const searchText = getQueryVariable('search', location.search) || '';\n const [searchType, setSearchType] = useState(undefined);\n const [fonts, setFonts] = useState([]);\n const [fontSize, setFontSize] = useState(DEFAULT_FONT_SIZE);\n const [previewText, setPreviewText] = useState(DEFAULT_FONT_PREVIEW_TEXT);\n const [reposFetched, setReposFetched] = useState(false);\n const [showDeleted, setShowDeleted] = useState(false);\n\n const onReposLoaded = () => setReposFetched(true);\n\n const onFontSizeChange = (size: number) => {\n setFontSize(size);\n };\n\n const onTextChange = (text: string) => {\n setPreviewText(text);\n };\n\n const toggleShowDeletedFonts = () => {\n setShowDeleted(!showDeleted);\n };\n\n useEffect(() => {\n if (location.search) {\n setSearchType(getSearchType(searchText));\n }\n }, [location.search, searchText]);\n\n useEffect(() => {\n setFonts(getFontList({ familyName, groupedFonts, searchText, searchType }));\n }, [familyName, groupedFonts, searchText, searchType]);\n\n return (\n <>\n { ((tenantsLoaded && availableTenants.length === 0) || (currentTenant && !hasManageFontPermission(currentTenant))) && (\n \n You have no COAM permission. Please contact your tenant admin.\n \n )\n }\n \n { tenantsLoaded && availableTenants.length > 0 && currentTenant && hasManageFontPermission(currentTenant) && (\n <>\n
\n
\n {(fontsLoaded && repoList.length > 0) && (\n
\n Show deleted fonts: \n
\n )}\n
\n {(fontsLoading || fontDeleting || !reposFetched || repoLoading) &&
}\n { ((repoList && repoList.length > 0) || (match.params.id)) && (\n
\n \n {familyName ? (\n
\n Family Name: {familyName}\n
\n ) :
\n }\n
\n \n
\n
\n \n
\n
\n {!familyName &&
}\n
\n {currentFontRepo.id && (\n \n )}\n \n )}\n >\n )}\n
\n >\n );\n}\n\nexport default connect(\n (state: RootState) => ({\n currentFontRepo: state.repository.currentFontRepo,\n groupedFonts: state.font.groupedFonts,\n fontsLoading: state.font.isLoading,\n fontsLoaded: state.font.loaded,\n fontDeleting: state.font.isDeleting,\n repoLoading: state.repository.isLoading,\n isRepoFetchCompleted: state.repository.isRepoFetchCompleted,\n repoList: state.repository.repoList,\n currentTenant: state.tenantInfo.tenant,\n availableTenants: state.tenantInfo.availableTenants,\n tenantsLoaded: state.tenantInfo.tenantsLoaded,\n }),\n)(Catalog);\n","/**\n * Parse out the query parameters from a given URL.\n * @param {string} property - key want to extract\n * @param {string} queryString - url\n */\n\nexport const getQueryVariable = (property: string, queryString: string) => {\n const params = new URLSearchParams(queryString);\n return params.get(property);\n};\n","import { GroupedFont } from '../../../fonts/saga';\nimport { Font } from '../../../clients/fontClient';\nimport { SearchType } from '../Catalog';\n\n\ninterface FontListParams {\n familyName: string;\n groupedFonts: GroupedFont[];\n searchText: string;\n searchType: SearchType | undefined;\n}\n\nconst getFont = (fonts: Font[], style?: string): Font => {\n if (style) {\n return fonts.filter(f => f.links && (f.links.draft || f.links.published)).find(f => f.style.toLocaleLowerCase() === style) || fonts[0];\n }\n return fonts.find(f => f.links && (f.links.draft || f.links.published)) || fonts[0];\n};\n\nconst getFontStyles = (fonts: Font[]) => fonts.map(f => f.style);\n\nexport const getFontList = ({ familyName, groupedFonts, searchText, searchType }: FontListParams) => {\n if (familyName) {\n const groupedFont = groupedFonts.find(g => g.familyName === familyName);\n if (searchText) {\n // filtering font by style when searched in detailed view\n const filtered = groupedFont && groupedFont.fonts.filter(s => s.style.toLocaleLowerCase().includes(searchText.toLocaleLowerCase()));\n return filtered || [];\n }\n return (groupedFont && groupedFont.fonts) || [];\n } if (searchType && searchText) {\n // filtering groupedFonts by style when searched in grouped view\n if (searchType === SearchType.fontStyle) {\n const fontsByStyle = groupedFonts\n .filter(gr => gr.fonts\n .find(g => g.style.toLocaleLowerCase() === searchText.toLocaleLowerCase()))\n .map(g => ({ ...getFont(g.fonts, searchText), existingStyles: getFontStyles(g.fonts) }));\n\n return fontsByStyle;\n }\n // filtering groupedFonts by familyName when searched in grouped view\n const fontsByFamilyName = groupedFonts\n .filter(groupedFont => groupedFont.familyName.toLocaleLowerCase().includes(searchText.toLocaleLowerCase()))\n .map(groupedFont => ({ ...getFont(groupedFont.fonts), existingStyles: getFontStyles(groupedFont.fonts) }));\n\n return fontsByFamilyName;\n }\n return groupedFonts.map(g => ({ ...getFont(g.fonts), existingStyles: getFontStyles(g.fonts) }));\n};\n","import { combineReducers } from 'redux-starter-kit';\nimport { StateType } from 'typesafe-actions';\nimport { connectRouter } from 'connected-react-router';\nimport { History } from 'history';\n\nimport { reducer as repository } from '../screens/repository/slice';\nimport { reducer as fontProperties } from '../screens/catalog/addFontPopup/slice';\nimport { reducer as tenantInfo } from '../components/header/slice';\nimport { reducer as font } from '../fonts/slice';\n\nconst rootReducer = (history: History) => combineReducers({\n router: connectRouter(history),\n repository,\n fontProperties,\n font,\n tenantInfo,\n});\n\nexport type RootState = StateType>;\nexport default rootReducer;\n","import { takeEvery, put, call, select } from 'redux-saga/effects';\nimport { PayloadAction } from 'redux-starter-kit';\n\nimport { addFont, addFontFile, getAllFontsDetails, Font, FontToAdd } from '../../../clients/fontClient';\nimport slice from './slice';\nimport { getCurrentRepoId } from '../../repository/slice';\nimport fontListSlice from '../../../fonts/slice';\nimport { FontProperties } from './AddFontPopup';\n\nfunction generateFontObj(fontProperties: FontProperties[]): FontToAdd {\n const fontObj: FontToAdd = {\n familyName: '',\n style: '',\n technology: '',\n license: '',\n };\n fontProperties.forEach((property) => {\n if (property.type === 'familyName') {\n fontObj.familyName = property.value;\n } else if (property.type === 'style') {\n fontObj.style = property.value;\n } else if (property.type === 'technology') {\n fontObj.technology = property.value;\n } else if (property.type === 'license') {\n fontObj.license = property.value;\n }\n });\n return fontObj;\n}\n\nexport function* onAddFont(action: PayloadAction) {\n const repoId = (yield select(getCurrentRepoId)) as string;\n const fontProperties = action.payload;\n try {\n const allFonts = (yield call(getAllFontsDetails, repoId, true)) as Font[];\n const fontObj = generateFontObj(fontProperties.properties);\n const existingFont = allFonts.find(font => font.familyName === fontObj.familyName && font.style === fontObj.style && font.tech === fontObj.technology);\n let fontId = existingFont ? existingFont.id : undefined;\n if (!existingFont) {\n const fontAddedProperties = yield call(addFont, fontObj, repoId);\n fontId = fontAddedProperties ? fontAddedProperties.id : undefined;\n }\n if (fontId && (!existingFont || !existingFont.links || !existingFont.links.draft)) {\n const propertiesWithFile = yield call(addFontFile, fontId, fontProperties.file, repoId);\n if (existingFont && existingFont.links && existingFont.links.published) {\n yield put(fontListSlice.actions.onPublishFont(fontId));\n }\n yield put(fontListSlice.actions.onQueryFonts());\n yield put(slice.actions.addFontSuccess(propertiesWithFile));\n } else {\n yield put(slice.actions.addFontFailed());\n }\n } catch {\n yield put(slice.actions.addFontFailed());\n }\n}\n\nexport default function* repositorySaga() {\n return yield takeEvery(slice.actions.onAddFont, onAddFont);\n}\n","import { takeEvery, put, call, select, all } from 'redux-saga/effects';\nimport _ from 'lodash';\n\nimport { PayloadAction } from 'redux-starter-kit';\nimport slice from './slice';\nimport { getAllFontsDetails, Font, publishFont, deleteFont, fetchAllRepositories, FontRepo } from '../clients/fontClient';\nimport { getCurrentRepoId, getCurrentRepo } from '../screens/repository/slice';\nimport { getCurrentTenant } from '../components/header/slice';\nimport { Tenant } from '../components/tenant/tenantClient';\n\nexport interface GroupedFont {\n familyName: string;\n fonts: Font[];\n}\nconst sortedFonts = (fonts: GroupedFont[]) => fonts.sort((a, b) => a.familyName.localeCompare(b.familyName));\n\nexport const groupFonts = (queryResponse: Font[]) => {\n const groupedFonts = _.groupBy(queryResponse, 'familyName');\n const groupByFamilyName = Object.keys(groupedFonts).map(fontName => ({\n familyName: fontName,\n fonts: groupedFonts[fontName],\n }));\n return sortedFonts(groupByFamilyName);\n};\n\nconst getUniqueProperties = (firstFont: Font, secondFont: Font) => firstFont.familyName === secondFont.familyName && firstFont.style === secondFont.style && firstFont.tech === secondFont.tech;\n\nconst getUniqueFonts = (masterFonts: Font[], localFonts: Font[]) => _.unionWith(masterFonts, localFonts, getUniqueProperties);\n\nexport function* onQueryFonts() {\n const repoId = (yield select(getCurrentRepoId)) as string;\n const repo = (yield select(getCurrentRepo)) as FontRepo;\n const { tenantId, tenantType } = (yield select(getCurrentTenant)) as Tenant;\n try {\n const tenantsRepoList = (yield call(fetchAllRepositories, { tenantId, tenantType })) as FontRepo[];\n let repoIds = [repoId];\n if (repo.inheritedRepositories) {\n repoIds = repoIds.concat(repo.inheritedRepositories);\n }\n const allFonts = (yield all(repoIds.map(rId => call(getAllFontsDetails, rId, !tenantsRepoList.some(tenantRepo => tenantRepo.id === rId))))) as Font[][];\n const [localFonts, masterFonts] = allFonts;\n const uniqueFonts = getUniqueFonts(masterFonts, localFonts);\n yield put(slice.actions.fontsLoadSuccess(groupFonts(uniqueFonts)));\n } catch {\n yield put(slice.actions.fontsLoadFailed());\n }\n}\n\nexport function* onPublishFont(action: PayloadAction) {\n const fontId = action.payload;\n const repoId = (yield select(getCurrentRepoId)) as string;\n try {\n yield call(publishFont, repoId, fontId);\n yield put(slice.actions.fontPublishSuccess());\n } catch {\n yield put(slice.actions.fontPublishFailed());\n }\n}\n\nexport function* onDeleteFont(action: PayloadAction) {\n const fontId = action.payload;\n const repoId = (yield select(getCurrentRepoId)) as string;\n try {\n yield call(deleteFont, repoId, fontId);\n yield put(slice.actions.onQueryFonts());\n yield put(slice.actions.fontDeleteSuccess());\n } catch {\n yield put(slice.actions.fontDeleteFailed());\n }\n}\n\nexport default function* fontListSaga() {\n return yield all([\n yield takeEvery(slice.actions.onQueryFonts, onQueryFonts),\n yield takeEvery(slice.actions.onPublishFont, onPublishFont),\n yield takeEvery(slice.actions.onDeleteFont, onDeleteFont),\n ]);\n}\n","import { all } from 'redux-saga/effects';\n\nimport repository from '../screens/repository/saga';\nimport fontProperties from '../screens/catalog/addFontPopup/saga';\nimport fontList from '../fonts/saga';\n\nexport default function* sagaRoot() {\n return yield all([\n repository(),\n fontProperties(),\n fontList(),\n ]);\n}\n","import { configureStore } from 'redux-starter-kit';\nimport sagaMiddlewareFactory from 'redux-saga';\nimport { createBrowserHistory } from 'history';\nimport { routerMiddleware } from 'connected-react-router';\n\nimport rootReducer from './rootReducer';\nimport sagaRoot from './sagaRoot';\nimport { basename } from '../settings';\n\nconst sagaMiddleware = sagaMiddlewareFactory();\n\nexport const history = createBrowserHistory({\n basename,\n});\n\nconst routeMiddleware = routerMiddleware(history);\n\n\nexport default function createStore() {\n const store = configureStore({\n reducer: rootReducer(history),\n middleware: [routeMiddleware, sagaMiddleware],\n });\n\n sagaMiddleware.run(sagaRoot);\n\n return store;\n}\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport { Provider } from 'react-redux';\nimport './index.css';\nimport App from './App';\nimport createStore from './state/createStore';\n\nconst store = createStore();\n\nReactDOM.render(\n \n \n ,\n document.getElementById('root'),\n);\n","import React from 'react';\nimport { Route, Switch } from 'react-router-dom';\nimport { ConnectedRouter } from 'connected-react-router';\nimport { CssLoader } from '@cimpress/react-components';\nimport { AuthProvider } from './components/AuthContext';\nimport Header from './components/header/Header';\nimport Catalog from './screens/catalog/Catalog';\nimport { history } from './state/createStore';\n\nexport default function App() {\n return (\n \n \n \n \n \n \n \n \n \n
\n \n \n \n );\n}\n"],"sourceRoot":""}