Skip to content

Commit 68c2f6f

Browse files
Merge branch '2026.1' into 2026.x
2 parents 526b183 + 298fc0a commit 68c2f6f

767 files changed

Lines changed: 1925 additions & 30367 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

assets/js/src/core/components/background/background.styles.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ export const useStyle = createStyles(({ css }, { phase, backgroundShade }: Style
9292
9393
.background-figure {
9494
position: absolute;
95-
will-change: transform;
9695
flex-shrink: 0;
9796
9897
${isOrbiting
@@ -132,6 +131,7 @@ export const useStyle = createStyles(({ css }, { phase, backgroundShade }: Style
132131
top: -80%;
133132
left: -30%;
134133
transform: rotate(65.637deg);
134+
filter: blur(90px);
135135
`
136136
}
137137
}
@@ -155,6 +155,7 @@ export const useStyle = createStyles(({ css }, { phase, backgroundShade }: Style
155155
top: 0;
156156
left: 0;
157157
transform: rotate(28.303deg);
158+
filter: blur(90px);
158159
`
159160
}
160161
}
@@ -178,6 +179,7 @@ export const useStyle = createStyles(({ css }, { phase, backgroundShade }: Style
178179
top: 55%;
179180
left: 33%;
180181
transform: rotate(65.637deg);
182+
filter: blur(90px);
181183
`
182184
}
183185
}
@@ -201,9 +203,16 @@ export const useStyle = createStyles(({ css }, { phase, backgroundShade }: Style
201203
backdropBlur: css`
202204
position: absolute;
203205
inset: 0;
204-
backdrop-filter: blur(90px);
205-
-webkit-backdrop-filter: blur(90px);
206206
pointer-events: none;
207+
opacity: ${isOrbiting ? 1 : 0};
208+
209+
${isOrbiting
210+
? css`
211+
backdrop-filter: blur(90px);
212+
-webkit-backdrop-filter: blur(90px);
213+
`
214+
: ''
215+
}
207216
`,
208217
logoImage: css` position: absolute;
209218
top: 50%;
@@ -223,7 +232,6 @@ export const useStyle = createStyles(({ css }, { phase, backgroundShade }: Style
223232
background: rgba(253, 255, 255, 0.35);
224233
filter: blur(75px);
225234
pointer-events: none;
226-
will-change: transform;
227235
228236
${isOrbiting
229237
? css`animation: ${logoOrbitCW} 3s linear infinite;`
@@ -245,7 +253,6 @@ export const useStyle = createStyles(({ css }, { phase, backgroundShade }: Style
245253
background: rgba(253, 255, 255, 0.35);
246254
filter: blur(75px);
247255
pointer-events: none;
248-
will-change: transform;
249256
250257
${isOrbiting
251258
? css`animation: ${logoOrbitCCW} 4s linear infinite;`

assets/js/src/core/components/form/localisation/localized-fields/form-item/types/form-control-with-element-context.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { useElementContext } from '@Pimcore/modules/element/hooks/use-element-co
2424
import { useLanguageSelection } from '@Pimcore/components/language-selection'
2525
import { useElementDraft } from '@Pimcore/modules/element/hooks/use-element-draft'
2626
import { useUser } from '@Pimcore/modules/auth/hooks/use-user'
27+
import { useLocalizedFields } from '../../provider/localized-fields-provider/use-localized-fields'
2728

2829
export interface KeyedFormItemControlProps {
2930
children: React.ReactNode
@@ -41,15 +42,21 @@ export const FormControlWithElementContext = ({ children, ...props }: KeyedFormI
4142
const element = useElementContext()
4243
const elementDraft = useElementDraft(element.id, element.elementType)
4344
const languageSelection = useLanguageSelection()
45+
const localizedContext = useLocalizedFields()
46+
const activeLanguage = localizedContext?.locales[0] ?? languageSelection.currentLanguage
4447

4548
if ('permissions' in elementDraft) {
4649
const permissions: Record<string, any> = elementDraft.permissions as Record<string, any>
4750
let editableLanguages: string[] = permissions?.localizedEdit?.split(',') ?? []
48-
if ((editableLanguages.length === 1 && editableLanguages[0] === 'default') || (editableLanguages.length === 0)) {
51+
52+
const isEmptyEditableLanguages = editableLanguages.length === 0
53+
54+
// empty array or 'default' means all languages are editable
55+
if ((editableLanguages.length === 1 && editableLanguages[0] === 'default') || isEmptyEditableLanguages) {
4956
editableLanguages = Array.isArray(user.contentLanguages) ? user.contentLanguages as string[] : []
5057
}
5158

52-
isDisabled = !editableLanguages.includes(languageSelection.currentLanguage)
59+
isDisabled = !isEmptyEditableLanguages && !editableLanguages.includes(activeLanguage)
5360
}
5461

5562
if (!isValidElement(Child)) {
@@ -64,5 +71,5 @@ export const FormControlWithElementContext = ({ children, ...props }: KeyedFormI
6471
{ ...props }
6572
disabled={ Child.props.disabled === true || (props.disabled !== true ? isDisabled : false) }
6673
/>
67-
), [Child, props])
74+
), [Child, isDisabled, props])
6875
}

assets/js/src/core/components/language-selection/language-selection.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,10 @@ export const LanguageSelection = ({ languages, customKeys = [], selectedLanguage
138138
})
139139
}, [customKeys, filteredLanguages, t, validLocales])
140140

141-
const handleMenuClick: MenuProps['onClick'] = ({ key }) => {
142-
handleLanguageChange(String(key))
141+
const handleMenuClick: MenuProps['onClick'] = (e) => {
142+
e.domEvent.stopPropagation()
143+
144+
handleLanguageChange(String(e.key))
143145
setDropdownOpen(false)
144146
setSearchQuery('')
145147
}

assets/js/src/core/modules/application-logger/components/table/table.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export const Table = ({ items, isLoading, sorting, onSortingChange }: TableProps
8585
enableSorting: true,
8686
cell: ({ row }): React.JSX.Element => {
8787
const column = row.original
88-
const fileObjectBasePath = '/admin/bundle/applicationlogger/log/show-file-object?filePath='
88+
const fileObjectBasePath = '/pimcore-studio/api/bundle/application-logger/file-object?filePath='
8989

9090
if (isNil(column.fileObject)) {
9191
return <></>

assets/js/src/core/modules/data-object/editor/toolbar/language-comparison-view/language-comparison-content.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { useStyles } from './language-comparison-modal.styles'
2626

2727
interface ILanguageComparisonColumnProps {
2828
locales: string[]
29+
editableLanguages: string[]
2930
layoutData: ILocalizedFieldDescriptor[]
3031
isAllowedToEdit: boolean
3132
}
@@ -52,7 +53,7 @@ const groupIntoSections = (items: ILocalizedFieldDescriptor[]): ILocalizedSectio
5253
return sections
5354
}
5455

55-
export const LanguageComparisonContent = ({ layoutData, locales, isAllowedToEdit }: ILanguageComparisonColumnProps): React.JSX.Element => {
56+
export const LanguageComparisonContent = ({ layoutData, locales, editableLanguages, isAllowedToEdit }: ILanguageComparisonColumnProps): React.JSX.Element => {
5657
const { styles } = useStyles()
5758

5859
const renderSectionTitle = ({ breadcrumbTitle, hideSectionTitle }: { breadcrumbTitle: string, hideSectionTitle: boolean }): React.JSX.Element | null => {
@@ -85,6 +86,9 @@ export const LanguageComparisonContent = ({ layoutData, locales, isAllowedToEdit
8586
const shouldWrapLocalizedProvider = item.localeInFormPath !== true
8687
const fieldKey = `${item.formPath.join('.')}-${fieldIndex}-${locale}`
8788

89+
// empty array means all languages are editable
90+
const isEmptyEditableLanguages = editableLanguages.length === 0
91+
8892
const fieldNode = (
8993
<CombinedFieldNameProvider
9094
combinedFieldNameParent={ combinedFieldNameParent }
@@ -93,7 +97,7 @@ export const LanguageComparisonContent = ({ layoutData, locales, isAllowedToEdit
9397
<Form.Group name={ resolvedGroupPath }>
9498
<ObjectComponent
9599
{ ...item.fieldData }
96-
noteditable={ !isAllowedToEdit }
100+
noteditable={ !isAllowedToEdit || (!isEmptyEditableLanguages && !editableLanguages.includes(locale)) }
97101
/>
98102
</Form.Group>
99103
</CombinedFieldNameProvider>

assets/js/src/core/modules/data-object/editor/toolbar/language-comparison-view/language-comparison-modal.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ export const LanguageComparisonModal = ({ open, onClose }: LanguageComparisonMod
6969
const [changedValues, setChangedValues] = useState<Record<string, any>>({})
7070

7171
const isAllowedToEdit = checkElementPermission(dataObject?.permissions, 'save')
72+
const viewableLanguages: string[] = useMemo(() => dataObject?.permissions?.localizedView?.split(',') ?? [], [dataObject?.permissions])
73+
const editableLanguages: string[] = useMemo(() => dataObject?.permissions?.localizedEdit?.split(',') ?? [], [dataObject?.permissions])
7274

7375
const initialValues: Partial<any> = useMemo(() => {
7476
return editForm.getFieldsValue(true)
@@ -86,11 +88,11 @@ export const LanguageComparisonModal = ({ open, onClose }: LanguageComparisonMod
8688

8789
useEffect(() => {
8890
if (open) {
89-
const languagesByPriority = [currentLanguage, ...contentLanguages.filter(language => language !== currentLanguage)]
91+
const languagesByPriority = [currentLanguage, ...contentLanguages.filter(language => viewableLanguages.includes(language) && language !== currentLanguage)]
9092

9193
setSelectedLocales([languagesByPriority[0] ?? currentLanguage, languagesByPriority[1] ?? null])
9294
}
93-
}, [open, currentLanguage, contentLanguages])
95+
}, [open, currentLanguage, contentLanguages, viewableLanguages])
9496

9597
useEffect(() => {
9698
if (isNil(layoutData) || !open) {
@@ -211,6 +213,7 @@ export const LanguageComparisonModal = ({ open, onClose }: LanguageComparisonMod
211213
preserve
212214
>
213215
<LanguageComparisonContent
216+
editableLanguages={ editableLanguages }
214217
isAllowedToEdit={ isAllowedToEdit }
215218
layoutData={ localizedFields }
216219
locales={ selectedLocales }

assets/js/src/core/modules/document/editor/shared-tab-manager/tabs/edit/components/editables-dialog/editable-dialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,10 @@ export const EditableDialog = ({ config, visible, onClose, editableDefinitions }
142142
return (
143143
<WindowModal
144144
cancelButtonProps={ { style: { display: 'none' } } }
145+
closable={ false }
145146
destroyOnClose
146147
getContainer={ () => document.body }
147148
okText={ t('save') }
148-
onCancel={ onClose }
149149
onOk={ onClose }
150150
open={ visible }
151151
size="L"

assets/js/src/core/modules/element/dynamic-types/definitions/pipelines/grid/source-fields/components/simple-field/simple-field.tsx

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,83 @@
1111
import { Form } from '@Pimcore/components/form/form'
1212
import { usePipelineConfig } from '@Pimcore/components/pipeline/provider/pipeline-config/use-pipeline-config'
1313
import { Select } from '@Pimcore/components/select/select'
14-
import React from 'react'
14+
import React, { useContext, useMemo } from 'react'
1515
import { useTranslation } from 'react-i18next'
16+
import { AvailableColumnsContext } from '@Pimcore/modules/element/listing/decorators/utils/column-configuration/context-layer/provider/available-columns/available-columns-provider'
17+
18+
interface SimpleFieldOption {
19+
label: string
20+
value: string
21+
}
22+
23+
const resolveGroupLabel = (group: unknown, translate: (key: string) => string): string | undefined => {
24+
if (group === undefined || group === null) {
25+
return undefined
26+
}
27+
28+
let parts: string[] = []
29+
30+
if (typeof group === 'string') {
31+
parts = group.split('.')
32+
} else if (Array.isArray(group)) {
33+
if (group.length === 0) {
34+
return undefined
35+
}
36+
37+
const firstPath = Array.isArray(group[0]) ? group[0] : group
38+
parts = (firstPath as unknown[]).map(part => String(part))
39+
} else {
40+
return undefined
41+
}
42+
43+
const labeledParts = parts.filter(part => part !== '').map(part => translate(part))
44+
45+
return labeledParts.length > 0 ? labeledParts.join(' / ') : undefined
46+
}
1647

1748
export const DynamicTypePipelineGridSourceFieldsSimpleFieldComponent = (): React.JSX.Element => {
1849
const { config } = usePipelineConfig()
1950
const { t } = useTranslation()
51+
const availableColumnsContext = useContext(AvailableColumnsContext)
2052

2153
const sourceFieldConfig = config?.simpleField
2254
if (sourceFieldConfig === undefined) {
2355
throw new Error('Source field configuration is missing')
2456
}
2557

26-
const sourceFieldOptions = sourceFieldConfig.map(configOption => ({
27-
label: configOption.name,
28-
value: configOption.key
29-
}))
58+
const sourceFieldOptions = useMemo(() => {
59+
const groupByKey = new Map<string, string | undefined>()
60+
for (const column of availableColumnsContext?.availableColumns ?? []) {
61+
groupByKey.set(String(column.key), resolveGroupLabel(column.group, t))
62+
}
63+
64+
const groups = new Map<string, SimpleFieldOption[]>()
65+
const ungrouped: SimpleFieldOption[] = []
66+
67+
for (const configOption of sourceFieldConfig) {
68+
const key = String(configOption.key)
69+
const option: SimpleFieldOption = {
70+
label: String(configOption.name),
71+
value: key
72+
}
73+
const groupLabel = groupByKey.get(key)
74+
75+
if (groupLabel === undefined) {
76+
ungrouped.push(option)
77+
continue
78+
}
79+
80+
const bucket = groups.get(groupLabel) ?? []
81+
bucket.push(option)
82+
groups.set(groupLabel, bucket)
83+
}
84+
85+
const groupedOptions = [...groups.entries()]
86+
.sort(([a], [b]) => a.localeCompare(b))
87+
.map(([label, options]) => ({ label, options }))
88+
89+
return [...groupedOptions, ...ungrouped]
90+
}, [sourceFieldConfig, availableColumnsContext?.availableColumns, t])
3091

3192
return (
3293
<Form.Item

assets/js/src/core/modules/execution-engine/jobs/batch-delete/abstract-batch-delete-job.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { isNil } from 'lodash'
1212
import trackError, { GeneralError } from '@Pimcore/modules/app/error-handler'
1313
import { type JobInterface, type JobRunOptions } from '../job-interface'
1414
import { MessageBusJobHandler, type JobCompletionData } from '../../message-handlers/message-bus-job/message-bus-job-handler'
15-
import { StepCompletionCalculator } from '../../message-handlers/message-bus-job/progress-calculator/step-completion-calculator'
15+
import { BatchedStepProgressCalculator } from '../../message-handlers/message-bus-job/progress-calculator/batched-step-progress-calculator'
1616
import { t } from 'i18next'
1717

1818
export interface AbstractBatchDeleteJobOptions {
@@ -85,7 +85,7 @@ export abstract class AbstractBatchDeleteJob implements JobInterface {
8585
return new MessageBusJobHandler({
8686
jobRunId: options.jobRunId,
8787
title: t('batch-delete.job-title'),
88-
progressCalculator: new StepCompletionCalculator(),
88+
progressCalculator: new BatchedStepProgressCalculator(),
8989
onJobCompletion: options.onJobCompletion,
9090
onRetry: options.onRetry
9191
})

assets/js/src/core/modules/execution-engine/jobs/delete/element-delete-job.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import trackError, { ApiError, GeneralError } from '@Pimcore/modules/app/error-h
1515
import { type JobInterface, type JobRunOptions } from '../job-interface'
1616
import { type ElementType } from '@Pimcore/types/enums/element/element-type'
1717
import { MessageBusJobHandler, type JobCompletionData } from '../../message-handlers/message-bus-job/message-bus-job-handler'
18-
import { StepCompletionCalculator } from '../../message-handlers/message-bus-job/progress-calculator/step-completion-calculator'
18+
import { BatchedStepProgressCalculator } from '../../message-handlers/message-bus-job/progress-calculator/batched-step-progress-calculator'
1919
import { api as elementApi } from '@Pimcore/modules/element/element-api-slice.gen'
2020
import { t } from 'i18next'
2121
import { type RehydratableJob, type JobRunList } from '../../services/job-rehydration-registry'
@@ -155,7 +155,7 @@ export class DeleteJob implements JobInterface {
155155
return new MessageBusJobHandler({
156156
jobRunId: options.jobRunId,
157157
title: t('element.delete.deleting-folder'),
158-
progressCalculator: new StepCompletionCalculator(),
158+
progressCalculator: new BatchedStepProgressCalculator(),
159159
onJobCompletion: options.onJobCompletion,
160160
onRetry: options.onRetry
161161
})

0 commit comments

Comments
 (0)