Skip to content

Commit 221ef66

Browse files
authored
Merge pull request #1646 from SenseNet/feature/2218-add-href-to-refences
Feature: Add link to reference fields
2 parents 795a153 + 66a7006 commit 221ef66

File tree

4 files changed

+105
-21
lines changed

4 files changed

+105
-21
lines changed

apps/sensenet/src/application-paths.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,28 @@ type RoutesWithContentBrowser = keyof Pick<
3030
type RoutesWithActionParam = keyof Pick<typeof PATHS, 'savedQueries' | 'localization' | 'configuration' | 'webhooks'>
3131

3232
type Options =
33-
| { path: (typeof PATHS)['events']['appPath']; params?: { eventGuid: string;[index: string]: string } }
33+
| { path: (typeof PATHS)['events']['appPath']; params?: { eventGuid: string; [index: string]: string } }
3434
| {
35-
path: (typeof PATHS)[RoutesWithContentBrowser]['appPath']
36-
params: { browseType: (typeof BrowseType)[number]; action?: string;[index: string]: string | undefined }
37-
}
35+
path: (typeof PATHS)[RoutesWithContentBrowser]['appPath']
36+
params: { browseType: (typeof BrowseType)[number]; action?: string; [index: string]: string | undefined }
37+
}
3838
| {
39-
path: (typeof PATHS)['custom']['appPath']
40-
params: {
41-
browseType: (typeof BrowseType)[number]
42-
path: string
43-
action?: string
44-
[index: string]: string | undefined
39+
path: (typeof PATHS)['custom']['appPath']
40+
params: {
41+
browseType: (typeof BrowseType)[number]
42+
path: string
43+
action?: string
44+
[index: string]: string | undefined
45+
}
4546
}
46-
}
4747
| {
48-
path: (typeof PATHS)[RoutesWithActionParam]['appPath']
49-
params?: { action: string;[index: string]: string }
50-
}
48+
path: (typeof PATHS)[RoutesWithActionParam]['appPath']
49+
params?: { action: string; [index: string]: string }
50+
}
5151
| {
52-
path: (typeof PATHS)['settings']['appPath']
53-
params?: { submenu: SettingsItemType;[index: string]: string | SettingsItemType }
54-
}
52+
path: (typeof PATHS)['settings']['appPath']
53+
params?: { submenu: SettingsItemType; [index: string]: string | SettingsItemType }
54+
}
5555

5656
export const resolvePathParams = ({ path, params }: Options) => {
5757
let currentPath: string = path

apps/sensenet/src/components/field-controls/reference-grid.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ReactClientFieldSetting, ReferenceGrid as SnReferenceGrid } from '@sensenet/controls-react'
22
import { clsx } from 'clsx'
33
import React from 'react'
4+
import { PATHS } from '../../application-paths'
45
import { useGlobalStyles } from '../../globalStyles'
56
import { DialogTitle } from '../dialogs/dialog-title'
67
import { Icon } from '../Icon'
@@ -14,6 +15,7 @@ export const ReferenceGrid: React.FC<ReactClientFieldSetting> = (props) => {
1415
dialogTitleComponent={DialogTitle}
1516
renderPickerIcon={(item) => <Icon item={item} />}
1617
pickerClasses={{ cancelButton: globalClasses.cancelButton }}
18+
paths={PATHS}
1719
/>
1820
)
1921
}

packages/sn-controls-react/src/fieldcontrols/reference-grid/default-item-template.tsx

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
/* eslint-disable require-jsdoc */
12
import {
23
Avatar,
4+
createStyles,
35
Icon,
46
IconButton,
57
ListItem,
68
ListItemAvatar,
79
ListItemIcon,
810
ListItemSecondaryAction,
911
ListItemText,
12+
makeStyles,
1013
} from '@material-ui/core'
1114
import { InsertDriveFile } from '@material-ui/icons'
1215
import { Repository } from '@sensenet/client-core'
@@ -15,6 +18,42 @@ import { GenericContent, Image, User } from '@sensenet/default-content-types'
1518
import React from 'react'
1619
import { renderIconDefault } from '../icon'
1720

21+
export type PathConfig = {
22+
appPath: string
23+
snPath?: string
24+
}
25+
26+
export type Paths = Record<string, PathConfig>
27+
28+
function getAppPathAndContent(PATHS: Paths, targetPath: string) {
29+
const matches = Object.entries(PATHS)
30+
.filter(([, config]) => config.snPath && targetPath.startsWith(config.snPath))
31+
.sort((a, b) => b[1].snPath!.length - a[1].snPath!.length)
32+
33+
if (!matches[0]) return undefined
34+
35+
const [, config] = matches[0]
36+
const contentePath = targetPath.substring(config.snPath!.length)
37+
38+
return {
39+
appPath: config.appPath,
40+
contentePath,
41+
}
42+
}
43+
44+
function buildCustomPath(path: string, action: string | undefined, contentePath: string) {
45+
const customPath = path
46+
.replace(':browseType', 'explorer')
47+
.replace('/:path', '') // Remove the path parameter
48+
.replace(':action?', action || 'default')
49+
50+
const url = new URL(window.location.origin)
51+
url.pathname = customPath
52+
url.searchParams.set('content', contentePath)
53+
54+
return url.toString()
55+
}
56+
1857
interface DefaultItemTemplateProps {
1958
content: GenericContent
2059
remove?: (id: number) => void
@@ -24,13 +63,29 @@ interface DefaultItemTemplateProps {
2463
repository?: Repository
2564
multiple: boolean
2665
renderIcon?: (name: string) => JSX.Element
66+
paths: Paths
2767
}
2868

69+
const useStyles = makeStyles(() =>
70+
createStyles({
71+
referenceItemText: {
72+
textAlign: 'left',
73+
paddingRight: 15,
74+
cursor: 'pointer',
75+
'&[data-clickable="true"]:hover': {
76+
textDecoration: 'underline',
77+
},
78+
},
79+
}),
80+
)
81+
2982
/**
3083
* Represents a default renderer for reference grid row
3184
*/
3285
export const DefaultItemTemplate: React.FC<DefaultItemTemplateProps> = (props) => {
33-
const { content, repository } = props
86+
const { content, repository, paths } = props
87+
88+
const classes = useStyles()
3489

3590
const renderIcon = (item: GenericContent | User | Image) => {
3691
if (repository?.schemas.isContentFromType<User>(item, 'User')) {
@@ -107,7 +162,26 @@ export const DefaultItemTemplate: React.FC<DefaultItemTemplateProps> = (props) =
107162
return (
108163
<ListItem style={props.actionName === 'browse' ? { padding: 0 } : undefined} key={content.Id} button={false}>
109164
{content.Type ? renderIcon(content) : null}
110-
<ListItemText primary={content.DisplayName} style={{ textAlign: 'left', paddingRight: 15 }} />
165+
<ListItemText
166+
onClick={() => {
167+
if (content.Id === -1 || !paths) {
168+
return
169+
}
170+
const referencedItemPaths = getAppPathAndContent(paths, content.Path)
171+
172+
if (!referencedItemPaths) {
173+
return
174+
}
175+
176+
const { appPath, contentePath } = referencedItemPaths
177+
178+
const fullUrl = buildCustomPath(appPath, props.actionName, contentePath)
179+
window.location.href = fullUrl
180+
}}
181+
primary={content.DisplayName}
182+
className={classes.referenceItemText}
183+
data-clickable={content.Id !== -1}
184+
/>
111185
{props.actionName && props.actionName !== 'browse' && !props.readOnly ? (
112186
<ListItemSecondaryAction>
113187
{content.Id > 0 ? (

packages/sn-controls-react/src/fieldcontrols/reference-grid/reference-grid.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { PickerClassKey } from '@sensenet/pickers-react'
1616
import React, { ElementType, useCallback, useEffect, useMemo, useState } from 'react'
1717
import { ReactClientFieldSetting } from '../client-field-setting'
1818
import { defaultLocalization } from '../localization'
19-
import { DefaultItemTemplate } from './default-item-template'
19+
import { DefaultItemTemplate, Paths } from './default-item-template'
2020
import { ReferencePicker } from './reference-picker'
2121

2222
const styles = {
@@ -38,6 +38,7 @@ interface ReferenceGridProps extends ReactClientFieldSetting<ReferenceFieldSetti
3838
dialogTitleComponent?: ElementType
3939
renderPickerIcon?: (item: any) => JSX.Element
4040
pickerClasses?: PickerClassKey
41+
paths: Paths
4142
}
4243

4344
export const ReferenceGrid: React.FC<ReferenceGridProps> = (props) => {
@@ -185,7 +186,11 @@ export const ReferenceGrid: React.FC<ReferenceGridProps> = (props) => {
185186
case 'new':
186187
case 'edit':
187188
return (
188-
<FormControl style={styles.root as any} component={'fieldset' as 'div'} required={props.settings.Compulsory}>
189+
<FormControl
190+
data-test="edit-refence"
191+
style={styles.root as any}
192+
component={'fieldset' as 'div'}
193+
required={props.settings.Compulsory}>
189194
<TextField
190195
name={props.content?.Name}
191196
autoComplete="off"
@@ -211,6 +216,7 @@ export const ReferenceGrid: React.FC<ReferenceGridProps> = (props) => {
211216
repository={props.repository}
212217
multiple={props.settings.AllowMultiple ? props.settings.AllowMultiple : false}
213218
renderIcon={props.renderIcon}
219+
paths={props.paths}
214220
/>
215221
))}
216222
{!props.settings.ReadOnly ? (
@@ -221,6 +227,7 @@ export const ReferenceGrid: React.FC<ReferenceGridProps> = (props) => {
221227
repository={props.repository}
222228
multiple={props.settings.AllowMultiple ? props.settings.AllowMultiple : false}
223229
renderIcon={props.renderIcon}
230+
paths={props.paths}
224231
/>
225232
) : null}
226233
</List>
@@ -271,6 +278,7 @@ export const ReferenceGrid: React.FC<ReferenceGridProps> = (props) => {
271278
repository={props.repository}
272279
multiple={props.settings.AllowMultiple ? props.settings.AllowMultiple : false}
273280
renderIcon={props.renderIcon}
281+
paths={props.paths}
274282
/>
275283
))
276284
) : (

0 commit comments

Comments
 (0)