Skip to content

Commit 5efcc00

Browse files
committed
Merge branch 'feature/sn-auth-package-extraimprovements' into feature/snauth-multi-repo-sessions
2 parents e855d78 + 389bc9d commit 5efcc00

10 files changed

Lines changed: 186 additions & 72 deletions

File tree

apps/sensenet/src/components/appbar/desktop-nav-menu.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ export const DesktopNavMenu: FunctionComponent = () => {
128128
service.setPersonalSettingsValue({ ...settings, preferDisplayName: event.target.checked })
129129
}
130130

131+
const toggleSortFoldersFirstValue = () => (event: ChangeEvent<HTMLInputElement>) => {
132+
const settings = service.userValue.getValue()
133+
service.setPersonalSettingsValue({ ...settings, sortFoldersFirst: event.target.checked })
134+
}
135+
131136
return (
132137
<div className={clsx(globalClasses.centered, classes.navMenu)}>
133138
<>
@@ -300,6 +305,22 @@ export const DesktopNavMenu: FunctionComponent = () => {
300305
</Grid>
301306
</Typography>
302307
</MenuItem>
308+
<MenuItem>
309+
<Typography component="div" className={classes.checkboxMenuItem} style={{ width: '100%' }}>
310+
<Grid component="label" container alignItems="center" justify="space-between">
311+
<Grid item style={{ paddingRight: '16px' }}>
312+
{localization.topMenu.sortFoldersFirst}
313+
</Grid>
314+
<Grid item>
315+
<Switch
316+
data-test="sort-folders-first-checkbox"
317+
checked={personalSettings.sortFoldersFirst}
318+
onChange={toggleSortFoldersFirstValue()}
319+
/>
320+
</Grid>
321+
</Grid>
322+
</Typography>
323+
</MenuItem>
303324
</MenuList>
304325
</ClickAwayListener>
305326
</div>

apps/sensenet/src/components/content-list/content-list.tsx

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import React, {
2222
useCallback,
2323
useContext,
2424
useEffect,
25+
useMemo,
2526
useState,
2627
} from 'react'
2728
import { TableCellProps } from 'react-virtualized'
@@ -33,7 +34,7 @@ import { ContentContextMenu } from '../context-menu/content-context-menu'
3334
import { useDialog } from '../dialogs'
3435
import { DropFileArea } from '../DropFileArea'
3536
import { SelectionControl } from '../SelectionControl'
36-
import { SETTINGS_FOLDER_FILTER } from '../tree/tree-with-data'
37+
import { isFolderLikeTreeItem, SETTINGS_FOLDER_FILTER } from '../tree/tree-helpers'
3738
import { ContextMenuWrapper } from './context-menu-wrapper'
3839
import {
3940
ActionsField,
@@ -94,6 +95,8 @@ export const isReferenceField = (fieldName: string, repo: Repository, schema = '
9495

9596
const rowHeightConst = 67
9697
const headerHeightConst = 48
98+
const displayNameInArray = ['DisplayName']
99+
const sortableColumns = ['DisplayName', 'Path', 'Type', 'Name', 'Version', 'CreationDate', 'ModificationDate']
97100

98101
/**
99102
* Compare passed minutes with
@@ -121,7 +124,7 @@ const ColumnSettingsContainer: ColumnSettingsContainerType = {}
121124
export const ContentList = <T extends GenericContent = GenericContent>(props: ContentListProps<T>) => {
122125
const selectionService = useSelectionService()
123126
const parentContent = useContext(CurrentContentContext)
124-
const children = useContext(CurrentChildrenContext) as T[]
127+
const currentChildren = useContext(CurrentChildrenContext) as T[]
125128
const ancestors = useContext(CurrentAncestorsContext) as T[]
126129
const device = useContext(ResponsiveContext)
127130
const personalSettings = useContext(ResponsivePersonalSettings)
@@ -133,8 +136,6 @@ export const ContentList = <T extends GenericContent = GenericContent>(props: Co
133136
const { openDialog, closeLastDialog } = useDialog()
134137
const logger = useLogger('ContentList')
135138
const localization = useLocalization()
136-
const [selected, setSelected] = useState<T[]>([])
137-
const [activeContent, setActiveContent] = useState<T>(children[0])
138139
const [isFocused, setIsFocused] = useState(true)
139140
const [isContextMenuOpened, setIsContextMenuOpened] = useState(false)
140141
const [schema, setSchema] = useState(repo.schemas.getSchemaByName(props.schema || 'GenericContent'))
@@ -150,6 +151,35 @@ export const ContentList = <T extends GenericContent = GenericContent>(props: Co
150151
const [currentDirection, setCurrentDirection] = useState<'asc' | 'desc'>(
151152
(loadChildrenSettingsOrderBy?.[0][1] as 'asc' | 'desc') || 'asc',
152153
)
154+
const children = useMemo(() => {
155+
return [...currentChildren].sort((a, b) => {
156+
if (userPersonalSettings.sortFoldersFirst) {
157+
const folderOrder = Number(!isFolderLikeTreeItem(a)) - Number(!isFolderLikeTreeItem(b))
158+
159+
if (folderOrder) {
160+
return folderOrder
161+
}
162+
}
163+
164+
if (sortableColumns.includes(String(currentOrder))) {
165+
const nameA = String(a[currentOrder] ?? '')
166+
const nameB = String(b[currentOrder] ?? '')
167+
168+
return currentDirection === 'asc' ? nameA.localeCompare(nameB) : nameB.localeCompare(nameA)
169+
}
170+
171+
if (currentOrder === 'CreatedBy' || currentOrder === 'ModifiedBy') {
172+
const nameA = String((a[currentOrder] as GenericContent)?.DisplayName ?? '')
173+
const nameB = String((b[currentOrder] as GenericContent)?.DisplayName ?? '')
174+
175+
return currentDirection === 'asc' ? nameA.localeCompare(nameB) : nameB.localeCompare(nameA)
176+
}
177+
178+
return 0
179+
})
180+
}, [currentChildren, currentDirection, currentOrder, userPersonalSettings.sortFoldersFirst])
181+
const [selected, setSelected] = useState<T[]>([])
182+
const [activeContent, setActiveContent] = useState<T>(children[0])
153183

154184
const [columnSettings, setColumnSettings] = useState<Array<ColumnSetting<GenericContent>>>(
155185
personalSettings.content.fields,
@@ -673,9 +703,6 @@ export const ContentList = <T extends GenericContent = GenericContent>(props: Co
673703
},
674704
}
675705

676-
const displayNameInArray = ['DisplayName']
677-
const sortableColumns = ['DisplayName', 'Path', 'Type', 'Name', 'Version', 'CreationDate', 'ModificationDate']
678-
679706
return (
680707
<div style={{ ...props.style, ...{ height: '100%' } }} {...props.containerProps}>
681708
{props.enableBreadcrumbs ? (
@@ -719,34 +746,7 @@ export const ContentList = <T extends GenericContent = GenericContent>(props: Co
719746
displayNameInArray) as any
720747
}
721748
getSelectionControl={getSelectionControl}
722-
/* If the Order by Column Is The Display. The client will sort it. Due to some locale and indexing issues */
723-
items={
724-
sortableColumns.includes(String(currentOrder))
725-
? children?.sort((a, b) => {
726-
// If no display Name
727-
const nameA = String(a[currentOrder]) ?? '' // Provide a default value if displayName is undefined
728-
const nameB = String(b[currentOrder]) ?? '' // Provide a default value if displayName is undefined
729-
730-
if (currentDirection === 'asc') {
731-
return nameA.localeCompare(nameB)
732-
}
733-
return nameB.localeCompare(nameA)
734-
})
735-
: currentOrder === 'CreatedBy' || currentOrder === 'ModifiedBy'
736-
? children?.sort((a, b) => {
737-
const aTmp = a[currentOrder] as GenericContent
738-
const bTmp = b[currentOrder] as GenericContent
739-
740-
const nameA = String(aTmp?.DisplayName) ?? ''
741-
const nameB = String(bTmp?.DisplayName) ?? ''
742-
743-
if (currentDirection === 'asc') {
744-
return nameA.localeCompare(nameB)
745-
}
746-
return nameB.localeCompare(nameA)
747-
})
748-
: children
749-
}
749+
items={children}
750750
onRequestOrderChange={onRequestOrderChangeFunc}
751751
onRequestSelectionChange={setSelected}
752752
orderBy={currentOrder}

apps/sensenet/src/components/grid/Grid.tsx

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ import {
1313
} from 'ag-grid-community'
1414
import { AgGridReact } from 'ag-grid-react'
1515
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
16-
import { useLocalization, useSelectionService } from '../../hooks'
16+
import { useLocalization, usePersonalSettings, useSelectionService } from '../../hooks'
1717
import { ContentContextMenu } from '../context-menu/content-context-menu'
1818
import { DropFileArea } from '../DropFileArea'
19+
import { compareTreeItems } from '../tree/tree-helpers'
1920
import { GridProps } from './Props/GridProps'
2021
import { useGridLoading } from './Providers/GridLoadingProvider'
2122

@@ -25,20 +26,13 @@ export function Grid<T extends GenericContent = GenericContent>(props: GridProps
2526
const { isGridLoading, setIsGridLoading } = useGridLoading()
2627
const selectionService = useSelectionService()
2728
const localization = useLocalization().common
29+
const personalSettings = usePersonalSettings()
2830
const parentContent = useContext(CurrentContentContext)
2931
const currentChildren = useContext(CurrentChildrenContext) as GenericContent[]
3032
const isCurrentChildrenLoading = useContext(CurrentChildrenIsLoadingContext)
3133
const children = useMemo(() => {
32-
return [...currentChildren].sort((a, b) => {
33-
const aIsFolder = a.Type?.toLowerCase().includes('folder') ?? false
34-
const bIsFolder = b.Type?.toLowerCase().includes('folder') ?? false
35-
36-
if (aIsFolder && !bIsFolder) return -1
37-
if (!aIsFolder && bIsFolder) return 1
38-
39-
return (a.DisplayName ?? '').localeCompare(b.DisplayName ?? '')
40-
})
41-
}, [currentChildren])
34+
return [...currentChildren].sort(compareTreeItems(true, personalSettings.sortFoldersFirst))
35+
}, [currentChildren, personalSettings.sortFoldersFirst])
4236

4337
const theme = useTheme()
4438
const [contextMenuItem, setContextMenuItem] = useState<GenericContent | null>(null)

apps/sensenet/src/components/tree/Contexts/ExpandedItemsProvider.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { GenericContent } from '@sensenet/default-content-types'
22
import { useRepository } from '@sensenet/hooks-react'
33
import React, { createContext, ReactNode, useRef, useState } from 'react'
4+
import { usePersonalSettings } from '../../../hooks'
5+
import { getTreeOrderBy, SETTINGS_FOLDER_FILTER } from '../tree-helpers'
46
// Meghatározzuk a Context típusát: egy string tömb és egy setter függvény
57
type ExpandItemsContextType = [
68
Set<string>,
@@ -20,13 +22,15 @@ const ExpandedItemsProvider = ({ children }: { children: ReactNode }) => {
2022
const [cacheTime, setCacheTime] = useState<number>(6000)
2123
const cache = useRef<{ [key: string]: { data: GenericContent[] | undefined; timestamp: number } }>({})
2224
const repo = useRepository()
25+
const personalSettings = usePersonalSettings()
2326

2427
const loadChildren = async (path: string): Promise<GenericContent[] | undefined> => {
2528
if (!path) return undefined
2629

2730
const now = Date.now()
31+
const cacheKey = `${path}|showHiddenItems:${personalSettings.showHiddenItems}`
2832

29-
const cached = cache.current[path]
33+
const cached = cache.current[cacheKey]
3034
if (cached) {
3135
if (cached.data !== undefined && now - cached.timestamp < cacheTime) {
3236
return cached.data
@@ -37,7 +41,7 @@ const ExpandedItemsProvider = ({ children }: { children: ReactNode }) => {
3741
// Polling-based wait
3842
for (let i = 0; i < 10; i++) {
3943
await new Promise((res) => setTimeout(res, 200))
40-
const recheck = cache.current[path]
44+
const recheck = cache.current[cacheKey]
4145
if (recheck?.data !== undefined && now - recheck.timestamp < cacheTime) {
4246
return recheck.data
4347
}
@@ -47,30 +51,35 @@ const ExpandedItemsProvider = ({ children }: { children: ReactNode }) => {
4751
}
4852

4953
// Mark as loading
50-
cache.current[path] = { data: undefined, timestamp: now }
54+
cache.current[cacheKey] = { data: undefined, timestamp: now }
5155

5256
try {
5357
const response = await repo.loadCollection<GenericContent>({
5458
path,
5559
oDataOptions: {
5660
select: ['Id', 'Path', 'Name', 'DisplayName', 'Type', 'Actions', 'Icon', 'ParentId'],
57-
filter: 'IsFolder eq true',
61+
filter: `IsFolder eq true ${!personalSettings.showHiddenItems ? `and (${SETTINGS_FOLDER_FILTER})` : ''}`,
62+
orderby: getTreeOrderBy(personalSettings.preferDisplayName),
5863
onlyselectList: true,
5964
},
6065
})
6166
const result = response?.d.results
62-
cache.current[path] = { data: result, timestamp: Date.now() }
67+
cache.current[cacheKey] = { data: result, timestamp: Date.now() }
6368
return result
6469
} catch (error) {
6570
console.error('#globalfetch: Fetch error:', error)
6671
// Clean up failed cache
67-
delete cache.current[path]
72+
delete cache.current[cacheKey]
6873
return undefined
6974
}
7075
}
7176

7277
const deleteCache = (path: string) => {
73-
delete cache.current[path]
78+
Object.keys(cache.current)
79+
.filter((key) => key.startsWith(`${path}|`))
80+
.forEach((key) => {
81+
delete cache.current[key]
82+
})
7483
}
7584

7685
return (

apps/sensenet/src/components/tree/StyledTreeItem.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ import { useRepository } from '@sensenet/hooks-react'
55
import React, { MouseEventHandler, useCallback, useContext, useEffect, useRef, useState } from 'react'
66
import { useHistory } from 'react-router'
77
import { ResponsivePersonalSettings } from '../../context'
8-
import { useQuery, useSelectionService, useSnRoute } from '../../hooks'
8+
import { usePersonalSettings, useQuery, useSelectionService, useSnRoute } from '../../hooks'
99
import { getPrimaryActionUrl, navigateToAction } from '../../services'
1010
import { ContentContextMenu } from '../context-menu/content-context-menu'
1111
import { Icon } from '../Icon'
1212
import { ExpandItemsContext } from './Contexts/ExpandedItemsProvider'
1313
import { useTreeLoading } from './Contexts/TreeLoadingProvider'
1414
import StyledTreeItemProps from './Props/StyledTreeItemProps'
15+
import { compareTreeItems, getTreeItemLabel } from './tree-helpers'
1516

1617
export const StyledTreeItem = ({
1718
contentvalue,
@@ -38,6 +39,7 @@ export const StyledTreeItem = ({
3839
const snRoute = useSnRoute()
3940
const uiSettings = useContext(ResponsivePersonalSettings)
4041
const selectionService = useSelectionService()
42+
const personalSettings = usePersonalSettings()
4143

4244
const currentPath = useQuery().get('path')
4345
const mountedRef = useRef(true)
@@ -60,11 +62,9 @@ export const StyledTreeItem = ({
6062
const children = await loadChildren(contentPath)
6163
if (!mountedRef.current) return
6264

63-
const sorted = children?.sort((a, b) => {
64-
const isAFolder = a.Type.toLowerCase().includes('folder') ? 0 : 1
65-
const isBFolder = b.Type.toLowerCase().includes('folder') ? 0 : 1
66-
return isAFolder - isBFolder || a.Name.localeCompare(b.Name)
67-
})
65+
const sorted = children
66+
? [...children].sort(compareTreeItems(personalSettings.preferDisplayName, personalSettings.sortFoldersFirst))
67+
: undefined
6868

6969
const elements = sorted?.map((child) => (
7070
<StyledTreeItem
@@ -87,7 +87,14 @@ export const StyledTreeItem = ({
8787
//
8888
}
8989
},
90-
[loadChildren, activeitempath, navigate, editMode],
90+
[
91+
loadChildren,
92+
personalSettings.preferDisplayName,
93+
personalSettings.sortFoldersFirst,
94+
activeitempath,
95+
navigate,
96+
editMode,
97+
],
9198
)
9299

93100
// Load children if expanded
@@ -114,7 +121,10 @@ export const StyledTreeItem = ({
114121
<ListItemIcon>
115122
<Icon item={contentvalue} style={{ height: 20, width: 20, fontSize: 15 }} />
116123
</ListItemIcon>
117-
<ListItemText style={{ fontSize: '11px', color: isDisabled ? 'grey' : undefined }} primary={contentvalue.Name} />
124+
<ListItemText
125+
style={{ fontSize: '11px', color: isDisabled ? 'grey' : undefined }}
126+
primary={getTreeItemLabel(contentvalue, personalSettings.preferDisplayName)}
127+
/>
118128
</>
119129
)
120130

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { ODataParams } from '@sensenet/client-core'
2+
import type { GenericContent } from '@sensenet/default-content-types'
3+
4+
type SortableTreeItem = Pick<GenericContent, 'DisplayName' | 'Name' | 'Type'> & { IsFolder?: boolean }
5+
6+
export const SETTINGS_FOLDER_FILTER = `not ((Name eq 'Settings') and (isOf('SystemFolder')))`
7+
8+
export const getTreeItemLabel = (item: SortableTreeItem, preferDisplayName: boolean) =>
9+
(preferDisplayName && item.DisplayName ? item.DisplayName : item.Name) || ''
10+
11+
export const isFolderLikeTreeItem = (item: SortableTreeItem) =>
12+
item.Type ? item.Type.toLowerCase().includes('folder') : item.IsFolder === true
13+
14+
export const compareTreeItems =
15+
(preferDisplayName: boolean, sortFoldersFirst: boolean) => (left: SortableTreeItem, right: SortableTreeItem) => {
16+
if (sortFoldersFirst) {
17+
const folderOrder = Number(!isFolderLikeTreeItem(left)) - Number(!isFolderLikeTreeItem(right))
18+
19+
if (folderOrder) {
20+
return folderOrder
21+
}
22+
}
23+
24+
return (
25+
getTreeItemLabel(left, preferDisplayName).localeCompare(getTreeItemLabel(right, preferDisplayName), undefined, {
26+
sensitivity: 'base',
27+
}) ||
28+
(left.Name || '').localeCompare(right.Name || '', undefined, {
29+
sensitivity: 'base',
30+
})
31+
)
32+
}
33+
34+
export const getTreeOrderBy = (preferDisplayName: boolean): ODataParams<GenericContent>['orderby'] =>
35+
preferDisplayName
36+
? [
37+
['DisplayName', 'asc'],
38+
['Name', 'asc'],
39+
]
40+
: [['Name', 'asc']]

0 commit comments

Comments
 (0)