diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c3da3f0e..b3252985 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -11,7 +11,7 @@ jobs:
build:
name: ⏳ Build, test and deploy artifacts
runs-on: judong
- timeout-minutes: 5
+ timeout-minutes: 7
env:
SIGN_KEY_ID: ${{ secrets.GPG_KEYNAME }}
SIGN_KEY_PASS: ${{ secrets.GPG_PASSPHRASE }}
diff --git a/judo-diff-checker-maven-plugin/LICENSE.txt b/judo-diff-checker-maven-plugin/LICENSE.txt
new file mode 100644
index 00000000..d3087e4c
--- /dev/null
+++ b/judo-diff-checker-maven-plugin/LICENSE.txt
@@ -0,0 +1,277 @@
+Eclipse Public License - v 2.0
+
+ THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+ PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION
+ OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+ a) in the case of the initial Contributor, the initial content
+ Distributed under this Agreement, and
+
+ b) in the case of each subsequent Contributor:
+ i) changes to the Program, and
+ ii) additions to the Program;
+ where such changes and/or additions to the Program originate from
+ and are Distributed by that particular Contributor. A Contribution
+ "originates" from a Contributor if it was added to the Program by
+ such Contributor itself or anyone acting on such Contributor's behalf.
+ Contributions do not include changes or additions to the Program that
+ are not Modified Works.
+
+"Contributor" means any person or entity that Distributes the Program.
+
+"Licensed Patents" mean patent claims licensable by a Contributor which
+are necessarily infringed by the use or sale of its Contribution alone
+or when combined with the Program.
+
+"Program" means the Contributions Distributed in accordance with this
+Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement
+or any Secondary License (as applicable), including Contributors.
+
+"Derivative Works" shall mean any work, whether in Source Code or other
+form, that is based on (or derived from) the Program and for which the
+editorial revisions, annotations, elaborations, or other modifications
+represent, as a whole, an original work of authorship.
+
+"Modified Works" shall mean any work in Source Code or other form that
+results from an addition to, deletion from, or modification of the
+contents of the Program, including, for purposes of clarity any new file
+in Source Code form that contains any contents of the Program. Modified
+Works shall not include works that contain only declarations,
+interfaces, types, classes, structures, or files of the Program solely
+in each case in order to link to, bind by name, or subclass the Program
+or Modified Works thereof.
+
+"Distribute" means the acts of a) distributing or b) making available
+in any manner that enables the transfer of a copy.
+
+"Source Code" means the form of a Program preferred for making
+modifications, including but not limited to software source code,
+documentation source, and configuration files.
+
+"Secondary License" means either the GNU General Public License,
+Version 2.0, or any later versions of that license, including any
+exceptions or additional permissions as identified by the initial
+Contributor.
+
+2. GRANT OF RIGHTS
+
+ a) Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free copyright
+ license to reproduce, prepare Derivative Works of, publicly display,
+ publicly perform, Distribute and sublicense the Contribution of such
+ Contributor, if any, and such Derivative Works.
+
+ b) Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free patent
+ license under Licensed Patents to make, use, sell, offer to sell,
+ import and otherwise transfer the Contribution of such Contributor,
+ if any, in Source Code or other form. This patent license shall
+ apply to the combination of the Contribution and the Program if, at
+ the time the Contribution is added by the Contributor, such addition
+ of the Contribution causes such combination to be covered by the
+ Licensed Patents. The patent license shall not apply to any other
+ combinations which include the Contribution. No hardware per se is
+ licensed hereunder.
+
+ c) Recipient understands that although each Contributor grants the
+ licenses to its Contributions set forth herein, no assurances are
+ provided by any Contributor that the Program does not infringe the
+ patent or other intellectual property rights of any other entity.
+ Each Contributor disclaims any liability to Recipient for claims
+ brought by any other entity based on infringement of intellectual
+ property rights or otherwise. As a condition to exercising the
+ rights and licenses granted hereunder, each Recipient hereby
+ assumes sole responsibility to secure any other intellectual
+ property rights needed, if any. For example, if a third party
+ patent license is required to allow Recipient to Distribute the
+ Program, it is Recipient's responsibility to acquire that license
+ before distributing the Program.
+
+ d) Each Contributor represents that to its knowledge it has
+ sufficient copyright rights in its Contribution, if any, to grant
+ the copyright license set forth in this Agreement.
+
+ e) Notwithstanding the terms of any Secondary License, no
+ Contributor makes additional grants to any Recipient (other than
+ those set forth in this Agreement) as a result of such Recipient's
+ receipt of the Program under the terms of a Secondary License
+ (if permitted under the terms of Section 3).
+
+3. REQUIREMENTS
+
+3.1 If a Contributor Distributes the Program in any form, then:
+
+ a) the Program must also be made available as Source Code, in
+ accordance with section 3.2, and the Contributor must accompany
+ the Program with a statement that the Source Code for the Program
+ is available under this Agreement, and informs Recipients how to
+ obtain it in a reasonable manner on or through a medium customarily
+ used for software exchange; and
+
+ b) the Contributor may Distribute the Program under a license
+ different than this Agreement, provided that such license:
+ i) effectively disclaims on behalf of all other Contributors all
+ warranties and conditions, express and implied, including
+ warranties or conditions of title and non-infringement, and
+ implied warranties or conditions of merchantability and fitness
+ for a particular purpose;
+
+ ii) effectively excludes on behalf of all other Contributors all
+ liability for damages, including direct, indirect, special,
+ incidental and consequential damages, such as lost profits;
+
+ iii) does not attempt to limit or alter the recipients' rights
+ in the Source Code under section 3.2; and
+
+ iv) requires any subsequent distribution of the Program by any
+ party to be under a license that satisfies the requirements
+ of this section 3.
+
+3.2 When the Program is Distributed as Source Code:
+
+ a) it must be made available under this Agreement, or if the
+ Program (i) is combined with other material in a separate file or
+ files made available under a Secondary License, and (ii) the initial
+ Contributor attached to the Source Code the notice described in
+ Exhibit A of this Agreement, then the Program may be made available
+ under the terms of such Secondary Licenses, and
+
+ b) a copy of this Agreement must be included with each copy of
+ the Program.
+
+3.3 Contributors may not remove or alter any copyright, patent,
+trademark, attribution notices, disclaimers of warranty, or limitations
+of liability ("notices") contained within the Program from any copy of
+the Program which they Distribute, provided that Contributors may add
+their own appropriate notices.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities
+with respect to end users, business partners and the like. While this
+license is intended to facilitate the commercial use of the Program,
+the Contributor who includes the Program in a commercial product
+offering should do so in a manner which does not create potential
+liability for other Contributors. Therefore, if a Contributor includes
+the Program in a commercial product offering, such Contributor
+("Commercial Contributor") hereby agrees to defend and indemnify every
+other Contributor ("Indemnified Contributor") against any losses,
+damages and costs (collectively "Losses") arising from claims, lawsuits
+and other legal actions brought by a third party against the Indemnified
+Contributor to the extent caused by the acts or omissions of such
+Commercial Contributor in connection with its distribution of the Program
+in a commercial product offering. The obligations in this section do not
+apply to any claims or Losses relating to any actual or alleged
+intellectual property infringement. In order to qualify, an Indemnified
+Contributor must: a) promptly notify the Commercial Contributor in
+writing of such claim, and b) allow the Commercial Contributor to control,
+and cooperate with the Commercial Contributor in, the defense and any
+related settlement negotiations. The Indemnified Contributor may
+participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial
+product offering, Product X. That Contributor is then a Commercial
+Contributor. If that Commercial Contributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Contributor's responsibility
+alone. Under this section, the Commercial Contributor would have to
+defend claims against the other Contributors related to those performance
+claims and warranties, and if a court requires any other Contributor to
+pay any damages as a result, the Commercial Contributor must pay
+those damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
+PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS"
+BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
+IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF
+TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
+PURPOSE. Each Recipient is solely responsible for determining the
+appropriateness of using and distributing the Program and assumes all
+risks associated with its exercise of rights under this Agreement,
+including but not limited to the risks and costs of program errors,
+compliance with applicable laws, damage to or loss of data, programs
+or equipment, and unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
+PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS
+SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
+PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
+EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further
+action by the parties hereto, such provision shall be reformed to the
+minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity
+(including a cross-claim or counterclaim in a lawsuit) alleging that the
+Program itself (excluding combinations of the Program with other software
+or hardware) infringes such Recipient's patent(s), then such Recipient's
+rights granted under Section 2(b) shall terminate as of the date such
+litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it
+fails to comply with any of the material terms or conditions of this
+Agreement and does not cure such failure in a reasonable period of
+time after becoming aware of such noncompliance. If all Recipient's
+rights under this Agreement terminate, Recipient agrees to cease use
+and distribution of the Program as soon as reasonably practicable.
+However, Recipient's obligations under this Agreement and any licenses
+granted by Recipient relating to the Program shall continue and survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement,
+but in order to avoid inconsistency the Agreement is copyrighted and
+may only be modified in the following manner. The Agreement Steward
+reserves the right to publish new versions (including revisions) of
+this Agreement from time to time. No one other than the Agreement
+Steward has the right to modify this Agreement. The Eclipse Foundation
+is the initial Agreement Steward. The Eclipse Foundation may assign the
+responsibility to serve as the Agreement Steward to a suitable separate
+entity. Each new version of the Agreement will be given a distinguishing
+version number. The Program (including Contributions) may always be
+Distributed subject to the version of the Agreement under which it was
+received. In addition, after a new version of the Agreement is published,
+Contributor may elect to Distribute the Program (including its
+Contributions) under the new version.
+
+Except as expressly stated in Sections 2(a) and 2(b) above, Recipient
+receives no rights or licenses to the intellectual property of any
+Contributor under this Agreement, whether expressly, by implication,
+estoppel or otherwise. All rights in the Program not expressly granted
+under this Agreement are reserved. Nothing in this Agreement is intended
+to be enforceable by any entity that is not a Contributor or Recipient.
+No third-party beneficiary rights are created under this Agreement.
+
+Exhibit A - Form of Secondary Licenses Notice
+
+"This Source Code may also be made available under the following
+Secondary Licenses when the conditions for such availability set forth
+in the Eclipse Public License, v. 2.0 are satisfied: {name license(s),
+version(s), and exceptions or additional permissions here}."
+
+ Simply including a copy of this Agreement, including this Exhibit A
+ is not sufficient to license the Source Code under Secondary Licenses.
+
+ If it is not possible or desirable to put the notice in a particular
+ file, then You may include the notice in a location (such as a LICENSE
+ file in a relevant directory) where a recipient would be likely to
+ look for such a notice.
+
+ You may add additional accurate notices of copyright ownership.
diff --git a/judo-diff-checker-maven-plugin/README.adoc b/judo-diff-checker-maven-plugin/README.adoc
new file mode 100644
index 00000000..a640d67d
--- /dev/null
+++ b/judo-diff-checker-maven-plugin/README.adoc
@@ -0,0 +1,52 @@
+= judo-diff-checker-maven-plugin
+
+Check diff for any number of files, and highlight them.
+
+== Usage
+
+[source,xml]
+----
+
+ hu.blackbelt.judo.generator
+ judo-diff-checker-maven-plugin
+ ${revision}
+
+
+
+ checkDiffs
+
+ generate-sources
+
+
+
+ ${project.basedir}/target/frontend-react/
+ ${project.basedir}/src/test/resources/snapshots/frontend-react/
+
+ src/pages/God/God/Galaxies/AccessViewPage/index.tsx
+ src/pages/God/God/Galaxies/AccessTablePage/index.tsx
+
+
+
+
+----
+
+== Configuration
+
+=== createSnapshotIfNotExists
+
+(Optional, default: `true`)
+
+The plugin will automatically create snapshot files if they do not exist, unless the `createSnapshotIfNotExists`
+configuration is explicitly set to `false`;
+
+=== forceSnapshotOverwrite
+
+(Optional, default: `false`)
+
+If you would like the plugin to always update the contents of snapshot files before checking, set this to `true`.
+
+Setting it to true can be useful if you are e.g. doing refactors and do not intend to have any changes in your snapshot.
+
+=== snapshotPostfix
+
+(Optional, default: `".snapshot"`)
diff --git a/judo-diff-checker-maven-plugin/pom.xml b/judo-diff-checker-maven-plugin/pom.xml
new file mode 100644
index 00000000..c9f28aa2
--- /dev/null
+++ b/judo-diff-checker-maven-plugin/pom.xml
@@ -0,0 +1,65 @@
+
+
+ 4.0.0
+
+ hu.blackbelt.judo.generator
+ judo-ui-react-template
+ ${revision}
+
+
+ judo-diff-checker-maven-plugin
+ maven-plugin
+
+ JUDO Diff Checker Maven Plugin
+ Tooling to check for diffs in files
+
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-annotations
+ 3.10.2
+ provided
+
+
+ org.apache.maven
+ maven-plugin-api
+ 3.9.6
+
+
+ io.github.java-diff-utils
+ java-diff-utils
+ 4.12
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-plugin-plugin
+ 3.8.2
+
+ snapshot-checker
+
+
+
+
+ descriptor
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+ ${maven.compiler.source}
+ ${maven.compiler.source}
+
+
+
+
+
diff --git a/judo-diff-checker-maven-plugin/src/main/java/hu/blackbelt/judo/diffchecker/DiffCheckerMojo.java b/judo-diff-checker-maven-plugin/src/main/java/hu/blackbelt/judo/diffchecker/DiffCheckerMojo.java
new file mode 100644
index 00000000..3eabb70c
--- /dev/null
+++ b/judo-diff-checker-maven-plugin/src/main/java/hu/blackbelt/judo/diffchecker/DiffCheckerMojo.java
@@ -0,0 +1,107 @@
+package hu.blackbelt.judo.diffchecker;
+
+import com.github.difflib.DiffUtils;
+import com.github.difflib.UnifiedDiffUtils;
+import com.github.difflib.patch.Patch;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+
+@Mojo(name = "checkDiffs", defaultPhase = LifecyclePhase.VERIFY)
+public class DiffCheckerMojo extends AbstractMojo {
+
+ @Parameter(property = "sources")
+ private List sources;
+
+ @Parameter(property = "snapshotPostfix", defaultValue = ".snapshot")
+ private String snapshotPostfix = ".snapshot";
+
+ @Parameter(property = "forceSnapshotOverwrite", defaultValue = "false")
+ private Boolean forceSnapshotOverwrite = false;
+
+ @Parameter(property = "sourceDirectory", required = true)
+ private String sourceDirectory;
+
+ @Parameter(property = "snapshotDirectory", required = true)
+ private String snapshotDirectory;
+
+ @Parameter(property = "createSnapshotIfNotExists", defaultValue = "true")
+ private Boolean createSnapshotIfNotExists = true;
+
+ private static final int CONTEXT_SIZE = 3;
+
+ @Override
+ public void execute() throws MojoExecutionException {
+ Path sourcePath = Paths.get(sourceDirectory);
+ Path snapshotPath = Paths.get(snapshotDirectory);
+
+ if (sources == null || sources.isEmpty()) {
+ throw new MojoExecutionException("No sources configured for snapshot checking.");
+ }
+
+ for (String source : sources) {
+ Path sourceFilePath = sourcePath.resolve(source);
+ Path snapshotFilePath = snapshotPath.resolve(source + snapshotPostfix);
+ File sourceFile = sourceFilePath.toFile();
+ File snapshotFile = snapshotFilePath.toFile();
+
+ if (!sourceFile.exists()) {
+ throw new MojoExecutionException("Source file does not exist: " + sourceFile.getPath());
+ }
+
+ try {
+ if (!snapshotFile.exists()) {
+ if (createSnapshotIfNotExists) {
+ createSnapshotFileFromSource(sourceFile, snapshotFile);
+ getLog().info("Created snapshot file: " + snapshotFile.getPath());
+ } else {
+ throw new MojoExecutionException("Snapshot file does not exist: " + snapshotFile.getPath());
+ }
+ } else {
+ if (forceSnapshotOverwrite) {
+ createSnapshotFileFromSource(sourceFile, snapshotFile);
+ getLog().info("Overwritten snapshot file: " + snapshotFile.getPath());
+ }
+ compareFiles(sourceFile, snapshotFile);
+ }
+ } catch (IOException e) {
+ throw new MojoExecutionException("Error processing files: " + sourceFile.getPath() + " and " + snapshotFile.getPath(), e);
+ }
+ }
+ }
+
+ private void createSnapshotFileFromSource(File sourceFile, File snapshotFile) throws IOException {
+ Files.createDirectories(snapshotFile.getParentFile().toPath());
+ Files.copy(sourceFile.toPath(), snapshotFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ private void compareFiles(File sourceFile, File snapshotFile) throws IOException, MojoExecutionException {
+ List sourceLines = Files.readAllLines(sourceFile.toPath());
+ List snapshotLines = Files.readAllLines(snapshotFile.toPath());
+
+ Patch diff = DiffUtils.diff(snapshotLines, sourceLines);
+
+ if (!diff.getDeltas().isEmpty()) {
+ displayGitLikeDiff(snapshotLines, sourceLines, "[SNAPSHOT] " + snapshotFile.getPath().substring(snapshotDirectory.length()), "[SOURCE] " + sourceFile.getPath().substring(sourceDirectory.length()));
+ throw new MojoExecutionException("Snapshot changes detected in " + sourceFile.getPath());
+ }
+ }
+
+ private void displayGitLikeDiff(List original, List revised, String snapshotPath, String sourcePath) {
+ Patch patch = DiffUtils.diff(original, revised);
+ List unifiedDiff = UnifiedDiffUtils.generateUnifiedDiff(snapshotPath, sourcePath, original, patch, CONTEXT_SIZE);
+ for (String line : unifiedDiff) {
+ System.out.println(line);
+ }
+ }
+}
diff --git a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/pom.xml b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/pom.xml
index 9f8bb3a5..12e3af75 100644
--- a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/pom.xml
+++ b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/pom.xml
@@ -93,7 +93,7 @@
${ui-model}
${generation-target}
- true
+ false
${model-name}
${appScope}
${appVersion}
@@ -179,7 +179,7 @@
pnpm
- generate-sources
+ test
run build
@@ -201,6 +201,36 @@
${generation-target}
+
+
+ hu.blackbelt.judo.generator
+ judo-diff-checker-maven-plugin
+ ${revision}
+
+
+
+ checkDiffs
+
+ generate-sources
+
+
+
+ ${project.basedir}/target/frontend-react/
+ ${project.basedir}/src/test/resources/snapshots/frontend-react/
+
+ src/pages/God/God/Galaxies/AccessViewPage/index.tsx
+ src/pages/God/God/Galaxies/AccessTablePage/index.tsx
+ src/containers/View/Galaxy/Form/ViewGalaxyForm.tsx
+ src/containers/View/Galaxy/Form/ViewGalaxyFormDialogContainer.tsx
+ src/containers/View/Galaxy/View/ViewGalaxyView.tsx
+ src/containers/View/Galaxy/View/ViewGalaxyViewDialogContainer.tsx
+ src/containers/View/Galaxy/View/ViewGalaxyViewPageContainer.tsx
+ src/containers/Planet/View/PlanetView.tsx
+ src/containers/Planet/View/PlanetViewPageContainer.tsx
+ src/pages/God/God/Earth/AccessViewPage/index.tsx
+
+
+
diff --git a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/Planet/View/PlanetView.tsx.snapshot b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/Planet/View/PlanetView.tsx.snapshot
new file mode 100644
index 00000000..44d03a1b
--- /dev/null
+++ b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/Planet/View/PlanetView.tsx.snapshot
@@ -0,0 +1,130 @@
+//////////////////////////////////////////////////////////////////////////////
+// G E N E R A T E D S O U R C E
+// --------------------------------
+// Factory expression: #application.pageContainers
+// Path expression: 'src/containers/'+#containerPath(#self)+'/'+#containerComponentName(#self)+'.tsx'
+// Template name: actor/src/containers/container.tsx
+// Template file: actor/src/containers/container.tsx.hbs
+
+import LoadingButton from '@mui/lab/LoadingButton';
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+import ButtonGroup from '@mui/material/ButtonGroup';
+import Container from '@mui/material/Container';
+import Grid from '@mui/material/Grid';
+import IconButton from '@mui/material/IconButton';
+import InputAdornment from '@mui/material/InputAdornment';
+import TextField from '@mui/material/TextField';
+import Typography from '@mui/material/Typography';
+import { OBJECTCLASS } from '@pandino/pandino-api';
+import { useTrackService } from '@pandino/react-hooks';
+import { clsx } from 'clsx';
+import type { Dispatch, FC, SetStateAction } from 'react';
+import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { DropdownButton, MdiIcon, useJudoNavigation } from '~/components';
+import { useConfirmDialog } from '~/components/dialog';
+import { useL10N } from '~/l10n/l10n-context';
+import { isErrorOperationFault, useErrorHandler } from '~/utilities';
+
+import {} from '@mui/x-date-pickers';
+import type {} from '@mui/x-date-pickers';
+import {} from '~/components/widgets';
+import { useConfirmationBeforeChange } from '~/hooks';
+import type { Planet, PlanetStored } from '~/services/data-api/model/Planet';
+import { PLANET_VIEW_CONTAINER_ACTIONS_HOOK_INTERFACE_KEY } from './customization';
+import type { PlanetViewContainerHook } from './customization';
+import type { PlanetViewActionDefinitions, PlanetViewProps } from './types';
+
+// XMIID: God/(esm/_iyJ2kORqEeuSU8xLq1yYbw)/TransferObjectViewPageContainer
+// Name: Planet::View
+export default function PlanetView(props: PlanetViewProps) {
+ // Container props
+ const {
+ refreshCounter,
+ isLoading,
+ isDraft,
+ dataPath,
+ actions: pageActions,
+ data,
+ isFormUpdateable,
+ isFormDeleteable,
+ storeDiff,
+ editMode,
+ validation,
+ setValidation,
+ submit,
+ } = props;
+
+ // Container hooks
+ const { t } = useTranslation();
+ const { navigate, back } = useJudoNavigation();
+ const { locale: l10nLocale } = useL10N();
+ const { openConfirmDialog } = useConfirmDialog();
+
+ useConfirmationBeforeChange(
+ editMode,
+ t('judo.form.navigation.confirmation', {
+ defaultValue: 'You have potential unsaved changes in your form, are you sure you would like to navigate away?',
+ }),
+ );
+ // Pandino Container Action overrides
+ const { service: customContainerHook } = useTrackService(
+ `(${OBJECTCLASS}=${PLANET_VIEW_CONTAINER_ACTIONS_HOOK_INTERFACE_KEY})`,
+ );
+ const containerActions: PlanetViewActionDefinitions = customContainerHook?.(data, editMode, storeDiff) || {};
+ const actions = useMemo(() => ({ ...pageActions, ...containerActions }), [pageActions, containerActions]);
+
+ return (
+
+
+
+
+
+
+ {
+ const realValue = event.target.value?.length === 0 ? null : event.target.value;
+ storeDiff('name', realValue);
+ }}
+ InputLabelProps={{ shrink: true }}
+ InputProps={{
+ readOnly: false || !isFormUpdateable(),
+ startAdornment: (
+
+
+
+ ),
+ }}
+ inputProps={{
+ maxLength: 255,
+ }}
+ />
+
+
+
+
+
+
+ );
+}
diff --git a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/Planet/View/PlanetViewPageContainer.tsx.snapshot b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/Planet/View/PlanetViewPageContainer.tsx.snapshot
new file mode 100644
index 00000000..099bd7f5
--- /dev/null
+++ b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/Planet/View/PlanetViewPageContainer.tsx.snapshot
@@ -0,0 +1,163 @@
+//////////////////////////////////////////////////////////////////////////////
+// G E N E R A T E D S O U R C E
+// --------------------------------
+// Factory expression: #getPageContainersToGenerate(#application)
+// Path expression: 'src/containers/'+#containerPath(#self)+'/'+#containerComponentName(#self)+'PageContainer.tsx'
+// Template name: actor/src/containers/page.tsx
+// Template file: actor/src/containers/page.tsx.hbs
+
+import LoadingButton from '@mui/lab/LoadingButton';
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+import Grid from '@mui/material/Grid';
+import { Suspense, lazy } from 'react';
+import type { Dispatch, SetStateAction } from 'react';
+import { useTranslation } from 'react-i18next';
+import { MdiIcon, PageHeader, useJudoNavigation } from '~/components';
+import { useConfirmDialog } from '~/components/dialog';
+import type { Planet, PlanetStored } from '~/services/data-api/model/Planet';
+import type { PlanetQueryCustomizer } from '~/services/data-api/rest/PlanetQueryCustomizer';
+import { mainContainerPadding } from '~/theme';
+import { processQueryCustomizer } from '~/utilities';
+import { getValue } from '~/utilities/helper';
+
+import type { PlanetViewActionDefinitions, PlanetViewPageActions, PlanetViewPageProps } from './types';
+
+const PlanetView = lazy(() => import('~/containers/Planet/View/PlanetView'));
+
+// Name: Planet::View
+export default function PlanetViewPage(props: PlanetViewPageProps) {
+ const { openConfirmDialog } = useConfirmDialog();
+
+ const { t } = useTranslation();
+ const { navigate, back } = useJudoNavigation();
+ const {
+ actions,
+ dataPath,
+ isLoading,
+ editMode,
+ refreshCounter,
+ data,
+ isFormUpdateable,
+ isFormDeleteable,
+ storeDiff,
+ validation,
+ setValidation,
+ submit,
+ } = props;
+ const isDraft = false; // currently no page can be opened as draft, but we need this variable anyway
+ const queryCustomizer: PlanetQueryCustomizer = {
+ _mask: actions.getMask?.(),
+ };
+
+ return (
+ <>
+
+ {!editMode && actions.backAction && (
+
+ }
+ disabled={isLoading || editMode}
+ onClick={async () => {
+ await actions.backAction!();
+ }}
+ >
+ {t('judo.action.back', { defaultValue: 'Back' })}
+
+
+ )}
+ {!isDraft && actions.refreshAction && (
+
+ }
+ disabled={isLoading || editMode}
+ onClick={async () => {
+ await actions.refreshAction!(processQueryCustomizer(queryCustomizer));
+ }}
+ >
+ {t('judo.action.refresh', { defaultValue: 'Refresh' })}
+
+
+ )}
+ {!editMode && (isFormDeleteable() || isDraft) && actions.deleteAction && (
+
+ }
+ disabled={isLoading || editMode}
+ onClick={async () => {
+ actions.deleteAction!();
+ }}
+ >
+ {t('judo.action.delete', { defaultValue: 'Delete' })}
+
+
+ )}
+ {editMode && actions.cancelAction && (
+
+ }
+ disabled={isLoading}
+ onClick={async () => {
+ await actions.cancelAction!();
+ }}
+ >
+ {t('judo.action.cancel', { defaultValue: 'Cancel' })}
+
+
+ )}
+ {editMode && actions.updateAction && (
+
+ }
+ disabled={isLoading}
+ onClick={async () => {
+ await actions.updateAction!();
+ }}
+ >
+ {t('judo.action.update', { defaultValue: 'Save' })}
+
+
+ )}
+ {/* Placeholder */}
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/Form/ViewGalaxyForm.tsx.snapshot b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/Form/ViewGalaxyForm.tsx.snapshot
new file mode 100644
index 00000000..598572ac
--- /dev/null
+++ b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/Form/ViewGalaxyForm.tsx.snapshot
@@ -0,0 +1,392 @@
+//////////////////////////////////////////////////////////////////////////////
+// G E N E R A T E D S O U R C E
+// --------------------------------
+// Factory expression: #application.pageContainers
+// Path expression: 'src/containers/'+#containerPath(#self)+'/'+#containerComponentName(#self)+'.tsx'
+// Template name: actor/src/containers/container.tsx
+// Template file: actor/src/containers/container.tsx.hbs
+
+import LoadingButton from '@mui/lab/LoadingButton';
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+import ButtonGroup from '@mui/material/ButtonGroup';
+import Checkbox from '@mui/material/Checkbox';
+import Container from '@mui/material/Container';
+import FormControl from '@mui/material/FormControl';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import FormGroup from '@mui/material/FormGroup';
+import FormHelperText from '@mui/material/FormHelperText';
+import Grid from '@mui/material/Grid';
+import IconButton from '@mui/material/IconButton';
+import InputAdornment from '@mui/material/InputAdornment';
+import MenuItem from '@mui/material/MenuItem';
+import TextField from '@mui/material/TextField';
+import Typography from '@mui/material/Typography';
+import { OBJECTCLASS } from '@pandino/pandino-api';
+import { useTrackService } from '@pandino/react-hooks';
+import { clsx } from 'clsx';
+import type { Dispatch, FC, SetStateAction } from 'react';
+import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { DropdownButton, MdiIcon, useJudoNavigation } from '~/components';
+import { useConfirmDialog } from '~/components/dialog';
+import { useL10N } from '~/l10n/l10n-context';
+import { isErrorOperationFault, useErrorHandler } from '~/utilities';
+
+import { DateTimePicker } from '@mui/x-date-pickers';
+import type { DateTimeValidationError } from '@mui/x-date-pickers';
+import { NumericInput } from '~/components/widgets';
+import { autoFocusRefDelay } from '~/config';
+import { useConfirmationBeforeChange } from '~/hooks';
+import type { ViewAstronomer, ViewAstronomerStored } from '~/services/data-api/model/ViewAstronomer';
+import type { ViewGalaxy, ViewGalaxyStored } from '~/services/data-api/model/ViewGalaxy';
+import { ViewGalaxyFormAstronomerComponent } from './components/ViewGalaxyFormAstronomerComponent';
+import { VIEW_GALAXY_FORM_CONTAINER_ACTIONS_HOOK_INTERFACE_KEY } from './customization';
+import type { ViewGalaxyFormContainerHook } from './customization';
+import type { ViewGalaxyFormActionDefinitions, ViewGalaxyFormProps } from './types';
+
+// XMIID: God/(esm/_YTwP0E7rEeycO-gUAWxcVg)/TransferObjectFormPageContainer
+// Name: View::Galaxy::Form
+export default function ViewGalaxyForm(props: ViewGalaxyFormProps) {
+ // Container props
+ const {
+ refreshCounter,
+ isLoading,
+ isDraft,
+ dataPath,
+ actions: pageActions,
+ data,
+ isFormUpdateable,
+ isFormDeleteable,
+ storeDiff,
+ editMode,
+ validation,
+ setValidation,
+ submit,
+ } = props;
+
+ // Container hooks
+ const { t } = useTranslation();
+ const { navigate, back } = useJudoNavigation();
+ const { locale: l10nLocale } = useL10N();
+ const { openConfirmDialog } = useConfirmDialog();
+
+ useConfirmationBeforeChange(
+ editMode,
+ t('judo.form.navigation.confirmation', {
+ defaultValue: 'You have potential unsaved changes in your form, are you sure you would like to navigate away?',
+ }),
+ );
+ // Pandino Container Action overrides
+ const { service: customContainerHook } = useTrackService(
+ `(${OBJECTCLASS}=${VIEW_GALAXY_FORM_CONTAINER_ACTIONS_HOOK_INTERFACE_KEY})`,
+ );
+ const containerActions: ViewGalaxyFormActionDefinitions = customContainerHook?.(data, editMode, storeDiff) || {};
+ const actions = useMemo(() => ({ ...pageActions, ...containerActions }), [pageActions, containerActions]);
+ const autoFocusInputRef = useRef(null);
+
+ useEffect(() => {
+ const timeout = setTimeout(() => {
+ if (typeof autoFocusInputRef?.current?.focus === 'function') {
+ autoFocusInputRef.current.focus();
+ }
+ }, autoFocusRefDelay);
+
+ return () => clearTimeout(timeout);
+ }, []);
+
+ return (
+
+
+
+
+
+
+ {
+ const realValue = event.target.value?.length === 0 ? null : event.target.value;
+ storeDiff('name', realValue);
+ }}
+ InputLabelProps={{ shrink: true }}
+ InputProps={{
+ readOnly: false || !isFormUpdateable(),
+ startAdornment: (
+
+
+
+ ),
+ }}
+ inputProps={{
+ maxLength: 255,
+ }}
+ />
+
+
+
+
+
+ (validation.has('real') ? theme.palette.error.main : 'primary'),
+ }}
+ disabled={
+ actions?.isRealDisabled
+ ? actions.isRealDisabled(data, editMode, isLoading)
+ : false || !isFormUpdateable() || isLoading
+ }
+ control={
+ (validation.has('real') ? theme.palette.error.main : 'primary') }}
+ onChange={(event) => {
+ storeDiff('real', event.target.checked);
+ }}
+ />
+ }
+ label={t('View.Galaxy.Form.real', { defaultValue: 'Real' }) as string}
+ />
+
+ {validation.has('real') && {validation.get('real')} }
+
+
+
+
+ {
+ const realValue = event.target.value?.length === 0 ? null : event.target.value;
+ storeDiff('constellation', realValue);
+ }}
+ InputLabelProps={{ shrink: true }}
+ InputProps={{
+ readOnly: false || !isFormUpdateable(),
+ startAdornment: (
+
+
+
+ ),
+ }}
+ inputProps={{
+ maxLength: 255,
+ }}
+ />
+
+
+
+
+
+ (validation.has('nakedEye') ? theme.palette.error.main : 'primary'),
+ }}
+ disabled={
+ actions?.isNakedEyeDisabled
+ ? actions.isNakedEyeDisabled(data, editMode, isLoading)
+ : false || !isFormUpdateable() || isLoading
+ }
+ control={
+ (validation.has('nakedEye') ? theme.palette.error.main : 'primary') }}
+ onChange={(event) => {
+ storeDiff('nakedEye', event.target.checked);
+ }}
+ />
+ }
+ label={t('View.Galaxy.Form.nakedEye', { defaultValue: 'Naked Eye' }) as string}
+ />
+
+ {validation.has('nakedEye') && {validation.get('nakedEye')} }
+
+
+
+
+ {
+ const newValue = values.floatValue === undefined ? null : values.floatValue;
+ if (data.magnitude !== newValue) {
+ storeDiff('magnitude', newValue);
+ }
+ }}
+ InputLabelProps={{ shrink: true }}
+ InputProps={{
+ readOnly: false || !isFormUpdateable(),
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+
+ {
+ const realValue = event.target.value?.length === 0 ? null : event.target.value;
+ storeDiff('originOfName', realValue);
+ }}
+ InputLabelProps={{ shrink: true }}
+ InputProps={{
+ readOnly: false || !isFormUpdateable(),
+ startAdornment: (
+
+
+
+ ),
+ }}
+ inputProps={{
+ maxLength: 255,
+ }}
+ />
+
+
+
+
+
+
+ ),
+ },
+ },
+ actionBar: {
+ actions: ['today', 'accept'],
+ },
+ }}
+ onError={(newError: DateTimeValidationError, value: any) => {
+ // https://mui.com/x/react-date-pickers/validation/#show-the-error
+ setValidation((prevValidation) => {
+ const copy = new Map(prevValidation);
+ copy.set(
+ 'discovered',
+ newError === 'invalidDate'
+ ? (t('judo.error.validation-failed.PATTERN_VALIDATION_FAILED', {
+ defaultValue: 'Value does not match the pattern requirements.',
+ }) as string)
+ : '',
+ );
+ return copy;
+ });
+ }}
+ views={['year', 'month', 'day', 'hours', 'minutes', 'seconds']}
+ label={t('View.Galaxy.Form.discovered', { defaultValue: 'Discovered' }) as string}
+ value={data.discovered ?? null}
+ readOnly={false || !isFormUpdateable()}
+ disabled={
+ actions?.isDiscoveredDisabled ? actions.isDiscoveredDisabled(data, editMode, isLoading) : isLoading
+ }
+ onChange={(newValue: Date | null) => {
+ storeDiff('discovered', newValue);
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/Form/ViewGalaxyFormDialogContainer.tsx.snapshot b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/Form/ViewGalaxyFormDialogContainer.tsx.snapshot
new file mode 100644
index 00000000..faacbd1b
--- /dev/null
+++ b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/Form/ViewGalaxyFormDialogContainer.tsx.snapshot
@@ -0,0 +1,200 @@
+//////////////////////////////////////////////////////////////////////////////
+// G E N E R A T E D S O U R C E
+// --------------------------------
+// Factory expression: #application.pageContainers
+// Path expression: 'src/containers/'+#containerPath(#self)+'/'+#containerComponentName(#self)+'DialogContainer.tsx'
+// Template name: actor/src/containers/dialog.tsx
+// Template file: actor/src/containers/dialog.tsx.hbs
+
+import LoadingButton from '@mui/lab/LoadingButton';
+import Button from '@mui/material/Button';
+import ButtonGroup from '@mui/material/ButtonGroup';
+import ClickAwayListener from '@mui/material/ClickAwayListener';
+import DialogActions from '@mui/material/DialogActions';
+import DialogContent from '@mui/material/DialogContent';
+import DialogTitle from '@mui/material/DialogTitle';
+import Grid from '@mui/material/Grid';
+import Grow from '@mui/material/Grow';
+import IconButton from '@mui/material/IconButton';
+import MenuItem from '@mui/material/MenuItem';
+import MenuList from '@mui/material/MenuList';
+import Paper from '@mui/material/Paper';
+import Popper from '@mui/material/Popper';
+import { Suspense, lazy, useCallback, useRef, useState } from 'react';
+import type { Dispatch, SetStateAction } from 'react';
+import { useTranslation } from 'react-i18next';
+import { MdiIcon, useJudoNavigation } from '~/components';
+import { useConfirmDialog } from '~/components/dialog';
+import type { ViewGalaxy, ViewGalaxyStored } from '~/services/data-api/model/ViewGalaxy';
+import type { ViewGalaxyQueryCustomizer } from '~/services/data-api/rest/ViewGalaxyQueryCustomizer';
+import { processQueryCustomizer } from '~/utilities';
+import { getValue } from '~/utilities/helper';
+
+import type { ViewGalaxyFormActionDefinitions, ViewGalaxyFormDialogActions, ViewGalaxyFormDialogProps } from './types';
+
+const ViewGalaxyForm = lazy(() => import('~/containers/View/Galaxy/Form/ViewGalaxyForm'));
+
+// Name: View::Galaxy::Form
+export default function ViewGalaxyFormDialog(props: ViewGalaxyFormDialogProps) {
+ const { openConfirmDialog } = useConfirmDialog();
+
+ const { t } = useTranslation();
+ const { navigate, back } = useJudoNavigation();
+ const [isCreateDropdownOpen, setIsCreateDropdownOpen] = useState(false);
+ const createDropdownRef = useRef(null);
+ const {
+ ownerData,
+ onClose,
+ actions,
+ dataPath,
+ isLoading,
+ editMode,
+ refreshCounter,
+ data,
+ isFormUpdateable,
+ isFormDeleteable,
+ storeDiff,
+ validation,
+ setValidation,
+ submit,
+ isDraft,
+ } = props;
+
+ const handleCreateDropdownToggle = useCallback(() => {
+ setIsCreateDropdownOpen((prevOpen) => !prevOpen);
+ }, [isCreateDropdownOpen]);
+ const handleCreateDropdownClose = useCallback(() => {
+ setIsCreateDropdownOpen(false);
+ }, [isCreateDropdownOpen]);
+
+ return (
+ <>
+
+ {actions.getPageTitle ? actions.getPageTitle(data) : ''}
+ theme.palette.grey[500],
+ }}
+ >
+
+
+
+
+
+
+
+
+
+ {editMode && actions.backAction && (
+
+ }
+ onClick={async () => {
+ await actions.backAction!();
+ }}
+ >
+ {t('judo.action.back', { defaultValue: 'Back' })}
+
+
+ )}
+ {editMode && actions.createAction && (
+
+
+ : }
+ onClick={async () => {
+ await actions.createAction!();
+ }}
+ >
+
+ {isDraft
+ ? t('judo.dialogs.draft.submit', { defaultValue: 'Ok' })
+ : t('judo.action.create', { defaultValue: 'Create' })}
+
+
+ {!isDraft && (
+
+
+
+ )}
+
+
+ {({ TransitionProps, placement }) => (
+
+
+
+
+
+
+
+ )}
+
+
+ )}
+
+ >
+ );
+}
diff --git a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/View/ViewGalaxyView.tsx.snapshot b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/View/ViewGalaxyView.tsx.snapshot
new file mode 100644
index 00000000..df5aa4ad
--- /dev/null
+++ b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/View/ViewGalaxyView.tsx.snapshot
@@ -0,0 +1,826 @@
+//////////////////////////////////////////////////////////////////////////////
+// G E N E R A T E D S O U R C E
+// --------------------------------
+// Factory expression: #application.pageContainers
+// Path expression: 'src/containers/'+#containerPath(#self)+'/'+#containerComponentName(#self)+'.tsx'
+// Template name: actor/src/containers/container.tsx
+// Template file: actor/src/containers/container.tsx.hbs
+
+import LoadingButton from '@mui/lab/LoadingButton';
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+import ButtonGroup from '@mui/material/ButtonGroup';
+import Card from '@mui/material/Card';
+import CardContent from '@mui/material/CardContent';
+import CardHeader from '@mui/material/CardHeader';
+import Checkbox from '@mui/material/Checkbox';
+import Container from '@mui/material/Container';
+import FormControl from '@mui/material/FormControl';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import FormGroup from '@mui/material/FormGroup';
+import FormHelperText from '@mui/material/FormHelperText';
+import Grid from '@mui/material/Grid';
+import IconButton from '@mui/material/IconButton';
+import InputAdornment from '@mui/material/InputAdornment';
+import MenuItem from '@mui/material/MenuItem';
+import TextField from '@mui/material/TextField';
+import Typography from '@mui/material/Typography';
+import { OBJECTCLASS } from '@pandino/pandino-api';
+import { ComponentProxy } from '@pandino/react-hooks';
+import { useTrackService } from '@pandino/react-hooks';
+import { clsx } from 'clsx';
+import type { Dispatch, FC, SetStateAction } from 'react';
+import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { DropdownButton, MdiIcon, useJudoNavigation } from '~/components';
+import { useConfirmDialog } from '~/components/dialog';
+import { CUSTOM_VISUAL_ELEMENT_INTERFACE_KEY } from '~/custom';
+import type { CustomFormVisualElementProps } from '~/custom';
+import { useL10N } from '~/l10n/l10n-context';
+import { isErrorOperationFault, useErrorHandler } from '~/utilities';
+
+import { DateTimePicker } from '@mui/x-date-pickers';
+import type { DateTimeValidationError } from '@mui/x-date-pickers';
+import { AssociationButton, NumericInput } from '~/components/widgets';
+import { useConfirmationBeforeChange } from '~/hooks';
+import type { ViewAstronomer, ViewAstronomerStored } from '~/services/data-api/model/ViewAstronomer';
+import type { ViewGalaxy, ViewGalaxyStored } from '~/services/data-api/model/ViewGalaxy';
+import type { ViewMatter, ViewMatterStored } from '~/services/data-api/model/ViewMatter';
+import type { ViewStar, ViewStarStored } from '~/services/data-api/model/ViewStar';
+import { ViewGalaxyViewAstronomerComponent } from './components/ViewGalaxyViewAstronomerComponent';
+import { ViewGalaxyViewStarsComponent } from './components/ViewGalaxyViewStarsComponent';
+import {
+ VIEW_GALAXY_VIEW_CONTAINER_ACTIONS_HOOK_INTERFACE_KEY,
+ VIEW_GALAXY_VIEW_DISCOVERER_COMPONENT,
+} from './customization';
+import type { ViewGalaxyViewContainerHook } from './customization';
+import type { ViewGalaxyViewActionDefinitions, ViewGalaxyViewProps } from './types';
+
+// XMIID: God/(esm/_YTr-YE7rEeycO-gUAWxcVg)/TransferObjectViewPageContainer
+// Name: View::Galaxy::View
+export default function ViewGalaxyView(props: ViewGalaxyViewProps) {
+ // Container props
+ const {
+ refreshCounter,
+ isLoading,
+ isDraft,
+ dataPath,
+ actions: pageActions,
+ data,
+ isFormUpdateable,
+ isFormDeleteable,
+ storeDiff,
+ editMode,
+ validation,
+ setValidation,
+ submit,
+ } = props;
+
+ // Container hooks
+ const { t } = useTranslation();
+ const { navigate, back } = useJudoNavigation();
+ const { locale: l10nLocale } = useL10N();
+ const { openConfirmDialog } = useConfirmDialog();
+
+ useConfirmationBeforeChange(
+ editMode,
+ t('judo.form.navigation.confirmation', {
+ defaultValue: 'You have potential unsaved changes in your form, are you sure you would like to navigate away?',
+ }),
+ );
+ // Pandino Container Action overrides
+ const { service: customContainerHook } = useTrackService(
+ `(${OBJECTCLASS}=${VIEW_GALAXY_VIEW_CONTAINER_ACTIONS_HOOK_INTERFACE_KEY})`,
+ );
+ const containerActions: ViewGalaxyViewActionDefinitions = customContainerHook?.(data, editMode, storeDiff) || {};
+ const actions = useMemo(() => ({ ...pageActions, ...containerActions }), [pageActions, containerActions]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {t('View.Galaxy.View.group', { defaultValue: 'Stars' })}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {t('View.Galaxy.View.group_2', { defaultValue: 'About' })}
+
+
+
+
+
+
+
+
+
+
+ {
+ const realValue =
+ event.target.value?.length === 0 ? null : event.target.value;
+ storeDiff('name', realValue);
+ }}
+ InputLabelProps={{ shrink: true }}
+ InputProps={{
+ readOnly: false || !isFormUpdateable(),
+ startAdornment: (
+
+
+
+ ),
+ }}
+ inputProps={{
+ maxLength: 255,
+ }}
+ />
+
+
+
+
+
+
+ validation.has('real') ? theme.palette.error.main : 'primary',
+ }}
+ disabled={
+ actions?.isRealDisabled
+ ? actions.isRealDisabled(data, editMode, isLoading)
+ : false || !isFormUpdateable() || isLoading
+ }
+ control={
+
+ validation.has('real') ? theme.palette.error.main : 'primary',
+ }}
+ onChange={(event) => {
+ storeDiff('real', event.target.checked);
+ }}
+ />
+ }
+ label={t('View.Galaxy.View.real', { defaultValue: 'Real' }) as string}
+ />
+
+ {validation.has('real') && (
+ {validation.get('real')}
+ )}
+
+
+
+
+
+
+
+
+ {
+ const realValue = event.target.value?.length === 0 ? null : event.target.value;
+ storeDiff('constellation', realValue);
+ }}
+ InputLabelProps={{ shrink: true }}
+ InputProps={{
+ readOnly: false || !isFormUpdateable(),
+ startAdornment: (
+
+
+
+ ),
+ }}
+ inputProps={{
+ maxLength: 255,
+ }}
+ />
+
+
+
+ {
+ const newValue = values.floatValue === undefined ? null : values.floatValue;
+ if (data.magnitude !== newValue) {
+ storeDiff('magnitude', newValue);
+ }
+ }}
+ InputLabelProps={{ shrink: true }}
+ InputProps={{
+ readOnly: false || !isFormUpdateable(),
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+
+
+
+
+ validation.has('nakedEye') ? theme.palette.error.main : 'primary',
+ }}
+ disabled={
+ actions?.isNakedEyeDisabled
+ ? actions.isNakedEyeDisabled(data, editMode, isLoading)
+ : false || !isFormUpdateable() || isLoading
+ }
+ control={
+
+ validation.has('nakedEye') ? theme.palette.error.main : 'primary',
+ }}
+ onChange={(event) => {
+ storeDiff('nakedEye', event.target.checked);
+ }}
+ />
+ }
+ label={t('View.Galaxy.View.nakedEye', { defaultValue: 'Naked Eye' }) as string}
+ />
+
+ {validation.has('nakedEye') && (
+ {validation.get('nakedEye')}
+ )}
+
+
+
+
+ {
+ const realValue = event.target.value?.length === 0 ? null : event.target.value;
+ storeDiff('originOfName', realValue);
+ }}
+ InputLabelProps={{ shrink: true }}
+ InputProps={{
+ readOnly: false || !isFormUpdateable(),
+ startAdornment: (
+
+
+
+ ),
+ }}
+ inputProps={{
+ maxLength: 255,
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {t('View.Galaxy.View.Discoverer', { defaultValue: 'Discoverer' })}
+
+
+
+
+
+
+
+
+
+ ),
+ },
+ },
+ actionBar: {
+ actions: ['today', 'accept'],
+ },
+ }}
+ onError={(newError: DateTimeValidationError, value: any) => {
+ // https://mui.com/x/react-date-pickers/validation/#show-the-error
+ setValidation((prevValidation) => {
+ const copy = new Map(prevValidation);
+ copy.set(
+ 'discovered',
+ newError === 'invalidDate'
+ ? (t('judo.error.validation-failed.PATTERN_VALIDATION_FAILED', {
+ defaultValue: 'Value does not match the pattern requirements.',
+ }) as string)
+ : '',
+ );
+ return copy;
+ });
+ }}
+ views={['year', 'month', 'day', 'hours', 'minutes', 'seconds']}
+ label={t('View.Galaxy.View.discovered', { defaultValue: 'Discovered' }) as string}
+ value={data.discovered ?? null}
+ readOnly={false || !isFormUpdateable()}
+ disabled={
+ actions?.isDiscoveredDisabled
+ ? actions.isDiscoveredDisabled(data, editMode, isLoading)
+ : isLoading
+ }
+ onChange={(newValue: Date | null) => {
+ storeDiff('discovered', newValue);
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {t('View.Galaxy.View.group_3', { defaultValue: 'Matter' })}
+
+
+
+
+
+
+
+ {t('judo.action.open-page', { defaultValue: 'Matter' })}
+
+
+
+
+
+ {
+ const newValue = values.floatValue === undefined ? null : values.floatValue;
+ if (data.darkMatter !== newValue) {
+ storeDiff('darkMatter', newValue);
+ }
+ }}
+ InputLabelProps={{ shrink: true }}
+ InputProps={{
+ readOnly: true || !isFormUpdateable(),
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+
+ {
+ const newValue = values.floatValue === undefined ? null : values.floatValue;
+ if (data.intergalacticDust !== newValue) {
+ storeDiff('intergalacticDust', newValue);
+ }
+ }}
+ InputLabelProps={{ shrink: true }}
+ InputProps={{
+ readOnly: true || !isFormUpdateable(),
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+
+ {
+ const newValue = values.floatValue === undefined ? null : values.floatValue;
+ if (data.interstellarMedium !== newValue) {
+ storeDiff('interstellarMedium', newValue);
+ }
+ }}
+ InputLabelProps={{ shrink: true }}
+ InputProps={{
+ readOnly: true || !isFormUpdateable(),
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+
+
+ }
+ menuItems={[
+ {
+ id: 'God/(esm/_5NwrQFyrEeylCdga_wJIBQ)/OperationFormVisualElement',
+ label: t('View.Galaxy.View.createDarkMatter', {
+ defaultValue: 'Create Dark Matter',
+ }) as string,
+ onClick: actions.createDarkMatterAction
+ ? async () => {
+ await actions.createDarkMatterAction!();
+ }
+ : undefined,
+ startIcon: ,
+ disabled: isLoading || editMode,
+ },
+ {
+ id: 'God/(esm/_5Nx5YFyrEeylCdga_wJIBQ)/OperationFormVisualElement',
+ label: t('View.Galaxy.View.createIntergalacticDust', {
+ defaultValue: 'Create Intergalactic Dust',
+ }) as string,
+ onClick: actions.createIntergalacticDustAction
+ ? async () => {
+ await actions.createIntergalacticDustAction!();
+ }
+ : undefined,
+ startIcon: ,
+ disabled: isLoading || editMode,
+ },
+ {
+ id: 'God/(esm/_5Nx5YVyrEeylCdga_wJIBQ)/OperationFormVisualElement',
+ label: t('View.Galaxy.View.createInterstellarMedium', {
+ defaultValue: 'Create Interstellar Medium',
+ }) as string,
+ onClick: actions.createInterstellarMediumAction
+ ? async () => {
+ await actions.createInterstellarMediumAction!();
+ }
+ : undefined,
+ startIcon: ,
+ disabled: isLoading || editMode,
+ },
+ ]}
+ >
+ {t('View.Galaxy.View.actionGroup', { defaultValue: 'Create Matter' })}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/View/ViewGalaxyViewDialogContainer.tsx.snapshot b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/View/ViewGalaxyViewDialogContainer.tsx.snapshot
new file mode 100644
index 00000000..66e86cdb
--- /dev/null
+++ b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/View/ViewGalaxyViewDialogContainer.tsx.snapshot
@@ -0,0 +1,187 @@
+//////////////////////////////////////////////////////////////////////////////
+// G E N E R A T E D S O U R C E
+// --------------------------------
+// Factory expression: #application.pageContainers
+// Path expression: 'src/containers/'+#containerPath(#self)+'/'+#containerComponentName(#self)+'DialogContainer.tsx'
+// Template name: actor/src/containers/dialog.tsx
+// Template file: actor/src/containers/dialog.tsx.hbs
+
+import LoadingButton from '@mui/lab/LoadingButton';
+import Button from '@mui/material/Button';
+import DialogActions from '@mui/material/DialogActions';
+import DialogContent from '@mui/material/DialogContent';
+import DialogTitle from '@mui/material/DialogTitle';
+import Grid from '@mui/material/Grid';
+import IconButton from '@mui/material/IconButton';
+import { Suspense, lazy } from 'react';
+import type { Dispatch, SetStateAction } from 'react';
+import { useTranslation } from 'react-i18next';
+import { MdiIcon, useJudoNavigation } from '~/components';
+import { useConfirmDialog } from '~/components/dialog';
+import type { ViewGalaxy, ViewGalaxyStored } from '~/services/data-api/model/ViewGalaxy';
+import type { ViewGalaxyQueryCustomizer } from '~/services/data-api/rest/ViewGalaxyQueryCustomizer';
+import { processQueryCustomizer } from '~/utilities';
+import { getValue } from '~/utilities/helper';
+
+import type { ViewGalaxyViewActionDefinitions, ViewGalaxyViewDialogActions, ViewGalaxyViewDialogProps } from './types';
+
+const ViewGalaxyView = lazy(() => import('~/containers/View/Galaxy/View/ViewGalaxyView'));
+
+// Name: View::Galaxy::View
+export default function ViewGalaxyViewDialog(props: ViewGalaxyViewDialogProps) {
+ const { openConfirmDialog } = useConfirmDialog();
+
+ const { t } = useTranslation();
+ const { navigate, back } = useJudoNavigation();
+ const {
+ ownerData,
+ onClose,
+ actions,
+ dataPath,
+ isLoading,
+ editMode,
+ refreshCounter,
+ data,
+ isFormUpdateable,
+ isFormDeleteable,
+ storeDiff,
+ validation,
+ setValidation,
+ submit,
+ isDraft,
+ } = props;
+ const queryCustomizer: ViewGalaxyQueryCustomizer = {
+ _mask: actions.getMask?.(),
+ };
+
+ return (
+ <>
+
+ {actions.getPageTitle ? actions.getPageTitle(data) : ''}
+ theme.palette.grey[500],
+ }}
+ >
+
+
+
+
+
+
+
+
+
+ {!editMode && actions.backAction && (
+
+ }
+ onClick={async () => {
+ await actions.backAction!();
+ }}
+ >
+ {t('judo.action.back', { defaultValue: 'Back' })}
+
+
+ )}
+ {!isDraft && actions.refreshAction && (
+
+ }
+ onClick={async () => {
+ await actions.refreshAction!(processQueryCustomizer(queryCustomizer));
+ }}
+ >
+ {t('judo.action.refresh', { defaultValue: 'Refresh' })}
+
+
+ )}
+ {!editMode && (isFormDeleteable() || isDraft) && actions.deleteAction && (
+
+ }
+ onClick={async () => {
+ actions.deleteAction!();
+ }}
+ >
+ {t('judo.action.delete', { defaultValue: 'Delete' })}
+
+
+ )}
+ {editMode && actions.cancelAction && (
+
+ }
+ onClick={async () => {
+ await actions.cancelAction!();
+ }}
+ >
+ {t('judo.action.cancel', { defaultValue: 'Cancel' })}
+
+
+ )}
+ {editMode && actions.updateAction && (
+
+ }
+ onClick={async () => {
+ await actions.updateAction!();
+ }}
+ >
+
+ {isDraft
+ ? t('judo.dialogs.draft.submit', { defaultValue: 'Ok' })
+ : t('judo.action.update', { defaultValue: 'Save' })}
+
+
+
+ )}
+
+ >
+ );
+}
diff --git a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/View/ViewGalaxyViewPageContainer.tsx.snapshot b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/View/ViewGalaxyViewPageContainer.tsx.snapshot
new file mode 100644
index 00000000..93139649
--- /dev/null
+++ b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/View/ViewGalaxyViewPageContainer.tsx.snapshot
@@ -0,0 +1,163 @@
+//////////////////////////////////////////////////////////////////////////////
+// G E N E R A T E D S O U R C E
+// --------------------------------
+// Factory expression: #getPageContainersToGenerate(#application)
+// Path expression: 'src/containers/'+#containerPath(#self)+'/'+#containerComponentName(#self)+'PageContainer.tsx'
+// Template name: actor/src/containers/page.tsx
+// Template file: actor/src/containers/page.tsx.hbs
+
+import LoadingButton from '@mui/lab/LoadingButton';
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+import Grid from '@mui/material/Grid';
+import { Suspense, lazy } from 'react';
+import type { Dispatch, SetStateAction } from 'react';
+import { useTranslation } from 'react-i18next';
+import { MdiIcon, PageHeader, useJudoNavigation } from '~/components';
+import { useConfirmDialog } from '~/components/dialog';
+import type { ViewGalaxy, ViewGalaxyStored } from '~/services/data-api/model/ViewGalaxy';
+import type { ViewGalaxyQueryCustomizer } from '~/services/data-api/rest/ViewGalaxyQueryCustomizer';
+import { mainContainerPadding } from '~/theme';
+import { processQueryCustomizer } from '~/utilities';
+import { getValue } from '~/utilities/helper';
+
+import type { ViewGalaxyViewActionDefinitions, ViewGalaxyViewPageActions, ViewGalaxyViewPageProps } from './types';
+
+const ViewGalaxyView = lazy(() => import('~/containers/View/Galaxy/View/ViewGalaxyView'));
+
+// Name: View::Galaxy::View
+export default function ViewGalaxyViewPage(props: ViewGalaxyViewPageProps) {
+ const { openConfirmDialog } = useConfirmDialog();
+
+ const { t } = useTranslation();
+ const { navigate, back } = useJudoNavigation();
+ const {
+ actions,
+ dataPath,
+ isLoading,
+ editMode,
+ refreshCounter,
+ data,
+ isFormUpdateable,
+ isFormDeleteable,
+ storeDiff,
+ validation,
+ setValidation,
+ submit,
+ } = props;
+ const isDraft = false; // currently no page can be opened as draft, but we need this variable anyway
+ const queryCustomizer: ViewGalaxyQueryCustomizer = {
+ _mask: actions.getMask?.(),
+ };
+
+ return (
+ <>
+
+ {!editMode && actions.backAction && (
+
+ }
+ disabled={isLoading || editMode}
+ onClick={async () => {
+ await actions.backAction!();
+ }}
+ >
+ {t('judo.action.back', { defaultValue: 'Back' })}
+
+
+ )}
+ {!isDraft && actions.refreshAction && (
+
+ }
+ disabled={isLoading || editMode}
+ onClick={async () => {
+ await actions.refreshAction!(processQueryCustomizer(queryCustomizer));
+ }}
+ >
+ {t('judo.action.refresh', { defaultValue: 'Refresh' })}
+
+
+ )}
+ {!editMode && (isFormDeleteable() || isDraft) && actions.deleteAction && (
+
+ }
+ disabled={isLoading || editMode}
+ onClick={async () => {
+ actions.deleteAction!();
+ }}
+ >
+ {t('judo.action.delete', { defaultValue: 'Delete' })}
+
+
+ )}
+ {editMode && actions.cancelAction && (
+
+ }
+ disabled={isLoading}
+ onClick={async () => {
+ await actions.cancelAction!();
+ }}
+ >
+ {t('judo.action.cancel', { defaultValue: 'Cancel' })}
+
+
+ )}
+ {editMode && actions.updateAction && (
+
+ }
+ disabled={isLoading}
+ onClick={async () => {
+ await actions.updateAction!();
+ }}
+ >
+ {t('judo.action.update', { defaultValue: 'Save' })}
+
+
+ )}
+ {/* Placeholder */}
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/pages/God/God/Earth/AccessViewPage/index.tsx.snapshot b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/pages/God/God/Earth/AccessViewPage/index.tsx.snapshot
new file mode 100644
index 00000000..60899327
--- /dev/null
+++ b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/pages/God/God/Earth/AccessViewPage/index.tsx.snapshot
@@ -0,0 +1,658 @@
+//////////////////////////////////////////////////////////////////////////////
+// G E N E R A T E D S O U R C E
+// --------------------------------
+// Factory expression: #getPagesForRouting(#application)
+// Path expression: 'src/pages/'+#pagePath(#self)+'/index.tsx'
+// Template name: actor/src/pages/index.tsx
+// Template file: actor/src/pages/index.tsx.hbs
+
+import type { GridFilterModel } from '@mui/x-data-grid';
+import { OBJECTCLASS } from '@pandino/pandino-api';
+import { useTrackService } from '@pandino/react-hooks';
+import { Suspense, lazy, useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import type { Dispatch, FC, ReactNode, SetStateAction } from 'react';
+import { useTranslation } from 'react-i18next';
+import { v4 as uuidv4 } from 'uuid';
+import { useJudoNavigation } from '~/components';
+import type { Filter, FilterOption } from '~/components-api';
+import { useConfirmDialog, useFilterDialog } from '~/components/dialog';
+import type { ViewPlanetViewPageActions, ViewPlanetViewPageProps } from '~/containers/View/Planet/View/types';
+import { useViewPlanetViewCreateChaosForInstanceInputForm } from '~/dialogs/View/Planet/View/CreateChaosForInstance/Input/Form/hooks';
+import { useViewPlanetViewCreateCreatureInputForm } from '~/dialogs/View/Planet/View/CreateCreature/Input/Form/hooks';
+import { useViewPlanetViewCreateStaticChaosInputForm } from '~/dialogs/View/Planet/View/CreateStaticChaos/Input/Form/hooks';
+import { useViewPlanetViewGroupActionGroupSendASignRelationTableCallSelector } from '~/dialogs/View/Planet/View/Group/ActionGroup/SendASign/Relation/Table/CallSelector/hooks';
+import { useViewPlanetViewGroup_2RightCreaturesPunishCreatureRelationTableCallSelector } from '~/dialogs/View/Planet/View/Group_2/Right/Creatures/PunishCreature/Relation/Table/CallSelector/hooks';
+import { useViewPlanetViewGroup_2RightCreaturesRewardCreatureRelationTableCallSelector } from '~/dialogs/View/Planet/View/Group_2/Right/Creatures/RewardCreature/Relation/Table/CallSelector/hooks';
+import { useViewPlanetViewGroup_2RightCreaturesTableAddSelectorPage } from '~/dialogs/View/Planet/View/Group_2/Right/Creatures/TableAddSelectorPage/hooks';
+import { useViewPlanetViewGroup_2RightCreaturesTalkToGodRelationTableCallSelector } from '~/dialogs/View/Planet/View/Group_2/Right/Creatures/TalkToGod/Relation/Table/CallSelector/hooks';
+import { useCRUDDialog, useSnacks, useViewData } from '~/hooks';
+import { routeToViewPlanetViewChooseTheMessiahOutputView } from '~/routes';
+import { routeToViewPlanetViewHateGodOutputView } from '~/routes';
+import { routeToViewPlanetViewLoveGodOutputView } from '~/routes';
+import { routeToViewPlanetCreaturesRelationViewPage } from '~/routes';
+import type { JudoIdentifiable } from '~/services/data-api/common/JudoIdentifiable';
+import { draftIdentifierPrefix } from '~/services/data-api/common/utils';
+import type { ViewCreature, ViewCreatureStored } from '~/services/data-api/model/ViewCreature';
+import type { ViewMessage, ViewMessageStored } from '~/services/data-api/model/ViewMessage';
+import type { ViewPlanet, ViewPlanetStored } from '~/services/data-api/model/ViewPlanet';
+import type { ViewCreatureQueryCustomizer } from '~/services/data-api/rest/ViewCreatureQueryCustomizer';
+import type { ViewMessageQueryCustomizer } from '~/services/data-api/rest/ViewMessageQueryCustomizer';
+import type { ViewPlanetQueryCustomizer } from '~/services/data-api/rest/ViewPlanetQueryCustomizer';
+import type { JudoRestResponse } from '~/services/data-api/rest/requestResponse';
+import { GodServiceForEarthImpl } from '~/services/data-axios/GodServiceForEarthImpl';
+import { judoAxiosProvider } from '~/services/data-axios/JudoAxiosProvider';
+import { PageContainerTransition } from '~/theme/animations';
+import { getValue, processQueryCustomizer, setValue, simpleCloneDeep, useErrorHandler } from '~/utilities';
+import type { DialogResult } from '~/utilities';
+import { type ViewPlanetViewViewModel, ViewPlanetViewViewModelContext } from './context';
+import type { ViewPlanetViewActionsHook } from './customization';
+import { GOD_GOD_EARTH_ACCESS_VIEW_PAGE_ACTIONS_HOOK_INTERFACE_KEY } from './customization';
+import type { ViewPlanetViewPageActionsExtended } from './types';
+
+const ViewPlanetViewPageContainer = lazy(() => import('~/containers/View/Planet/View/ViewPlanetViewPageContainer'));
+
+// XMIID: God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewPageDefinition
+// Name: God::God::earth::AccessViewPage
+export default function GodGodEarthAccessViewPage() {
+ const dataPath = '';
+ const isDraft = false;
+ const owner = useRef(null);
+
+ // Services
+ const godServiceForEarthImpl = useMemo(() => new GodServiceForEarthImpl(judoAxiosProvider), []);
+
+ // Hooks section
+ const { t } = useTranslation();
+ const { showSuccessSnack, showErrorSnack } = useSnacks();
+ const { navigate, back: navigateBack } = useJudoNavigation();
+ const { openFilterDialog } = useFilterDialog();
+ const { openConfirmDialog } = useConfirmDialog();
+ const { setLatestViewData, setRouterPageData } = useViewData();
+ const handleError = useErrorHandler();
+ const openCRUDDialog = useCRUDDialog();
+
+ // State section
+ const [isLoading, setIsLoading] = useState(false);
+ const [editMode, setEditMode] = useState(false);
+ const [refreshCounter, setRefreshCounter] = useState(0);
+ const [data, setData] = useState({} as ViewPlanetStored);
+ const [validation, setValidation] = useState>(new Map());
+
+ // Ref section
+ const singletonHost = useRef<{ __signedIdentifier: string }>({} as unknown as { __signedIdentifier: string });
+
+ // Callback section
+ const storeDiff: (attributeName: keyof ViewPlanet, value: any) => void = useCallback(
+ (attributeName: keyof ViewPlanet, value: any) => {
+ setData((prevData) => ({
+ ...prevData,
+ [attributeName]: value,
+ }));
+ if (!editMode) {
+ setEditMode(true);
+ }
+ },
+ [data, editMode],
+ );
+ const isFormUpdateable = useCallback(() => {
+ return true && typeof data?.__updateable === 'boolean' && data?.__updateable;
+ }, [data]);
+ const isFormDeleteable = useCallback(() => {
+ return true && typeof data?.__deleteable === 'boolean' && data?.__deleteable;
+ }, [data]);
+
+ const getPageQueryCustomizer: () => ViewPlanetQueryCustomizer = () => ({
+ _mask: actions.getMask
+ ? actions.getMask!()
+ : '{habitable,inhabited,name,peaceful,creatures{id,name,punished,rewarded}}',
+ });
+
+ // Masks
+ const getMask = () => '{habitable,inhabited,name,peaceful,creatures{id,name,punished,rewarded}}';
+ const getCreaturesMask = () => '{id,name,punished,rewarded}';
+
+ // Private actions
+ const submit = async () => {
+ await updateAction();
+ await createLifeForPlanetAction();
+ };
+ const refresh = async (forced = false) => {
+ if (!editMode || forced) {
+ if (actions.refreshAction) {
+ await actions.refreshAction!(processQueryCustomizer(getPageQueryCustomizer()));
+ }
+ }
+ };
+ const produceDataAdjustedOwner = useCallback(() => {
+ const copy = simpleCloneDeep(owner.current);
+ setValue(copy, dataPath, simpleCloneDeep(data));
+ return copy;
+ }, [data, owner]);
+
+ // Validation
+ const validate: (target: any) => Promise = useCallback(
+ async (target) => {
+ await godServiceForEarthImpl.validateUpdate(target);
+ },
+ [data, godServiceForEarthImpl],
+ );
+
+ // Pandino Action overrides
+ const { service: customActionsHook } = useTrackService(
+ `(${OBJECTCLASS}=${GOD_GOD_EARTH_ACCESS_VIEW_PAGE_ACTIONS_HOOK_INTERFACE_KEY})`,
+ );
+ const customActions: ViewPlanetViewPageActionsExtended | undefined = customActionsHook?.(
+ data,
+ editMode,
+ storeDiff,
+ refresh,
+ submit,
+ );
+
+ // Dialog hooks
+ const openViewPlanetViewCreateChaosForInstanceInputForm = useViewPlanetViewCreateChaosForInstanceInputForm();
+ const openViewPlanetViewCreateCreatureInputForm = useViewPlanetViewCreateCreatureInputForm();
+ const openViewPlanetViewCreateStaticChaosInputForm = useViewPlanetViewCreateStaticChaosInputForm();
+ const openViewPlanetViewGroupActionGroupSendASignRelationTableCallSelector =
+ useViewPlanetViewGroupActionGroupSendASignRelationTableCallSelector();
+ const openViewPlanetViewGroup_2RightCreaturesTableAddSelectorPage =
+ useViewPlanetViewGroup_2RightCreaturesTableAddSelectorPage();
+ const openViewPlanetViewGroup_2RightCreaturesPunishCreatureRelationTableCallSelector =
+ useViewPlanetViewGroup_2RightCreaturesPunishCreatureRelationTableCallSelector();
+ const openViewPlanetViewGroup_2RightCreaturesRewardCreatureRelationTableCallSelector =
+ useViewPlanetViewGroup_2RightCreaturesRewardCreatureRelationTableCallSelector();
+ const openViewPlanetViewGroup_2RightCreaturesTalkToGodRelationTableCallSelector =
+ useViewPlanetViewGroup_2RightCreaturesTalkToGodRelationTableCallSelector();
+
+ // Action section
+ const getPageTitle = (data: ViewPlanet): string => {
+ return data.name as string;
+ };
+ // BackAction: God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewBackAction
+ const backAction = async () => {
+ navigateBack();
+ };
+ // CancelAction: God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewCancelAction
+ const cancelAction = async () => {
+ setValidation(new Map());
+ // no need to set editMode to false, given refresh should do it implicitly
+ await refresh(true);
+ };
+ // DeleteAction: God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewDeleteAction
+ const deleteAction = async () => {
+ try {
+ const confirmed = await openConfirmDialog(
+ 'delete-action',
+ t('judo.modal.confirm.confirm-delete', {
+ defaultValue: 'Are you sure you would like to delete the selected element?',
+ }),
+ t('judo.modal.confirm.confirm-title', { defaultValue: 'Confirm action' }),
+ );
+ if (confirmed) {
+ await godServiceForEarthImpl.delete(data);
+ showSuccessSnack(t('judo.action.delete.success', { defaultValue: 'Delete successful' }));
+ navigateBack();
+ }
+ } catch (error) {
+ handleError(error, undefined, data);
+ }
+ };
+ // RefreshAction: God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewRefreshAction
+ const refreshAction = async (
+ queryCustomizer: ViewPlanetQueryCustomizer,
+ ): Promise> => {
+ try {
+ setIsLoading(true);
+ setEditMode(false);
+ const response = await godServiceForEarthImpl.refresh(owner.current, getPageQueryCustomizer());
+ const { data: result } = response;
+ if (!isDraft) {
+ owner.current = result;
+ }
+ setData(result);
+ setLatestViewData(result);
+ setRouterPageData(result);
+ if (customActions?.postRefreshAction) {
+ await customActions?.postRefreshAction(result, storeDiff, setValidation);
+ }
+ return response;
+ } catch (error) {
+ handleError(error);
+ setLatestViewData(null);
+ setRouterPageData(null);
+ return Promise.reject(error);
+ } finally {
+ setIsLoading(false);
+ setRefreshCounter((prevCounter) => prevCounter + 1);
+ }
+ };
+ // UpdateAction: God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewUpdateAction
+ const updateAction = async () => {
+ setIsLoading(true);
+ try {
+ const { data: res } = await godServiceForEarthImpl.update(data);
+ if (res) {
+ showSuccessSnack(t('judo.action.save.success', { defaultValue: 'Changes saved' }));
+ setValidation(new Map());
+ setEditMode(false);
+ await actions.refreshAction!(getPageQueryCustomizer());
+ }
+ } catch (error) {
+ handleError(error, { setValidation }, data);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ // ParameterlessCallOperationAction: God/(esm/_bkGTMNAmEe65Ld16OAb1kw)/OperationFormCallAction/(discriminator/God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const bangForPlanetAction = async (target?: ViewPlanetStored) => {
+ try {
+ setIsLoading(true);
+ await godServiceForEarthImpl.bang(data);
+ if (customActions?.postBangForPlanetAction) {
+ await customActions.postBangForPlanetAction(target!);
+ } else {
+ showSuccessSnack(
+ t('judo.action.operation.success', { defaultValue: 'Operation executed successfully' }) as string,
+ );
+ await refresh();
+ }
+ } catch (error) {
+ handleError(error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ // OpenOperationInputFormAction: God/(esm/_sbMMINAmEe65Ld16OAb1kw)/OperationFormCallAction/(discriminator/God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const createChaosForInstanceAction = async (isDraft?: boolean, ownerValidation?: (target: any) => Promise) => {
+ const { result, data: returnedData } = await openViewPlanetViewCreateChaosForInstanceInputForm({
+ ownerData: data,
+ });
+ if (result === 'submit' && !editMode) {
+ await refresh();
+ }
+ };
+ // OpenOperationInputFormAction: God/(esm/_j_0QYNAoEe65Ld16OAb1kw)/OperationFormCallAction/(discriminator/God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const createStaticChaosAction = async (isDraft?: boolean, ownerValidation?: (target: any) => Promise) => {
+ const { result, data: returnedData } = await openViewPlanetViewCreateStaticChaosInputForm({
+ ownerData: data,
+ });
+ if (result === 'submit' && !editMode) {
+ await refresh();
+ }
+ };
+ // ParameterlessCallOperationAction: God/(esm/_a1neIFDkEeyjf_w6-3_EHA)/OperationFormCallAction/(discriminator/God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const endWarForPlanetAction = async (target?: ViewPlanetStored) => {
+ try {
+ setIsLoading(true);
+ await godServiceForEarthImpl.endWar(data);
+ if (customActions?.postEndWarForPlanetAction) {
+ await customActions.postEndWarForPlanetAction(target!);
+ } else {
+ showSuccessSnack(
+ t('judo.action.operation.success', { defaultValue: 'Operation executed successfully' }) as string,
+ );
+ await refresh();
+ }
+ } catch (error) {
+ handleError(error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ // OpenOperationInputSelectorAction: God/(esm/_a1osQVDkEeyjf_w6-3_EHA)/OperationFormCallAction/(discriminator/God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const sendASignAction = async () => {
+ const { result, data: returnedData } = await openViewPlanetViewGroupActionGroupSendASignRelationTableCallSelector({
+ ownerData: data,
+ });
+ if (result === 'submit') {
+ await refresh();
+ }
+ };
+ // ParameterlessCallOperationAction: God/(esm/_a1pTUFDkEeyjf_w6-3_EHA)/OperationFormCallAction/(discriminator/God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const startWarForPlanetAction = async (target?: ViewPlanetStored) => {
+ try {
+ setIsLoading(true);
+ await godServiceForEarthImpl.startWar(data);
+ if (customActions?.postStartWarForPlanetAction) {
+ await customActions.postStartWarForPlanetAction(target!);
+ } else {
+ showSuccessSnack(
+ t('judo.action.operation.success', { defaultValue: 'Operation executed successfully' }) as string,
+ );
+ await refresh();
+ }
+ } catch (error) {
+ handleError(error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ // ParameterlessCallOperationAction: God/(esm/_a1m3EFDkEeyjf_w6-3_EHA)/OperationFormCallAction/(discriminator/God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const createLifeForPlanetAction = async (target?: ViewPlanetStored) => {
+ try {
+ setIsLoading(true);
+ await godServiceForEarthImpl.createLife(data);
+ if (customActions?.postCreateLifeForPlanetAction) {
+ await customActions.postCreateLifeForPlanetAction(target!);
+ } else {
+ showSuccessSnack(
+ t('judo.action.operation.success', { defaultValue: 'Operation executed successfully' }) as string,
+ );
+ await refresh();
+ }
+ } catch (error) {
+ handleError(error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ // ParameterlessCallOperationAction: God/(esm/_a1m3EVDkEeyjf_w6-3_EHA)/OperationFormCallAction/(discriminator/God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const destroyLifeForPlanetAction = async (target?: ViewPlanetStored) => {
+ try {
+ setIsLoading(true);
+ await godServiceForEarthImpl.destroyLife(data);
+ if (customActions?.postDestroyLifeForPlanetAction) {
+ await customActions.postDestroyLifeForPlanetAction(target!);
+ } else {
+ showSuccessSnack(
+ t('judo.action.operation.success', { defaultValue: 'Operation executed successfully' }) as string,
+ );
+ await refresh();
+ }
+ } catch (error) {
+ handleError(error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ // OpenAddSelectorAction: God/(esm/_QKcpkE7vEeycO-gUAWxcVg)/TabularReferenceTableAddSelectorOpenPageAction/(discriminator/God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const creaturesOpenAddSelectorAction = async () => {
+ const { result, data: returnedData } = await openViewPlanetViewGroup_2RightCreaturesTableAddSelectorPage({
+ ownerData: data,
+ alreadySelected: data.creatures ?? [],
+ });
+ if (result === 'submit') {
+ if (Array.isArray(returnedData) && returnedData.length) {
+ storeDiff('creatures', [...(data.creatures || []), ...returnedData]);
+ }
+ }
+ };
+
+ // BulkDeleteAction: God/(esm/_QKcpkE7vEeycO-gUAWxcVg)/TabularReferenceTableBulkDeleteAction/(discriminator/God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const creaturesBulkDeleteAction = async (
+ selectedRows: ViewCreatureStored[],
+ ): Promise>> => {
+ return new Promise((resolve) => {
+ const selectedIds = selectedRows.map((r) => r.__identifier);
+ const newList = (data?.creatures ?? []).filter((c: any) => !selectedIds.includes(c.__identifier));
+ storeDiff('creatures', newList);
+ resolve({
+ result: 'delete',
+ data: [],
+ });
+ });
+ };
+ // BulkRemoveAction: God/(esm/_QKcpkE7vEeycO-gUAWxcVg)/TabularReferenceTableBulkRemoveAction/(discriminator/God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const creaturesBulkRemoveAction = async (
+ selectedRows: ViewCreatureStored[],
+ ): Promise>> => {
+ return new Promise((resolve) => {
+ const selectedIds = selectedRows.map((r) => r.__identifier);
+ const newList = (data?.creatures ?? []).filter((c: any) => !selectedIds.includes(c.__identifier));
+ storeDiff('creatures', newList);
+ resolve({
+ result: 'submit',
+ data: [],
+ });
+ });
+ };
+ // ClearAction: God/(esm/_QKcpkE7vEeycO-gUAWxcVg)/TabularReferenceTableClearAction/(discriminator/God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const creaturesClearAction = async () => {
+ storeDiff('creatures', []);
+ };
+ // FilterAction: God/(esm/_QKcpkE7vEeycO-gUAWxcVg)/TabularReferenceTableFilterAction/(discriminator/God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const creaturesFilterAction = async (
+ id: string,
+ filterOptions: FilterOption[],
+ model?: GridFilterModel,
+ filters?: Filter[],
+ ): Promise<{ model?: GridFilterModel; filters?: Filter[] }> => {
+ const newFilters = await openFilterDialog(id, filterOptions, filters);
+ return {
+ filters: newFilters,
+ };
+ };
+ // RowDeleteAction: God/(esm/_QKcpkE7vEeycO-gUAWxcVg)/TabularReferenceTableRowDeleteAction/(discriminator/God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const creaturesRowDeleteAction = async (target: ViewCreatureStored) => {
+ try {
+ const confirmed = await openConfirmDialog(
+ 'row-delete-action',
+ t('judo.modal.confirm.confirm-delete', {
+ defaultValue: 'Are you sure you would like to delete the selected element?',
+ }),
+ t('judo.modal.confirm.confirm-title', { defaultValue: 'Confirm action' }),
+ );
+ if (confirmed) {
+ const newList = (data?.creatures ?? []).filter((c: any) => c.__identifier !== target.__identifier);
+ storeDiff('creatures', newList);
+ }
+ } catch (error) {
+ handleError(error, undefined, target);
+ }
+ };
+ // RemoveAction: God/(esm/_QKcpkE7vEeycO-gUAWxcVg)/TabularReferenceTableRowRemoveAction/(discriminator/God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const creaturesRemoveAction = async (target: ViewCreatureStored) => {
+ const newList = (data?.creatures ?? []).filter((c: any) => c.__identifier !== target.__identifier);
+ storeDiff('creatures', newList);
+ };
+ // OpenPageAction: God/(esm/_QKcpkE7vEeycO-gUAWxcVg)/TabularReferenceTableRowViewAction/(discriminator/God/(esm/_oaE_YM_fEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const creaturesOpenPageAction = async (target: ViewCreatureStored, isDraftParam?: boolean) => {
+ const itemIndex = (data.creatures || []).findIndex((r) => r.__identifier === target.__identifier)!;
+ // if the `target` is missing we are likely navigating to a relation table page, in which case we need the owner's id
+ navigate(routeToViewPlanetCreaturesRelationViewPage(((target as ViewCreatureStored) || data).__signedIdentifier));
+ };
+ // ParameterlessCallOperationAction: God/(esm/_6l6P0TV7Ee-rMcMaep1uNQ)/OperationFormTableTableCallAction/(discriminator/God/(esm/_QKcpkE7vEeycO-gUAWxcVg)/TabularReferenceField)
+ const creaturesChooseTheMessiahForPlanetAction = async (target?: ViewPlanetStored) => {
+ try {
+ setIsLoading(true);
+ const { data: result } = await godServiceForEarthImpl.chooseTheMessiah(data);
+ if (customActions?.postCreaturesChooseTheMessiahForPlanetAction) {
+ await customActions.postCreaturesChooseTheMessiahForPlanetAction(target!, result);
+ } else {
+ showSuccessSnack(
+ t('judo.action.operation.success', { defaultValue: 'Operation executed successfully' }) as string,
+ );
+
+ navigate(routeToViewPlanetViewChooseTheMessiahOutputView(result.__signedIdentifier));
+ }
+ } catch (error) {
+ handleError(error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ // OpenOperationInputFormAction: God/(esm/_R_BrUTV8Ee-rMcMaep1uNQ)/OperationFormTableTableCallAction/(discriminator/God/(esm/_QKcpkE7vEeycO-gUAWxcVg)/TabularReferenceField)
+ const creaturesCreateCreatureAction = async (
+ target: ViewPlanetStored,
+ isDraft?: boolean,
+ ownerValidation?: (target: any) => Promise,
+ ) => {
+ const { result, data: returnedData } = await openViewPlanetViewCreateCreatureInputForm({
+ ownerData: data,
+ });
+ if (result === 'submit' && !editMode) {
+ await refresh();
+ }
+ };
+ // ParameterlessCallOperationAction: God/(esm/_3_lGwTV7Ee-rMcMaep1uNQ)/OperationFormTableRowCallAction/(discriminator/God/(esm/_QKcpkE7vEeycO-gUAWxcVg)/TabularReferenceField)
+ const creaturesHateGodForCreatureAction = async (target?: ViewCreatureStored) => {
+ try {
+ setIsLoading(true);
+ const { data: result } = await godServiceForEarthImpl.hateGodForCreatures(target!);
+ if (customActions?.postCreaturesHateGodForCreatureAction) {
+ await customActions.postCreaturesHateGodForCreatureAction(target!, result);
+ } else {
+ showSuccessSnack(
+ t('judo.action.operation.success', { defaultValue: 'Operation executed successfully' }) as string,
+ );
+
+ navigate(routeToViewPlanetViewHateGodOutputView(result.__signedIdentifier));
+ }
+ } catch (error) {
+ handleError(error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ // ParameterlessCallOperationAction: God/(esm/_3_lGwzV7Ee-rMcMaep1uNQ)/OperationFormTableRowCallAction/(discriminator/God/(esm/_QKcpkE7vEeycO-gUAWxcVg)/TabularReferenceField)
+ const creaturesLoveGodForCreatureAction = async (target?: ViewCreatureStored) => {
+ try {
+ setIsLoading(true);
+ const { data: result } = await godServiceForEarthImpl.loveGodForCreatures(target!);
+ if (customActions?.postCreaturesLoveGodForCreatureAction) {
+ await customActions.postCreaturesLoveGodForCreatureAction(target!, result);
+ } else {
+ showSuccessSnack(
+ t('judo.action.operation.success', { defaultValue: 'Operation executed successfully' }) as string,
+ );
+
+ navigate(routeToViewPlanetViewLoveGodOutputView(result.__signedIdentifier));
+ }
+ } catch (error) {
+ handleError(error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ // OpenOperationInputSelectorAction: God/(esm/_ASgmQTV8Ee-rMcMaep1uNQ)/OperationFormTableTableCallAction/(discriminator/God/(esm/_QKcpkE7vEeycO-gUAWxcVg)/TabularReferenceField)
+ const creaturesPunishCreatureAction = async (target?: ViewPlanetStored) => {
+ const { result, data: returnedData } =
+ await openViewPlanetViewGroup_2RightCreaturesPunishCreatureRelationTableCallSelector({
+ ownerData: data,
+ });
+ if (result === 'submit') {
+ await refresh();
+ }
+ };
+ // OpenOperationInputSelectorAction: God/(esm/_AShNUTV8Ee-rMcMaep1uNQ)/OperationFormTableTableCallAction/(discriminator/God/(esm/_QKcpkE7vEeycO-gUAWxcVg)/TabularReferenceField)
+ const creaturesRewardCreatureAction = async (target?: ViewPlanetStored) => {
+ const { result, data: returnedData } =
+ await openViewPlanetViewGroup_2RightCreaturesRewardCreatureRelationTableCallSelector({
+ ownerData: data,
+ });
+ if (result === 'submit') {
+ await refresh();
+ }
+ };
+ // OpenOperationInputSelectorAction: God/(esm/_3_lGxTV7Ee-rMcMaep1uNQ)/OperationFormTableRowCallAction/(discriminator/God/(esm/_QKcpkE7vEeycO-gUAWxcVg)/TabularReferenceField)
+ const creaturesTalkToGodAction = async (target?: ViewPlanetStored) => {
+ const { result, data: returnedData } =
+ await openViewPlanetViewGroup_2RightCreaturesTalkToGodRelationTableCallSelector({
+ ownerData: target!,
+ });
+ if (result === 'submit') {
+ await refresh();
+ }
+ };
+ const getSingletonPayload = async (): Promise> => {
+ const { data: sp } = await godServiceForEarthImpl.refreshForEarth({
+ _mask: '{}',
+ });
+ return sp;
+ };
+
+ const actions: ViewPlanetViewPageActions = {
+ getPageTitle,
+ backAction,
+ cancelAction,
+ deleteAction,
+ refreshAction,
+ updateAction,
+ bangForPlanetAction,
+ createChaosForInstanceAction,
+ createStaticChaosAction,
+ endWarForPlanetAction,
+ sendASignAction,
+ startWarForPlanetAction,
+ createLifeForPlanetAction,
+ destroyLifeForPlanetAction,
+ creaturesOpenAddSelectorAction,
+ creaturesBulkDeleteAction,
+ creaturesBulkRemoveAction,
+ creaturesClearAction,
+ creaturesFilterAction,
+ creaturesRowDeleteAction,
+ creaturesRemoveAction,
+ creaturesOpenPageAction,
+ creaturesChooseTheMessiahForPlanetAction,
+ creaturesCreateCreatureAction,
+ creaturesHateGodForCreatureAction,
+ creaturesLoveGodForCreatureAction,
+ creaturesPunishCreatureAction,
+ creaturesRewardCreatureAction,
+ creaturesTalkToGodAction,
+ getMask,
+ getCreaturesMask,
+ ...(customActions ?? {}),
+ };
+
+ // ViewModel setup
+ const viewModel: ViewPlanetViewViewModel = {
+ actions,
+ isLoading,
+ setIsLoading,
+ refreshCounter,
+ editMode,
+ setEditMode,
+ refresh,
+ data,
+ validation,
+ setValidation,
+ storeDiff,
+ submit,
+ isFormUpdateable,
+ isFormDeleteable,
+ };
+
+ // Effect section
+ useEffect(() => {
+ (async () => {
+ const res = await getSingletonPayload();
+ if (res?.__signedIdentifier) {
+ singletonHost.current = res;
+ owner.current = res;
+ } else {
+ navigate('*');
+ return;
+ }
+ await actions.refreshAction!(getPageQueryCustomizer());
+ })();
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/pages/God/God/Galaxies/AccessTablePage/index.tsx.snapshot b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/pages/God/God/Galaxies/AccessTablePage/index.tsx.snapshot
new file mode 100644
index 00000000..217f20e8
--- /dev/null
+++ b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/pages/God/God/Galaxies/AccessTablePage/index.tsx.snapshot
@@ -0,0 +1,299 @@
+//////////////////////////////////////////////////////////////////////////////
+// G E N E R A T E D S O U R C E
+// --------------------------------
+// Factory expression: #getPagesForRouting(#application)
+// Path expression: 'src/pages/'+#pagePath(#self)+'/index.tsx'
+// Template name: actor/src/pages/index.tsx
+// Template file: actor/src/pages/index.tsx.hbs
+
+import type { GridFilterModel } from '@mui/x-data-grid';
+import { OBJECTCLASS } from '@pandino/pandino-api';
+import { useTrackService } from '@pandino/react-hooks';
+import { Suspense, lazy, useCallback, useMemo, useRef, useState } from 'react';
+import type { Dispatch, FC, ReactNode, SetStateAction } from 'react';
+import { useTranslation } from 'react-i18next';
+import { v4 as uuidv4 } from 'uuid';
+import { useJudoNavigation } from '~/components';
+import type { Filter, FilterOption } from '~/components-api';
+import { useConfirmDialog, useFilterDialog } from '~/components/dialog';
+import type { ViewGalaxyTablePageActions, ViewGalaxyTablePageProps } from '~/containers/View/Galaxy/Table/types';
+import { useGodGodGalaxiesAccessFormPage } from '~/dialogs/God/God/Galaxies/AccessFormPage/hooks';
+import { useViewGalaxyTableCreateDarkMatterInputForm } from '~/dialogs/View/Galaxy/Table/CreateDarkMatter/Input/Form/hooks';
+import { useViewGalaxyTableCreateIntergalacticDustInputForm } from '~/dialogs/View/Galaxy/Table/CreateIntergalacticDust/Input/Form/hooks';
+import { useViewGalaxyTableCreateInterstellarMediumInputForm } from '~/dialogs/View/Galaxy/Table/CreateInterstellarMedium/Input/Form/hooks';
+import { useCRUDDialog, useSnacks, useViewData } from '~/hooks';
+import { routeToGodGodGalaxiesAccessViewPage } from '~/routes';
+import type { JudoIdentifiable } from '~/services/data-api/common/JudoIdentifiable';
+import { draftIdentifierPrefix } from '~/services/data-api/common/utils';
+import type { ViewGalaxy, ViewGalaxyStored } from '~/services/data-api/model/ViewGalaxy';
+import type { ViewGalaxyQueryCustomizer } from '~/services/data-api/rest/ViewGalaxyQueryCustomizer';
+import type { JudoRestResponse } from '~/services/data-api/rest/requestResponse';
+import { GodServiceForGalaxiesImpl } from '~/services/data-axios/GodServiceForGalaxiesImpl';
+import { judoAxiosProvider } from '~/services/data-axios/JudoAxiosProvider';
+import { PageContainerTransition } from '~/theme/animations';
+import { getValue, processQueryCustomizer, setValue, simpleCloneDeep, useErrorHandler } from '~/utilities';
+import type { DialogResult } from '~/utilities';
+import { type ViewGalaxyTableViewModel, ViewGalaxyTableViewModelContext } from './context';
+import type { ViewGalaxyTableActionsHook } from './customization';
+import { GOD_GOD_GALAXIES_ACCESS_TABLE_PAGE_ACTIONS_HOOK_INTERFACE_KEY } from './customization';
+import type { ViewGalaxyTablePageActionsExtended } from './types';
+
+const ViewGalaxyTablePageContainer = lazy(() => import('~/containers/View/Galaxy/Table/ViewGalaxyTablePageContainer'));
+
+// XMIID: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTablePageDefinition
+// Name: God::God::galaxies::AccessTablePage
+export default function GodGodGalaxiesAccessTablePage() {
+ const dataPath = '';
+ const isDraft = false;
+ const owner = useRef(null);
+
+ // Services
+ const godServiceForGalaxiesImpl = useMemo(() => new GodServiceForGalaxiesImpl(judoAxiosProvider), []);
+
+ // Hooks section
+ const { t } = useTranslation();
+ const { showSuccessSnack, showErrorSnack } = useSnacks();
+ const { navigate, back: navigateBack } = useJudoNavigation();
+ const { openFilterDialog } = useFilterDialog();
+ const { openConfirmDialog } = useConfirmDialog();
+ const { setLatestViewData, setRouterPageData } = useViewData();
+ const handleError = useErrorHandler();
+ const openCRUDDialog = useCRUDDialog();
+
+ // State section
+ const [isLoading, setIsLoading] = useState(false);
+ const [editMode, setEditMode] = useState(false);
+ const [refreshCounter, setRefreshCounter] = useState(0);
+ const [data, setData] = useState([]);
+
+ // Masks
+ const getMask: () => string = () => {
+ return '{constellation,darkMatter,discovered,intergalacticDust,interstellarMedium,magnitude,nakedEye,name,real}';
+ };
+
+ // Private actions
+ const submit = async () => {};
+ const refresh = async (forced = false) => {
+ setRefreshCounter((prev) => prev + 1);
+ };
+ const produceDataAdjustedOwner = useCallback(() => {
+ const copy = simpleCloneDeep(owner.current);
+ setValue(copy, dataPath, simpleCloneDeep(data));
+ return copy;
+ }, [data, owner]);
+
+ // Validation
+
+ // Pandino Action overrides
+ const { service: customActionsHook } = useTrackService(
+ `(${OBJECTCLASS}=${GOD_GOD_GALAXIES_ACCESS_TABLE_PAGE_ACTIONS_HOOK_INTERFACE_KEY})`,
+ );
+ const customActions: ViewGalaxyTablePageActionsExtended | undefined = customActionsHook?.(data, editMode);
+
+ // Dialog hooks
+ const openGodGodGalaxiesAccessFormPage = useGodGodGalaxiesAccessFormPage();
+ const openViewGalaxyTableCreateDarkMatterInputForm = useViewGalaxyTableCreateDarkMatterInputForm();
+ const openViewGalaxyTableCreateIntergalacticDustInputForm = useViewGalaxyTableCreateIntergalacticDustInputForm();
+ const openViewGalaxyTableCreateInterstellarMediumInputForm = useViewGalaxyTableCreateInterstellarMediumInputForm();
+
+ // Action section
+ const getPageTitle = (): string => {
+ return t('View.Galaxy.Table', { defaultValue: 'Galaxies' });
+ };
+ // BackAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTableBackAction
+ const backAction = async () => {
+ navigateBack();
+ };
+ // BulkDeleteAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTableBulkDeleteAction
+ const bulkDeleteAction = async (selectedRows: ViewGalaxyStored[]): Promise>> => {
+ return new Promise((resolve) => {
+ openCRUDDialog({
+ dialogTitle: t('judo.action.bulk-delete', { defaultValue: 'Delete' }),
+ itemTitleFn: (item) => (actions?.getRowRepresentation ? actions.getRowRepresentation(item) : item.name!),
+ selectedItems: selectedRows,
+ action: async (item, successHandler: () => void, errorHandler: (error: any) => void) => {
+ try {
+ await godServiceForGalaxiesImpl.delete(item);
+ successHandler();
+ } catch (error) {
+ errorHandler(error);
+ }
+ },
+ autoCloseOnSuccess: true,
+ onClose: async (needsRefresh) => {
+ if (needsRefresh) {
+ setRefreshCounter((prev) => prev + 1);
+ resolve({
+ result: 'delete',
+ data: [],
+ });
+ } else {
+ resolve({
+ result: 'close',
+ data: [],
+ });
+ }
+ },
+ });
+ });
+ };
+ // OpenCreateFormAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTableCreateAction
+ const openCreateFormAction = async () => {
+ const {
+ result,
+ data: returnedData,
+ openCreated,
+ } = await openGodGodGalaxiesAccessFormPage({
+ ownerData: produceDataAdjustedOwner(),
+ isDraft: false,
+ dataPath: `${dataPath ? dataPath + '.' : ''}`,
+ });
+ if (result === 'submit') {
+ setRefreshCounter((prev) => prev + 1);
+ }
+ if (openCreated && returnedData) {
+ await openPageAction(returnedData!);
+ }
+ };
+ // RowDeleteAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTableRowDeleteAction
+ const rowDeleteAction = async (target: ViewGalaxyStored) => {
+ try {
+ const confirmed = await openConfirmDialog(
+ 'row-delete-action',
+ t('judo.modal.confirm.confirm-delete', {
+ defaultValue: 'Are you sure you would like to delete the selected element?',
+ }),
+ t('judo.modal.confirm.confirm-title', { defaultValue: 'Confirm action' }),
+ );
+ if (confirmed) {
+ await godServiceForGalaxiesImpl.delete(target);
+ showSuccessSnack(t('judo.action.delete.success', { defaultValue: 'Delete successful' }));
+ setRefreshCounter((prev) => prev + 1);
+ }
+ } catch (error) {
+ handleError(error, undefined, target);
+ }
+ };
+ // FilterAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTableTableFilterAction
+ const filterAction = async (
+ id: string,
+ filterOptions: FilterOption[],
+ model?: GridFilterModel,
+ filters?: Filter[],
+ ): Promise<{ model?: GridFilterModel; filters?: Filter[] }> => {
+ const newFilters = await openFilterDialog(id, filterOptions, filters);
+ return {
+ filters: newFilters,
+ };
+ };
+ // RefreshAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTableTableRefreshAction
+ const refreshAction = async (
+ queryCustomizer: ViewGalaxyQueryCustomizer,
+ ): Promise> => {
+ try {
+ setIsLoading(true);
+ setEditMode(false);
+ return godServiceForGalaxiesImpl.list(undefined, queryCustomizer);
+ } catch (error) {
+ handleError(error);
+ setLatestViewData(null);
+ setRouterPageData(null);
+ return Promise.reject(error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ // OpenPageAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTableRowViewAction
+ const openPageAction = async (target: ViewGalaxyStored, isDraftParam?: boolean) => {
+ // if the `target` is missing we are likely navigating to a relation table page, in which case we need the owner's id
+ navigate(routeToGodGodGalaxiesAccessViewPage((target as ViewGalaxyStored)!.__signedIdentifier));
+ };
+ // OpenOperationInputFormAction: God/(esm/_uqp7kDV3Ee-rMcMaep1uNQ)/OperationFormTableRowCallAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTablePageDefinition)
+ const createDarkMatterAction = async (
+ target: ViewGalaxyStored,
+ isDraft?: boolean,
+ ownerValidation?: (target: any) => Promise,
+ ) => {
+ const { result, data: returnedData } = await openViewGalaxyTableCreateDarkMatterInputForm({
+ ownerData: target,
+ });
+ if (result === 'submit') {
+ await refresh();
+ }
+ };
+ // OpenOperationInputFormAction: God/(esm/_uqqioTV3Ee-rMcMaep1uNQ)/OperationFormTableRowCallAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTablePageDefinition)
+ const createIntergalacticDustAction = async (
+ target: ViewGalaxyStored,
+ isDraft?: boolean,
+ ownerValidation?: (target: any) => Promise,
+ ) => {
+ const { result, data: returnedData } = await openViewGalaxyTableCreateIntergalacticDustInputForm({
+ ownerData: target,
+ });
+ if (result === 'submit') {
+ await refresh();
+ }
+ };
+ // OpenOperationInputFormAction: God/(esm/_uqqiozV3Ee-rMcMaep1uNQ)/OperationFormTableRowCallAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTablePageDefinition)
+ const createInterstellarMediumAction = async (
+ target: ViewGalaxyStored,
+ isDraft?: boolean,
+ ownerValidation?: (target: any) => Promise,
+ ) => {
+ const { result, data: returnedData } = await openViewGalaxyTableCreateInterstellarMediumInputForm({
+ ownerData: target,
+ });
+ if (result === 'submit') {
+ await refresh();
+ }
+ };
+
+ const actions: ViewGalaxyTablePageActions = {
+ getPageTitle,
+ backAction,
+ bulkDeleteAction,
+ openCreateFormAction,
+ rowDeleteAction,
+ filterAction,
+ refreshAction,
+ openPageAction,
+ createDarkMatterAction,
+ createIntergalacticDustAction,
+ createInterstellarMediumAction,
+ getMask,
+ ...(customActions ?? {}),
+ };
+
+ // ViewModel setup
+ const viewModel: ViewGalaxyTableViewModel = {
+ actions,
+ isLoading,
+ setIsLoading,
+ refreshCounter,
+ editMode,
+ setEditMode,
+ refresh,
+ };
+
+ // Effect section
+
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/pages/God/God/Galaxies/AccessViewPage/index.tsx.snapshot b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/pages/God/God/Galaxies/AccessViewPage/index.tsx.snapshot
new file mode 100644
index 00000000..10dfc5c8
--- /dev/null
+++ b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/pages/God/God/Galaxies/AccessViewPage/index.tsx.snapshot
@@ -0,0 +1,603 @@
+//////////////////////////////////////////////////////////////////////////////
+// G E N E R A T E D S O U R C E
+// --------------------------------
+// Factory expression: #getPagesForRouting(#application)
+// Path expression: 'src/pages/'+#pagePath(#self)+'/index.tsx'
+// Template name: actor/src/pages/index.tsx
+// Template file: actor/src/pages/index.tsx.hbs
+
+import type { GridFilterModel } from '@mui/x-data-grid';
+import { OBJECTCLASS } from '@pandino/pandino-api';
+import { useTrackService } from '@pandino/react-hooks';
+import { Suspense, lazy, useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import type { Dispatch, FC, ReactNode, SetStateAction } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useParams } from 'react-router-dom';
+import { v4 as uuidv4 } from 'uuid';
+import { useJudoNavigation } from '~/components';
+import type { Filter, FilterOption } from '~/components-api';
+import { useConfirmDialog, useFilterDialog } from '~/components/dialog';
+import type { ViewGalaxyViewPageActions, ViewGalaxyViewPageProps } from '~/containers/View/Galaxy/View/types';
+import { useViewGalaxyAstronomerRelationFormPage } from '~/dialogs/View/Galaxy/Astronomer/RelationFormPage/hooks';
+import { useViewGalaxyStarsRelationFormPage } from '~/dialogs/View/Galaxy/Stars/RelationFormPage/hooks';
+import { useViewGalaxyStarsRelationViewPage } from '~/dialogs/View/Galaxy/Stars/RelationViewPage/hooks';
+import { useViewGalaxyViewCreateDarkMatterInputForm } from '~/dialogs/View/Galaxy/View/CreateDarkMatter/Input/Form/hooks';
+import { useViewGalaxyViewCreateIntergalacticDustInputForm } from '~/dialogs/View/Galaxy/View/CreateIntergalacticDust/Input/Form/hooks';
+import { useViewGalaxyViewCreateInterstellarMediumInputForm } from '~/dialogs/View/Galaxy/View/CreateInterstellarMedium/Input/Form/hooks';
+import { useViewGalaxyViewGroupDiscovererAstronomerLinkSetSelectorPage } from '~/dialogs/View/Galaxy/View/Group/Discoverer/Astronomer/LinkSetSelectorPage/hooks';
+import { useCRUDDialog, useSnacks, useViewData } from '~/hooks';
+import { routeToViewGalaxyAstronomerRelationViewPage } from '~/routes';
+import { routeToViewGalaxyMatterRelationTablePage } from '~/routes';
+import type { JudoIdentifiable } from '~/services/data-api/common/JudoIdentifiable';
+import { draftIdentifierPrefix } from '~/services/data-api/common/utils';
+import type { ViewAstronomer, ViewAstronomerStored } from '~/services/data-api/model/ViewAstronomer';
+import type { ViewGalaxy, ViewGalaxyStored } from '~/services/data-api/model/ViewGalaxy';
+import type { ViewMatter, ViewMatterStored } from '~/services/data-api/model/ViewMatter';
+import type { ViewStar, ViewStarStored } from '~/services/data-api/model/ViewStar';
+import type { ViewAstronomerQueryCustomizer } from '~/services/data-api/rest/ViewAstronomerQueryCustomizer';
+import type { ViewGalaxyQueryCustomizer } from '~/services/data-api/rest/ViewGalaxyQueryCustomizer';
+import type { ViewMatterQueryCustomizer } from '~/services/data-api/rest/ViewMatterQueryCustomizer';
+import type { ViewStarQueryCustomizer } from '~/services/data-api/rest/ViewStarQueryCustomizer';
+import type { JudoRestResponse } from '~/services/data-api/rest/requestResponse';
+import { GodServiceForGalaxiesImpl } from '~/services/data-axios/GodServiceForGalaxiesImpl';
+import { judoAxiosProvider } from '~/services/data-axios/JudoAxiosProvider';
+import { PageContainerTransition } from '~/theme/animations';
+import {
+ fileHandling,
+ getValue,
+ processQueryCustomizer,
+ setValue,
+ simpleCloneDeep,
+ useErrorHandler,
+} from '~/utilities';
+import type { DialogResult } from '~/utilities';
+import { type ViewGalaxyViewViewModel, ViewGalaxyViewViewModelContext } from './context';
+import type { ViewGalaxyViewActionsHook } from './customization';
+import { GOD_GOD_GALAXIES_ACCESS_VIEW_PAGE_ACTIONS_HOOK_INTERFACE_KEY } from './customization';
+import type { ViewGalaxyViewPageActionsExtended } from './types';
+
+const ViewGalaxyViewPageContainer = lazy(() => import('~/containers/View/Galaxy/View/ViewGalaxyViewPageContainer'));
+
+// XMIID: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition
+// Name: God::God::galaxies::AccessViewPage
+export default function GodGodGalaxiesAccessViewPage() {
+ const dataPath = '';
+ const isDraft = false;
+ const owner = useRef(null);
+
+ // Router params section
+ const { signedIdentifier } = useParams();
+
+ // Services
+ const godServiceForGalaxiesImpl = useMemo(() => new GodServiceForGalaxiesImpl(judoAxiosProvider), []);
+
+ // Hooks section
+ const { t } = useTranslation();
+ const { showSuccessSnack, showErrorSnack } = useSnacks();
+ const { navigate, back: navigateBack } = useJudoNavigation();
+ const { openFilterDialog } = useFilterDialog();
+ const { openConfirmDialog } = useConfirmDialog();
+ const { setLatestViewData, setRouterPageData } = useViewData();
+ const handleError = useErrorHandler();
+ const openCRUDDialog = useCRUDDialog();
+ const { exportFile } = fileHandling();
+
+ // State section
+ const [isLoading, setIsLoading] = useState(false);
+ const [editMode, setEditMode] = useState(false);
+ const [refreshCounter, setRefreshCounter] = useState(0);
+ const [data, setData] = useState({ __signedIdentifier: signedIdentifier } as ViewGalaxyStored);
+ const [validation, setValidation] = useState>(new Map());
+
+ // Ref section
+
+ // Callback section
+ const storeDiff: (attributeName: keyof ViewGalaxy, value: any) => void = useCallback(
+ (attributeName: keyof ViewGalaxy, value: any) => {
+ setData((prevData) => ({
+ ...prevData,
+ [attributeName]: value,
+ }));
+ if (!editMode) {
+ setEditMode(true);
+ }
+ },
+ [data, editMode],
+ );
+ const isFormUpdateable = useCallback(() => {
+ return true && typeof data?.__updateable === 'boolean' && data?.__updateable;
+ }, [data]);
+ const isFormDeleteable = useCallback(() => {
+ return true && typeof data?.__deleteable === 'boolean' && data?.__deleteable;
+ }, [data]);
+
+ const getPageQueryCustomizer: () => ViewGalaxyQueryCustomizer = () => ({
+ _mask: actions.getMask
+ ? actions.getMask!()
+ : '{constellation,darkMatter,discovered,intergalacticDust,interstellarMedium,magnitude,nakedEye,name,originOfName,real,astronomer{born,name,derivedMessage{message},messages{message},singleMessage{message}},stars{name,lastObservation{date,name},observations{date,name},planets{habitable,inhabited,name,peaceful,creatures{id,name}}}}',
+ });
+
+ // Masks
+ const getMask = () =>
+ '{constellation,darkMatter,discovered,intergalacticDust,interstellarMedium,magnitude,nakedEye,name,originOfName,real,astronomer{born,name,derivedMessage{message},messages{message},singleMessage{message}},stars{name,lastObservation{date,name},observations{date,name},planets{habitable,inhabited,name,peaceful,creatures{id,name}}}}';
+ const getAstronomerMask = () => '{born,name,derivedMessage{message},messages{message},singleMessage{message}}';
+ const getStarsMask = () =>
+ '{name,lastObservation{date,name},observations{date,name},planets{habitable,inhabited,name,peaceful,creatures{id,name,punished,rewarded}}}';
+
+ // Private actions
+ const submit = async () => {
+ await updateAction();
+ };
+ const refresh = async (forced = false) => {
+ if (!editMode || forced) {
+ if (actions.refreshAction) {
+ await actions.refreshAction!(processQueryCustomizer(getPageQueryCustomizer()));
+ }
+ }
+ };
+ const produceDataAdjustedOwner = useCallback(() => {
+ const copy = simpleCloneDeep(owner.current);
+ setValue(copy, dataPath, simpleCloneDeep(data));
+ return copy;
+ }, [data, owner]);
+
+ // Validation
+ const validate: (target: any) => Promise = useCallback(
+ async (target) => {
+ await godServiceForGalaxiesImpl.validateUpdate(target);
+ },
+ [data, godServiceForGalaxiesImpl],
+ );
+
+ // Pandino Action overrides
+ const { service: customActionsHook } = useTrackService(
+ `(${OBJECTCLASS}=${GOD_GOD_GALAXIES_ACCESS_VIEW_PAGE_ACTIONS_HOOK_INTERFACE_KEY})`,
+ );
+ const customActions: ViewGalaxyViewPageActionsExtended | undefined = customActionsHook?.(
+ data,
+ editMode,
+ storeDiff,
+ refresh,
+ submit,
+ );
+
+ // Dialog hooks
+ const openViewGalaxyViewCreateDarkMatterInputForm = useViewGalaxyViewCreateDarkMatterInputForm();
+ const openViewGalaxyViewCreateIntergalacticDustInputForm = useViewGalaxyViewCreateIntergalacticDustInputForm();
+ const openViewGalaxyViewCreateInterstellarMediumInputForm = useViewGalaxyViewCreateInterstellarMediumInputForm();
+ const openViewGalaxyViewGroupDiscovererAstronomerLinkSetSelectorPage =
+ useViewGalaxyViewGroupDiscovererAstronomerLinkSetSelectorPage();
+ const openViewGalaxyAstronomerRelationFormPage = useViewGalaxyAstronomerRelationFormPage();
+ const openViewGalaxyStarsRelationFormPage = useViewGalaxyStarsRelationFormPage();
+ const openViewGalaxyStarsRelationViewPage = useViewGalaxyStarsRelationViewPage();
+
+ // Action section
+ const getPageTitle = (data: ViewGalaxy): string => {
+ return data.name as string;
+ };
+ // BackAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewBackAction
+ const backAction = async () => {
+ navigateBack();
+ };
+ // CancelAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewCancelAction
+ const cancelAction = async () => {
+ setValidation(new Map());
+ // no need to set editMode to false, given refresh should do it implicitly
+ await refresh(true);
+ };
+ // DeleteAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewDeleteAction
+ const deleteAction = async () => {
+ try {
+ const confirmed = await openConfirmDialog(
+ 'delete-action',
+ t('judo.modal.confirm.confirm-delete', {
+ defaultValue: 'Are you sure you would like to delete the selected element?',
+ }),
+ t('judo.modal.confirm.confirm-title', { defaultValue: 'Confirm action' }),
+ );
+ if (confirmed) {
+ await godServiceForGalaxiesImpl.delete(data);
+ showSuccessSnack(t('judo.action.delete.success', { defaultValue: 'Delete successful' }));
+ navigateBack();
+ }
+ } catch (error) {
+ handleError(error, undefined, data);
+ }
+ };
+ // RefreshAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewRefreshAction
+ const refreshAction = async (
+ queryCustomizer: ViewGalaxyQueryCustomizer,
+ ): Promise> => {
+ try {
+ setIsLoading(true);
+ setEditMode(false);
+ const response = await godServiceForGalaxiesImpl.refresh(
+ { __signedIdentifier: signedIdentifier } as any,
+ getPageQueryCustomizer(),
+ );
+ const { data: result } = response;
+ if (!isDraft) {
+ owner.current = result;
+ }
+ setData(result);
+ setLatestViewData(result);
+ setRouterPageData(result);
+ if (customActions?.postRefreshAction) {
+ await customActions?.postRefreshAction(result, storeDiff, setValidation);
+ }
+ return response;
+ } catch (error) {
+ handleError(error);
+ setLatestViewData(null);
+ setRouterPageData(null);
+ return Promise.reject(error);
+ } finally {
+ setIsLoading(false);
+ setRefreshCounter((prevCounter) => prevCounter + 1);
+ }
+ };
+ // UpdateAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewUpdateAction
+ const updateAction = async () => {
+ setIsLoading(true);
+ try {
+ const { data: res } = await godServiceForGalaxiesImpl.update(data);
+ if (res) {
+ showSuccessSnack(t('judo.action.save.success', { defaultValue: 'Changes saved' }));
+ setValidation(new Map());
+ setEditMode(false);
+ await actions.refreshAction!(getPageQueryCustomizer());
+ }
+ } catch (error) {
+ handleError(error, { setValidation }, data);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ // AutocompleteRangeAction: God/(esm/_conRARMcEe2_DOUDKkB20Q)/TabularReferenceFieldLinkAutocompleteRangeAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const astronomerAutocompleteRangeAction = async (
+ queryCustomizer: ViewAstronomerQueryCustomizer,
+ ): Promise => {
+ try {
+ const { data: result } = await godServiceForGalaxiesImpl.getRangeForAstronomer(data, queryCustomizer);
+ return result;
+ } catch (error: any) {
+ handleError(error);
+ return Promise.resolve([]);
+ }
+ };
+ // AutocompleteSetAction: God/(esm/_conRARMcEe2_DOUDKkB20Q)/TabularReferenceFieldLinkAutocompleteSetAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const astronomerAutocompleteSetAction = async (selected: ViewAstronomerStored) => {
+ try {
+ storeDiff('astronomer', selected);
+ } catch (error) {
+ handleError(error);
+ return Promise.reject(error);
+ }
+ };
+ // OpenCreateFormAction: God/(esm/_conRARMcEe2_DOUDKkB20Q)/TabularReferenceFieldLinkCreateAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const astronomerOpenCreateFormAction = async () => {
+ const {
+ result,
+ data: returnedData,
+ openCreated,
+ } = await openViewGalaxyAstronomerRelationFormPage({
+ ownerData: produceDataAdjustedOwner(),
+ ownerValidation: validate,
+ isDraft: true,
+ dataPath: `${dataPath ? dataPath + '.' : ''}astronomer`,
+ });
+ if (result === 'submit' && !editMode) {
+ await actions.refreshAction!(processQueryCustomizer(getPageQueryCustomizer()));
+ } else if (result === 'submit-draft' && returnedData) {
+ const decoratedData = {
+ ...returnedData,
+ __identifier: `${draftIdentifierPrefix}${uuidv4()}`,
+ };
+ const newData = decoratedData;
+ storeDiff('astronomer', newData);
+ return;
+ }
+ if (openCreated && returnedData) {
+ await astronomerOpenPageAction(returnedData!);
+ }
+ };
+ // RowDeleteAction: God/(esm/_conRARMcEe2_DOUDKkB20Q)/TabularReferenceFieldLinkDeleteAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const astronomerRowDeleteAction = async (target: ViewAstronomerStored) => {
+ try {
+ const confirmed = await openConfirmDialog(
+ 'row-delete-action',
+ t('judo.modal.confirm.confirm-delete', {
+ defaultValue: 'Are you sure you would like to delete the selected element?',
+ }),
+ t('judo.modal.confirm.confirm-title', { defaultValue: 'Confirm action' }),
+ );
+ if (confirmed) {
+ storeDiff('astronomer', null);
+ }
+ } catch (error) {
+ handleError(error, undefined, target);
+ }
+ };
+ // OpenSelectorAction: God/(esm/_conRARMcEe2_DOUDKkB20Q)/TabularReferenceFieldLinkSetSelectorOpenPageAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const astronomerOpenSetSelectorAction = async (): Promise => {
+ const { result, data: returnedData } = await openViewGalaxyViewGroupDiscovererAstronomerLinkSetSelectorPage({
+ ownerData: data,
+ alreadySelected: data.astronomer ? [data.astronomer] : [],
+ });
+ if (result === 'submit') {
+ if (Array.isArray(returnedData) && returnedData.length) {
+ try {
+ storeDiff('astronomer', returnedData[0]);
+ return returnedData[0];
+ } catch (error) {
+ console.error(error);
+ return undefined;
+ }
+ }
+ }
+ return undefined;
+ };
+ // UnsetAction: God/(esm/_conRARMcEe2_DOUDKkB20Q)/TabularReferenceFieldLinkUnsetAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const astronomerUnsetAction = async (target: ViewAstronomer | ViewAstronomerStored) => {
+ storeDiff('astronomer', null);
+ };
+ // OpenPageAction: God/(esm/_conRARMcEe2_DOUDKkB20Q)/TabularReferenceFieldLinkViewAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const astronomerOpenPageAction = async (target: ViewAstronomerStored, isDraftParam?: boolean) => {
+ // if the `target` is missing we are likely navigating to a relation table page, in which case we need the owner's id
+ navigate(
+ routeToViewGalaxyAstronomerRelationViewPage(((target as ViewAstronomerStored) || data).__signedIdentifier),
+ );
+ };
+ // BulkDeleteAction: God/(esm/_8AxbAE7tEeycO-gUAWxcVg)/TabularReferenceTableBulkDeleteAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const starsBulkDeleteAction = async (
+ selectedRows: ViewStarStored[],
+ ): Promise>> => {
+ return new Promise((resolve) => {
+ const selectedIds = selectedRows.map((r) => r.__identifier);
+ const newList = (data?.stars ?? []).filter((c: any) => !selectedIds.includes(c.__identifier));
+ storeDiff('stars', newList);
+ resolve({
+ result: 'delete',
+ data: [],
+ });
+ });
+ };
+ // OpenCreateFormAction: God/(esm/_8AxbAE7tEeycO-gUAWxcVg)/TabularReferenceTableCreateAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const starsOpenCreateFormAction = async () => {
+ const itemIndex = (data.stars || []).length; // length gives next without -1-ing it
+ const {
+ result,
+ data: returnedData,
+ openCreated,
+ } = await openViewGalaxyStarsRelationFormPage({
+ ownerData: produceDataAdjustedOwner(),
+ ownerValidation: validate,
+ isDraft: true,
+ dataPath: `${dataPath ? dataPath + '.' : ''}stars[${itemIndex}]`,
+ });
+ if (result === 'submit' && !editMode) {
+ await actions.refreshAction!(processQueryCustomizer(getPageQueryCustomizer()));
+ } else if (result === 'submit-draft' && returnedData) {
+ const decoratedData = {
+ ...returnedData,
+ __identifier: `${draftIdentifierPrefix}${uuidv4()}`,
+ };
+ const newData = [...(data.stars || []), decoratedData];
+ storeDiff('stars', newData);
+ return;
+ }
+ if (openCreated && returnedData) {
+ await starsOpenPageAction(returnedData!);
+ }
+ };
+ // ExportAction: God/(esm/_8AxbAE7tEeycO-gUAWxcVg)/TabularReferenceTableExportAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const starsExportAction = async (queryCustomizer: ViewStarQueryCustomizer) => {
+ try {
+ setIsLoading(true);
+ const response = await godServiceForGalaxiesImpl.exportStars(
+ { __signedIdentifier: signedIdentifier } as any,
+ queryCustomizer,
+ );
+
+ exportFile(response);
+ } catch (error) {
+ console.error(error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ // FilterAction: God/(esm/_8AxbAE7tEeycO-gUAWxcVg)/TabularReferenceTableFilterAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const starsFilterAction = async (
+ id: string,
+ filterOptions: FilterOption[],
+ model?: GridFilterModel,
+ filters?: Filter[],
+ ): Promise<{ model?: GridFilterModel; filters?: Filter[] }> => {
+ const newFilters = await openFilterDialog(id, filterOptions, filters);
+ return {
+ filters: newFilters,
+ };
+ };
+ // RowDeleteAction: God/(esm/_8AxbAE7tEeycO-gUAWxcVg)/TabularReferenceTableRowDeleteAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const starsRowDeleteAction = async (target: ViewStarStored) => {
+ try {
+ const confirmed = await openConfirmDialog(
+ 'row-delete-action',
+ t('judo.modal.confirm.confirm-delete', {
+ defaultValue: 'Are you sure you would like to delete the selected element?',
+ }),
+ t('judo.modal.confirm.confirm-title', { defaultValue: 'Confirm action' }),
+ );
+ if (confirmed) {
+ const newList = (data?.stars ?? []).filter((c: any) => c.__identifier !== target.__identifier);
+ storeDiff('stars', newList);
+ }
+ } catch (error) {
+ handleError(error, undefined, target);
+ }
+ };
+ // OpenPageAction: God/(esm/_8AxbAE7tEeycO-gUAWxcVg)/TabularReferenceTableRowViewAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const starsOpenPageAction = async (target: ViewStarStored, isDraftParam?: boolean) => {
+ const itemIndex = (data.stars || []).findIndex((r) => r.__identifier === target.__identifier)!;
+ if (isDraftParam) {
+ const { result, data: returnedData } = await openViewGalaxyStarsRelationViewPage({
+ ownerData: produceDataAdjustedOwner(),
+ isDraft: true,
+ ownerValidation: validate,
+ dataPath: `${dataPath ? dataPath + '.' : ''}stars[${itemIndex!}]`,
+ });
+ // we might need to differentiate result handling between operation inputs and crud relation creates
+ if (result === 'submit-draft' && returnedData) {
+ const existingIndex = (data.stars || []).findIndex(
+ (r: { __identifier?: string }) => r.__identifier === returnedData.__identifier,
+ );
+ if (existingIndex > -1) {
+ // we need to create a copy to keep order, and because mui datagrid freezes elements, and crashes on reference updates
+ const updatedList = [...(data.stars || [])];
+ updatedList[existingIndex] = {
+ ...returnedData,
+ };
+ storeDiff('stars', updatedList);
+ }
+ return;
+ }
+ if (result === 'delete' && returnedData) {
+ const existingIndex = (data.stars || []).findIndex(
+ (r: { __identifier?: string }) => r.__identifier === returnedData.__identifier,
+ );
+ if (existingIndex > -1) {
+ // we need to create a copy to keep order, and because mui datagrid freezes elements, and crashes on reference updates
+ const updatedList = [...(data.stars || [])];
+ updatedList.splice(existingIndex, 1);
+ storeDiff('stars', updatedList);
+ }
+ return;
+ }
+ if (result === 'close') {
+ return;
+ }
+ } else {
+ await openViewGalaxyStarsRelationViewPage({
+ ownerData: target!,
+ });
+ await refresh();
+ }
+ };
+ // OpenOperationInputFormAction: God/(esm/_5NwrQFyrEeylCdga_wJIBQ)/OperationFormCallAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const createDarkMatterAction = async (isDraft?: boolean, ownerValidation?: (target: any) => Promise) => {
+ const { result, data: returnedData } = await openViewGalaxyViewCreateDarkMatterInputForm({
+ ownerData: data,
+ });
+ if (result === 'submit' && !editMode) {
+ await refresh();
+ }
+ };
+ // OpenOperationInputFormAction: God/(esm/_5Nx5YFyrEeylCdga_wJIBQ)/OperationFormCallAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const createIntergalacticDustAction = async (isDraft?: boolean, ownerValidation?: (target: any) => Promise) => {
+ const { result, data: returnedData } = await openViewGalaxyViewCreateIntergalacticDustInputForm({
+ ownerData: data,
+ });
+ if (result === 'submit' && !editMode) {
+ await refresh();
+ }
+ };
+ // OpenOperationInputFormAction: God/(esm/_5Nx5YVyrEeylCdga_wJIBQ)/OperationFormCallAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const createInterstellarMediumAction = async (
+ isDraft?: boolean,
+ ownerValidation?: (target: any) => Promise,
+ ) => {
+ const { result, data: returnedData } = await openViewGalaxyViewCreateInterstellarMediumInputForm({
+ ownerData: data,
+ });
+ if (result === 'submit' && !editMode) {
+ await refresh();
+ }
+ };
+ // OpenPageAction: God/(esm/_8A3hoE7tEeycO-gUAWxcVg)/TabularReferenceFieldButtonOpenPageAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition)
+ const matterOpenPageAction = async (target: ViewMatterStored, isDraftParam?: boolean) => {
+ // if the `target` is missing we are likely navigating to a relation table page, in which case we need the owner's id
+ navigate(routeToViewGalaxyMatterRelationTablePage(((target as ViewMatterStored) || data).__signedIdentifier));
+ };
+
+ const actions: ViewGalaxyViewPageActions = {
+ getPageTitle,
+ backAction,
+ cancelAction,
+ deleteAction,
+ refreshAction,
+ updateAction,
+ astronomerAutocompleteRangeAction,
+ astronomerAutocompleteSetAction,
+ astronomerOpenCreateFormAction,
+ astronomerRowDeleteAction,
+ astronomerOpenSetSelectorAction,
+ astronomerUnsetAction,
+ astronomerOpenPageAction,
+ starsBulkDeleteAction,
+ starsOpenCreateFormAction,
+ starsExportAction,
+ starsFilterAction,
+ starsRowDeleteAction,
+ starsOpenPageAction,
+ createDarkMatterAction,
+ createIntergalacticDustAction,
+ createInterstellarMediumAction,
+ matterOpenPageAction,
+ getMask,
+ getAstronomerMask,
+ getStarsMask,
+ ...(customActions ?? {}),
+ };
+
+ // ViewModel setup
+ const viewModel: ViewGalaxyViewViewModel = {
+ actions,
+ isLoading,
+ setIsLoading,
+ refreshCounter,
+ editMode,
+ setEditMode,
+ refresh,
+ data,
+ validation,
+ setValidation,
+ storeDiff,
+ submit,
+ isFormUpdateable,
+ isFormDeleteable,
+ };
+
+ // Effect section
+ useEffect(() => {
+ (async () => {
+ await actions.refreshAction!(getPageQueryCustomizer());
+ })();
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/pom.xml b/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/pom.xml
index 91621de6..5513af9f 100644
--- a/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/pom.xml
+++ b/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/pom.xml
@@ -93,7 +93,7 @@
${ui-model}
${generation-target}
- true
+ false
${model-name}
${appScope}
${appVersion}
diff --git a/judo-ui-react-itest/CRUDActionsTest/crudactions_test__actors__actor/pom.xml b/judo-ui-react-itest/CRUDActionsTest/crudactions_test__actors__actor/pom.xml
index d64ca21f..840d1126 100644
--- a/judo-ui-react-itest/CRUDActionsTest/crudactions_test__actors__actor/pom.xml
+++ b/judo-ui-react-itest/CRUDActionsTest/crudactions_test__actors__actor/pom.xml
@@ -93,7 +93,7 @@
${ui-model}
${generation-target}
- true
+ false
${model-name}
${appScope}
${appVersion}
diff --git a/judo-ui-react-itest/CRUDActionsTest/crudactions_test__actors__collections__collection_dashboard_actor/pom.xml b/judo-ui-react-itest/CRUDActionsTest/crudactions_test__actors__collections__collection_dashboard_actor/pom.xml
index ac4a857d..91cd1fcb 100644
--- a/judo-ui-react-itest/CRUDActionsTest/crudactions_test__actors__collections__collection_dashboard_actor/pom.xml
+++ b/judo-ui-react-itest/CRUDActionsTest/crudactions_test__actors__collections__collection_dashboard_actor/pom.xml
@@ -93,7 +93,7 @@
${ui-model}
${generation-target}
- true
+ false
${model-name}
${appScope}
${appVersion}
diff --git a/judo-ui-react-itest/CRUDActionsTest/crudactions_test__actors__singles__single_dashboard_actor/pom.xml b/judo-ui-react-itest/CRUDActionsTest/crudactions_test__actors__singles__single_dashboard_actor/pom.xml
index 80993b57..8a47a2e5 100644
--- a/judo-ui-react-itest/CRUDActionsTest/crudactions_test__actors__singles__single_dashboard_actor/pom.xml
+++ b/judo-ui-react-itest/CRUDActionsTest/crudactions_test__actors__singles__single_dashboard_actor/pom.xml
@@ -93,7 +93,7 @@
${ui-model}
${generation-target}
- true
+ false
${model-name}
${appScope}
${appVersion}
diff --git a/judo-ui-react-itest/OperationParametersTest/operation_parameters_test__actor/pom.xml b/judo-ui-react-itest/OperationParametersTest/operation_parameters_test__actor/pom.xml
index a25f110d..312c8db5 100644
--- a/judo-ui-react-itest/OperationParametersTest/operation_parameters_test__actor/pom.xml
+++ b/judo-ui-react-itest/OperationParametersTest/operation_parameters_test__actor/pom.xml
@@ -93,7 +93,7 @@
${ui-model}
${generation-target}
- true
+ false
${model-name}
${appScope}
${appVersion}
diff --git a/judo-ui-react-itest/RelationTest/relation_test__actor/pom.xml b/judo-ui-react-itest/RelationTest/relation_test__actor/pom.xml
index 7d4d6c28..70d1ae3d 100644
--- a/judo-ui-react-itest/RelationTest/relation_test__actor/pom.xml
+++ b/judo-ui-react-itest/RelationTest/relation_test__actor/pom.xml
@@ -93,7 +93,7 @@
${ui-model}
${generation-target}
- true
+ false
${model-name}
${appScope}
${appVersion}
diff --git a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPageContainerHelper.java b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPageContainerHelper.java
index e90d75d0..d9e6971d 100644
--- a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPageContainerHelper.java
+++ b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPageContainerHelper.java
@@ -504,4 +504,13 @@ public static PageDefinition getCallerPageForOperationFormCallButton(Button butt
.filter(p -> p.getActions().stream().anyMatch(a -> a.getActionDefinition().equals(button.getActionDefinition())))
.findFirst().orElse(null);
}
+
+ public static boolean isVisualElementContainerButton(VisualElement visualElement) {
+ if (visualElement instanceof Button button) {
+ if (button.eContainer() instanceof ButtonGroup buttonGroup) {
+ return buttonGroup.eContainer() instanceof PageContainer;
+ }
+ }
+ return false;
+ }
}
diff --git a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPandinoHelper.java b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPandinoHelper.java
index 352aa9d0..8577da23 100644
--- a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPandinoHelper.java
+++ b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPandinoHelper.java
@@ -58,6 +58,18 @@ public static SortedSet getVisualElementsWithCustomImplementation
return result;
}
+ public static SortedSet getContainerActionsVisualElementsWithCustomImplementation(PageContainer container) {
+ SortedSet result = new TreeSet<>(Comparator.comparing((VisualElement v) -> v.getFQName().trim()));
+
+ collectVisualElementsMatchingCondition(container.getActionButtonGroup(), VisualElement::isCustomImplementation, result);
+
+ return result;
+ }
+
+ public static boolean containerHasActionsWithCustomImplementation(PageContainer container) {
+ return !getContainerActionsVisualElementsWithCustomImplementation(container).isEmpty();
+ }
+
public static String pageActionFQName(Action action) {
String adn = simpleActionDefinitionName(action.getActionDefinition());
PageDefinition pageDefinition = (PageDefinition) action.eContainer();
diff --git a/judo-ui-react/src/main/resources/actor/src/containers/customization.ts.hbs b/judo-ui-react/src/main/resources/actor/src/containers/customization.ts.hbs
index 9daa66c4..498ef46f 100644
--- a/judo-ui-react/src/main/resources/actor/src/containers/customization.ts.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/containers/customization.ts.hbs
@@ -1,6 +1,6 @@
{{# unless (containerIsEmptyDashboard container) }}
import type { FC } from 'react';
- import type { CustomFormVisualElementProps } from '~/custom';
+ import type { CustomFormVisualElementProps, CustomFormVisualElementPropsWithActions } from '~/custom';
{{# each (getContainerApiImports container) as |imp| }}
import type { {{ classDataName imp '' }}, {{ classDataName imp 'Stored' }} } from '~/services/data-api/model/{{ classDataName imp '' }}';
{{/ each }}
@@ -13,7 +13,7 @@
{{# unless (containerIsEmptyDashboard container) }}
{{# each (getVisualElementsWithCustomImplementation container) as |ve| }}
export const {{ getCustomizationComponentInterfaceKey ve }} = '{{ getCustomizationComponentInterface ve }}';
- export interface {{ getCustomizationComponentInterface ve }} extends FC> {}
+ export interface {{ getCustomizationComponentInterface ve }} extends FC> {}
{{/ each }}
export const {{ camelCaseNameToInterfaceKey (containerComponentName container) }}_CONTAINER_ACTIONS_HOOK_INTERFACE_KEY = '{{ camelCaseNameToInterfaceKey (containerComponentName container) }}_CONTAINER_ACTIONS_HOOK';
diff --git a/judo-ui-react/src/main/resources/actor/src/containers/dialog.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/containers/dialog.tsx.hbs
index 5e803a5f..2912ab62 100644
--- a/judo-ui-react/src/main/resources/actor/src/containers/dialog.tsx.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/containers/dialog.tsx.hbs
@@ -2,6 +2,11 @@
import { lazy, Suspense{{# if (containerHasCreateAction container) }}, useRef, useState, useCallback{{/ if }} } from 'react';
import type { Dispatch, SetStateAction } from 'react';
+{{# if (containerHasActionsWithCustomImplementation container) }}
+ import { OBJECTCLASS } from '@pandino/pandino-api';
+ import { ComponentProxy, useTrackService } from '@pandino/react-hooks';
+ import { CUSTOM_VISUAL_ELEMENT_INTERFACE_KEY } from '~/custom';
+{{/ if }}
import Grid from '@mui/material/Grid';
import Button from '@mui/material/Button';
import DialogTitle from '@mui/material/DialogTitle';
@@ -39,6 +44,14 @@ import { useConfirmDialog } from '~/components/dialog';
{{ containerComponentName container }}DialogActions,
{{ containerComponentName container }}DialogProps,
} from './types';
+
+ {{# if (containerHasActionsWithCustomImplementation container) }}
+ import {
+ {{# each (getContainerActionsVisualElementsWithCustomImplementation container) as |ve| }}
+ {{ getCustomizationComponentInterfaceKey ve }},
+ {{/ each }}
+ } from './customization';
+ {{/ if }}
{{/ unless }}
{{# unless (containerIsEmptyDashboard container) }}
@@ -160,6 +173,23 @@ export default function {{ containerComponentName container }}Dialog({{# unless
{{/ unless }}
{{/ if }}
+ {{# if button.customImplementation }}
+
+ {{/ if }}
+ {{# if button.customImplementation }}
+
+ {{/ if }}
{{# if button.actionDefinition.isCreateAction }}
{{# unless button.actionDefinition.autoOpenAfterCreate }}
{!isDraft &&
diff --git a/judo-ui-react/src/main/resources/actor/src/containers/page.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/containers/page.tsx.hbs
index a77dc665..a5e972c9 100644
--- a/judo-ui-react/src/main/resources/actor/src/containers/page.tsx.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/containers/page.tsx.hbs
@@ -2,6 +2,11 @@
import { lazy, Suspense } from 'react';
import type { Dispatch, SetStateAction } from 'react';
+{{# if (containerHasActionsWithCustomImplementation container) }}
+ import { OBJECTCLASS } from '@pandino/pandino-api';
+ import { ComponentProxy, useTrackService } from '@pandino/react-hooks';
+ import { CUSTOM_VISUAL_ELEMENT_INTERFACE_KEY } from '~/custom';
+{{/ if }}
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import Button from '@mui/material/Button';
@@ -28,6 +33,14 @@ import { mainContainerPadding } from '~/theme';
{{ containerComponentName container }}PageActions,
{{ containerComponentName container }}PageProps,
} from './types';
+
+ {{# if (containerHasActionsWithCustomImplementation container) }}
+ import {
+ {{# each (getContainerActionsVisualElementsWithCustomImplementation container) as |ve| }}
+ {{ getCustomizationComponentInterfaceKey ve }},
+ {{/ each }}
+ } from './customization';
+ {{/ if }}
{{/ unless }}
{{# unless (containerIsEmptyDashboard container) }}
@@ -57,6 +70,20 @@ export default function {{ containerComponentName container }}Page ({{# unless (
{{# each container.actionButtonGroup.buttons as |button| }}
{ {{{ containerButtonAvailable button }}} && actions.{{ simpleActionDefinitionName actionDefinition }} && (
+ {{# if button.customImplementation }}
+
+ {{/ if }}
{t('{{ getTranslationKeyForVisualElement button }}', { defaultValue: '{{ button.label }}' })}
+ {{# if button.customImplementation }}
+
+ {{/ if }}
)}
{{/ each }}
diff --git a/judo-ui-react/src/main/resources/actor/src/custom/custom-element-types.ts.hbs b/judo-ui-react/src/main/resources/actor/src/custom/custom-element-types.ts.hbs
index 2499bd97..fab3ed11 100644
--- a/judo-ui-react/src/main/resources/actor/src/custom/custom-element-types.ts.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/custom/custom-element-types.ts.hbs
@@ -29,3 +29,7 @@ export interface CustomFormVisualElementProps {
*/
storeDiff: (attributeName: keyof T, value: any) => void;
}
+
+export interface CustomFormVisualElementPropsWithActions extends CustomFormVisualElementProps {
+ actions: A;
+}
diff --git a/judo-ui-react/src/main/resources/actor/src/custom/interfaces.ts.hbs b/judo-ui-react/src/main/resources/actor/src/custom/interfaces.ts.hbs
index 56e26adb..5f824c13 100644
--- a/judo-ui-react/src/main/resources/actor/src/custom/interfaces.ts.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/custom/interfaces.ts.hbs
@@ -11,7 +11,7 @@ export interface GenericProxyProps {
validation: Map;
editMode?: boolean;
storeDiff: (attributeName: keyof T, value: any) => void;
- isLoading: () => boolean;
+ isLoading: boolean;
actions: A;
}
diff --git a/pom.xml b/pom.xml
index 7e081a84..c7b8da86 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,6 +77,7 @@
docs
+ judo-diff-checker-maven-plugin
judo-ui-react
judo-ui-react-itest