diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/sample/domain/ui_actions/MakeCompletedAction.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/sample/domain/ui_actions/MakeCompletedAction.java index e6bceba2ad4..0064a6acb91 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/sample/domain/ui_actions/MakeCompletedAction.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/sample/domain/ui_actions/MakeCompletedAction.java @@ -1,18 +1,15 @@ package ua.com.fielden.platform.sample.domain.ui_actions; -import static ua.com.fielden.platform.entity.NoKey.NO_KEY; -import static ua.com.fielden.platform.reflection.TitlesDescsGetter.getEntityTitleAndDesc; - import ua.com.fielden.platform.entity.AbstractFunctionalEntityWithCentreContext; import ua.com.fielden.platform.entity.NoKey; -import ua.com.fielden.platform.entity.annotation.CompanionObject; -import ua.com.fielden.platform.entity.annotation.IsProperty; -import ua.com.fielden.platform.entity.annotation.KeyType; -import ua.com.fielden.platform.entity.annotation.Observable; -import ua.com.fielden.platform.entity.annotation.SkipEntityExistsValidation; +import ua.com.fielden.platform.entity.annotation.*; +import ua.com.fielden.platform.processors.metamodel.IConvertableToPath; import ua.com.fielden.platform.sample.domain.TgPersistentEntityWithProperties; import ua.com.fielden.platform.utils.Pair; +import static ua.com.fielden.platform.entity.NoKey.NO_KEY; +import static ua.com.fielden.platform.reflection.TitlesDescsGetter.getEntityTitleAndDesc; + /** * Functional entity to make {@link TgPersistentEntityWithProperties} entity completed and save. * It applies all user modifications, sets 'completed' property and 'dateProp' and saves the instance. @@ -34,6 +31,15 @@ public MakeCompletedAction() { setKey(NO_KEY); } + public enum Property implements IConvertableToPath { + masterEntity; + + @Override + public String toPath() { + return name(); + } + } + @IsProperty @SkipEntityExistsValidation private TgPersistentEntityWithProperties masterEntity; diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/application/AbstractWebUiResources.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/application/AbstractWebUiResources.java index 9369c200588..ad2f539c962 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/application/AbstractWebUiResources.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/application/AbstractWebUiResources.java @@ -22,6 +22,8 @@ import ua.com.fielden.platform.web.resources.RestServerUtil; import ua.com.fielden.platform.web.security.DefaultWebResourceGuard; +import static ua.com.fielden.platform.web.resources.webui.AppIndexResource.BINDING_PATH; + /** * Represents a web application that is running on the server. * It is responsible for request routing and serving web resources. @@ -105,7 +107,7 @@ public final Restlet createInboundRoot() { final RestServerUtil restUtil = injector.getInstance(RestServerUtil.class); // Attach main application resource. - guardedRouter.attach("/", new AppIndexResourceFactory(webResourceLoader, webApp, userProvider, deviceProvider, dates, injector.getInstance(ICriteriaGenerator.class))); + guardedRouter.attach(BINDING_PATH, new AppIndexResourceFactory(webResourceLoader, webApp, userProvider, deviceProvider, dates, injector.getInstance(ICriteriaGenerator.class))); guardedRouter.attach("/app/tg-app-config.js", new WebUiPreferencesResourceFactory(webResourceLoader, deviceProvider, dates)); guardedRouter.attach("/app/tg-app.js", new MainWebUiComponentResourceFactory(webResourceLoader, deviceProvider, dates)); // type meta info resource diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/ioc/WebResourceLoader.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/ioc/WebResourceLoader.java index f0319d9d5e0..af946cff136 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/ioc/WebResourceLoader.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/ioc/WebResourceLoader.java @@ -38,7 +38,9 @@ import static ua.com.fielden.platform.utils.ResourceLoader.getStream; import static ua.com.fielden.platform.utils.ResourceLoader.getText; import static ua.com.fielden.platform.web.factories.webui.ResourceFactoryUtils.*; +import static ua.com.fielden.platform.web.resources.webui.AppIndexResource.FILE_APP_INDEX_HTML; import static ua.com.fielden.platform.web.resources.webui.FileResource.generateFileName; +import static ua.com.fielden.platform.web.resources.webui.LoginInitiateResetResource.FILE_APP_LOGIN_INITIATE_RESET_HTML; /** * {@link IWebResourceLoader} implementation. @@ -77,11 +79,11 @@ public InputStream loadStream(final String resourceUri) { private Optional getSource(final String resourceUri) { if ("/app/application-startup-resources.js".equalsIgnoreCase(resourceUri)) { return getApplicationStartupResourcesSource(webUiConfig); - } else if ("/app/tg-app-index.html".equalsIgnoreCase(resourceUri)) { + } else if (FILE_APP_INDEX_HTML.equalsIgnoreCase(resourceUri)) { return injectServiceWorkerScriptInto(webUiConfig.genAppIndex()); } else if ("/app/logout.html".equalsIgnoreCase(resourceUri)) { return getFileSource("/resources/logout.html", webUiConfig.resourcePaths()).map(src -> StringUtils.replace(src, "@title", "Logout")); - } else if ("/app/login-initiate-reset.html".equalsIgnoreCase(resourceUri)) { + } else if (FILE_APP_LOGIN_INITIATE_RESET_HTML.equalsIgnoreCase(resourceUri)) { return getFileSource("/resources/login-initiate-reset.html", webUiConfig.resourcePaths()).map(src -> StringUtils.replace(src, "@title", "Login Reset Request")); } else if ("/app/tg-app-config.js".equalsIgnoreCase(resourceUri)) { return ofNullable(webUiConfig.genWebUiPreferences()); @@ -109,6 +111,11 @@ public Optional checksum(final String resourceURI) { return webUiConfig.checksum(resourceURI); } + @Override + public SequencedSet deploymentResourcePaths() { + return webUiConfig.deploymentResourcePaths(); + } + /** * Generates 'tg-reflector' resource with type table containing master configurations. * diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/AbstractWebUiConfig.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/AbstractWebUiConfig.java index d624c450ce8..095929d36e4 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/AbstractWebUiConfig.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/AbstractWebUiConfig.java @@ -17,7 +17,6 @@ import ua.com.fielden.platform.types.tuples.T2; import ua.com.fielden.platform.ui.menu.MiWithConfigurationSupport; import ua.com.fielden.platform.utils.IDates; -import ua.com.fielden.platform.utils.ResourceLoader; import ua.com.fielden.platform.web.action.CentreConfigurationWebUiConfig; import ua.com.fielden.platform.web.action.StandardMastersWebUiConfig; import ua.com.fielden.platform.web.app.IWebUiConfig; @@ -32,6 +31,7 @@ import ua.com.fielden.platform.web.ioc.exceptions.MissingWebResourceException; import ua.com.fielden.platform.web.menu.IMainMenuBuilder; import ua.com.fielden.platform.web.menu.impl.MainMenuBuilder; +import ua.com.fielden.platform.web.minijs.CombinedJsImports; import ua.com.fielden.platform.web.minijs.JsCode; import ua.com.fielden.platform.web.ref_hierarchy.ReferenceHierarchyWebUiConfig; import ua.com.fielden.platform.web.resources.webui.exceptions.InvalidUiConfigException; @@ -48,20 +48,25 @@ import static java.lang.String.format; import static java.util.Arrays.asList; +import static java.util.Objects.requireNonNull; import static java.util.Optional.of; import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toCollection; import static org.apache.commons.validator.routines.UrlValidator.ALLOW_LOCAL_URLS; import static ua.com.fielden.platform.error.Result.failuref; import static ua.com.fielden.platform.error.Result.successful; import static ua.com.fielden.platform.types.Hyperlink.SupportedProtocols.HTTPS; import static ua.com.fielden.platform.types.tuples.T2.t2; import static ua.com.fielden.platform.utils.ResourceLoader.getStream; +import static ua.com.fielden.platform.utils.ResourceLoader.getText; import static ua.com.fielden.platform.web.centre.CentreUpdater.getDefaultCentre; import static ua.com.fielden.platform.web.centre.api.actions.impl.EntityActionBuilder.action; import static ua.com.fielden.platform.web.centre.api.context.impl.EntityCentreContextSelector.context; import static ua.com.fielden.platform.web.minijs.JsCode.jsCode; +import static ua.com.fielden.platform.web.resources.webui.AppIndexResource.FILE_APP_INDEX_HTML; import static ua.com.fielden.platform.web.resources.webui.CentreResourceUtils.SAVE_OWN_COPY_MSG; import static ua.com.fielden.platform.web.resources.webui.FileResource.generateFileName; +import static ua.com.fielden.platform.web.resources.webui.LoginInitiateResetResource.FILE_APP_LOGIN_INITIATE_RESET_HTML; import static ua.com.fielden.platform.web.view.master.api.actions.impl.MasterActionOptions.ALL_OFF; /** @@ -78,6 +83,8 @@ public abstract class AbstractWebUiConfig implements IWebUiConfig { private static final String ERR_IN_COMPOUND_EMITTER = "Event source compound emitter should have cought this error. Something went wrong in WebUiConfig."; private static final String CREATE_DEFAULT_CONFIG_INFO = "Creating default configurations for [%s]-typed centres (caching)..."; private static final int DEFAULT_EXTERNAL_SITE_EXPIRY_DAYS = 183; + /// Name for a constant in generated `tg-app-template` containing imports from main menu actions. + private static final String MAIN_MENU_ACTION_IMPORTS = "mainMenuActionImports"; private final String title; private final Optional ideaUri; @@ -97,7 +104,7 @@ public abstract class AbstractWebUiConfig implements IWebUiConfig { */ private final List resourcePaths; private final Workflows workflow; - private final Map checksums; + private final SequencedMap checksums; private final boolean independentTimeZone; private final MasterActionOptions masterActionOptions; @@ -268,9 +275,18 @@ public final String genWebUiPreferences() { return webUiBuilder.genWebUiPrefComponent(); } + /// [CombinedJsImports] from main menu actions on both desktop / mobile configurations. + private CombinedJsImports mainMenuActionImports() { + final var combinedImports = new CombinedJsImports(); + combinedImports.addAll(desktopMainMenuConfig.mainMenuActionImports()); + combinedImports.addAll(mobileMainMenuConfig.mainMenuActionImports()); + return combinedImports; + } + @Override public final String genMainWebUIComponent() { - final String mainWebUiComponent = ResourceLoader.getText("ua/com/fielden/platform/web/app/tg-app-template.js"); + final String mainWebUiComponent = requireNonNull(getText("ua/com/fielden/platform/web/app/tg-app-template.js")) + .replace("@%s".formatted(MAIN_MENU_ACTION_IMPORTS), mainMenuActionImports().toStringWith(MAIN_MENU_ACTION_IMPORTS)); if (Workflows.deployment == workflow || Workflows.vulcanizing == workflow) { return mainWebUiComponent.replace("//@use-empty-console.log", "console.log = () => {};\n"); } else { @@ -375,6 +391,20 @@ public Optional checksum(final String resourceURI) { return ofNullable(checksums.get(resourceURI)); } + @Override + public SequencedSet deploymentResourcePaths() { + return checksums.keySet().stream() + .map(path -> switch (path) { + // Only two deployment resources are generated and have special binding paths. + // See `VulcanizingUtility.vulcanize` for full list. + // All generated paths start with `/app/...`, but only two of them is outside main vulcanised file. + case FILE_APP_INDEX_HTML -> AppIndexResource.BINDING_PATH; + case FILE_APP_LOGIN_INITIATE_RESET_HTML -> LoginInitiateResetResource.BINDING_PATH; + default -> path; + }) + .collect(toCollection(LinkedHashSet::new)); + } + @Override public boolean independentTimeZone() { return independentTimeZone; diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/AppIndexResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/AppIndexResource.java index c63254c7968..84e437b2ce0 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/AppIndexResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/AppIndexResource.java @@ -1,19 +1,12 @@ package ua.com.fielden.platform.web.resources.webui; -import static org.restlet.data.MediaType.TEXT_HTML; -import static ua.com.fielden.platform.web.resources.webui.FileResource.createRepresentation; - -import java.lang.management.ManagementFactory; - import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; import org.restlet.representation.Representation; import org.restlet.resource.Get; - import ua.com.fielden.platform.basic.config.Workflows; import ua.com.fielden.platform.criteria.generator.ICriteriaGenerator; -import ua.com.fielden.platform.criteria.generator.impl.CriteriaGenerator; import ua.com.fielden.platform.security.user.IUserProvider; import ua.com.fielden.platform.security.user.User; import ua.com.fielden.platform.utils.IDates; @@ -21,6 +14,16 @@ import ua.com.fielden.platform.web.app.IWebUiConfig; import ua.com.fielden.platform.web.interfaces.IDeviceProvider; +import java.io.ByteArrayInputStream; +import java.lang.management.ManagementFactory; + +import static com.google.common.base.Charsets.UTF_8; +import static java.lang.String.join; +import static org.restlet.data.MediaType.TEXT_HTML; +import static org.restlet.data.MediaType.TEXT_URI_LIST; +import static ua.com.fielden.platform.web.resources.RestServerUtil.encodedRepresentation; +import static ua.com.fielden.platform.web.resources.webui.FileResource.createRepresentation; + /** * Responds to GET request with a generated application specific index resource (for desktop and mobile web apps). *

@@ -30,6 +33,11 @@ * */ public class AppIndexResource extends AbstractWebResource { + public static final String BINDING_PATH = "/"; + public static final String FILE_APP_INDEX_HTML = "/app/tg-app-index.html"; + private static final String RESOURCES_URL_SUFFIX = "?resources=true"; + private static final String RESOURCES_DELIMITER = "\n"; + private final IWebUiConfig webUiConfig; private final IUserProvider userProvider; private final IWebResourceLoader webResourceLoader; @@ -69,7 +77,12 @@ public Representation get() { webUiConfig.clearConfiguration(); webUiConfig.initConfiguration(); } - return createRepresentation(webResourceLoader, TEXT_HTML, "/app/tg-app-index.html", getReference().getRemainingPart()); + // Handle special Service Worker '?resources=true' GET request against `AppIndexResource` (aka '/'). + if (getReference().getRemainingPart().endsWith(RESOURCES_URL_SUFFIX)) { + return encodedRepresentation(new ByteArrayInputStream(join(RESOURCES_DELIMITER, webResourceLoader.deploymentResourcePaths()).getBytes(UTF_8)), TEXT_URI_LIST); + } + // Handle actual `AppIndexResource` generated file (see 'index.html'). + return createRepresentation(webResourceLoader, TEXT_HTML, FILE_APP_INDEX_HTML, getReference().getRemainingPart()); } /** diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/FileResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/FileResource.java index 184074ec0f1..b47ae1ddcee 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/FileResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/FileResource.java @@ -31,6 +31,7 @@ * */ public class FileResource extends AbstractWebResource { + public static final String CHECKSUM_URL_SUFFIX = "?checksum=true"; private final List resourcePaths; private final IWebResourceLoader webResourceLoader; @@ -74,7 +75,7 @@ public Representation load() { * @return */ private static Representation returnChecksumRepresentationOr(final Supplier createRepresentation, final IWebResourceLoader webResourceLoader, final MediaType mediaType, final String path, final String remainingPart) { - if (remainingPart.endsWith("?checksum=true")) { + if (remainingPart.endsWith(CHECKSUM_URL_SUFFIX)) { return encodedRepresentation(new ByteArrayInputStream(webResourceLoader.checksum(path).orElse("").getBytes(UTF_8)), mediaType); } else { return createRepresentation.get(); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/UserRoleWebUiConfig.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/UserRoleWebUiConfig.java index 03203ba23aa..99914266743 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/UserRoleWebUiConfig.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/UserRoleWebUiConfig.java @@ -1,22 +1,6 @@ package ua.com.fielden.platform.web.resources.webui; -import static java.lang.String.format; -import static java.util.Optional.empty; -import static ua.com.fielden.platform.entity.AbstractEntity.DESC; -import static ua.com.fielden.platform.entity.AbstractEntity.KEY; -import static ua.com.fielden.platform.entity.ActivatableAbstractEntity.ACTIVE; -import static ua.com.fielden.platform.web.PrefDim.mkDim; -import static ua.com.fielden.platform.web.action.pre.ConfirmationPreAction.okCancel; -import static ua.com.fielden.platform.web.centre.api.actions.impl.EntityActionBuilder.action; -import static ua.com.fielden.platform.web.centre.api.context.impl.EntityCentreContextSelector.context; -import static ua.com.fielden.platform.web.layout.api.impl.LayoutBuilder.cell; -import static ua.com.fielden.platform.web.layout.api.impl.LayoutCellBuilder.layout; -import static ua.com.fielden.platform.web.layout.api.impl.LayoutComposer.mkActionLayoutForMaster; - -import java.util.Optional; - import com.google.inject.Injector; - import ua.com.fielden.platform.entity.ActivatableAbstractEntity; import ua.com.fielden.platform.entity.EntityDeleteAction; import ua.com.fielden.platform.entity.EntityEditAction; @@ -28,7 +12,6 @@ import ua.com.fielden.platform.ui.menu.sample.MiUserRole; import ua.com.fielden.platform.web.PrefDim.Unit; import ua.com.fielden.platform.web.action.CentreConfigurationWebUiConfig.CentreConfigActions; -import ua.com.fielden.platform.web.action.pre.EntityNavigationPreAction; import ua.com.fielden.platform.web.app.config.IWebUiBuilder; import ua.com.fielden.platform.web.centre.EntityCentre; import ua.com.fielden.platform.web.centre.api.actions.EntityActionConfig; @@ -41,6 +24,22 @@ import ua.com.fielden.platform.web.view.master.api.actions.MasterActions; import ua.com.fielden.platform.web.view.master.api.impl.SimpleMasterBuilder; +import java.util.Optional; + +import static java.lang.String.format; +import static java.util.Optional.empty; +import static ua.com.fielden.platform.entity.AbstractEntity.DESC; +import static ua.com.fielden.platform.entity.AbstractEntity.KEY; +import static ua.com.fielden.platform.entity.ActivatableAbstractEntity.ACTIVE; +import static ua.com.fielden.platform.web.PrefDim.mkDim; +import static ua.com.fielden.platform.web.action.pre.PreActions.entityNavigation; +import static ua.com.fielden.platform.web.action.pre.PreActions.okCancel; +import static ua.com.fielden.platform.web.centre.api.actions.impl.EntityActionBuilder.action; +import static ua.com.fielden.platform.web.centre.api.context.impl.EntityCentreContextSelector.context; +import static ua.com.fielden.platform.web.layout.api.impl.LayoutBuilder.cell; +import static ua.com.fielden.platform.web.layout.api.impl.LayoutCellBuilder.layout; +import static ua.com.fielden.platform.web.layout.api.impl.LayoutComposer.mkActionLayoutForMaster; + /** * {@link UserRole} Web UI configuration. * @@ -177,7 +176,7 @@ public EntityActionConfig mkAction() { public EntityActionConfig mkAction() { return action(EntityEditAction.class) .withContext(context().withCurrentEntity().withSelectionCrit().build()) - .preAction(new EntityNavigationPreAction("User Role")) + .preAction(entityNavigation("User Role")) .icon("editor:mode-edit") .shortDesc("Edit User Role") .longDesc("Opens master for User Role editing.") diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/UserWebUiConfig.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/UserWebUiConfig.java index 60456bf7cea..ae3312a4491 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/UserWebUiConfig.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/UserWebUiConfig.java @@ -1,33 +1,12 @@ package ua.com.fielden.platform.web.resources.webui; -import static java.util.Optional.empty; -import static ua.com.fielden.platform.dao.AbstractOpenCompoundMasterDao.enhanceEmbededCentreQuery; -import static ua.com.fielden.platform.entity.ActivatableAbstractEntity.ACTIVE; -import static ua.com.fielden.platform.entity_centre.review.DynamicQueryBuilder.createConditionProperty; -import static ua.com.fielden.platform.security.user.User.EMAIL; -import static ua.com.fielden.platform.security.user.User.SSO_ONLY; -import static ua.com.fielden.platform.web.PrefDim.mkDim; -import static ua.com.fielden.platform.web.action.pre.ConfirmationPreAction.okCancel; -import static ua.com.fielden.platform.web.centre.api.actions.impl.EntityActionBuilder.action; -import static ua.com.fielden.platform.web.centre.api.context.impl.EntityCentreContextSelector.context; -import static ua.com.fielden.platform.web.layout.api.impl.LayoutComposer.mkActionLayoutForMaster; -import static ua.com.fielden.platform.web.test.server.config.LocatorFactory.mkLocator; - -import java.util.Optional; - import com.google.inject.Injector; - import ua.com.fielden.platform.entity.EntityDeleteAction; import ua.com.fielden.platform.entity.EntityEditAction; import ua.com.fielden.platform.entity.EntityNewAction; import ua.com.fielden.platform.entity.query.fluent.EntityQueryProgressiveInterfaces.ICompleted; import ua.com.fielden.platform.entity.query.fluent.EntityQueryProgressiveInterfaces.IWhere0; -import ua.com.fielden.platform.security.user.ReUser; -import ua.com.fielden.platform.security.user.User; -import ua.com.fielden.platform.security.user.UserAndRoleAssociation; -import ua.com.fielden.platform.security.user.UserRole; -import ua.com.fielden.platform.security.user.UserRolesUpdater; -import ua.com.fielden.platform.security.user.UserRolesUpdaterProducer; +import ua.com.fielden.platform.security.user.*; import ua.com.fielden.platform.security.user.locator.UserLocator; import ua.com.fielden.platform.security.user.master.menu.actions.UserMaster_OpenMain_MenuItem; import ua.com.fielden.platform.security.user.master.menu.actions.UserMaster_OpenUserAndRoleAssociation_MenuItem; @@ -39,7 +18,6 @@ import ua.com.fielden.platform.web.PrefDim; import ua.com.fielden.platform.web.PrefDim.Unit; import ua.com.fielden.platform.web.action.CentreConfigurationWebUiConfig.CentreConfigActions; -import ua.com.fielden.platform.web.action.pre.EntityNavigationPreAction; import ua.com.fielden.platform.web.app.config.IWebUiBuilder; import ua.com.fielden.platform.web.centre.CentreContext; import ua.com.fielden.platform.web.centre.EntityCentre; @@ -57,6 +35,22 @@ import ua.com.fielden.platform.web.view.master.api.compound.impl.CompoundMasterBuilder; import ua.com.fielden.platform.web.view.master.api.impl.SimpleMasterBuilder; +import java.util.Optional; + +import static java.util.Optional.empty; +import static ua.com.fielden.platform.dao.AbstractOpenCompoundMasterDao.enhanceEmbededCentreQuery; +import static ua.com.fielden.platform.entity.ActivatableAbstractEntity.ACTIVE; +import static ua.com.fielden.platform.entity_centre.review.DynamicQueryBuilder.createConditionProperty; +import static ua.com.fielden.platform.security.user.User.EMAIL; +import static ua.com.fielden.platform.security.user.User.SSO_ONLY; +import static ua.com.fielden.platform.web.PrefDim.mkDim; +import static ua.com.fielden.platform.web.action.pre.PreActions.entityNavigation; +import static ua.com.fielden.platform.web.action.pre.PreActions.okCancel; +import static ua.com.fielden.platform.web.centre.api.actions.impl.EntityActionBuilder.action; +import static ua.com.fielden.platform.web.centre.api.context.impl.EntityCentreContextSelector.context; +import static ua.com.fielden.platform.web.layout.api.impl.LayoutComposer.mkActionLayoutForMaster; +import static ua.com.fielden.platform.web.test.server.config.LocatorFactory.mkLocator; + /** * {@link User} Web UI configuration. * @@ -265,7 +259,7 @@ public EntityActionConfig mkAction() { public EntityActionConfig mkAction() { return action(EntityEditAction.class) .withContext(context().withCurrentEntity().withSelectionCrit().build()) - .preAction(new EntityNavigationPreAction("User")) + .preAction(entityNavigation("User")) .icon("editor:mode-edit") .shortDesc("Edit User") .longDesc("Opens master for User editing.") diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/security/AbstractWebResourceGuard.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/security/AbstractWebResourceGuard.java index b501141ab69..27b3c8018f0 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/security/AbstractWebResourceGuard.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/security/AbstractWebResourceGuard.java @@ -1,11 +1,6 @@ package ua.com.fielden.platform.web.security; -import static java.lang.String.format; -import static ua.com.fielden.platform.security.session.Authenticator.fromString; -import static ua.com.fielden.platform.web.resources.webui.LoginResource.BINDING_PATH; - -import java.util.Optional; - +import com.google.inject.Injector; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -16,9 +11,6 @@ import org.restlet.data.CookieSetting; import org.restlet.data.Method; import org.restlet.data.Status; - -import com.google.inject.Injector; - import ua.com.fielden.platform.security.exceptions.SecurityException; import ua.com.fielden.platform.security.session.Authenticator; import ua.com.fielden.platform.security.session.IUserSession; @@ -27,6 +19,13 @@ import ua.com.fielden.platform.utils.IUniversalConstants; import ua.com.fielden.platform.web.sse.SseUtils; +import java.util.Optional; + +import static java.lang.String.format; +import static ua.com.fielden.platform.security.session.Authenticator.fromString; +import static ua.com.fielden.platform.web.resources.webui.FileResource.CHECKSUM_URL_SUFFIX; +import static ua.com.fielden.platform.web.resources.webui.LoginResource.BINDING_PATH; + /** * This is a guard that is based on the new TG authentication scheme, developed as part of the Web UI initiative. It it used to restrict access to sensitive web resources. *

@@ -127,7 +126,7 @@ public boolean authenticate(final Request request, final Response response) { protected void redirectGetToLoginOrForbid(final Request request, final Response response) { // GET requests can be redirected to the login resource, which takes care of both RSO and SSO workflows. // Need to forbid requests from SW containing "?checksum=true", which is specifically used to redirect to /login from the client side. - if (Method.GET.equals(request.getMethod()) && !request.getResourceRef().toString().contains("?checksum=true")) { + if (Method.GET.equals(request.getMethod()) && !request.getResourceRef().toString().contains(CHECKSUM_URL_SUFFIX)) { response.redirectTemporary(BINDING_PATH); } else { forbid(response); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/WebUiConfig.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/WebUiConfig.java index 1c245fa0f14..d84a3c77517 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/WebUiConfig.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/WebUiConfig.java @@ -30,9 +30,6 @@ import ua.com.fielden.platform.web.PrefDim.Unit; import ua.com.fielden.platform.web.action.CentreConfigurationWebUiConfig.CentreConfigActions; import ua.com.fielden.platform.web.action.StandardMastersWebUiConfig; -import ua.com.fielden.platform.web.action.post.BindSavedPropertyPostActionError; -import ua.com.fielden.platform.web.action.post.BindSavedPropertyPostActionSuccess; -import ua.com.fielden.platform.web.action.post.FileSaverPostAction; import ua.com.fielden.platform.web.app.IWebUiConfig; import ua.com.fielden.platform.web.app.config.IWebUiBuilder; import ua.com.fielden.platform.web.centre.CentreContext; @@ -89,13 +86,15 @@ import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.fetchOnly; import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.select; import static ua.com.fielden.platform.reflection.TitlesDescsGetter.getEntityTitleAndDesc; +import static ua.com.fielden.platform.sample.domain.ui_actions.MakeCompletedAction.Property.masterEntity; import static ua.com.fielden.platform.types.tuples.T2.t2; import static ua.com.fielden.platform.utils.Pair.pair; import static ua.com.fielden.platform.web.PrefDim.mkDim; import static ua.com.fielden.platform.web.action.StandardMastersWebUiConfig.MASTER_ACTION_DEFAULT_WIDTH; import static ua.com.fielden.platform.web.action.StandardMastersWebUiConfig.MASTER_ACTION_SPECIFICATION; -import static ua.com.fielden.platform.web.action.pre.ConfirmationPreAction.okCancel; -import static ua.com.fielden.platform.web.action.pre.ConfirmationPreAction.yesNo; +import static ua.com.fielden.platform.web.action.post.PostActions.*; +import static ua.com.fielden.platform.web.action.pre.PreActions.okCancel; +import static ua.com.fielden.platform.web.action.pre.PreActions.yesNo; import static ua.com.fielden.platform.web.centre.api.actions.impl.EntityActionBuilder.action; import static ua.com.fielden.platform.web.centre.api.actions.impl.EntityActionBuilder.editAction; import static ua.com.fielden.platform.web.centre.api.actions.multi.EntityMultiActionConfigBuilder.multiAction; @@ -651,8 +650,8 @@ public void initConfiguration() { .addAction(action(MakeCompletedAction.class) .withContext(context().withMasterEntity().build()) // .postActionSuccess(() -> new JsCode(new BindSavedPropertyPostActionSuccess("masterEntity").build().toString() + "self.publishCloseForcibly();")) // use this for additional manual testing of forced closing - .postActionSuccess(new BindSavedPropertyPostActionSuccess("masterEntity")) - .postActionError(new BindSavedPropertyPostActionError("masterEntity")) + .postActionSuccess(bindSavedProperty(masterEntity)) + .postActionError(bindSavedPropertyError(masterEntity)) .shortDesc("Complete") .longDesc("Complete this entity.") .build() @@ -1504,7 +1503,7 @@ public JsCode build() { action(ExportAction.class). withContext(context().withSelectionCrit().withSelectedEntities().build()) .preAction(yesNo("Would you like to proceed with data export?")) - .postActionSuccess(new FileSaverPostAction()) + .postActionSuccess(saveFile()) .icon("icons:save") .shortDesc("Export Data") .build() @@ -1518,7 +1517,7 @@ public JsCode build() { context().withSelectionCrit().withSelectedEntities().withMasterEntity().build()).build()) .extendWithInsertionPointContext(TgCentreInvokerWithCentreContext.class, context().withSelectionCrit().withSelectedEntities().withMasterEntity().build()).build()) - .postActionSuccess(new FileSaverPostAction()) + .postActionSuccess(saveFile()) .icon("icons:save") .shortDesc("Export Data") .withNoParentCentreRefresh() diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/config/StandardActions.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/config/StandardActions.java index f42283a1d7a..6d94d05bc2b 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/config/StandardActions.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/config/StandardActions.java @@ -1,34 +1,27 @@ package ua.com.fielden.platform.web.test.server.config; -import static java.lang.String.format; -import static ua.com.fielden.platform.reflection.TitlesDescsGetter.getEntityTitleAndDesc; -import static ua.com.fielden.platform.web.action.pre.ConfirmationPreAction.okCancel; -import static ua.com.fielden.platform.web.centre.api.actions.impl.EntityActionBuilder.action; -import static ua.com.fielden.platform.web.centre.api.context.impl.EntityCentreContextSelector.context; -import static ua.com.fielden.platform.web.test.server.config.StandardActionsStyles.STANDARD_ACTION_COLOUR; -import static ua.com.fielden.platform.web.test.server.config.StandardMessages.DELETE_CONFIRMATION; - -import java.util.Optional; -import java.util.function.BiFunction; - -import ua.com.fielden.platform.entity.AbstractEntity; -import ua.com.fielden.platform.entity.AbstractFunctionalEntityWithCentreContext; -import ua.com.fielden.platform.entity.EntityDeleteAction; -import ua.com.fielden.platform.entity.EntityEditAction; -import ua.com.fielden.platform.entity.EntityExportAction; -import ua.com.fielden.platform.entity.EntityNewAction; +import ua.com.fielden.platform.entity.*; import ua.com.fielden.platform.reflection.TitlesDescsGetter; import ua.com.fielden.platform.web.PrefDim; import ua.com.fielden.platform.web.action.exceptions.ActionConfigurationException; -import ua.com.fielden.platform.web.action.post.FileSaverPostAction; -import ua.com.fielden.platform.web.action.pre.EntityNavigationPreAction; -import ua.com.fielden.platform.web.action.pre.SequentialEditPreAction; import ua.com.fielden.platform.web.centre.CentreContext; import ua.com.fielden.platform.web.centre.api.actions.EntityActionConfig; import ua.com.fielden.platform.web.centre.api.context.IEntityCentreContextSelectorDone; import ua.com.fielden.platform.web.minijs.JsCode; import ua.com.fielden.platform.web.view.master.api.actions.pre.IPreAction; +import java.util.Optional; +import java.util.function.BiFunction; + +import static java.lang.String.format; +import static ua.com.fielden.platform.reflection.TitlesDescsGetter.getEntityTitleAndDesc; +import static ua.com.fielden.platform.web.action.post.PostActions.saveFile; +import static ua.com.fielden.platform.web.action.pre.PreActions.*; +import static ua.com.fielden.platform.web.centre.api.actions.impl.EntityActionBuilder.action; +import static ua.com.fielden.platform.web.centre.api.context.impl.EntityCentreContextSelector.context; +import static ua.com.fielden.platform.web.test.server.config.StandardActionsStyles.STANDARD_ACTION_COLOUR; +import static ua.com.fielden.platform.web.test.server.config.StandardMessages.DELETE_CONFIRMATION; + /** * Enumeration of standard UI action configurations that can be uniformly used throughout Web UI configuration for different entities. * @@ -179,7 +172,7 @@ private EntityActionConfig mkAction(final Class> ent return action(EntityEditAction.class) .withContext(contextConfig.build()) - .preAction(new EntityNavigationPreAction(entityTitle)) + .preAction(entityNavigation(entityTitle)) .icon(iconName.orElse("editor:mode-edit")) .withStyle(iconStyle.orElse(STANDARD_ACTION_COLOUR)) .shortDesc(format("Edit %s", entityTitle)) @@ -238,7 +231,7 @@ private EntityActionConfig mkAction(final Class> ent return action(EntityEditAction.class) .withContext(contextConfig.build()) - .preAction(new SequentialEditPreAction()) + .preAction(sequentialEdit()) .icon(iconName.orElse("editor:mode-edit")) .withStyle(iconStyle.orElse(STANDARD_ACTION_COLOUR)) .shortDesc(format("Edit %s", entityTitle)) @@ -351,7 +344,7 @@ private EntityActionConfig mkAction(final Class> ent return action(EntityExportAction.class) .withContext(contextConfig.build()) - .postActionSuccess(new FileSaverPostAction()) + .postActionSuccess(saveFile()) .icon(iconName.orElse("icons:save")) .withStyle(iconStyle.orElse(STANDARD_ACTION_COLOUR)) .shortDesc(desc) @@ -407,7 +400,7 @@ private EntityActionConfig mkAction(final Class> ent return action(EntityExportAction.class) .withContext(contextConfig.build()) - .postActionSuccess(new FileSaverPostAction()) + .postActionSuccess(saveFile()) .icon(iconName.orElse("icons:save")) .withStyle(iconStyle.orElse(STANDARD_ACTION_COLOUR)) .shortDesc(desc) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/config/TgGeneratedEntityForTrippleDecAnalysisWebUiConfig.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/config/TgGeneratedEntityForTrippleDecAnalysisWebUiConfig.java index 0809c7dae60..cc16185df97 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/config/TgGeneratedEntityForTrippleDecAnalysisWebUiConfig.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/config/TgGeneratedEntityForTrippleDecAnalysisWebUiConfig.java @@ -38,7 +38,7 @@ import static java.lang.String.format; import static ua.com.fielden.platform.entity.IContextDecomposer.decompose; import static ua.com.fielden.platform.web.PrefDim.mkDim; -import static ua.com.fielden.platform.web.action.pre.ConfirmationPreAction.okCancel; +import static ua.com.fielden.platform.web.action.pre.PreActions.okCancel; import static ua.com.fielden.platform.web.centre.api.actions.impl.EntityActionBuilder.action; import static ua.com.fielden.platform.web.centre.api.context.impl.EntityCentreContextSelector.context; import static ua.com.fielden.platform.web.layout.api.impl.LayoutBuilder.cell; diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/vulcanizer/VulcanizingUtility.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/vulcanizer/VulcanizingUtility.java index 9e3049a45e7..12f99e1fd4f 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/vulcanizer/VulcanizingUtility.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/vulcanizer/VulcanizingUtility.java @@ -20,6 +20,7 @@ import java.util.function.Function; import java.util.stream.Stream; +import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT; import static java.io.File.pathSeparator; import static java.lang.String.format; import static java.lang.System.arraycopy; @@ -33,6 +34,8 @@ import static ua.com.fielden.platform.cypher.Checksum.sha256; import static ua.com.fielden.platform.types.tuples.T3.t3; import static ua.com.fielden.platform.utils.CollectionUtil.listOf; +import static ua.com.fielden.platform.web.resources.webui.AppIndexResource.FILE_APP_INDEX_HTML; +import static ua.com.fielden.platform.web.resources.webui.LoginInitiateResetResource.FILE_APP_LOGIN_INITIATE_RESET_HTML; /** * A set of utilities to facilitate Web UI application vulcanization. @@ -145,21 +148,20 @@ public static void vulcanize( LOGGER.info(format("\tGenerating checksums...")); final List allExternalResources = listOf( - "/app/tg-app-index.html", + FILE_APP_INDEX_HTML, // '/', see AppIndexResource.BINDING_PATH "/resources/startup-resources-vulcanized.js", "/resources/polymer/@webcomponents/webcomponentsjs/webcomponents-bundle.js", "/resources/polymer/web-animations-js/web-animations-next-lite.min.js", - "/resources/filesaver/FileSaver.min.js", "/resources/manifest.webmanifest", "/resources/icons/tg-icon192x192.png", "/resources/icons/tg-icon144x144.png", - // Other page trees (logout.html, login.html, login-initiate-reset.html, login-initiated-reset.html). + // Other page trees (logout.html, login.html, login-initiate-reset.html ('/forgotten'), login-initiated-reset.html). // Please note that login.html cannot go through service worker caching due to the need to redirect to index.html when authenticator appears. // And logout.html cannot go through service worker to support request redirection during Single Log-Out lifecycle. "/resources/zxcvbn/zxcvbn.js", "/resources/login-startup-resources-vulcanized.js", "/resources/icons/tg-icon.png", - "/app/login-initiate-reset.html", + FILE_APP_LOGIN_INITIATE_RESET_HTML, // '/forgotten', see LoginInitiateResetResource.BINDING_PATH "/resources/login-initiated-reset.html", "/resources/graphiql/graphiql.min.css", "/resources/graphiql/react.production.min.js", @@ -192,6 +194,7 @@ public static void vulcanize( final Map checksums = generateChecksums(allExternalResources.toArray(new String[0])); try { final ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.enable(INDENT_OUTPUT); objectMapper.writeValue(new File(mobileAndDesktopAppSpecificPath + prefix + "checksums.json"), checksums); } catch (final IOException ex) { final String msg = "Could not write checksum.json"; diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/GuardCentreRegenerationAction.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/GuardCentreRegenerationAction.java new file mode 100644 index 00000000000..51d7a292821 --- /dev/null +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/GuardCentreRegenerationAction.java @@ -0,0 +1,51 @@ +package ua.com.fielden.platform.web.action; + +import ua.com.fielden.platform.web.centre.EntityCentre; +import ua.com.fielden.platform.web.minijs.JsCode; +import ua.com.fielden.platform.web.view.master.api.actions.IAction; + +/// Represents standard JS code to be used in centre with generators that support _modified data regeneration_ prompt. +/// +/// Use it through [EntityCentre#injectCustomCodeOnAttach(JsCode)] passing `new ForceCentreRegenerationPostAction(...).build()` into it. +/// +/// Corresponding generator should return `Result.failure(forceRegenerationExceptionMessage)`. +/// This is the case where the user tries to regenerate already modified data. +/// +/// @author TG Team +public class GuardCentreRegenerationAction implements IAction { + private final String forceRegenerationExceptionMessage; + private final String confirmationQuestion; + + /// Creates standard [GuardCentreRegenerationAction] with custom `confirmationQuestion` and `forceRegenerationExceptionMessage`. + /// + /// @param forceRegenerationExceptionMessage exception message while trying to regenerate already modified data; + /// this appears as toast + /// @param confirmationQuestion this appears as dialog's message (to confirm or reject data regeneration); + /// it is provided with 'Yes' and 'No' buttons + public GuardCentreRegenerationAction(final String forceRegenerationExceptionMessage, final String confirmationQuestion) { + this.forceRegenerationExceptionMessage = forceRegenerationExceptionMessage; + this.confirmationQuestion = confirmationQuestion; + } + + @Override + public JsCode build() { + return new JsCode(""" + if (!self.old_postRun) { + self.old_postRun = self._postRun; + self._postRun = (function (criteriaEntity, newBindingEntity, result) { + self.old_postRun(criteriaEntity, newBindingEntity, result); + + if (criteriaEntity !== null && !criteriaEntity.isValidWithoutException() && criteriaEntity.exceptionOccurred() !== null && criteriaEntity.exceptionOccurred().message === '%s') { + self.confirm('%s', [{name:'Yes', confirm:true, autofocus:true}, {name:'No'}]).then(function () { + return self.run(undefined, undefined, true); // forceRegeneration is true (isAutoRunning and isSortingAction are undefined) + }, function () {}); // skip legal rejection of promise (when 'No' button has been pressed) + } + }).bind(self); + } + """.formatted( + forceRegenerationExceptionMessage, + confirmationQuestion + )); + } + +} diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/BindSavedPropertyPostAction.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/BindSavedPropertyPostAction.java new file mode 100644 index 00000000000..7ea9a016a54 --- /dev/null +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/BindSavedPropertyPostAction.java @@ -0,0 +1,46 @@ +package ua.com.fielden.platform.web.action.post; + +import ua.com.fielden.platform.processors.metamodel.IConvertableToPath; +import ua.com.fielden.platform.web.centre.api.actions.IEntityActionBuilder2; +import ua.com.fielden.platform.web.minijs.JsCode; +import ua.com.fielden.platform.web.minijs.JsImport; +import ua.com.fielden.platform.web.view.master.api.actions.post.IPostAction; + +import java.util.Set; + +import static java.util.Objects.requireNonNull; +import static java.util.Set.of; +import static ua.com.fielden.platform.web.minijs.JsCode.jsCode; +import static ua.com.fielden.platform.web.minijs.JsImport.namedImport; + +/// In case if functional entity saves its master entity, it is necessary to bind saved instance to its respective entity master. +/// For this purpose, use this [IPostAction] in [IEntityActionBuilder2#postActionSuccess(IPostAction)] call. +/// Or in [IEntityActionBuilder2#postActionError(IPostAction)] call with `erroneous == true` parameter. +/// +/// @author TG Team +public class BindSavedPropertyPostAction implements IPostAction { + protected final IConvertableToPath property; + protected final boolean erroneous; + + protected BindSavedPropertyPostAction(final IConvertableToPath property, final boolean erroneous) { + this.property = requireNonNull(property); + this.erroneous = erroneous; + } + + @Override + public Set importStatements() { + return of(namedImport("bindSavedProperty", "master/actions/tg-bind-saved-property")); + } + + @Deprecated(since = WARN_DEPRECATION_DANGEROUS_CODE_CONCATENATION_WITHOUT_IMPORTS) + @Override + public JsCode build() { + return jsCode(""" + bindSavedProperty(functionalEntity, '%s', self, %s); + """.formatted( + property.toPath(), + erroneous + )); + } + +} \ No newline at end of file diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/BindSavedPropertyPostActionError.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/BindSavedPropertyPostActionError.java deleted file mode 100644 index 51761d781b4..00000000000 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/BindSavedPropertyPostActionError.java +++ /dev/null @@ -1,33 +0,0 @@ -package ua.com.fielden.platform.web.action.post; - -import static ua.com.fielden.platform.web.action.post.BindSavedPropertyPostActionSuccess.createPostAction; - -import ua.com.fielden.platform.web.centre.api.actions.IEntityActionBuilder2; -import ua.com.fielden.platform.web.minijs.JsCode; -import ua.com.fielden.platform.web.view.master.api.actions.post.IPostAction; - -/** - * In case if functional entity saves its master entity, it is necessary to bind saved instance to its respective entity master. - * Use this {@link IPostAction} in {@link IEntityActionBuilder2#postActionError(IPostAction)} call. - * - * @author TG Team - * - */ -public class BindSavedPropertyPostActionError implements IPostAction { - private final String propertyName; - - /** - * Creates {@link BindSavedPropertyPostActionError} with {@code propertyName} indicating where master entity resides. - * - * @param propertyName - */ - public BindSavedPropertyPostActionError(final String propertyName) { - this.propertyName = propertyName; - } - - @Override - public JsCode build() { - return createPostAction(true, propertyName); - } - -} \ No newline at end of file diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/BindSavedPropertyPostActionSuccess.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/BindSavedPropertyPostActionSuccess.java deleted file mode 100644 index c737176be52..00000000000 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/BindSavedPropertyPostActionSuccess.java +++ /dev/null @@ -1,56 +0,0 @@ -package ua.com.fielden.platform.web.action.post; - -import static java.lang.String.format; - -import ua.com.fielden.platform.web.centre.api.actions.IEntityActionBuilder2; -import ua.com.fielden.platform.web.minijs.JsCode; -import ua.com.fielden.platform.web.view.master.api.actions.post.IPostAction; - -/** - * In case if functional entity saves its master entity, it is necessary to bind saved instance to its respective entity master. - * Use this {@link IPostAction} in {@link IEntityActionBuilder2#postActionSuccess(IPostAction)} call. - * - * @author TG Team - * - */ -public class BindSavedPropertyPostActionSuccess implements IPostAction { - private final String propertyName; - - /** - * Creates {@link BindSavedPropertyPostActionSuccess} with {@code propertyName} indicating where master entity resides. - * - * @param propertyName - */ - public BindSavedPropertyPostActionSuccess(final String propertyName) { - this.propertyName = propertyName; - } - - @Override - public JsCode build() { - return createPostAction(false, propertyName); - } - - /** - * Creates {@link IPostAction}. For {@code erroneous} one we also attaches 'exceptionOccurred' to the master entity. - * - * @param erroneous - * @return - */ - static JsCode createPostAction(final boolean erroneous, final String propertyName) { - return new JsCode(format("" - + "const parentMasterName = `tg-${functionalEntity.type().prop('%s').type()._simpleClassName()}-master`;\n" - + "const parentMaster = getParentAnd(self, parent => parent.matches(parentMasterName));\n" - + "const masterEntity = functionalEntity.get('%s');\n" - + (erroneous ? "parentMaster._provideExceptionOccurred(masterEntity, functionalEntity.exceptionOccurred());\n" : "") - // in successful case leave propertyActionIndices as previously; - // we are not able to calculate them in companion 'save' methods because multi-action selectors are UI concept; - // still, this temporal unsyncing is not a problem; - // this is because propertyActionIndices will be updated on parentMaster 'validate' process following immediately after postActionSuccess (see tg-ui-action._onExecuted.postSaved postal publish) - // in unsuccessful case it is even more important to leave propertyActionIndices as previously (not to clear them or something); - // this is because parentMaster update will not be performed and, in case of clearing, all actions on parentMaster will disappear (at this stage even non-multi action should have zero index) - + "parentMaster._postSavedDefault([masterEntity, { propertyActionIndices: parentMaster._propertyActionIndices }]);\n", - propertyName, propertyName - )); - } - -} \ No newline at end of file diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/CloseForciblyPostAction.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/CloseForciblyPostAction.java new file mode 100644 index 00000000000..9cb7fb9cca7 --- /dev/null +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/CloseForciblyPostAction.java @@ -0,0 +1,22 @@ +package ua.com.fielden.platform.web.action.post; + +import ua.com.fielden.platform.web.minijs.JsCode; +import ua.com.fielden.platform.web.view.master.api.actions.post.IPostAction; + +import static ua.com.fielden.platform.web.minijs.JsCode.jsCode; + +/// A standard [IPostAction] that closes parent Entity Master forcibly. +/// +/// @author TG Team +public class CloseForciblyPostAction implements IPostAction { + + protected CloseForciblyPostAction() {} + + @Override + public JsCode build() { + return jsCode(""" + self.publishCloseForcibly(); + """); + } + +} diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/FileDownloadPostAction.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/FileDownloadPostAction.java deleted file mode 100644 index 4011fbf7fe9..00000000000 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/FileDownloadPostAction.java +++ /dev/null @@ -1,32 +0,0 @@ -package ua.com.fielden.platform.web.action.post; - -import ua.com.fielden.platform.web.minijs.JsCode; -import ua.com.fielden.platform.web.view.master.api.actions.post.IPostAction; - -/** - * This is an alternative implementation to {@link FileSaverPostAction}, which depends on HTML5 feature a.download. - * - * @author TG Team - * - */ -public class FileDownloadPostAction implements IPostAction { - - @Override - public JsCode build() { - final JsCode jsCode = new JsCode( - "var byteCharacters = atob(functionalEntity.data);" - + "var byteNumbers = new Uint8Array(byteCharacters.length);\n" - + "for (var index = 0; index < byteCharacters.length; index++) {\n" - + " byteNumbers[index] = byteCharacters.charCodeAt(index);\n" - + "}\n" - + "var blob = new Blob([byteNumbers], {type: functionalEntity.mime});\n" - + "var url = URL.createObjectURL(blob);\n" - + "var a = document.createElement('a');\n" - + "a.setAttribute('href', url);\n" - + "a.setAttribute('download', functionalEntity.fileName);\n" - + "a.click();\n" - + "URL.revokeObjectURL(url)"); - return jsCode; - } - -} diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/FileSaverPostAction.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/FileSaverPostAction.java index 9d3b325b8e3..2e610a14943 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/FileSaverPostAction.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/FileSaverPostAction.java @@ -1,38 +1,39 @@ package ua.com.fielden.platform.web.action.post; import ua.com.fielden.platform.web.minijs.JsCode; +import ua.com.fielden.platform.web.minijs.JsImport; import ua.com.fielden.platform.web.view.master.api.actions.post.IPostAction; -/** - * A standard post-action that should be used for saving data to a local file. - * Its implementation depends on the contract that the underlying functional entity has properties: - *

    - *
  • mime -- a MIME type for the data being exported - *
  • fileName -- a file name including file extension where the data should be saved - *
  • data -- base64 string representing a binary array - *
- * - * See also an alternative implementation {@link FileDownloadPostAction}. - * - * @author TG Team - * - */ +import java.util.Set; + +import static java.util.Set.of; +import static ua.com.fielden.platform.web.minijs.JsCode.jsCode; +import static ua.com.fielden.platform.web.minijs.JsImport.namedImport; + +/// A standard [IPostAction] that should be used for saving data to a local file. +/// Its implementation depends on the contract that the underlying functional entity has properties: +/// - mime -- a MIME type for the data being exported +/// - fileName -- a file name including file extension where the data should be saved +/// - data -- base64 string representing a binary array +/// +/// Clears EGI selection by default (if performed in Entity Centre context). +/// +/// @author TG Team public class FileSaverPostAction implements IPostAction { + protected FileSaverPostAction() {} + + @Override + public Set importStatements() { + return of(namedImport("saveFile", "centre/actions/tg-save-file")); + } + + @Deprecated(since = WARN_DEPRECATION_DANGEROUS_CODE_CONCATENATION_WITHOUT_IMPORTS) @Override public JsCode build() { - final JsCode jsCode = new JsCode( - "const byteCharacters = atob(functionalEntity.data);" - + "const byteNumbers = new Uint8Array(byteCharacters.length);\n" - + "for (let index = 0; index < byteCharacters.length; index++) {\n" - + " byteNumbers[index] = byteCharacters.charCodeAt(index);\n" - + "}\n" - + "const data = new Blob([byteNumbers], {type: functionalEntity.mime});\n" - + "saveAs(data, functionalEntity.fileName);\n" - + "if (self.$.egi && self.$.egi.clearPageSelection) {\n" - + " self.$.egi.clearPageSelection();\n" - + "}\n"); - return jsCode; + return jsCode(""" + saveFile(functionalEntity, self); + """); } } diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/GuardCentreRegenerationPostAction.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/GuardCentreRegenerationPostAction.java deleted file mode 100644 index 4d1e8e5353e..00000000000 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/GuardCentreRegenerationPostAction.java +++ /dev/null @@ -1,52 +0,0 @@ -package ua.com.fielden.platform.web.action.post; - -import ua.com.fielden.platform.web.centre.EntityCentre; -import ua.com.fielden.platform.web.minijs.JsCode; -import ua.com.fielden.platform.web.view.master.api.actions.post.IPostAction; - -/** - * Represents standard JS code to be used in centre with generators that support modified data regeneration prompt. - *

- * Use it through {@link EntityCentre#injectCustomCodeOnAttach(JsCode)} passing new ForceCentreRegenerationPostAction(...).build() into it. - *

- * Please note, that corresponding generator should return Result.failure(forceRegenerationExceptionMessage) (to be used with this standard post action) - * when user tries to regenerate already modified data. - * - * @author TG Team - * - */ -public class GuardCentreRegenerationPostAction implements IPostAction { - private final String forceRegenerationExceptionMessage; - private final String confirmationQuestion; - - /** - * Creates standard {@link GuardCentreRegenerationPostAction} with custom confirmationQuestion and forceRegenerationExceptionMessage. - * - * @param forceRegenerationExceptionMessage -- exception message while trying to regenerate already modified data (this appears as toast) - * @param confirmationQuestion -- this appears as dialog's message (to confirm or reject data regeneration) and is provided with 'Yes' and 'No' buttons - */ - public GuardCentreRegenerationPostAction(final String forceRegenerationExceptionMessage, final String confirmationQuestion) { - this.forceRegenerationExceptionMessage = forceRegenerationExceptionMessage; - this.confirmationQuestion = confirmationQuestion; - } - - @Override - public JsCode build() { - final JsCode jsCode = new JsCode(String.format("" - + "if (!self.old_postRun) {\n" - + " self.old_postRun = self._postRun;\n" - + " self._postRun = (function (criteriaEntity, newBindingEntity, result) {\n" - + " self.old_postRun(criteriaEntity, newBindingEntity, result);\n" - + " \n" - + " if (criteriaEntity !== null && !criteriaEntity.isValidWithoutException() && criteriaEntity.exceptionOccurred() !== null && criteriaEntity.exceptionOccurred().message === '%s') {\n" - + " self.confirm('%s', [{name:'Yes', confirm:true, autofocus:true}, {name:'No'}]).then(function () {\n" - + " return self.run(undefined, undefined, true);\n" // forceRegeneration is true (isAutoRunning and isSortingAction are undefined) - + " }, function () {});\n" // skip legal rejection of promise (when 'No' button has been pressed) - + " }\n" - + " }).bind(self);\n" - + "}\n", - forceRegenerationExceptionMessage, confirmationQuestion)); - return jsCode; - } - -} diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/OpenLinkPostAction.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/OpenLinkPostAction.java new file mode 100644 index 00000000000..b0929a3a846 --- /dev/null +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/OpenLinkPostAction.java @@ -0,0 +1,45 @@ +package ua.com.fielden.platform.web.action.post; + +import ua.com.fielden.platform.processors.metamodel.IConvertableToPath; +import ua.com.fielden.platform.web.minijs.JsCode; +import ua.com.fielden.platform.web.minijs.JsImport; +import ua.com.fielden.platform.web.view.master.api.actions.post.IPostAction; + +import java.util.Set; + +import static java.util.Objects.requireNonNull; +import static java.util.Set.of; +import static ua.com.fielden.platform.web.minijs.JsCode.jsCode; +import static ua.com.fielden.platform.web.minijs.JsImport.namedImport; + +/// A standard post-action that should be used for opening a link from a property. +/// +/// Standard `'window.open'` opening can be susceptible to tab-napping. +/// So it is always recommended to use this post-action for user-entered links. +/// +/// See more in `'tg-polymer-utils.openLink'`. +/// +/// @author TG Team +public class OpenLinkPostAction implements IPostAction { + protected final IConvertableToPath property; + + protected OpenLinkPostAction(final IConvertableToPath property) { + this.property = requireNonNull(property); + } + + @Override + public Set importStatements() { + return of(namedImport("postActionLinkOpen", "components/actions/tg-post-link-opener")); + } + + @Deprecated(since = WARN_DEPRECATION_DANGEROUS_CODE_CONCATENATION_WITHOUT_IMPORTS) + @Override + public JsCode build() { + return jsCode(""" + postActionLinkOpen(functionalEntity.get('%s')); + """.formatted( + property.toPath() + )); + } + +} diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/PostActions.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/PostActions.java new file mode 100644 index 00000000000..6e25739995a --- /dev/null +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/post/PostActions.java @@ -0,0 +1,42 @@ +package ua.com.fielden.platform.web.action.post; + +import ua.com.fielden.platform.processors.metamodel.IConvertableToPath; +import ua.com.fielden.platform.web.view.master.api.actions.post.IPostAction; + +/// Factory for [IPostAction]s. +/// +/// @author TG Team +public interface PostActions { + + /// Creates [IPostAction] that binds saved `property` value to parent Entity Master. + static IPostAction bindSavedProperty(final IConvertableToPath property) { + return new BindSavedPropertyPostAction(property, false); + } + + /// Creates [IPostAction]`Error` that binds saved `property` value to parent Entity Master. + static IPostAction bindSavedPropertyError(final IConvertableToPath property) { + return new BindSavedPropertyPostAction(property, true); + } + + /// Creates [IPostAction] that should be used for saving data to a local file. + /// Its implementation depends on the contract that the underlying functional entity has properties: + /// - mime -- a MIME type for the data being exported + /// - fileName -- a file name including file extension where the data should be saved + /// - data -- base64 string representing a binary array + /// + /// Clears EGI selection by default (if performed in Entity Centre context). + static IPostAction saveFile() { + return new FileSaverPostAction(); + } + + /// Creates [IPostAction] that opens a link from [String] `property`. + static IPostAction openLink(final IConvertableToPath property) { + return new OpenLinkPostAction(property); + } + + /// Creates [IPostAction] that closes parent Entity Master forcibly. + static IPostAction closeForcibly() { + return new CloseForciblyPostAction(); + } + +} diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/ConfirmationPreAction.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/ConfirmationPreAction.java index a52bc9baceb..8d5d8a779ed 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/ConfirmationPreAction.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/ConfirmationPreAction.java @@ -1,69 +1,49 @@ package ua.com.fielden.platform.web.action.pre; -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.lang3.StringUtils; - import ua.com.fielden.platform.web.minijs.JsCode; import ua.com.fielden.platform.web.view.master.api.actions.pre.IPreAction; -/** - * A standard confirmation pre-action. - * - * @author TG Team - * - */ -public class ConfirmationPreAction implements IPreAction { +import java.util.ArrayList; +import java.util.List; - private final String message; - private final List buttons = new ArrayList<>(); +import static org.apache.commons.lang3.StringUtils.join; +import static ua.com.fielden.platform.web.minijs.JsCode.jsCode; - private enum ConfirmationButtons { +/// A standard confirmation [IPreAction] that allows proceeding / rejecting the whole action through simple dialog. +/// +/// @author TG Team +public class ConfirmationPreAction implements IPreAction { + protected final String message; + protected final List buttons = new ArrayList<>(); + protected enum ConfirmationButtons { YES("{name:'Yes', confirm:true, autofocus:true}"), NO("{name:'No'}"), OK("{name:'Ok', confirm:true, autofocus:true}"), CANCEL("{name:'Cancel'}"); + public final String code; - private final String code; - private ConfirmationButtons(final String code) { + ConfirmationButtons(final String code) { this.code = code; } } - private ConfirmationPreAction(final String message, final ConfirmationButtons... buttons) { + protected ConfirmationPreAction(final String message, final ConfirmationButtons... buttons) { this.message = message; - for (int buttonIndex = 0; buttonIndex < buttons.length; buttonIndex++) { - this.buttons.add(buttons[buttonIndex].code); + for (final ConfirmationButtons button : buttons) { + this.buttons.add(button.code); } } - - /** - * A convenient factory method to produce a confirmation dialog with buttons NO and YES. - * - * @param msg - * @return - */ - public static ConfirmationPreAction yesNo(final String msg) { - return new ConfirmationPreAction(msg, ConfirmationButtons.NO, ConfirmationButtons.YES); - } - - /** - * A convenient factory method to produce a confirmation dialog with buttons CANCEL and OK. - * - * @param msg - * @return - */ - public static ConfirmationPreAction okCancel(final String msg) { - return new ConfirmationPreAction(msg, ConfirmationButtons.CANCEL, ConfirmationButtons.OK); - } - @Override public JsCode build() { - return new JsCode("return self.confirm('" + this.message + "', [" + StringUtils.join(buttons, ",") + "])"); + return jsCode(""" + return self.confirm('%s', [%s]); + """.formatted( + message, + join(buttons, ",") + )); } } diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/EntityNavigationPreAction.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/EntityNavigationPreAction.java index 742b162b23b..e876e0b984b 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/EntityNavigationPreAction.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/EntityNavigationPreAction.java @@ -1,32 +1,28 @@ package ua.com.fielden.platform.web.action.pre; -import static java.lang.String.format; - import ua.com.fielden.platform.web.minijs.JsCode; import ua.com.fielden.platform.web.view.master.api.actions.pre.IPreAction; -/** - * This pre-action implementation should be used only with navigation or edit. - * - * @author TG Team - * - */ -public class EntityNavigationPreAction implements IPreAction { +import static ua.com.fielden.platform.web.minijs.JsCode.jsCode; - private final String navigationType; +/// A standard "entity navigation" [IPreAction] that allows navigation to previous / next / first / last entity. +/// It is only applicable on Entity Centre `Edit` actions. +/// +/// @author TG Team +public class EntityNavigationPreAction implements IPreAction { + protected final String navigationType; - /** - * Creates pre-action for action that allows to navigate to another entity without closing dialog, such action can work only on EGI. - * - * @param navigationType - type description that is used to inform user what type of entity is currently opened and is navigating. - */ - public EntityNavigationPreAction(final String navigationType) { + protected EntityNavigationPreAction(final String navigationType) { this.navigationType = navigationType; } @Override public JsCode build() { - return new JsCode(format("self.navigationPreAction(action, '%s');%n", navigationType)); + return jsCode(""" + self.navigationPreAction(action, '%s'); + """.formatted( + navigationType + )); } } diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/PreActions.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/PreActions.java new file mode 100644 index 00000000000..945b13846ab --- /dev/null +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/PreActions.java @@ -0,0 +1,42 @@ +package ua.com.fielden.platform.web.action.pre; + +import ua.com.fielden.platform.web.ref_hierarchy.ReferenceHierarchyPreAction; +import ua.com.fielden.platform.web.view.master.api.actions.pre.IPreAction; + +import static ua.com.fielden.platform.web.action.pre.ConfirmationPreAction.ConfirmationButtons.*; + +/// Factory for [IPreAction]s. +/// +/// @author TG Team +public interface PreActions { + + /// Creates [IPreAction] that opens a confirmation dialog with custom `msg` and buttons NO and YES. + static IPreAction yesNo(final String msg) { + return new ConfirmationPreAction(msg, NO, YES); + } + + /// Creates [IPreAction] that opens a confirmation dialog with custom `msg` and buttons CANCEL and OK. + static IPreAction okCancel(final String msg) { + return new ConfirmationPreAction(msg, CANCEL, OK); + } + + /// Creates [IPreAction] that allows further Entity Centre `Edit`ing for the next entity on successful `SAVE`. + static IPreAction sequentialEdit() { + return new SequentialEditPreAction(); + } + + /// Creates [IPreAction] for Entity Centre `Edit` actions that allows navigation to another entity without closing the dialog. + /// + /// @param navigationType type description to inform user what type of entity is currently opened and is navigating + static IPreAction entityNavigation(final String navigationType) { + return new EntityNavigationPreAction(navigationType); + } + + /// Creates [IPreAction] for Reference Hierarchy action. + /// + /// @param useMasterEntity indicates whether master entity should be used for processing + static IPreAction referenceHierarchy(final boolean useMasterEntity) { + return new ReferenceHierarchyPreAction(useMasterEntity); + } + +} diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/SequentialEditPreAction.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/SequentialEditPreAction.java index 8c9d978bfa5..93b863780de 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/SequentialEditPreAction.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/SequentialEditPreAction.java @@ -1,102 +1,34 @@ package ua.com.fielden.platform.web.action.pre; import ua.com.fielden.platform.web.minijs.JsCode; +import ua.com.fielden.platform.web.minijs.JsImport; import ua.com.fielden.platform.web.view.master.api.actions.pre.IPreAction; -/** - * This pre-action implementation should be used only with sequential edit action. - * - * @author TG Team - * - */ +import java.util.Set; + +import static java.util.Set.of; +import static ua.com.fielden.platform.web.minijs.JsCode.jsCode; +import static ua.com.fielden.platform.web.minijs.JsImport.namedImport; + +/// A standard "sequential editing" [IPreAction], that allows further `Edit`ing of the next entity on successful `SAVE`. +/// It is only applicable on Entity Centre `Edit` actions. +/// +/// @author TG Team public class SequentialEditPreAction implements IPreAction { + protected SequentialEditPreAction() {} + + @Override + public Set importStatements() { + return of(namedImport("sequentialEdit", "centre/actions/tg-sequential-edit")); + } + + @Deprecated(since = WARN_DEPRECATION_DANGEROUS_CODE_CONCATENATION_WITHOUT_IMPORTS) @Override public JsCode build() { - return new JsCode("\n" - + "if(!self.seqEditEntities && !self.$.egi.isEditing()) {\n" - + " if (self.$.egi.getSelectedEntities().length === 0) {\n" - + " self.$.egi.selectAll(true);\n" - + " }\n" - + " self.seqEditEntities = self.$.egi.getSelectedEntities();\n" - + " const firstEntity = self.seqEditEntities.shift();\n" - + " action.currentEntity = () => firstEntity;\n" - + " action._oldRestoreActionState = action.restoreActionState;\n" - + " action.restoreActionState = function () {\n" - + " action._oldRestoreActionState();\n" - + " cancelEditing();\n" - + " }.bind(self);\n" - + " const cancelEditing = (function (data) {\n" - + " delete this.seqEditEntities;\n" - + " this.seqEditSuccessPostal.unsubscribe();\n" - + " this.seqEditCancelPostal.unsubscribe();\n" - + " const master = action._masterReferenceForTesting;\n" - + " if (master) {\n" - + " master.publishCloseForcibly();\n" - + " }\n" - + " action.currentEntity = () => null;\n" - + " action.restoreActionState = action._oldRestoreActionState;\n" - + " }).bind(self);\n" - + " const updateCacheAndContinueSeqSaving = (function (shouldUnselect) {\n" - + " const nextEntity = this.seqEditEntities && this.seqEditEntities.shift();\n" - + " if (shouldUnselect !== false) {\n" - + " self.$.egi.selectEntity(action.currentEntity(), false);\n" - + " }\n" - + " if (nextEntity) {\n" - + " setEntityAndReload(nextEntity, shouldUnselect ? null : 'skipNext');\n" - + " } else {\n" - + " cancelEditing();\n" - + " }\n" - + " }).bind(self);\n" - + " const setEntityAndReload = function (entity, spinnerInvoked) {\n" - + " if (entity) {\n" - + " action.currentEntity = () => entity;\n" - + " const master = action._masterReferenceForTesting;\n" - + " if (master) {\n" - + " master.fire('tg-action-navigation-invoked', {spinner: spinnerInvoked});\n" - + " master.savingContext = action._createContextHolderForAction();\n" - + " master.retrieve(master.savingContext).then(function(ironRequest) {\n" - + " if (action.modifyFunctionalEntity) {\n" - + " action.modifyFunctionalEntity(master._currBindingEntity, master, action);\n" - + " }\n" - + " master.addEventListener('binding-entity-loaded-and-focused', restoreNavigationButtonState);\n" - + " master.save().then(function(value) {}, function (error) {\n" - + " fireNavigationChangeEvent(true);\n" - + " }.bind(self));\n" - + " }.bind(self), function (error) {\n" - + " fireNavigationChangeEvent(true);\n" - + " }.bind(self));\n" - + " }\n" - + " }\n" - + " }.bind(self),\n" - + " fireNavigationChangeEvent = function (shouldResetSpinner) {\n" - + " const master = action._masterReferenceForTesting;\n" - + " if (master) {\n" - + " master.fire('tg-action-navigation-changed', {\n" - + " shouldResetSpinner: shouldResetSpinner\n," - + " });\n" - + " }\n" - + " }.bind(self),\n" - + " restoreNavigationButtonState = function (e) {\n" - + " fireNavigationChangeEvent(false);\n" - + " const master = action._masterReferenceForTesting;\n" - + " master.removeEventListener('binding-entity-loaded-and-focused', restoreNavigationButtonState);\n" - + " }.bind(self);\n" - + " action.continuous = true;\n" - + " action.skipNext = function() {\n" - + " updateCacheAndContinueSeqSaving(false);\n" - + " };\n" - + " self.seqEditSuccessPostal = postal.subscribe({\n" - + " channel: self.uuid,\n" - + " topic: 'save.post.success',\n" - + " callback: updateCacheAndContinueSeqSaving\n" - + " }).defer();\n" - + " self.seqEditCancelPostal = postal.subscribe({\n" - + " channel: self.uuid,\n" - + " topic: 'refresh.post.success',\n" - + " callback: cancelEditing" - + " }).defer();\n" - + "}\n"); + return jsCode(""" + sequentialEdit(action, self); + """); } } diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/app/IWebResourceLoader.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/app/IWebResourceLoader.java index 1e24f09e723..fd46b8a5079 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/app/IWebResourceLoader.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/app/IWebResourceLoader.java @@ -2,6 +2,7 @@ import java.io.InputStream; import java.util.Optional; +import java.util.SequencedSet; /** * The contract for loading resources by their URIs. @@ -47,5 +48,11 @@ public interface IWebResourceLoader { * @return */ Optional checksum(final String resourceUri); - + + /** + * Returns a set of resource paths for deployment mode of an application. + * All these resources are cached through a Service Worker on a client. + */ + SequencedSet deploymentResourcePaths(); + } \ No newline at end of file diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/app/IWebUiConfig.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/app/IWebUiConfig.java index 0017c642f22..f9824844afc 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/app/IWebUiConfig.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/app/IWebUiConfig.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.SequencedSet; /** * Represent a contract for Web UI configuring. @@ -197,6 +198,12 @@ default boolean isEmbeddedCentreAndNotAllowCustomised(final Class checksum(final String resourceURI); + /** + * Returns a set of resource paths for deployment mode of an application. + * All these resources are cached through a Service Worker on a client. + */ + SequencedSet deploymentResourcePaths(); + /** * Returns true if server and client applications operate in the same time-zone, otherwise false. * The only exception is handling of 'now': it calculates based on real user time-zone (and later converts to server time-zone). diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/EntityCentre.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/EntityCentre.java index f568de2c174..b8debed76c4 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/EntityCentre.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/EntityCentre.java @@ -45,7 +45,10 @@ import ua.com.fielden.platform.web.app.IWebUiConfig; import ua.com.fielden.platform.web.app.exceptions.WebUiBuilderException; import ua.com.fielden.platform.web.centre.api.EntityCentreConfig; -import ua.com.fielden.platform.web.centre.api.EntityCentreConfig.*; +import ua.com.fielden.platform.web.centre.api.EntityCentreConfig.MatcherOptions; +import ua.com.fielden.platform.web.centre.api.EntityCentreConfig.OrderDirection; +import ua.com.fielden.platform.web.centre.api.EntityCentreConfig.ResultSetProp; +import ua.com.fielden.platform.web.centre.api.EntityCentreConfig.SummaryPropDef; import ua.com.fielden.platform.web.centre.api.ICentre; import ua.com.fielden.platform.web.centre.api.actions.EntityActionConfig; import ua.com.fielden.platform.web.centre.api.actions.multi.EntityMultiActionConfig; @@ -70,9 +73,13 @@ import ua.com.fielden.platform.web.interfaces.ILayout.Device; import ua.com.fielden.platform.web.interfaces.IRenderable; import ua.com.fielden.platform.web.layout.FlexLayout; +import ua.com.fielden.platform.web.minijs.CombinedJsImports; import ua.com.fielden.platform.web.minijs.JsCode; +import ua.com.fielden.platform.web.minijs.JsImport; import ua.com.fielden.platform.web.sse.IEventSource; import ua.com.fielden.platform.web.utils.EntityResourceUtils; +import ua.com.fielden.platform.web.view.master.api.actions.post.IPostAction; +import ua.com.fielden.platform.web.view.master.api.actions.pre.IPreAction; import java.math.BigDecimal; import java.util.*; @@ -864,7 +871,7 @@ public Optional> getRenderingCustomiser() { if (dslDefaultConfig.getResultSetRenderingCustomiserType().isPresent()) { return Optional.of(injector.getInstance(dslDefaultConfig.getResultSetRenderingCustomiserType().get())); } else { - return Optional.empty(); + return empty(); } } @@ -947,6 +954,7 @@ public ICentreDomainTreeManagerAndEnhancer createDefaultCentre() { private IRenderable createRenderableRepresentation(final ICentreDomainTreeManagerAndEnhancer centre) { final LinkedHashSet importPaths = new LinkedHashSet<>(); + final SortedSet actionImports = new CombinedJsImports(); importPaths.add("master/tg-entity-master"); logger.debug("Initiating layout..."); @@ -1025,6 +1033,7 @@ private IRenderable createRenderableRepresentation(final ICentreDomainTreeManage } column.getActions().forEach(action -> { importPaths.add(action.importPath()); + actionImports.addAll(action.actionImports()); propActionsObject.append(prefix + createActionObject(action)); }); egiColumns.add(column.render()); @@ -1058,6 +1067,7 @@ private IRenderable createRenderableRepresentation(final ICentreDomainTreeManage final DomElement groupElement = createActionGroupDom(i); for (final FunctionalActionElement el : actionGroups.get(i)) { importPaths.add(el.importPath()); + actionImports.addAll(el.actionImports()); groupElement.add(el.render()); functionalActionsObjects.append(prefix + createActionObject(el)); } @@ -1075,7 +1085,7 @@ private IRenderable createRenderableRepresentation(final ICentreDomainTreeManage importPaths.add(el.importPath()); primaryActionDom.add(el.render().attr("slot", "primary-action").attr("hidden", null)); - primaryActionObject.append(prefix + el.createActionObject(importPaths)); + primaryActionObject.append(prefix + el.createActionObject(importPaths, actionImports)); } ////////////////////Primary result-set action [END] ////////////// @@ -1088,6 +1098,7 @@ private IRenderable createRenderableRepresentation(final ICentreDomainTreeManage for (int actionIndex = 0; actionIndex < frontActions.size(); actionIndex++) { final FunctionalActionElement actionElement = new FunctionalActionElement(frontActions.get(actionIndex), actionIndex, FunctionalActionKind.FRONT); importPaths.add(actionElement.importPath()); + actionImports.addAll(actionElement.actionImports()); frontActionsDom.add(actionElement.render()); frontActionsObjects.append(prefix + createActionObject(actionElement)); } @@ -1102,6 +1113,7 @@ private IRenderable createRenderableRepresentation(final ICentreDomainTreeManage for (int actionIndex = 0; actionIndex < shareActions.size(); actionIndex++) { final FunctionalActionElement actionElement = new FunctionalActionElement(shareActions.get(actionIndex), actionIndex, FunctionalActionKind.SHARE); importPaths.add(actionElement.importPath()); + actionImports.addAll(actionElement.actionImports()); shareActionsDom.add(actionElement.render()); shareActionsObjects.append(prefix + createActionObject(actionElement)); } @@ -1118,7 +1130,7 @@ private IRenderable createRenderableRepresentation(final ICentreDomainTreeManage final FunctionalMultiActionElement el = new FunctionalMultiActionElement(multiActionConfig, numberOfAction, FunctionalActionKind.SECONDARY_RESULT_SET); importPaths.add(el.importPath()); secondaryActionsDom.add(el.render().attr("slot", "secondary-action")); - secondaryActionsObjects.append(prefix + el.createActionObject(importPaths)); + secondaryActionsObjects.append(prefix + el.createActionObject(importPaths, actionImports)); numberOfAction += multiActionConfig.actions().size(); } } @@ -1136,6 +1148,7 @@ private IRenderable createRenderableRepresentation(final ICentreDomainTreeManage final StringBuilder insertionPointActionsObjects = new StringBuilder(); for (final InsertionPointBuilder el : insertionPointActionsElements) { importPaths.addAll(el.importPaths()); + actionImports.addAll(el.actionImports()); insertionPointActionsDom.add(el.renderInsertionPointAction()); insertionPointActionsObjects.append(prefix + el.code()); } @@ -1169,7 +1182,7 @@ private IRenderable createRenderableRepresentation(final ICentreDomainTreeManage bottomInsertionPointsDom.add(insertionPoint); } else if (el.whereToInsert() == InsertionPoints.ALTERNATIVE_VIEW) { final Optional switchButtons = switchViewButtons(insertionPointActionsElements, Optional.of(el)); - final Optional topActions = alternativeViewActions(el, importPaths, functionalActionsObjects, alternativeViewActionOrder); + final Optional topActions = alternativeViewActions(el, importPaths, actionImports, functionalActionsObjects, alternativeViewActionOrder); alternativeViewsDom.add(insertionPoint.toString() .replace(SWITCH_VIEW_ACTION_DOM, switchButtons.map(domElem -> domElem.toString()).orElse("")) .replace(EGI_FUNCTIONAL_ACTION_DOM, topActions.map(domElem -> domElem.toString()).orElse(""))); @@ -1178,7 +1191,7 @@ private IRenderable createRenderableRepresentation(final ICentreDomainTreeManage } } - final Optional egiSwitchViewButtons = switchViewButtons(insertionPointActionsElements, Optional.empty()); + final Optional egiSwitchViewButtons = switchViewButtons(insertionPointActionsElements, empty()); //Generating shortcuts for EGI final StringBuilder shortcuts = new StringBuilder(); @@ -1212,7 +1225,7 @@ private IRenderable createRenderableRepresentation(final ICentreDomainTreeManage final String text = ResourceLoader.getText("ua/com/fielden/platform/web/centre/tg-entity-centre-template.js"); logger.debug("Replacing some parts..."); final String entityCentreStr = text. - replace(IMPORTS, createImports(importPaths) + customImports.map(ci -> ci.toString()).orElse("")). + replace(IMPORTS, createImports(importPaths) + customImports.map(ci -> ci.toString()).orElse("") + actionImports). replace(EGI_LAYOUT, gridLayoutConfig.getKey()). replace(FULL_ENTITY_TYPE, entityType.getName()). replace(MI_TYPE, flattenedNameOf(miType)). @@ -1287,21 +1300,18 @@ public DomElement render() { return representation; } - /** - * Generates DOM for alternative view actions. Also updates action's order, import path and action object. - * - * @param el - * @param importPaths - * @param functionalActionsObjects - * @param alternativeViewActionOrder - * @return - */ - private Optional alternativeViewActions(final InsertionPointBuilder el, final LinkedHashSet importPaths, final StringBuilder functionalActionsObjects, final AtomicInteger alternativeViewActionOrder) { + /// Generates DOM, JavaScript objects and import statements for alternative view actions. + /// Updates action's order number (`alternativeViewActionOrder`) during building. + /// + /// @param importPaths unnamed imports for alternative view actions DOM (usually only `tg-ui-action`) + /// @param actionImports named or default {@link JsImport}s from {@link IPreAction}s / {@link IPostAction}s + private Optional alternativeViewActions(final InsertionPointBuilder el, final LinkedHashSet importPaths, final SortedSet actionImports, final StringBuilder functionalActionsObjects, final AtomicInteger alternativeViewActionOrder) { if (!el.getActions().isEmpty()) { final DomElement domContainer = new DomContainer(); for (final EntityActionConfig actionConfig: el.getActions()) { final FunctionalActionElement funcAction = new FunctionalActionElement(actionConfig, alternativeViewActionOrder.getAndIncrement(), FunctionalActionKind.TOP_LEVEL); importPaths.add(funcAction.importPath()); + actionImports.addAll(funcAction.actionImports()); domContainer.add(funcAction.render()); functionalActionsObjects.append(",\n" + createActionObject(funcAction)); } @@ -1615,7 +1625,7 @@ public Optional> getAdditionalFetchProviderForTooltipPropertie tooltipProps.add(property.tooltipProp.get()); } })); - return tooltipProps.isEmpty() ? Optional.empty() : Optional.of(EntityUtils.fetchNotInstrumented(entityType).with(tooltipProps)); + return tooltipProps.isEmpty() ? empty() : Optional.of(EntityUtils.fetchNotInstrumented(entityType).with(tooltipProps)); } public Optional, Optional>> getQueryEnhancerConfig() { @@ -1624,7 +1634,7 @@ public Optional, Optional>> getQuery final Class> queryEnhancerType = queryEnhancerConfig.get().getKey(); return Optional.of(new Pair<>(injector.getInstance(queryEnhancerType), queryEnhancerConfig.get().getValue())); } else { - return Optional.empty(); + return empty(); } } diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/api/actions/EntityActionConfig.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/api/actions/EntityActionConfig.java index a863e0a56a5..3298fb737da 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/api/actions/EntityActionConfig.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/api/actions/EntityActionConfig.java @@ -1,17 +1,23 @@ package ua.com.fielden.platform.web.centre.api.actions; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; - import ua.com.fielden.platform.entity.AbstractFunctionalEntityWithCentreContext; import ua.com.fielden.platform.reflection.TitlesDescsGetter; import ua.com.fielden.platform.web.PrefDim; import ua.com.fielden.platform.web.centre.api.context.CentreContextConfig; import ua.com.fielden.platform.web.centre.api.insertion_points.InsertionPoints; +import ua.com.fielden.platform.web.minijs.JsImport; +import ua.com.fielden.platform.web.view.master.api.actions.IAction; +import ua.com.fielden.platform.web.view.master.api.actions.IComposableAction; import ua.com.fielden.platform.web.view.master.api.actions.post.IPostAction; import ua.com.fielden.platform.web.view.master.api.actions.pre.IPreAction; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import static java.util.stream.Stream.of; + /** * Configuration of a specific entity action, which is associated with an entity on an entity centre. * @@ -315,4 +321,22 @@ public boolean equals(final Object obj) { } return true; } + + /// Combined action [JsImport]s from all pre/postActions. + public Stream importStatements() { + return of( + importStatements(preAction), + importStatements(successPostAction), + importStatements(errorPostAction) + ).flatMap(s -> s); + } + + /// [Stream] of [JsImport]s for optional [IComposableAction] descendant. + /// Returns empty [Stream] of `actionOpt` is empty. + public Stream importStatements(final Optional actionOpt) { + return actionOpt + .map(action -> action.importStatements().stream()) + .orElseGet(Stream::empty); + } + } diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/api/actions/multi/FunctionalMultiActionElement.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/api/actions/multi/FunctionalMultiActionElement.java index 7d90d7392fe..5b7bead1089 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/api/actions/multi/FunctionalMultiActionElement.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/api/actions/multi/FunctionalMultiActionElement.java @@ -1,9 +1,5 @@ package ua.com.fielden.platform.web.centre.api.actions.multi; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; - import ua.com.fielden.platform.dom.DomElement; import ua.com.fielden.platform.web.centre.api.actions.EntityActionConfig; import ua.com.fielden.platform.web.centre.api.crit.impl.AbstractCriterionWidget; @@ -11,6 +7,12 @@ import ua.com.fielden.platform.web.centre.api.resultset.impl.FunctionalActionKind; import ua.com.fielden.platform.web.interfaces.IImportable; import ua.com.fielden.platform.web.interfaces.IRenderable; +import ua.com.fielden.platform.web.minijs.JsImport; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.SortedSet; /** * {@link IRenderable} and {@link IImportable} element that represents multiple action element and it is renderable into tg-egi-multi-action component instance. @@ -56,11 +58,12 @@ public DomElement render() { * * @return */ - public String createActionObject(final LinkedHashSet importPaths) { + public String createActionObject(final LinkedHashSet importPaths, final SortedSet actionImports) { final String prefix = ",\n"; final StringBuilder actionsObjects = new StringBuilder(); for(final FunctionalActionElement el: actionElements) { importPaths.add(el.importPath()); + actionImports.addAll(el.actionImports()); actionsObjects.append(prefix + el.createActionObject()); } final int prefixLength = prefix.length(); @@ -68,7 +71,6 @@ public String createActionObject(final LinkedHashSet importPaths) { return actionString.length() > prefixLength ? actionString.substring(prefixLength) : actionString; } - @Override public String importPath() { return widgetPath; diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/api/insertion_points/InsertionPointBuilder.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/api/insertion_points/InsertionPointBuilder.java index 63b5978c8df..7e17b241c8e 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/api/insertion_points/InsertionPointBuilder.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/api/insertion_points/InsertionPointBuilder.java @@ -7,6 +7,13 @@ import ua.com.fielden.platform.web.interfaces.IExecutable; import ua.com.fielden.platform.web.interfaces.IRenderable; import ua.com.fielden.platform.web.minijs.JsCode; +import ua.com.fielden.platform.web.minijs.JsImport; + +import java.util.*; + +import static org.apache.commons.lang3.StringUtils.join; +import static ua.com.fielden.platform.web.centre.api.insertion_points.InsertionPoints.ALTERNATIVE_VIEW; +import static ua.com.fielden.platform.web.centre.api.resultset.impl.FunctionalActionKind.INSERTION_POINT; import java.util.*; @@ -81,6 +88,11 @@ public DomElement renderInsertionPointAction() { return insertionPointActionElement.render().clazz("insertion-point-action").attr("hidden", null); } + /// [JsImport]s for currently configured insertion point action. + public Set actionImports() { + return insertionPointActionElement.actionImports(); + } + /** * Returns the import paths for this insertion point * diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/api/resultset/impl/FunctionalActionElement.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/api/resultset/impl/FunctionalActionElement.java index a328238b591..706bd08716e 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/api/resultset/impl/FunctionalActionElement.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/api/resultset/impl/FunctionalActionElement.java @@ -7,6 +7,7 @@ import ua.com.fielden.platform.web.centre.api.crit.impl.AbstractCriterionWidget; import ua.com.fielden.platform.web.interfaces.IImportable; import ua.com.fielden.platform.web.interfaces.IRenderable; +import ua.com.fielden.platform.web.minijs.JsImport; import ua.com.fielden.platform.web.view.master.api.actions.IAction; import ua.com.fielden.platform.web.view.master.api.actions.post.IPostAction; import ua.com.fielden.platform.web.view.master.api.actions.pre.IPreAction; @@ -14,12 +15,12 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; +import java.util.Set; import static java.lang.String.format; import static java.util.Optional.empty; import static java.util.Optional.of; -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.*; import static org.apache.commons.lang3.StringUtils.join; /** @@ -406,4 +407,10 @@ public boolean isForMaster() { public void setForMaster(final boolean forMaster) { this.forMaster = forMaster; } + + /// [JsImport]s for currently configured action. + public Set actionImports() { + return entityActionConfig.importStatements().collect(toSet()); + } + } diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/menu/impl/MainMenuBuilder.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/menu/impl/MainMenuBuilder.java index c159fab91e7..db2040a1223 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/menu/impl/MainMenuBuilder.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/menu/impl/MainMenuBuilder.java @@ -12,6 +12,9 @@ import ua.com.fielden.platform.web.menu.layout.IMenuLayoutConfig0; import ua.com.fielden.platform.web.menu.layout.impl.LayoutConfig; import ua.com.fielden.platform.web.menu.module.impl.ModuleConfig; +import ua.com.fielden.platform.web.minijs.JsImport; + +import java.util.Set; /** * An implementation of {@link IMainMenuBuilderWithLayout} contract, which serves both as the main menu builder and the representation of the final main menu configuration. @@ -55,4 +58,10 @@ public Menu getMenu() { setWhenTablet(tileLayout.getLayout(Device.TABLET, null).get()). setWhenMobile(tileLayout.getLayout(Device.MOBILE, null).get()); } + + /// [JsImport]s for currently configured main menu actions. + public Set mainMenuActionImports() { + return mainMenu.actionImports(); + } + } diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/menu/impl/WebMainMenu.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/menu/impl/WebMainMenu.java index 0a28af7e1a6..d5866f1a737 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/menu/impl/WebMainMenu.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/menu/impl/WebMainMenu.java @@ -1,16 +1,19 @@ package ua.com.fielden.platform.web.menu.impl; -import static java.util.stream.Collectors.toList; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - import ua.com.fielden.platform.menu.ModuleMenu; import ua.com.fielden.platform.web.centre.api.actions.EntityActionConfig; import ua.com.fielden.platform.web.centre.api.resultset.impl.FunctionalActionKind; import ua.com.fielden.platform.web.menu.module.impl.WebMenuModule; import ua.com.fielden.platform.web.minijs.JsCode; +import ua.com.fielden.platform.web.minijs.JsImport; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; public class WebMainMenu { @@ -52,4 +55,12 @@ public EntityActionConfig getActionConfig(final int actionNumber, final Function JsCode createActionsObject() { return new JsCode(null); } + + /// [JsImport]s for currently configured actions from all modules. + public Set actionImports() { + return modules.stream() + .flatMap(module -> module.getActions().stream().flatMap(EntityActionConfig::importStatements)) + .collect(toSet()); + } + } diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/minijs/CombinedJsImports.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/minijs/CombinedJsImports.java new file mode 100644 index 00000000000..b9b175121cd --- /dev/null +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/minijs/CombinedJsImports.java @@ -0,0 +1,59 @@ +package ua.com.fielden.platform.web.minijs; + +import ua.com.fielden.platform.web.minijs.exceptions.JsCodeException; + +import java.util.SortedSet; +import java.util.TreeSet; + +import static java.lang.String.join; +import static org.apache.tika.utils.StringUtils.isBlank; + +/// A [SortedSet] of [JsImport]s with name conflict validation and ability to convert to code [String]s. +/// +/// @author TG Team +public class CombinedJsImports extends TreeSet { + static final String ERR_ACTION_IMPORT_NAMES_ARE_IN_CONFLICT = "Action import names are in conflict.\n%s"; + + /// Overridden to validate [CombinedJsImports] on naming conflicts. + /// + /// Some `jsImports` can be aliased and others - not. + /// To validate on naming conflicts, all `jsImports` should be converted to aliased form and only then added to set. + /// Sorting will be done using [JsImport#ALIASED_FORM_COMPARATOR]. + @Override + public boolean add(final JsImport jsImport) { + final var changed = super.add(jsImport.convertToAliasedForm()); + if (stream().map(JsImport::alias).distinct().toList().size() < size()) { + throw new JsCodeException(ERR_ACTION_IMPORT_NAMES_ARE_IN_CONFLICT.formatted(this)); + } + return changed; + } + + /// Overridden to return JavaScript code representation (in [String] form). + @Override + public String toString() { + if (isEmpty()) { + return ""; + } + return "\n" + join("\n", stream().map(JsImport::genAliasedCode).toList()); + } + + /// Overridden to return JavaScript code representation (in [String] form). + /// + /// @param importObjectName a name for special JS object containing `importedFunction: importedFunction` pairs; + /// these pairs can be referenced in dynamic JavaScript functions (`new Function(" some JS code ")`) + public String toStringWith(final String importObjectName) { + final var codeStr = toString(); + if (isBlank(codeStr)) { + return ""; + } + return codeStr + "\nconst %s = {%s};".formatted( + importObjectName, + join(",", stream() + .map(jsImport -> jsImport.alias().get()) // always present + .map(alias -> "%s: %s".formatted(alias, alias)) + .toList() + ) + ); + } + +} diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/minijs/JsImport.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/minijs/JsImport.java new file mode 100644 index 00000000000..06128254d5e --- /dev/null +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/minijs/JsImport.java @@ -0,0 +1,77 @@ +package ua.com.fielden.platform.web.minijs; + +import ua.com.fielden.platform.web.minijs.exceptions.JsCodeException; + +import java.util.Comparator; +import java.util.Optional; + +import static java.util.Comparator.comparing; +import static java.util.Objects.requireNonNull; +import static java.util.Optional.empty; +import static java.util.Optional.of; + +/// An abstraction for JavaScript imports covering named imports (including aliased) and default imports. +/// +/// @author TG Team +public record JsImport(String name, String path, Optional alias) implements Comparable { + private static final String DEFAULT = "default"; // reserved JavaScript word for default imports + private static final String ERR_JAVA_SCRIPT_IMPORT_NAME_IS_BLANK = "JavaScript import statement name [%s] is blank. Should either be [%s] or actual named export identifier from [%s] module."; + private static final String ERR_JAVA_SCRIPT_IMPORT_PATH_IS_BLANK = "JavaScript import statement path [%s] is blank."; + public static final String ERR_JAVA_SCRIPT_DEFAULT_IMPORT_ALIAS_IS_NOT_PROVIDED = "JavaScript default import statement alias is not provided."; + public static final String ERR_JAVA_SCRIPT_IMPORT_ALIAS_IS_BLANK = "JavaScript import statement alias [%s] is blank."; + + private static final Comparator ALIASED_FORM_COMPARATOR = + comparing(JsImport::path) + .thenComparing(jsImport -> jsImport.alias().get()); + + public JsImport { + if (requireNonNull(name).isBlank()) { + throw new JsCodeException(ERR_JAVA_SCRIPT_IMPORT_NAME_IS_BLANK.formatted(name, DEFAULT, path)); + } + if (requireNonNull(path).isBlank()) { + throw new JsCodeException(ERR_JAVA_SCRIPT_IMPORT_PATH_IS_BLANK.formatted(path)); + } + if (DEFAULT.equals(name) && alias.isEmpty()) { + throw new JsCodeException(ERR_JAVA_SCRIPT_DEFAULT_IMPORT_ALIAS_IS_NOT_PROVIDED); + } + if (alias.isPresent() && alias.get().isBlank()) { + throw new JsCodeException(ERR_JAVA_SCRIPT_IMPORT_ALIAS_IS_BLANK.formatted(alias)); + } + } + + /// Creates a named [JsImport] importing concrete `name` from a `path` module. + public static JsImport namedImport(final String name, final String path) { + return new JsImport(name, path, empty()); + } + + /// Creates a named [JsImport] importing concrete `name` with an `alias` from a `path` module. + public static JsImport namedImport(final String name, final String path, final String alias) { + return new JsImport(name, path, of(alias)); + } + + /// Creates default [JsImport] with an `alias` from a `path` module. + public static JsImport defaultImport(final String alias, final String path) { + return new JsImport("default", path, of(alias)); + } + + /// Converts import statement to single aliased form being able to analyse in a relation to others: + /// `import{exportedName|default as exportedName|myIdentifier}from 'path';` + public JsImport convertToAliasedForm() { + return namedImport(name, path, alias().orElse(name())); + } + + /// Generates [String] JavaScript code, assuming that this [JsImport] is in aliased form ([#alias()] is present). + public String genAliasedCode() { + return "import { %s as %s } from '%s.js';".formatted( + name(), + alias().get(), // always present + (path().startsWith("/") ? "" : "/resources/") + path() // support "full" and short paths ('/app/...' vs 'reflection/...') + ); + } + + @Override + public int compareTo(final JsImport other) { + return ALIASED_FORM_COMPARATOR.compare(this, other); + } + +} diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/minijs/exceptions/JsCodeException.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/minijs/exceptions/JsCodeException.java new file mode 100644 index 00000000000..8bf2e2067fa --- /dev/null +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/minijs/exceptions/JsCodeException.java @@ -0,0 +1,18 @@ +package ua.com.fielden.platform.web.minijs.exceptions; + +import ua.com.fielden.platform.exceptions.AbstractPlatformRuntimeException; + +/// A runtime exception that indicates erroneous situation pertaining to JavaScript code. +/// +/// @author TG Team +public class JsCodeException extends AbstractPlatformRuntimeException { + + public JsCodeException(final String msg) { + super(msg); + } + + public JsCodeException(final String msg, final Throwable cause) { + super(msg, cause); + } + +} \ No newline at end of file diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/ref_hierarchy/ReferenceHierarchyPreAction.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/ref_hierarchy/ReferenceHierarchyPreAction.java new file mode 100644 index 00000000000..14ac65c4545 --- /dev/null +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/ref_hierarchy/ReferenceHierarchyPreAction.java @@ -0,0 +1,33 @@ +package ua.com.fielden.platform.web.ref_hierarchy; + +import ua.com.fielden.platform.web.minijs.JsCode; +import ua.com.fielden.platform.web.minijs.JsImport; +import ua.com.fielden.platform.web.view.master.api.actions.pre.IPreAction; + +import java.util.Set; + +import static java.util.Set.of; +import static ua.com.fielden.platform.web.minijs.JsCode.jsCode; +import static ua.com.fielden.platform.web.minijs.JsImport.namedImport; + +/// Common [IPreAction] for [ua.com.fielden.platform.ref_hierarchy.ReferenceHierarchy] action. +/// +/// @author TG Team +public record ReferenceHierarchyPreAction(boolean useMasterEntity) implements IPreAction { + + @Override + public Set importStatements() { + return of(namedImport("referenceHierarchy", "centre/actions/tg-reference-hierarchy")); + } + + @Deprecated(since = WARN_DEPRECATION_DANGEROUS_CODE_CONCATENATION_WITHOUT_IMPORTS) + @Override + public JsCode build() { + return jsCode(""" + referenceHierarchy(action, self, %s); + """.formatted( + useMasterEntity + )); + } + +} diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/ref_hierarchy/ReferenceHierarchyWebUiConfig.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/ref_hierarchy/ReferenceHierarchyWebUiConfig.java index 45787c2f3bd..73954505417 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/ref_hierarchy/ReferenceHierarchyWebUiConfig.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/ref_hierarchy/ReferenceHierarchyWebUiConfig.java @@ -5,16 +5,14 @@ import ua.com.fielden.platform.ref_hierarchy.ReferenceHierarchy; import ua.com.fielden.platform.web.centre.api.actions.EntityActionConfig; import ua.com.fielden.platform.web.centre.api.context.CentreContextConfig; -import ua.com.fielden.platform.web.minijs.JsCode; import ua.com.fielden.platform.web.view.master.EntityMaster; -import ua.com.fielden.platform.web.view.master.api.actions.pre.IPreAction; import ua.com.fielden.platform.web.view.master.hierarchy.ReferenceHierarchyMaster; import static ua.com.fielden.platform.error.Result.failuref; import static ua.com.fielden.platform.reflection.PropertyTypeDeterminator.determinePropertyType; +import static ua.com.fielden.platform.web.action.pre.PreActions.referenceHierarchy; import static ua.com.fielden.platform.web.centre.api.actions.impl.EntityActionBuilder.action; import static ua.com.fielden.platform.web.centre.api.context.impl.EntityCentreContextSelector.context; -import static ua.com.fielden.platform.web.minijs.JsCode.jsCode; /** * Web UI configuration for reference hierarchy master and action needed to call reference hierarchy. @@ -45,7 +43,7 @@ public static EntityMaster createReferenceHierarchyMaster(fi public static EntityActionConfig mkAction() { return action(ReferenceHierarchy.class) .withContext(context().withSelectedEntities().build()) - .preAction(new ReferenceHierarchyPreAction(false)) + .preAction(referenceHierarchy(false)) .icon("tg-reference-hierarchy:hierarchy") .shortDesc("Reference Hierarchy") .longDesc("Opens Reference Hierarchy") @@ -61,7 +59,7 @@ public static EntityActionConfig mkAction() { public static EntityActionConfig mkPropAction() { return action(ReferenceHierarchy.class) .withContext(context().withMasterEntity().build()) - .preAction(new ReferenceHierarchyPreAction(false)) + .preAction(referenceHierarchy(false)) .icon("tg-reference-hierarchy:hierarchy") .shortDesc("Reference Hierarchy") .longDesc("Opens Reference Hierarchy") @@ -87,7 +85,7 @@ public static EntityActionConfig mkPropActionForMasterEntity() { }).build(); return action(ReferenceHierarchy.class) .withContext(contextConfig) - .preAction(new ReferenceHierarchyPreAction(true)) + .preAction(referenceHierarchy(true)) .icon("tg-reference-hierarchy:hierarchy") .shortDesc("Reference Hierarchy") .longDesc("Opens Reference Hierarchy") @@ -105,7 +103,7 @@ public static EntityActionConfig mkAction(final CentreContextConfig ccConfig) { } return action(ReferenceHierarchy.class) .withContext(ccConfig) - .preAction(new ReferenceHierarchyPreAction(false)) + .preAction(referenceHierarchy(false)) .icon("tg-reference-hierarchy:hierarchy") .shortDesc("Reference Hierarchy") .longDesc("Opens Reference Hierarchy") @@ -113,34 +111,4 @@ public static EntityActionConfig mkAction(final CentreContextConfig ccConfig) { .build(); } - /** - * Common {@link IPreAction} for reference hierarchy action. - * - * @author TG Team - */ - private record ReferenceHierarchyPreAction(boolean useMasterEntity) implements IPreAction { - @Override - public JsCode build() { - return jsCode(""" - const reflector = new TgReflector(); - let entity = null; - if (action.requireSelectedEntities === 'ONE') { - entity = action.currentEntity(); - } else if (action.requireSelectedEntities === 'ALL' && self.$.egi.getSelectedEntities().length > 0) { - entity = self.$.egi.getSelectedEntities()[0]; - } else if (action.requireMasterEntity === "true") { - if(%s) { - entity = action.parentElement.entity['@@origin']; - } else { - const value = reflector.tg_getFullValue(action.parentElement.entity, action.parentElement.propertyName); - entity = reflector.isEntity(value) ? value : action.parentElement.entity['@@origin']; - } - } - if (entity) { - action.shortDesc = reflector.getType(entity.constructor.prototype.type.call(entity).notEnhancedFullClassName()).entityTitle(); - } - """.formatted(this.useMasterEntity)); - } - } - } diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/actions/IAction.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/actions/IAction.java index 080d6cb70db..4447f2add72 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/actions/IAction.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/actions/IAction.java @@ -1,14 +1,14 @@ package ua.com.fielden.platform.web.view.master.api.actions; import ua.com.fielden.platform.web.minijs.JsCode; -import ua.com.fielden.platform.web.view.master.api.actions.post.IPostAction; -import ua.com.fielden.platform.web.view.master.api.actions.pre.IPreAction; - -/** - * Common ancestor for {@link IPreAction} and {@link IPostAction}. - * - * @author TG Team - */ + +/// An abstraction for a piece of JavaScript code through [#build()] method. +/// May be used as a lambda `() -> jsCode("...")` function for convenience. +/// +/// @author TG Team public interface IAction { + + /// Builds actual [JsCode] for this JavaScript action. JsCode build(); + } \ No newline at end of file diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/actions/IComposableAction.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/actions/IComposableAction.java new file mode 100644 index 00000000000..b1b3b460eb7 --- /dev/null +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/actions/IComposableAction.java @@ -0,0 +1,31 @@ +package ua.com.fielden.platform.web.view.master.api.actions; + +import ua.com.fielden.platform.web.minijs.JsCode; +import ua.com.fielden.platform.web.minijs.JsImport; +import ua.com.fielden.platform.web.view.master.api.actions.post.IPostAction; +import ua.com.fielden.platform.web.view.master.api.actions.pre.IPreAction; + +import java.util.Set; + +import static java.util.Set.of; + +/// An abstraction for a composable piece of JavaScript code through [#build()] method with JS import statements support. +/// May be used as a lambda `() -> jsCode("...")` function for convenience. +/// +/// Common ancestor for [IPreAction] and [IPostAction]. +/// +/// @author TG Team +public interface IComposableAction extends IAction { + /// Warning for [#build()] method of a typical [JsCode] concatenation usage.\ + /// May be dangerous for [IComposableAction]s with defined [#importStatements()]. + String WARN_DEPRECATION_DANGEROUS_CODE_CONCATENATION_WITHOUT_IMPORTS = "3.0.0. Don't use this for JsCode concatenation; use andThen(...) instead."; + + /// A set of [JsImport]s, required for this JavaScript action. + default Set importStatements() { + return of(); + } + + /// Composes this [IComposableAction] (`thisAction`) with `thatAction` to be performed in `thisAction => thatAction` order. + ACTION andThen(final ACTION thatAction); + +} \ No newline at end of file diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/actions/post/IPostAction.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/actions/post/IPostAction.java index 5e9bc360fdd..e4c9f9035f2 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/actions/post/IPostAction.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/actions/post/IPostAction.java @@ -1,14 +1,41 @@ package ua.com.fielden.platform.web.view.master.api.actions.post; -import ua.com.fielden.platform.web.view.master.api.actions.IAction; - -/** - * A contract that should be implemented by all concrete implementations of post-action behaviour for Entity Master actions. - * - * Post-actions execute at the client side, and thus in case of a HTML application they should emit the valid HTML and JavaScript code during client code generation. - * - * @author TG Team - * - */ -public interface IPostAction extends IAction { +import ua.com.fielden.platform.web.minijs.JsCode; +import ua.com.fielden.platform.web.minijs.JsImport; +import ua.com.fielden.platform.web.view.master.api.actions.IComposableAction; + +import java.util.Set; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toUnmodifiableSet; +import static ua.com.fielden.platform.web.minijs.JsCode.jsCode; + +/// A contract that should be implemented by all concrete implementations of post-action behaviour for Entity Master / Centre actions. +/// Post-actions execute at the client side. +/// They should emit the valid JavaScript code during client code generation. +/// +/// @author TG Team +public interface IPostAction extends IComposableAction { + + @Override + default IPostAction andThen(final IPostAction thatAction) { + return new IPostAction() { + @Override + public Set importStatements() { + return Stream.concat( + IPostAction.this.importStatements().stream(), + thatAction.importStatements().stream() + ).collect(toUnmodifiableSet()); + } + + @Override + public JsCode build() { + return jsCode( + IPostAction.this.build().toString() + + thatAction.build().toString() + ); + } + }; + } + } \ No newline at end of file diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/actions/pre/IPreAction.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/actions/pre/IPreAction.java index 938bf8b5b7c..f62404f87da 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/actions/pre/IPreAction.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/actions/pre/IPreAction.java @@ -1,14 +1,41 @@ package ua.com.fielden.platform.web.view.master.api.actions.pre; -import ua.com.fielden.platform.web.view.master.api.actions.IAction; - -/** - * A contract that should be implemented by all concrete implementations of pre-action behaviour for Entity Master actions. - * - * Pre-actions execute at the client side, and thus in case of a HTML application they should emit the valid HTML and JavaScript code during client code generation. - * - * @author TG Team - * - */ -public interface IPreAction extends IAction { +import ua.com.fielden.platform.web.minijs.JsCode; +import ua.com.fielden.platform.web.minijs.JsImport; +import ua.com.fielden.platform.web.view.master.api.actions.IComposableAction; + +import java.util.Set; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toUnmodifiableSet; +import static ua.com.fielden.platform.web.minijs.JsCode.jsCode; + +/// A contract that should be implemented by all concrete implementations of pre-action behaviour for Entity Master / Centre actions. +/// Pre-actions execute at the client side. +/// They should emit the valid JavaScript code during client code generation. +/// +/// @author TG Team +public interface IPreAction extends IComposableAction { + + @Override + default IPreAction andThen(final IPreAction thatAction) { + return new IPreAction() { + @Override + public Set importStatements() { + return Stream.concat( + IPreAction.this.importStatements().stream(), + thatAction.importStatements().stream() + ).collect(toUnmodifiableSet()); + } + + @Override + public JsCode build() { + return jsCode( + IPreAction.this.build().toString() + + thatAction.build().toString() + ); + } + }; + } + } \ No newline at end of file diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/compound/Compound.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/compound/Compound.java index 9af8649ccf4..9f89e0dd8ef 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/compound/Compound.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/compound/Compound.java @@ -1,23 +1,11 @@ package ua.com.fielden.platform.web.view.master.api.compound; -import static java.util.Optional.empty; -import static java.util.Optional.of; -import static java.util.Optional.ofNullable; -import static ua.com.fielden.platform.web.centre.api.actions.impl.EntityActionBuilder.action; -import static ua.com.fielden.platform.web.centre.api.context.impl.EntityCentreContextSelector.context; - -import java.util.Optional; -import java.util.function.BiFunction; - -import org.apache.commons.lang3.StringUtils; - import com.google.inject.Injector; - +import org.apache.commons.lang3.StringUtils; import ua.com.fielden.platform.entity.AbstractEntity; import ua.com.fielden.platform.entity.AbstractFunctionalEntityForCompoundMenuItem; import ua.com.fielden.platform.entity.AbstractFunctionalEntityWithCentreContext; import ua.com.fielden.platform.web.PrefDim; -import ua.com.fielden.platform.web.action.pre.EntityNavigationPreAction; import ua.com.fielden.platform.web.centre.CentreContext; import ua.com.fielden.platform.web.centre.EntityCentre; import ua.com.fielden.platform.web.centre.api.actions.EntityActionConfig; @@ -30,6 +18,14 @@ import ua.com.fielden.platform.web.view.master.api.with_centre.impl.MasterWithCentreBuilder; import ua.com.fielden.platform.web.view.master.api.with_master.impl.MasterWithMasterBuilder; +import java.util.Optional; +import java.util.function.BiFunction; + +import static java.util.Optional.*; +import static ua.com.fielden.platform.web.action.pre.PreActions.entityNavigation; +import static ua.com.fielden.platform.web.centre.api.actions.impl.EntityActionBuilder.action; +import static ua.com.fielden.platform.web.centre.api.context.impl.EntityCentreContextSelector.context; + public class Compound { protected static , MENU_ITEM extends AbstractFunctionalEntityForCompoundMenuItem> EntityMaster miMaster( @@ -171,7 +167,7 @@ public static , OPEN_ACTION extends AbstractFunctionalEn final Class openCompoundMasterActionType, final String shortDesc, final PrefDim prefDim) { - return open(openCompoundMasterActionType, of(new EntityNavigationPreAction(shortDesc)), empty(), empty(), shortDesc, empty(), prefDim, context().withCurrentEntity().build()); + return open(openCompoundMasterActionType, of(entityNavigation(shortDesc)), empty(), empty(), shortDesc, empty(), prefDim, context().withCurrentEntity().build()); } /** @@ -188,7 +184,7 @@ public static , OPEN_ACTION extends AbstractFunctionalEn final String shortDesc, final String longDesc, final PrefDim prefDim) { - return open(openCompoundMasterActionType, of(new EntityNavigationPreAction(shortDesc)), empty(), empty(), shortDesc, ofNullable(longDesc), prefDim, context().withCurrentEntity().build()); + return open(openCompoundMasterActionType, of(entityNavigation(shortDesc)), empty(), empty(), shortDesc, ofNullable(longDesc), prefDim, context().withCurrentEntity().build()); } /** @@ -207,7 +203,7 @@ public static , OPEN_ACTION extends AbstractFunctionalEn final String shortDesc, final String longDesc, final PrefDim prefDim) { - return open(openCompoundMasterActionType, of(new EntityNavigationPreAction(shortDesc)), ofNullable(icon), empty(), shortDesc, ofNullable(longDesc), prefDim, context().withCurrentEntity().build()); + return open(openCompoundMasterActionType, of(entityNavigation(shortDesc)), ofNullable(icon), empty(), shortDesc, ofNullable(longDesc), prefDim, context().withCurrentEntity().build()); } /** @@ -228,7 +224,7 @@ public static , OPEN_ACTION extends AbstractFunctionalEn final String shortDesc, final String longDesc, final PrefDim prefDim) { - return open(openCompoundMasterActionType, of(new EntityNavigationPreAction(shortDesc)), ofNullable(icon), empty(), shortDesc, ofNullable(longDesc), prefDim, context().withCurrentEntity().withComputation(computation).build()); + return open(openCompoundMasterActionType, of(entityNavigation(shortDesc)), ofNullable(icon), empty(), shortDesc, ofNullable(longDesc), prefDim, context().withCurrentEntity().withComputation(computation).build()); } diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/compound/impl/MasterWithMenu.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/compound/impl/MasterWithMenu.java index 1a5ee9ed37a..514be3254c0 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/compound/impl/MasterWithMenu.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/compound/impl/MasterWithMenu.java @@ -1,19 +1,6 @@ package ua.com.fielden.platform.web.view.master.api.compound.impl; -import static java.lang.String.format; -import static org.apache.logging.log4j.LogManager.getLogger; -import static ua.com.fielden.platform.web.centre.EntityCentre.IMPORTS; -import static ua.com.fielden.platform.web.view.master.EntityMaster.ENTITY_TYPE; -import static ua.com.fielden.platform.web.view.master.EntityMaster.flattenedNameOf; -import static ua.com.fielden.platform.web.view.master.api.impl.SimpleMasterBuilder.createImports; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; - import org.apache.logging.log4j.Logger; - import ua.com.fielden.platform.basic.IValueMatcherWithContext; import ua.com.fielden.platform.dom.DomContainer; import ua.com.fielden.platform.dom.DomElement; @@ -25,8 +12,19 @@ import ua.com.fielden.platform.web.centre.api.resultset.impl.FunctionalActionElement; import ua.com.fielden.platform.web.centre.api.resultset.impl.FunctionalActionKind; import ua.com.fielden.platform.web.interfaces.IRenderable; +import ua.com.fielden.platform.web.minijs.CombinedJsImports; +import ua.com.fielden.platform.web.minijs.JsImport; import ua.com.fielden.platform.web.view.master.api.IMaster; +import java.util.*; + +import static java.lang.String.format; +import static org.apache.logging.log4j.LogManager.getLogger; +import static ua.com.fielden.platform.web.centre.EntityCentre.IMPORTS; +import static ua.com.fielden.platform.web.view.master.EntityMaster.ENTITY_TYPE; +import static ua.com.fielden.platform.web.view.master.EntityMaster.flattenedNameOf; +import static ua.com.fielden.platform.web.view.master.api.impl.SimpleMasterBuilder.createImports; + /** * A compound entity master that has a menu, that extends {@link IMaster} contract. * @@ -57,6 +55,7 @@ class MasterWithMenu, F extends AbstractFunctionalEn throw new IllegalArgumentException(format("The default menu item index %s is outside of the range for the provided menu items.", defaultMenuItemIndex)); } + final SortedSet actionImports = new CombinedJsImports(); final LinkedHashSet importPaths = new LinkedHashSet<>(); importPaths.add("master/menu/tg-master-menu"); importPaths.add("master/menu/tg-master-menu-item-section"); @@ -77,6 +76,7 @@ class MasterWithMenu, F extends AbstractFunctionalEn for (final FunctionalActionElement el : menuItemActionsElements) { importPaths.add(el.importPath()); + actionImports.addAll(el.actionImports()); menuItemActionsDom.add(el.render()); jsMenuItemActionObjects.append(el.createActionObject() + ",\n"); menuItemViewsDom.add( @@ -102,7 +102,7 @@ class MasterWithMenu, F extends AbstractFunctionalEn // generate the final master with menu final String entityMasterStr = ResourceLoader.getText("ua/com/fielden/platform/web/master/tg-entity-master-template.js") - .replace(IMPORTS, createImports(importPaths)) + .replace(IMPORTS, createImports(importPaths) + actionImports) .replace(ENTITY_TYPE, flattenedNameOf(functionalEntityType)) .replace("", format("" diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/impl/SimpleMasterBuilder.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/impl/SimpleMasterBuilder.java index 526b88ddcf9..fb3592fc6ab 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/impl/SimpleMasterBuilder.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/impl/SimpleMasterBuilder.java @@ -1,25 +1,5 @@ package ua.com.fielden.platform.web.view.master.api.impl; -import static java.lang.String.format; -import static java.util.Optional.empty; -import static java.util.Optional.of; -import static java.util.stream.Collectors.toMap; -import static ua.com.fielden.platform.types.tuples.T2.t2; -import static ua.com.fielden.platform.utils.CollectionUtil.setOf; -import static ua.com.fielden.platform.web.centre.EntityCentre.IMPORTS; -import static ua.com.fielden.platform.web.centre.api.actions.EntityActionConfig.setRole; -import static ua.com.fielden.platform.web.view.master.EntityMaster.ENTITY_TYPE; -import static ua.com.fielden.platform.web.view.master.EntityMaster.flattenedNameOf; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; - import ua.com.fielden.platform.basic.IValueMatcherWithContext; import ua.com.fielden.platform.dom.DomContainer; import ua.com.fielden.platform.dom.DomElement; @@ -37,7 +17,9 @@ import ua.com.fielden.platform.web.interfaces.ILayout.Orientation; import ua.com.fielden.platform.web.interfaces.IRenderable; import ua.com.fielden.platform.web.layout.FlexLayout; +import ua.com.fielden.platform.web.minijs.CombinedJsImports; import ua.com.fielden.platform.web.minijs.JsCode; +import ua.com.fielden.platform.web.minijs.JsImport; import ua.com.fielden.platform.web.view.master.api.IMaster; import ua.com.fielden.platform.web.view.master.api.ISimpleMasterBuilder; import ua.com.fielden.platform.web.view.master.api.actions.MasterActions; @@ -46,18 +28,27 @@ import ua.com.fielden.platform.web.view.master.api.actions.entity.IEntityActionConfigWithoutNew; import ua.com.fielden.platform.web.view.master.api.actions.entity.impl.DefaultEntityAction; import ua.com.fielden.platform.web.view.master.api.actions.entity.impl.EntityActionConfig; -import ua.com.fielden.platform.web.view.master.api.helpers.IActionBarLayoutConfig1; -import ua.com.fielden.platform.web.view.master.api.helpers.IComplete; -import ua.com.fielden.platform.web.view.master.api.helpers.ILayoutConfig; -import ua.com.fielden.platform.web.view.master.api.helpers.ILayoutConfigWithDimensionsAndDone; -import ua.com.fielden.platform.web.view.master.api.helpers.IPropertySelector; -import ua.com.fielden.platform.web.view.master.api.helpers.IWidgetSelector; +import ua.com.fielden.platform.web.view.master.api.helpers.*; import ua.com.fielden.platform.web.view.master.api.helpers.impl.WidgetSelector; import ua.com.fielden.platform.web.view.master.api.widgets.IDividerConfig; import ua.com.fielden.platform.web.view.master.api.widgets.IHtmlTextConfig; import ua.com.fielden.platform.web.view.master.api.widgets.autocompleter.impl.AbstractEntityAutocompletionWidget; import ua.com.fielden.platform.web.view.master.exceptions.EntityMasterConfigurationException; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +import static java.lang.String.format; +import static java.util.Optional.empty; +import static java.util.Optional.of; +import static java.util.stream.Collectors.toMap; +import static ua.com.fielden.platform.types.tuples.T2.t2; +import static ua.com.fielden.platform.utils.CollectionUtil.setOf; +import static ua.com.fielden.platform.web.centre.EntityCentre.IMPORTS; +import static ua.com.fielden.platform.web.centre.api.actions.EntityActionConfig.setRole; +import static ua.com.fielden.platform.web.view.master.EntityMaster.ENTITY_TYPE; +import static ua.com.fielden.platform.web.view.master.EntityMaster.flattenedNameOf; + public class SimpleMasterBuilder> implements ISimpleMasterBuilder, IPropertySelector, ILayoutConfig, ILayoutConfigWithDimensionsAndDone, IEntityActionConfig5, IActionBarLayoutConfig1 { private static final String ERR_WIDGET_IS_ALREADY_PRESENT = "A widget with property [%s] is already present in the entity master for [%s]."; @@ -222,8 +213,8 @@ public ILayoutConfigWithDimensionsAndDone setLayoutFor(final Device device, f @Override public IMaster done() { + final SortedSet actionImports = new CombinedJsImports(); final LinkedHashSet importPaths = new LinkedHashSet<>(); - // importPaths.add("polymer/polymer/polymer"); // FIXME check and delete if all good -- this is not really needed due to tg-entity-master-template-behavior dependencies final AtomicInteger funcActionSeq = new AtomicInteger(0); // used for both entity and property level functional actions final String prefix = ",\n"; @@ -248,6 +239,7 @@ public IMaster done() { shortcuts.append(actionConfig.shortcut.get() + " "); } importPaths.add(el.importPath()); + actionImports.addAll(el.actionImports()); widgetElement.add(el.render().attr("slot", "property-action").clazz("property-action-icon")); primaryActionObjects.append(prefix + el.createActionObject()); }); @@ -278,6 +270,7 @@ public IMaster done() { shortcuts.append(config.shortcut.get() + " "); } importPaths.add(el.importPath()); + actionImports.addAll(el.actionImports()); actionContainer.add(el.render().clazz("primary-action")); primaryActionObjects.append(prefix + el.createActionObject()); } @@ -297,7 +290,7 @@ public IMaster done() { final String dimensionsString = prefDimBuilder.toString(); final String entityMasterStr = ResourceLoader.getText("ua/com/fielden/platform/web/master/tg-entity-master-template.js") - .replace(IMPORTS, createImports(importPaths) + customImports.map(ci -> ci.toString()).orElse("")) + .replace(IMPORTS, createImports(importPaths) + customImports.map(ci -> ci.toString()).orElse("") + actionImports) .replace(ENTITY_TYPE, flattenedNameOf(entityType)) .replace("", elementContainer.toString()) // TODO should contain prop actions .replace("//@ready-callback", diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/with_master/impl/CalendarEntityMaster.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/with_master/impl/CalendarEntityMaster.java index 5b7687a3d4d..a065ba5bf60 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/with_master/impl/CalendarEntityMaster.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/api/with_master/impl/CalendarEntityMaster.java @@ -1,15 +1,5 @@ package ua.com.fielden.platform.web.view.master.api.with_master.impl; -import static java.util.Optional.empty; -import static ua.com.fielden.platform.web.centre.EntityCentre.IMPORTS; -import static ua.com.fielden.platform.web.centre.api.resultset.impl.FunctionalActionKind.PRIMARY_RESULT_SET; -import static ua.com.fielden.platform.web.view.master.EntityMaster.ENTITY_TYPE; -import static ua.com.fielden.platform.web.view.master.EntityMaster.flattenedNameOf; -import static ua.com.fielden.platform.web.view.master.api.impl.SimpleMasterBuilder.createImports; - -import java.util.LinkedHashSet; -import java.util.Optional; - import ua.com.fielden.platform.basic.IValueMatcherWithContext; import ua.com.fielden.platform.dom.DomElement; import ua.com.fielden.platform.dom.InnerTextElement; @@ -19,8 +9,21 @@ import ua.com.fielden.platform.web.centre.api.resultset.impl.FunctionalActionElement; import ua.com.fielden.platform.web.centre.api.resultset.impl.FunctionalActionKind; import ua.com.fielden.platform.web.interfaces.IRenderable; +import ua.com.fielden.platform.web.minijs.CombinedJsImports; +import ua.com.fielden.platform.web.minijs.JsImport; import ua.com.fielden.platform.web.view.master.api.IMaster; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.SortedSet; + +import static java.util.Optional.empty; +import static ua.com.fielden.platform.web.centre.EntityCentre.IMPORTS; +import static ua.com.fielden.platform.web.centre.api.resultset.impl.FunctionalActionKind.PRIMARY_RESULT_SET; +import static ua.com.fielden.platform.web.view.master.EntityMaster.ENTITY_TYPE; +import static ua.com.fielden.platform.web.view.master.EntityMaster.flattenedNameOf; +import static ua.com.fielden.platform.web.view.master.api.impl.SimpleMasterBuilder.createImports; + /** * An entity master that contains a calendar view. * @@ -61,6 +64,7 @@ public CalendarEntityMaster( this.editAction = editAction; + final SortedSet actionImports = new CombinedJsImports(); final LinkedHashSet importPaths = new LinkedHashSet<>(); importPaths.add(calendarComponentUri); @@ -81,6 +85,7 @@ public CalendarEntityMaster( final FunctionalActionElement el = FunctionalActionElement.newEntityActionForMaster(editAction, 0); importPaths.add(el.importPath()); + actionImports.addAll(el.actionImports()); calendar.add(el.render().attr("hidden", true).clazz("primary-action").attr("slot", "calendar-action")); final String editActionObjectString = el.createActionObject(); @@ -88,8 +93,10 @@ public CalendarEntityMaster( prefDimBuilder.append("{'width': function() {return '100%'}, 'height': function() {return '100%'}, 'widthUnit': '', 'heightUnit': ''}"); final String entityMasterStr = ResourceLoader.getText("ua/com/fielden/platform/web/master/tg-entity-master-template.js") - .replace(IMPORTS, createImports(importPaths) + - "\nimport { TgEntityBinderBehavior } from '/resources/binding/tg-entity-binder-behavior.js';\n") + .replace(IMPORTS, createImports(importPaths) + + "\nimport { TgEntityBinderBehavior } from '/resources/binding/tg-entity-binder-behavior.js';\n" + + actionImports + ) .replace(ENTITY_TYPE, flattenedNameOf(entityType)) .replace("", calendar.toString()) .replace("//generatedPrimaryActions", editActionObjectString) diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/chart/decker/api/impl/ChartDeckerMaster.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/chart/decker/api/impl/ChartDeckerMaster.java index 2229be17282..6b68604faf3 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/chart/decker/api/impl/ChartDeckerMaster.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/chart/decker/api/impl/ChartDeckerMaster.java @@ -1,24 +1,6 @@ package ua.com.fielden.platform.web.view.master.chart.decker.api.impl; -import static java.util.Optional.ofNullable; -import static org.apache.commons.lang3.StringUtils.isEmpty; -import static org.apache.commons.lang3.StringUtils.join; -import static ua.com.fielden.platform.serialisation.jackson.DefaultValueContract.getTimePortionToDisplay; -import static ua.com.fielden.platform.serialisation.jackson.DefaultValueContract.getTimeZone; -import static ua.com.fielden.platform.web.centre.EntityCentre.IMPORTS; -import static ua.com.fielden.platform.web.centre.api.resultset.impl.FunctionalActionKind.PRIMARY_RESULT_SET; -import static ua.com.fielden.platform.web.view.master.EntityMaster.ENTITY_TYPE; -import static ua.com.fielden.platform.web.view.master.EntityMaster.flattenedNameOf; -import static ua.com.fielden.platform.web.view.master.api.impl.SimpleMasterBuilder.createImports; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; - import org.apache.commons.lang3.StringUtils; - import ua.com.fielden.platform.basic.IValueMatcherWithContext; import ua.com.fielden.platform.dom.DomContainer; import ua.com.fielden.platform.dom.DomElement; @@ -32,25 +14,41 @@ import ua.com.fielden.platform.web.centre.api.resultset.impl.FunctionalActionElement; import ua.com.fielden.platform.web.centre.api.resultset.impl.FunctionalActionKind; import ua.com.fielden.platform.web.interfaces.IRenderable; +import ua.com.fielden.platform.web.minijs.CombinedJsImports; +import ua.com.fielden.platform.web.minijs.JsImport; import ua.com.fielden.platform.web.view.master.api.IMaster; import ua.com.fielden.platform.web.view.master.chart.decker.api.IChartDeckerConfig; +import java.util.*; +import java.util.function.Function; + +import static java.util.Optional.ofNullable; +import static org.apache.commons.lang3.StringUtils.isEmpty; +import static org.apache.commons.lang3.StringUtils.join; +import static ua.com.fielden.platform.serialisation.jackson.DefaultValueContract.getTimePortionToDisplay; +import static ua.com.fielden.platform.serialisation.jackson.DefaultValueContract.getTimeZone; +import static ua.com.fielden.platform.web.centre.EntityCentre.IMPORTS; +import static ua.com.fielden.platform.web.centre.api.resultset.impl.FunctionalActionKind.PRIMARY_RESULT_SET; +import static ua.com.fielden.platform.web.view.master.EntityMaster.ENTITY_TYPE; +import static ua.com.fielden.platform.web.view.master.EntityMaster.flattenedNameOf; +import static ua.com.fielden.platform.web.view.master.api.impl.SimpleMasterBuilder.createImports; + public class ChartDeckerMaster> implements IMaster { private final IRenderable renderable; private final List actions = new ArrayList<>(); public ChartDeckerMaster(final IChartDeckerConfig deckerConfig) { - + final SortedSet actionImports = new CombinedJsImports(); final LinkedHashSet importPaths = new LinkedHashSet<>(); importPaths.add("components/tg-bar-chart/tg-bar-chart"); final DomElement decks = createDeckElements(deckerConfig); - final Pair actions = generateActions(deckerConfig, importPaths); + final Pair actions = generateActions(deckerConfig, importPaths, actionImports); decks.add(actions.getValue()); final String entityMasterStr = ResourceLoader.getText("ua/com/fielden/platform/web/components/chart-decker/tg-chart-decker-template.js") - .replace(IMPORTS, createImports(importPaths)) + .replace(IMPORTS, createImports(importPaths) + actionImports) .replace(ENTITY_TYPE, flattenedNameOf(deckerConfig.getEntityType())) .replace("", decks.toString()) .replace("//generatedPrimaryActions", actions.getKey()) @@ -66,7 +64,7 @@ public DomElement render() { }; } - private Pair generateActions(final IChartDeckerConfig deckerConfig, final LinkedHashSet importPaths) { + private Pair generateActions(final IChartDeckerConfig deckerConfig, final LinkedHashSet importPaths, final SortedSet actionImports) { final DomElement container = new DomContainer(); final List primaryActionObjects = new ArrayList<>(); final List> decs = deckerConfig.getDecs(); @@ -79,6 +77,7 @@ private Pair generateActions(final IChartDeckerConfig dec if (config != null) { final FunctionalActionElement el = FunctionalActionElement.newPropertyActionForMaster(config, deckIndex, s.getPropertyName()); importPaths.add(el.importPath()); + actionImports.addAll(el.actionImports()); container.add(el.render().clazz("chart-action").attr("hidden", true).attr("action-index", seriesIndex).attr("deck-index", deckIndex)); primaryActionObjects.add(el.createActionObject()); } diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/hierarchy/ReferenceHierarchyMaster.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/hierarchy/ReferenceHierarchyMaster.java index 7eefe6801fb..73ecfd96ab4 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/hierarchy/ReferenceHierarchyMaster.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/hierarchy/ReferenceHierarchyMaster.java @@ -11,13 +11,12 @@ import ua.com.fielden.platform.web.centre.api.resultset.impl.FunctionalActionElement; import ua.com.fielden.platform.web.centre.api.resultset.impl.FunctionalActionKind; import ua.com.fielden.platform.web.interfaces.IRenderable; +import ua.com.fielden.platform.web.minijs.CombinedJsImports; +import ua.com.fielden.platform.web.minijs.JsImport; import ua.com.fielden.platform.web.ref_hierarchy.ReferenceHierarchyWebUiConfig; import ua.com.fielden.platform.web.view.master.api.IMaster; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; +import java.util.*; import static java.util.Optional.empty; import static ua.com.fielden.platform.web.centre.EntityCentre.IMPORTS; @@ -38,14 +37,13 @@ public class ReferenceHierarchyMaster implements IMaster { private final IRenderable renderable; public ReferenceHierarchyMaster () { - + final SortedSet actionImports = new CombinedJsImports(); final LinkedHashSet importPaths = new LinkedHashSet<>(); importPaths.add("components/tg-reference-hierarchy"); importPaths.add("editors/tg-singleline-text-editor"); importPaths.add("editors/tg-boolean-editor"); importPaths.add("actions/tg-ui-action"); - this.actions.add(EntityActionBuilder.editAction().withContext(context().withCurrentEntity().build()) .icon("editor:mode-edit") .longDesc("Opens master for editing this entity") @@ -97,6 +95,7 @@ public ReferenceHierarchyMaster () { for (int actionIdx = 0; actionIdx < this.actions.size(); actionIdx++) { final EntityActionConfig action = this.actions.get(actionIdx); final FunctionalActionElement el = FunctionalActionElement.newEntityActionForMaster(action, actionIdx); + actionImports.addAll(el.actionImports()); importPaths.add(el.importPath()); referenceHierarchyDom.add(el.render().attr("hidden", null).clazz("primary-action").attr("slot", "reference-hierarchy-action")); customActionObjects.append(prefix + el.createActionObject()); @@ -107,8 +106,10 @@ public ReferenceHierarchyMaster () { prefDimBuilder.append("{'width': function() {return '50%'}, 'height': function() {return '70%'}, 'widthUnit': '', 'heightUnit': ''}"); final String entityMasterStr = ResourceLoader.getText("ua/com/fielden/platform/web/master/tg-entity-master-template.js") - .replace(IMPORTS, createImports(importPaths)+ - "\nimport { TgEntityBinderBehavior } from '/resources/binding/tg-entity-binder-behavior.js';\n") + .replace(IMPORTS, createImports(importPaths) + + "\nimport { TgEntityBinderBehavior } from '/resources/binding/tg-entity-binder-behavior.js';\n" + + actionImports + ) .replace(ENTITY_TYPE, flattenedNameOf(ReferenceHierarchy.class)) .replace("", referenceHierarchyDom.toString()) .replace("//generatedPrimaryActions", customActionObjectsString.length() > prefixLength ? customActionObjectsString.substring(prefixLength) diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/scatterplot/api/implementation/ScatterPlotMaster.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/scatterplot/api/implementation/ScatterPlotMaster.java index c25a936330b..698283961c1 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/scatterplot/api/implementation/ScatterPlotMaster.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/view/master/scatterplot/api/implementation/ScatterPlotMaster.java @@ -14,6 +14,8 @@ import ua.com.fielden.platform.web.centre.api.resultset.impl.FunctionalActionElement; import ua.com.fielden.platform.web.centre.api.resultset.impl.FunctionalActionKind; import ua.com.fielden.platform.web.interfaces.IRenderable; +import ua.com.fielden.platform.web.minijs.CombinedJsImports; +import ua.com.fielden.platform.web.minijs.JsImport; import ua.com.fielden.platform.web.view.master.api.IMaster; import java.util.*; @@ -52,11 +54,12 @@ public class ScatterPlotMaster> implements IMaster scatterPlotMasterBuilder) { this.action = scatterPlotMasterBuilder.getAction(); + final SortedSet actionImports = new CombinedJsImports(); final LinkedHashSet importPaths = new LinkedHashSet<>(); - final Optional> actionPair = generateAction(importPaths, scatterPlotMasterBuilder.getAction()); + final Optional> actionPair = generateAction(importPaths, actionImports, scatterPlotMasterBuilder.getAction()); final String entityMasterStr = ResourceLoader.getText("ua/com/fielden/platform/web/master/scatter-plot/tg-scatter-plot-master-template.js") - .replace(IMPORTS, createImports(importPaths)) + .replace(IMPORTS, createImports(importPaths) + actionImports) .replace(ENTITY_TYPE, flattenedNameOf(scatterPlotMasterBuilder.getEntityType())) .replace("", actionPair.map(a -> a.getValue().toString()).orElse("")) .replace("//generatedPrimaryActions", actionPair.map(a -> a.getKey().toString()).orElse("")) @@ -73,10 +76,11 @@ public DomElement render() { }; } - private Optional> generateAction(final LinkedHashSet importPaths, final EntityActionConfig action) { + private Optional> generateAction(final LinkedHashSet importPaths, final SortedSet actionImports, final EntityActionConfig action) { if (action != null) { final FunctionalActionElement el = FunctionalActionElement.newEntityActionForMaster(action, 0); importPaths.add(el.importPath()); + actionImports.addAll(el.actionImports()); return of(new Pair<>(el.createActionObject(), el.render().clazz("chart-action").attr("hidden", true))); } return empty(); diff --git a/platform-web-ui/src/main/resources/_virtual/FileSaver.js b/platform-web-ui/src/main/resources/_virtual/FileSaver.js new file mode 100644 index 00000000000..32500616aa2 --- /dev/null +++ b/platform-web-ui/src/main/resources/_virtual/FileSaver.js @@ -0,0 +1,5 @@ +import { __require as requireFileSaver } from '../polymer/file-saver/dist/FileSaver.js'; + +var FileSaverExports = requireFileSaver(); + +export { FileSaverExports as F }; diff --git a/platform-web-ui/src/main/resources/_virtual/FileSaver2.js b/platform-web-ui/src/main/resources/_virtual/FileSaver2.js new file mode 100644 index 00000000000..5ac0508f3ea --- /dev/null +++ b/platform-web-ui/src/main/resources/_virtual/FileSaver2.js @@ -0,0 +1,3 @@ +var FileSaver = {exports: {}}; + +export { FileSaver as __module }; diff --git a/platform-web-ui/src/main/resources/_virtual/_commonjsHelpers.js b/platform-web-ui/src/main/resources/_virtual/_commonjsHelpers.js index 7b7c5f4f531..fb22ddb24b6 100644 --- a/platform-web-ui/src/main/resources/_virtual/_commonjsHelpers.js +++ b/platform-web-ui/src/main/resources/_virtual/_commonjsHelpers.js @@ -1,5 +1,7 @@ +var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } -export { getDefaultExportFromCjs }; +export { commonjsGlobal, getDefaultExportFromCjs }; diff --git a/platform-web-ui/src/main/resources/filesaver/FileSaver.js b/platform-web-ui/src/main/resources/filesaver/FileSaver.js deleted file mode 100644 index fd5927d2d0e..00000000000 --- a/platform-web-ui/src/main/resources/filesaver/FileSaver.js +++ /dev/null @@ -1,280 +0,0 @@ -/* FileSaver.js - * A saveAs() FileSaver implementation. - * 1.1.20160328 - * - * By Eli Grey, http://eligrey.com - * License: MIT - * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md - */ - -/*global self */ -/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ - -/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ - -var saveAs = saveAs || (function(view) { - "use strict"; - // IE <10 is explicitly unsupported - if (typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) { - return; - } - var - doc = view.document - // only get URL when necessary in case Blob.js hasn't overridden it yet - , get_URL = function() { - return view.URL || view.webkitURL || view; - } - , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") - , can_use_save_link = "download" in save_link - , click = function(node) { - var event = new MouseEvent("click"); - node.dispatchEvent(event); - } - , is_safari = /Version\/[\d\.]+.*Safari/.test(navigator.userAgent) - , webkit_req_fs = view.webkitRequestFileSystem - , req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem - , throw_outside = function(ex) { - (view.setImmediate || view.setTimeout)(function() { - throw ex; - }, 0); - } - , force_saveable_type = "application/octet-stream" - , fs_min_size = 0 - // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to - , arbitrary_revoke_timeout = 1000 * 40 // in ms - , revoke = function(file) { - var revoker = function() { - if (typeof file === "string") { // file is an object URL - get_URL().revokeObjectURL(file); - } else { // file is a File - file.remove(); - } - }; - /* // Take note W3C: - var - uri = typeof file === "string" ? file : file.toURL() - , revoker = function(evt) { - // idealy DownloadFinishedEvent.data would be the URL requested - if (evt.data === uri) { - if (typeof file === "string") { // file is an object URL - get_URL().revokeObjectURL(file); - } else { // file is a File - file.remove(); - } - } - } - ; - view.addEventListener("downloadfinished", revoker); - */ - setTimeout(revoker, arbitrary_revoke_timeout); - } - , dispatch = function(filesaver, event_types, event) { - event_types = [].concat(event_types); - var i = event_types.length; - while (i--) { - var listener = filesaver["on" + event_types[i]]; - if (typeof listener === "function") { - try { - listener.call(filesaver, event || filesaver); - } catch (ex) { - throw_outside(ex); - } - } - } - } - , auto_bom = function(blob) { - // prepend BOM for UTF-8 XML and text/* types (including HTML) - if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { - return new Blob(["\ufeff", blob], {type: blob.type}); - } - return blob; - } - , FileSaver = function(blob, name, no_auto_bom) { - if (!no_auto_bom) { - blob = auto_bom(blob); - } - // First try a.download, then web filesystem, then object URLs - var - filesaver = this - , type = blob.type - , blob_changed = false - , object_url - , target_view - , dispatch_all = function() { - dispatch(filesaver, "writestart progress write writeend".split(" ")); - } - // on any filesys errors revert to saving with object URLs - , fs_error = function() { - if (target_view && is_safari && typeof FileReader !== "undefined") { - // Safari doesn't allow downloading of blob urls - var reader = new FileReader(); - reader.onloadend = function() { - var base64Data = reader.result; - target_view.location.href = "data:attachment/file" + base64Data.slice(base64Data.search(/[,;]/)); - filesaver.readyState = filesaver.DONE; - dispatch_all(); - }; - reader.readAsDataURL(blob); - filesaver.readyState = filesaver.INIT; - return; - } - // don't create more object URLs than needed - if (blob_changed || !object_url) { - object_url = get_URL().createObjectURL(blob); - } - if (target_view) { - target_view.location.href = object_url; - } else { - var new_tab = view.open(object_url, "_blank"); - if (new_tab === undefined && is_safari) { - //Apple do not allow window.open, see http://bit.ly/1kZffRI - view.location.href = object_url - } - } - filesaver.readyState = filesaver.DONE; - dispatch_all(); - revoke(object_url); - } - , abortable = function(func) { - return function() { - if (filesaver.readyState !== filesaver.DONE) { - return func.apply(this, arguments); - } - }; - } - , create_if_not_found = {create: true, exclusive: false} - , slice - ; - filesaver.readyState = filesaver.INIT; - if (!name) { - name = "download"; - } - if (can_use_save_link) { - object_url = get_URL().createObjectURL(blob); - setTimeout(function() { - save_link.href = object_url; - save_link.download = name; - click(save_link); - dispatch_all(); - revoke(object_url); - filesaver.readyState = filesaver.DONE; - }); - return; - } - // Object and web filesystem URLs have a problem saving in Google Chrome when - // viewed in a tab, so I force save with application/octet-stream - // http://code.google.com/p/chromium/issues/detail?id=91158 - // Update: Google errantly closed 91158, I submitted it again: - // https://code.google.com/p/chromium/issues/detail?id=389642 - if (view.chrome && type && type !== force_saveable_type) { - slice = blob.slice || blob.webkitSlice; - blob = slice.call(blob, 0, blob.size, force_saveable_type); - blob_changed = true; - } - // Since I can't be sure that the guessed media type will trigger a download - // in WebKit, I append .download to the filename. - // https://bugs.webkit.org/show_bug.cgi?id=65440 - if (webkit_req_fs && name !== "download") { - name += ".download"; - } - if (type === force_saveable_type || webkit_req_fs) { - target_view = view; - } - if (!req_fs) { - fs_error(); - return; - } - fs_min_size += blob.size; - req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) { - fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) { - var save = function() { - dir.getFile(name, create_if_not_found, abortable(function(file) { - file.createWriter(abortable(function(writer) { - writer.onwriteend = function(event) { - target_view.location.href = file.toURL(); - filesaver.readyState = filesaver.DONE; - dispatch(filesaver, "writeend", event); - revoke(file); - }; - writer.onerror = function() { - var error = writer.error; - if (error.code !== error.ABORT_ERR) { - fs_error(); - } - }; - "writestart progress write abort".split(" ").forEach(function(event) { - writer["on" + event] = filesaver["on" + event]; - }); - writer.write(blob); - filesaver.abort = function() { - writer.abort(); - filesaver.readyState = filesaver.DONE; - }; - filesaver.readyState = filesaver.WRITING; - }), fs_error); - }), fs_error); - }; - dir.getFile(name, {create: false}, abortable(function(file) { - // delete file if it already exists - file.remove(); - save(); - }), abortable(function(ex) { - if (ex.code === ex.NOT_FOUND_ERR) { - save(); - } else { - fs_error(); - } - })); - }), fs_error); - }), fs_error); - } - , FS_proto = FileSaver.prototype - , saveAs = function(blob, name, no_auto_bom) { - return new FileSaver(blob, name, no_auto_bom); - } - ; - // IE 10+ (native saveAs) - if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { - return function(blob, name, no_auto_bom) { - if (!no_auto_bom) { - blob = auto_bom(blob); - } - return navigator.msSaveOrOpenBlob(blob, name || "download"); - }; - } - - FS_proto.abort = function() { - var filesaver = this; - filesaver.readyState = filesaver.DONE; - dispatch(filesaver, "abort"); - }; - FS_proto.readyState = FS_proto.INIT = 0; - FS_proto.WRITING = 1; - FS_proto.DONE = 2; - - FS_proto.error = - FS_proto.onwritestart = - FS_proto.onprogress = - FS_proto.onwrite = - FS_proto.onabort = - FS_proto.onerror = - FS_proto.onwriteend = - null; - - return saveAs; -}( - typeof self !== "undefined" && self - || typeof window !== "undefined" && window - || this.content -)); -// `self` is undefined in Firefox for Android content script context -// while `this` is nsIContentFrameMessageManager -// with an attribute `content` that corresponds to the window - -if (typeof module !== "undefined" && module.exports) { - module.exports.saveAs = saveAs; -} else if ((typeof define !== "undefined" && define !== null) && (define.amd !== null)) { - define([], function() { - return saveAs; - }); -} diff --git a/platform-web-ui/src/main/resources/filesaver/FileSaver.min.js b/platform-web-ui/src/main/resources/filesaver/FileSaver.min.js deleted file mode 100644 index 6268ec99dde..00000000000 --- a/platform-web-ui/src/main/resources/filesaver/FileSaver.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ -var saveAs=saveAs||function(e){"use strict";if("undefined"==typeof navigator||!/MSIE [1-9]\./.test(navigator.userAgent)){var t=e.document,n=function(){return e.URL||e.webkitURL||e},o=t.createElementNS("http://www.w3.org/1999/xhtml","a"),r="download"in o,i=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},a=/Version\/[\d\.]+.*Safari/.test(navigator.userAgent),c=e.webkitRequestFileSystem,f=e.requestFileSystem||c||e.mozRequestFileSystem,u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},d="application/octet-stream",s=0,l=4e4,v=function(e){var t=function(){"string"==typeof e?n().revokeObjectURL(e):e.remove()};setTimeout(t,l)},p=function(e,t,n){t=[].concat(t);for(var o=t.length;o--;){var r=e["on"+t[o]];if("function"==typeof r)try{r.call(e,n||e)}catch(i){u(i)}}},w=function(e){return/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)?new Blob(["\ufeff",e],{type:e.type}):e},y=function(t,u,l){l||(t=w(t));var y,m,S,h=this,R=t.type,O=!1,g=function(){p(h,"writestart progress write writeend".split(" "))},b=function(){if(m&&a&&"undefined"!=typeof FileReader){var o=new FileReader;return o.onloadend=function(){var e=o.result;m.location.href="data:attachment/file"+e.slice(e.search(/[,;]/)),h.readyState=h.DONE,g()},o.readAsDataURL(t),void(h.readyState=h.INIT)}if((O||!y)&&(y=n().createObjectURL(t)),m)m.location.href=y;else{var r=e.open(y,"_blank");void 0===r&&a&&(e.location.href=y)}h.readyState=h.DONE,g(),v(y)},E=function(e){return function(){return h.readyState!==h.DONE?e.apply(this,arguments):void 0}},N={create:!0,exclusive:!1};return h.readyState=h.INIT,u||(u="download"),r?(y=n().createObjectURL(t),void setTimeout(function(){o.href=y,o.download=u,i(o),g(),v(y),h.readyState=h.DONE})):(e.chrome&&R&&R!==d&&(S=t.slice||t.webkitSlice,t=S.call(t,0,t.size,d),O=!0),c&&"download"!==u&&(u+=".download"),(R===d||c)&&(m=e),f?(s+=t.size,void f(e.TEMPORARY,s,E(function(e){e.root.getDirectory("saved",N,E(function(e){var n=function(){e.getFile(u,N,E(function(e){e.createWriter(E(function(n){n.onwriteend=function(t){m.location.href=e.toURL(),h.readyState=h.DONE,p(h,"writeend",t),v(e)},n.onerror=function(){var e=n.error;e.code!==e.ABORT_ERR&&b()},"writestart progress write abort".split(" ").forEach(function(e){n["on"+e]=h["on"+e]}),n.write(t),h.abort=function(){n.abort(),h.readyState=h.DONE},h.readyState=h.WRITING}),b)}),b)};e.getFile(u,{create:!1},E(function(e){e.remove(),n()}),E(function(e){e.code===e.NOT_FOUND_ERR?n():b()}))}),b)}),b)):void b())},m=y.prototype,S=function(e,t,n){return new y(e,t,n)};return"undefined"!=typeof navigator&&navigator.msSaveOrOpenBlob?function(e,t,n){return n||(e=w(e)),navigator.msSaveOrOpenBlob(e,t||"download")}:(m.abort=function(){var e=this;e.readyState=e.DONE,p(e,"abort")},m.readyState=m.INIT=0,m.WRITING=1,m.DONE=2,m.error=m.onwritestart=m.onprogress=m.onwrite=m.onabort=m.onerror=m.onwriteend=null,S)}}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this.content);"undefined"!=typeof module&&module.exports?module.exports.saveAs=saveAs:"undefined"!=typeof define&&null!==define&&null!==define.amd&&define([],function(){return saveAs}); \ No newline at end of file diff --git a/platform-web-ui/src/main/resources/lib/file-saver-lib.js b/platform-web-ui/src/main/resources/lib/file-saver-lib.js new file mode 100644 index 00000000000..80cb65f8020 --- /dev/null +++ b/platform-web-ui/src/main/resources/lib/file-saver-lib.js @@ -0,0 +1 @@ +export { saveAs } from 'file-saver/dist/FileSaver'; // use explicit file instead of 'file-saver' to avoid minified version \ No newline at end of file diff --git a/platform-web-ui/src/main/resources/package-lock.json b/platform-web-ui/src/main/resources/package-lock.json index 7d932c40371..f1208b96bc6 100644 --- a/platform-web-ui/src/main/resources/package-lock.json +++ b/platform-web-ui/src/main/resources/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "dependencies": { - "@fullcalendar/moment-timezone": "^6.1.15", + "@fullcalendar/moment-timezone": "^6.1.17", "@google-web-components/google-chart": "^3.4.0", "@polymer/app-layout": "^3.1.0", "@polymer/app-route": "^3.0.2", @@ -63,9 +63,10 @@ "@webcomponents/shadycss": "^1.11.2", "@webcomponents/webcomponentsjs": "^2.8.0", "antlr4": "^4.13.2", - "fullcalendar": "^6.1.15", + "file-saver": "^2.0.5", + "fullcalendar": "^6.1.17", "moment": "^2.30.1", - "moment-timezone": "^0.5.47", + "moment-timezone": "^0.5.48", "web-animations-js": "^2.3.2" }, "devDependencies": { @@ -83,73 +84,73 @@ "optional": true }, "node_modules/@fullcalendar/core": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.15.tgz", - "integrity": "sha512-BuX7o6ALpLb84cMw1FCB9/cSgF4JbVO894cjJZ6kP74jzbUZNjtwffwRdA+Id8rrLjT30d/7TrkW90k4zbXB5Q==", + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.17.tgz", + "integrity": "sha512-0W7lnIrv18ruJ5zeWBeNZXO8qCWlzxDdp9COFEsZnyNjiEhUVnrW/dPbjRKYpL0edGG0/Lhs0ghp1z/5ekt8ZA==", "license": "MIT", "dependencies": { "preact": "~10.12.1" } }, "node_modules/@fullcalendar/daygrid": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.15.tgz", - "integrity": "sha512-j8tL0HhfiVsdtOCLfzK2J0RtSkiad3BYYemwQKq512cx6btz6ZZ2RNc/hVnIxluuWFyvx5sXZwoeTJsFSFTEFA==", + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.17.tgz", + "integrity": "sha512-K7m+pd7oVJ9fW4h7CLDdDGJbc9szJ1xDU1DZ2ag+7oOo1aCNLv44CehzkkknM6r8EYlOOhgaelxQpKAI4glj7A==", "license": "MIT", "peerDependencies": { - "@fullcalendar/core": "~6.1.15" + "@fullcalendar/core": "~6.1.17" } }, "node_modules/@fullcalendar/interaction": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.15.tgz", - "integrity": "sha512-DOTSkofizM7QItjgu7W68TvKKvN9PSEEvDJceyMbQDvlXHa7pm/WAVtAc6xSDZ9xmB1QramYoWGLHkCYbTW1rQ==", + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.17.tgz", + "integrity": "sha512-AudvQvgmJP2FU89wpSulUUjeWv24SuyCx8FzH2WIPVaYg+vDGGYarI7K6PcM3TH7B/CyaBjm5Rqw9lXgnwt5YA==", "license": "MIT", "peerDependencies": { - "@fullcalendar/core": "~6.1.15" + "@fullcalendar/core": "~6.1.17" } }, "node_modules/@fullcalendar/list": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.15.tgz", - "integrity": "sha512-U1bce04tYDwkFnuVImJSy2XalYIIQr6YusOWRPM/5ivHcJh67Gm8CIMSWpi3KdRSNKFkqBxLPkfZGBMaOcJYug==", + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.17.tgz", + "integrity": "sha512-fkyK49F9IxwlGUBVhJGsFpd/LTi/vRVERLIAe1HmBaGkjwpxnynm8TMLb9mZip97wvDk3CmZWduMe6PxscAlow==", "license": "MIT", "peerDependencies": { - "@fullcalendar/core": "~6.1.15" + "@fullcalendar/core": "~6.1.17" } }, "node_modules/@fullcalendar/moment-timezone": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/@fullcalendar/moment-timezone/-/moment-timezone-6.1.15.tgz", - "integrity": "sha512-hXPhI/HeSF1dXWD4WUJ2+2B0b4aNuw6/jCRoUQ2k58ye8rNVBlUglLTKQa+SZLw0q6Fpk0lUCu9q0RcxbbbfEA==", + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/moment-timezone/-/moment-timezone-6.1.17.tgz", + "integrity": "sha512-MEtuZSkXDuPRc5xpr8TmNxp+vB/rZROm82miGkA1MyzUQT+HrIA9iFvuIUi6XXGgUGb4Av2kxRfk6tgOKjTm0g==", "license": "MIT", "peerDependencies": { - "@fullcalendar/core": "~6.1.15", + "@fullcalendar/core": "~6.1.17", "moment-timezone": "^0.5.40" } }, "node_modules/@fullcalendar/multimonth": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.15.tgz", - "integrity": "sha512-sEZY6jbOYkeF9TwhUldG+UUVv+hiPlGkS8zZEgPR7ypcjhipyA03c5rPjx7N6huOHqh6lCMH59zlohLooQRlaw==", + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.17.tgz", + "integrity": "sha512-ZxA9mkTzKayCdxR5je9P9++qqhSeSbuvXmvZ6doZw6omv8K52cD7XJii+P7gvxATXxtI6hg4i+DuMyOHxP1E2g==", "license": "MIT", "dependencies": { - "@fullcalendar/daygrid": "~6.1.15" + "@fullcalendar/daygrid": "~6.1.17" }, "peerDependencies": { - "@fullcalendar/core": "~6.1.15" + "@fullcalendar/core": "~6.1.17" } }, "node_modules/@fullcalendar/timegrid": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.15.tgz", - "integrity": "sha512-61ORr3A148RtxQ2FNG7JKvacyA/TEVZ7z6I+3E9Oeu3dqTf6M928bFcpehRTIK6zIA6Yifs7BeWHgOE9dFnpbw==", + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.17.tgz", + "integrity": "sha512-K4PlA3L3lclLOs3IX8cvddeiJI9ZVMD7RA9IqaWwbvac771971foc9tFze9YY+Pqesf6S+vhS2dWtEVlERaGlQ==", "license": "MIT", "dependencies": { - "@fullcalendar/daygrid": "~6.1.15" + "@fullcalendar/daygrid": "~6.1.17" }, "peerDependencies": { - "@fullcalendar/core": "~6.1.15" + "@fullcalendar/core": "~6.1.17" } }, "node_modules/@google-web-components/google-chart": { @@ -1129,9 +1130,9 @@ } }, "node_modules/dompurify": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz", - "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz", + "integrity": "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -1167,6 +1168,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==", + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1230,17 +1237,17 @@ } }, "node_modules/fullcalendar": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-6.1.15.tgz", - "integrity": "sha512-CFnh1yswjRh9puJVDk8VGwTlyZ6eXxr4qLI7QCA0+bozyAm+BluP1US5mOtgk0gEq23nQxGSNDoBvAraz++saQ==", + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-6.1.17.tgz", + "integrity": "sha512-5pq3jYo9cJnVn8TrnukJdP3uNZWk2V1uiTqVXIaSbO5qIXeF3H1jE11PAB5fBOacnZ9HLI/98IT82Y1rz/2VIw==", "license": "MIT", "dependencies": { - "@fullcalendar/core": "~6.1.15", - "@fullcalendar/daygrid": "~6.1.15", - "@fullcalendar/interaction": "~6.1.15", - "@fullcalendar/list": "~6.1.15", - "@fullcalendar/multimonth": "~6.1.15", - "@fullcalendar/timegrid": "~6.1.15" + "@fullcalendar/core": "~6.1.17", + "@fullcalendar/daygrid": "~6.1.17", + "@fullcalendar/interaction": "~6.1.17", + "@fullcalendar/list": "~6.1.17", + "@fullcalendar/multimonth": "~6.1.17", + "@fullcalendar/timegrid": "~6.1.17" } }, "node_modules/get-caller-file": { @@ -1539,9 +1546,9 @@ } }, "node_modules/moment-timezone": { - "version": "0.5.47", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.47.tgz", - "integrity": "sha512-UbNt/JAWS0m/NJOebR0QMRHBk0hu03r5dx9GK8Cs0AS3I81yDcOc9k+DytPItgVvBP7J6Mf6U2n3BPAacAV9oA==", + "version": "0.5.48", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", + "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", "license": "MIT", "dependencies": { "moment": "^2.29.4" @@ -1683,9 +1690,9 @@ } }, "node_modules/prosemirror-commands": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.0.tgz", - "integrity": "sha512-6toodS4R/Aah5pdsrIwnTYPEjW70SlO5a66oo5Kk+CIrgJz3ukOoS+FYDGqvQlAX5PxoGWDX1oD++tn5X3pyRA==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", "license": "MIT", "dependencies": { "prosemirror-model": "^1.0.0", @@ -1726,9 +1733,9 @@ } }, "node_modules/prosemirror-model": { - "version": "1.25.0", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.0.tgz", - "integrity": "sha512-/8XUmxWf0pkj2BmtqZHYJipTBMHIdVjuvFzMvEoxrtyGNmfvdhBiRwYt/eFwy2wA9DtBW3RLqvZnjurEkHaFCw==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.1.tgz", + "integrity": "sha512-AUvbm7qqmpZa5d9fPKMvH1Q5bqYQvAZWOGRvxsB6iFLyycvC9MwNemNVjHVrWgjaoxAfY8XVg7DbvQ/qxvI9Eg==", "license": "MIT", "dependencies": { "orderedmap": "^2.0.0" @@ -1746,18 +1753,18 @@ } }, "node_modules/prosemirror-transform": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.3.tgz", - "integrity": "sha512-Nhh/+1kZGRINbEHmVu39oynhcap4hWTs/BlU7NnxWj3+l0qi8I1mu67v6mMdEe/ltD8hHvU4FV6PHiCw2VSpMw==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.4.tgz", + "integrity": "sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==", "license": "MIT", "dependencies": { "prosemirror-model": "^1.21.0" } }, "node_modules/prosemirror-view": { - "version": "1.38.1", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.38.1.tgz", - "integrity": "sha512-4FH/uM1A4PNyrxXbD+RAbAsf0d/mM0D/wAKSVVWK7o0A9Q/oOXJBrw786mBf2Vnrs/Edly6dH6Z2gsb7zWwaUw==", + "version": "1.39.2", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.39.2.tgz", + "integrity": "sha512-BmOkml0QWNob165gyUxXi5K5CVUgVPpqMEAAml/qzgKn9boLUWVPzQ6LtzXw8Cn1GtRQX4ELumPxqtLTDaAKtg==", "license": "MIT", "dependencies": { "prosemirror-model": "^1.20.0", diff --git a/platform-web-ui/src/main/resources/package.json b/platform-web-ui/src/main/resources/package.json index 320cce00afe..c272bd16722 100644 --- a/platform-web-ui/src/main/resources/package.json +++ b/platform-web-ui/src/main/resources/package.json @@ -1,5 +1,6 @@ { "dependencies": { + "@fullcalendar/moment-timezone": "^6.1.17", "@google-web-components/google-chart": "^3.4.0", "@polymer/app-layout": "^3.1.0", "@polymer/app-route": "^3.0.2", @@ -57,10 +58,10 @@ "@webcomponents/shadycss": "^1.11.2", "@webcomponents/webcomponentsjs": "^2.8.0", "antlr4": "^4.13.2", - "fullcalendar": "^6.1.15", - "@fullcalendar/moment-timezone": "^6.1.15", + "file-saver": "^2.0.5", + "fullcalendar": "^6.1.17", "moment": "^2.30.1", - "moment-timezone": "^0.5.47", + "moment-timezone": "^0.5.48", "web-animations-js": "^2.3.2" }, "devDependencies": { @@ -75,7 +76,7 @@ }, "overrides": { "@toast-ui/editor": { - "dompurify": "^3.2.4", + "dompurify": "^3.2.5", "@types/trusted-types": "../_EXCLUDED_" }, "wct-browser-legacy": { diff --git a/platform-web-ui/src/main/resources/polymer/.package-lock.json b/platform-web-ui/src/main/resources/polymer/.package-lock.json index 3139f43e336..78917cbc24f 100644 --- a/platform-web-ui/src/main/resources/polymer/.package-lock.json +++ b/platform-web-ui/src/main/resources/polymer/.package-lock.json @@ -7,73 +7,73 @@ "optional": true }, "node_modules/@fullcalendar/core": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.15.tgz", - "integrity": "sha512-BuX7o6ALpLb84cMw1FCB9/cSgF4JbVO894cjJZ6kP74jzbUZNjtwffwRdA+Id8rrLjT30d/7TrkW90k4zbXB5Q==", + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.17.tgz", + "integrity": "sha512-0W7lnIrv18ruJ5zeWBeNZXO8qCWlzxDdp9COFEsZnyNjiEhUVnrW/dPbjRKYpL0edGG0/Lhs0ghp1z/5ekt8ZA==", "license": "MIT", "dependencies": { "preact": "~10.12.1" } }, "node_modules/@fullcalendar/daygrid": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.15.tgz", - "integrity": "sha512-j8tL0HhfiVsdtOCLfzK2J0RtSkiad3BYYemwQKq512cx6btz6ZZ2RNc/hVnIxluuWFyvx5sXZwoeTJsFSFTEFA==", + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.17.tgz", + "integrity": "sha512-K7m+pd7oVJ9fW4h7CLDdDGJbc9szJ1xDU1DZ2ag+7oOo1aCNLv44CehzkkknM6r8EYlOOhgaelxQpKAI4glj7A==", "license": "MIT", "peerDependencies": { - "@fullcalendar/core": "~6.1.15" + "@fullcalendar/core": "~6.1.17" } }, "node_modules/@fullcalendar/interaction": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.15.tgz", - "integrity": "sha512-DOTSkofizM7QItjgu7W68TvKKvN9PSEEvDJceyMbQDvlXHa7pm/WAVtAc6xSDZ9xmB1QramYoWGLHkCYbTW1rQ==", + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.17.tgz", + "integrity": "sha512-AudvQvgmJP2FU89wpSulUUjeWv24SuyCx8FzH2WIPVaYg+vDGGYarI7K6PcM3TH7B/CyaBjm5Rqw9lXgnwt5YA==", "license": "MIT", "peerDependencies": { - "@fullcalendar/core": "~6.1.15" + "@fullcalendar/core": "~6.1.17" } }, "node_modules/@fullcalendar/list": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.15.tgz", - "integrity": "sha512-U1bce04tYDwkFnuVImJSy2XalYIIQr6YusOWRPM/5ivHcJh67Gm8CIMSWpi3KdRSNKFkqBxLPkfZGBMaOcJYug==", + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.17.tgz", + "integrity": "sha512-fkyK49F9IxwlGUBVhJGsFpd/LTi/vRVERLIAe1HmBaGkjwpxnynm8TMLb9mZip97wvDk3CmZWduMe6PxscAlow==", "license": "MIT", "peerDependencies": { - "@fullcalendar/core": "~6.1.15" + "@fullcalendar/core": "~6.1.17" } }, "node_modules/@fullcalendar/moment-timezone": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/@fullcalendar/moment-timezone/-/moment-timezone-6.1.15.tgz", - "integrity": "sha512-hXPhI/HeSF1dXWD4WUJ2+2B0b4aNuw6/jCRoUQ2k58ye8rNVBlUglLTKQa+SZLw0q6Fpk0lUCu9q0RcxbbbfEA==", + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/moment-timezone/-/moment-timezone-6.1.17.tgz", + "integrity": "sha512-MEtuZSkXDuPRc5xpr8TmNxp+vB/rZROm82miGkA1MyzUQT+HrIA9iFvuIUi6XXGgUGb4Av2kxRfk6tgOKjTm0g==", "license": "MIT", "peerDependencies": { - "@fullcalendar/core": "~6.1.15", + "@fullcalendar/core": "~6.1.17", "moment-timezone": "^0.5.40" } }, "node_modules/@fullcalendar/multimonth": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.15.tgz", - "integrity": "sha512-sEZY6jbOYkeF9TwhUldG+UUVv+hiPlGkS8zZEgPR7ypcjhipyA03c5rPjx7N6huOHqh6lCMH59zlohLooQRlaw==", + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.17.tgz", + "integrity": "sha512-ZxA9mkTzKayCdxR5je9P9++qqhSeSbuvXmvZ6doZw6omv8K52cD7XJii+P7gvxATXxtI6hg4i+DuMyOHxP1E2g==", "license": "MIT", "dependencies": { - "@fullcalendar/daygrid": "~6.1.15" + "@fullcalendar/daygrid": "~6.1.17" }, "peerDependencies": { - "@fullcalendar/core": "~6.1.15" + "@fullcalendar/core": "~6.1.17" } }, "node_modules/@fullcalendar/timegrid": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.15.tgz", - "integrity": "sha512-61ORr3A148RtxQ2FNG7JKvacyA/TEVZ7z6I+3E9Oeu3dqTf6M928bFcpehRTIK6zIA6Yifs7BeWHgOE9dFnpbw==", + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.17.tgz", + "integrity": "sha512-K4PlA3L3lclLOs3IX8cvddeiJI9ZVMD7RA9IqaWwbvac771971foc9tFze9YY+Pqesf6S+vhS2dWtEVlERaGlQ==", "license": "MIT", "dependencies": { - "@fullcalendar/daygrid": "~6.1.15" + "@fullcalendar/daygrid": "~6.1.17" }, "peerDependencies": { - "@fullcalendar/core": "~6.1.15" + "@fullcalendar/core": "~6.1.17" } }, "node_modules/@google-web-components/google-chart": { @@ -1053,9 +1053,9 @@ } }, "node_modules/dompurify": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz", - "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz", + "integrity": "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -1091,6 +1091,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==", + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1139,17 +1145,17 @@ "license": "ISC" }, "node_modules/fullcalendar": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-6.1.15.tgz", - "integrity": "sha512-CFnh1yswjRh9puJVDk8VGwTlyZ6eXxr4qLI7QCA0+bozyAm+BluP1US5mOtgk0gEq23nQxGSNDoBvAraz++saQ==", + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-6.1.17.tgz", + "integrity": "sha512-5pq3jYo9cJnVn8TrnukJdP3uNZWk2V1uiTqVXIaSbO5qIXeF3H1jE11PAB5fBOacnZ9HLI/98IT82Y1rz/2VIw==", "license": "MIT", "dependencies": { - "@fullcalendar/core": "~6.1.15", - "@fullcalendar/daygrid": "~6.1.15", - "@fullcalendar/interaction": "~6.1.15", - "@fullcalendar/list": "~6.1.15", - "@fullcalendar/multimonth": "~6.1.15", - "@fullcalendar/timegrid": "~6.1.15" + "@fullcalendar/core": "~6.1.17", + "@fullcalendar/daygrid": "~6.1.17", + "@fullcalendar/interaction": "~6.1.17", + "@fullcalendar/list": "~6.1.17", + "@fullcalendar/multimonth": "~6.1.17", + "@fullcalendar/timegrid": "~6.1.17" } }, "node_modules/get-caller-file": { @@ -1448,9 +1454,9 @@ } }, "node_modules/moment-timezone": { - "version": "0.5.47", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.47.tgz", - "integrity": "sha512-UbNt/JAWS0m/NJOebR0QMRHBk0hu03r5dx9GK8Cs0AS3I81yDcOc9k+DytPItgVvBP7J6Mf6U2n3BPAacAV9oA==", + "version": "0.5.48", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", + "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", "license": "MIT", "dependencies": { "moment": "^2.29.4" @@ -1592,9 +1598,9 @@ } }, "node_modules/prosemirror-commands": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.0.tgz", - "integrity": "sha512-6toodS4R/Aah5pdsrIwnTYPEjW70SlO5a66oo5Kk+CIrgJz3ukOoS+FYDGqvQlAX5PxoGWDX1oD++tn5X3pyRA==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", "license": "MIT", "dependencies": { "prosemirror-model": "^1.0.0", @@ -1635,9 +1641,9 @@ } }, "node_modules/prosemirror-model": { - "version": "1.25.0", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.0.tgz", - "integrity": "sha512-/8XUmxWf0pkj2BmtqZHYJipTBMHIdVjuvFzMvEoxrtyGNmfvdhBiRwYt/eFwy2wA9DtBW3RLqvZnjurEkHaFCw==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.1.tgz", + "integrity": "sha512-AUvbm7qqmpZa5d9fPKMvH1Q5bqYQvAZWOGRvxsB6iFLyycvC9MwNemNVjHVrWgjaoxAfY8XVg7DbvQ/qxvI9Eg==", "license": "MIT", "dependencies": { "orderedmap": "^2.0.0" @@ -1655,18 +1661,18 @@ } }, "node_modules/prosemirror-transform": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.3.tgz", - "integrity": "sha512-Nhh/+1kZGRINbEHmVu39oynhcap4hWTs/BlU7NnxWj3+l0qi8I1mu67v6mMdEe/ltD8hHvU4FV6PHiCw2VSpMw==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.4.tgz", + "integrity": "sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==", "license": "MIT", "dependencies": { "prosemirror-model": "^1.21.0" } }, "node_modules/prosemirror-view": { - "version": "1.38.1", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.38.1.tgz", - "integrity": "sha512-4FH/uM1A4PNyrxXbD+RAbAsf0d/mM0D/wAKSVVWK7o0A9Q/oOXJBrw786mBf2Vnrs/Edly6dH6Z2gsb7zWwaUw==", + "version": "1.39.2", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.39.2.tgz", + "integrity": "sha512-BmOkml0QWNob165gyUxXi5K5CVUgVPpqMEAAml/qzgKn9boLUWVPzQ6LtzXw8Cn1GtRQX4ELumPxqtLTDaAKtg==", "license": "MIT", "dependencies": { "prosemirror-model": "^1.20.0", diff --git a/platform-web-ui/src/main/resources/polymer/@fullcalendar/core/index.js b/platform-web-ui/src/main/resources/polymer/@fullcalendar/core/index.js index 206d09f18d4..27250e462ef 100644 --- a/platform-web-ui/src/main/resources/polymer/@fullcalendar/core/index.js +++ b/platform-web-ui/src/main/resources/polymer/@fullcalendar/core/index.js @@ -1,5 +1,5 @@ -import { T as Theme, B as BaseComponent, Y as setRef, Z as Interaction, _ as getElSeg, $ as elementClosest, a0 as EventImpl, a1 as listenBySelector, a2 as listenToHoverBySelector, a3 as PureComponent, z as memoize, a5 as getUniqueDomId, a6 as parseInteractionSettings, a7 as interactionSettingsStore, D as DelayedRunner, a8 as getNow, V as ViewContextType, a9 as CalendarImpl, aa as flushSync, ad as ensureElHasStyles, i as isArraysEqual, ae as applyStyleProp, g as guid, v as hashValuesToArray, A as memoizeObjArg, F as Emitter, G as getInitialDate, H as rangeContainsMarker, I as createEmptyEventStore, J as reduceCurrentDate, K as reduceEventStore, L as rezoneEventStoreDates, M as mergeRawOptions, N as BASE_OPTION_REFINERS, O as CALENDAR_LISTENER_REFINERS, P as CALENDAR_OPTION_REFINERS, Q as COMPLEX_OPTION_COMPARATORS, e as BASE_OPTION_DEFAULTS, R as VIEW_OPTION_REFINERS, S as DateEnv, a as mapHash, a4 as buildViewContext, ac as RenderId, ab as CalendarRoot, m as mergeProps, c as greatestDurationDenominator, d as createDuration, f as arrayToHash, h as filterHash, j as buildEventSourceRefiners, p as parseEventSource, k as formatWithOrdinals, u as unpromisify, l as buildRangeApiWithTimeZone, n as identity, r as requestJson, s as subtractDurations, o as intersectRanges, q as startOfDay, t as addDays, w as buildEventApis, x as createFormatter, y as diffWholeDays, E as isPropsEqual, U as DateProfileGenerator, W as createEventUi, X as parseBusinessHours, C as ContentContainer, b as buildViewClassNames } from './internal-common.js'; -export { ag as JsonRequestError } from './internal-common.js'; +import { T as Theme, B as BaseComponent, W as setRef, X as Interaction, Y as getElSeg, Z as elementClosest, _ as EventImpl, $ as listenBySelector, a0 as listenToHoverBySelector, a1 as PureComponent, z as memoize, a3 as getUniqueDomId, a4 as parseInteractionSettings, a5 as interactionSettingsStore, D as DelayedRunner, a6 as NowTimer, V as ViewContextType, a7 as CalendarImpl, a8 as flushSync, ab as ensureElHasStyles, i as isArraysEqual, ac as applyStyleProp, g as guid, v as hashValuesToArray, A as memoizeObjArg, F as Emitter, G as rangeContainsMarker, H as createEmptyEventStore, I as reduceEventStore, J as rezoneEventStoreDates, K as mergeRawOptions, L as BASE_OPTION_REFINERS, M as CALENDAR_LISTENER_REFINERS, N as CALENDAR_OPTION_REFINERS, O as COMPLEX_OPTION_COMPARATORS, e as BASE_OPTION_DEFAULTS, P as VIEW_OPTION_REFINERS, Q as DateEnv, a as mapHash, a2 as buildViewContext, aa as RenderId, a9 as CalendarRoot, m as mergeProps, c as greatestDurationDenominator, d as createDuration, f as arrayToHash, h as filterHash, j as buildEventSourceRefiners, p as parseEventSource, k as formatWithOrdinals, u as unpromisify, l as buildRangeApiWithTimeZone, n as identity, r as requestJson, s as subtractDurations, o as intersectRanges, q as startOfDay, t as addDays, w as buildEventApis, x as createFormatter, y as diffWholeDays, E as isPropsEqual, R as DateProfileGenerator, S as createEventUi, U as parseBusinessHours, C as ContentContainer, b as buildViewClassNames } from './internal-common.js'; +export { ae as JsonRequestError } from './internal-common.js'; import { createElement as y, createRef as d, Fragment as _, render as D } from '../../preact/dist/preact.module.js'; import '../../preact/compat/dist/compat.module.js'; @@ -459,6 +459,25 @@ function reduceViewType(viewType, action) { return viewType; } +function reduceCurrentDate(currentDate, action) { + switch (action.type) { + case 'CHANGE_DATE': + return action.dateMarker; + default: + return currentDate; + } +} +// should be initialized once and stay constant +// this will change too +function getInitialDate(options, dateEnv, nowManager) { + let initialDateInput = options.initialDate; + // compute the initial ambig-timezone date + if (initialDateInput != null) { + return dateEnv.createMarker(initialDateInput); + } + return nowManager.getDateMarker(); +} + function reduceDynamicOptionOverrides(dynamicOptionOverrides, action) { switch (action.type) { case 'SET_OPTION': @@ -968,6 +987,7 @@ let recurring = { endTime: refined.endTime || null, startRecur: refined.startRecur ? dateEnv.createMarker(refined.startRecur) : null, endRecur: refined.endRecur ? dateEnv.createMarker(refined.endRecur) : null, + dateEnv, }; let duration; if (refined.duration) { @@ -987,7 +1007,7 @@ let recurring = { expand(typeData, framingRange, dateEnv) { let clippedFramingRange = intersectRanges(framingRange, { start: typeData.startRecur, end: typeData.endRecur }); if (clippedFramingRange) { - return expandRanges(typeData.daysOfWeek, typeData.startTime, clippedFramingRange, dateEnv); + return expandRanges(typeData.daysOfWeek, typeData.startTime, typeData.dateEnv, dateEnv, clippedFramingRange); } return []; }, @@ -997,7 +1017,7 @@ const simpleRecurringEventsPlugin = createPlugin({ recurringTypes: [recurring], eventRefiners: SIMPLE_RECURRING_REFINERS, }); -function expandRanges(daysOfWeek, startTime, framingRange, dateEnv) { +function expandRanges(daysOfWeek, startTime, eventDateEnv, calendarDateEnv, framingRange) { let dowHash = daysOfWeek ? arrayToHash(daysOfWeek) : null; let dayMarker = startOfDay(framingRange.start); let endMarker = framingRange.end; @@ -1007,12 +1027,12 @@ function expandRanges(daysOfWeek, startTime, framingRange, dateEnv) { // if everyday, or this particular day-of-week if (!dowHash || dowHash[dayMarker.getUTCDay()]) { if (startTime) { - instanceStart = dateEnv.add(dayMarker, startTime); + instanceStart = calendarDateEnv.add(dayMarker, startTime); } else { instanceStart = dayMarker; } - instanceStarts.push(instanceStart); + instanceStarts.push(calendarDateEnv.createMarker(eventDateEnv.toDate(instanceStart))); } dayMarker = addDays(dayMarker, 1); } @@ -1177,6 +1197,49 @@ function buildTitleFormat(dateProfile) { return { year: 'numeric', month: 'long', day: 'numeric' }; } +/* +TODO: test switching timezones when NO timezone plugin +*/ +class CalendarNowManager { + constructor() { + this.resetListeners = new Set(); + } + handleInput(dateEnv, // will change if timezone setup changed + nowInput) { + const oldDateEnv = this.dateEnv; + if (dateEnv !== oldDateEnv) { + if (typeof nowInput === 'function') { + this.nowFn = nowInput; + } + else if (!oldDateEnv) { // first time? + this.nowAnchorDate = dateEnv.toDate(nowInput + ? dateEnv.createMarker(nowInput) + : dateEnv.createNowMarker()); + this.nowAnchorQueried = Date.now(); + } + this.dateEnv = dateEnv; + // not first time? fire reset handlers + if (oldDateEnv) { + for (const resetListener of this.resetListeners.values()) { + resetListener(); + } + } + } + } + getDateMarker() { + return this.nowAnchorDate + ? this.dateEnv.timestampToMarker(this.nowAnchorDate.valueOf() + + (Date.now() - this.nowAnchorQueried)) + : this.dateEnv.createMarker(this.nowFn()); + } + addResetListener(handler) { + this.resetListeners.add(handler); + } + removeResetListener(handler) { + this.resetListeners.delete(handler); + } +} + // in future refactor, do the redux-style function(state=initial) for initial-state // also, whatever is happening in constructor, have it happen in action queue too class CalendarDataManager { @@ -1196,6 +1259,7 @@ class CalendarDataManager { this.buildEventUiBases = memoize(buildEventUiBases); this.parseContextBusinessHours = memoizeObjArg(parseContextBusinessHours); this.buildTitle = memoize(buildTitle); + this.nowManager = new CalendarNowManager(); this.emitter = new Emitter(); this.actionRunner = new TaskRunner(this._handleAction.bind(this), this.updateData.bind(this)); this.currentCalendarOptionsInput = {}; @@ -1211,6 +1275,7 @@ class CalendarDataManager { }; this.props = props; this.actionRunner.pause(); + this.nowManager = new CalendarNowManager(); let dynamicOptionOverrides = {}; let optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi); let currentViewType = optionsData.calendarOptions.initialView || optionsData.pluginHooks.initialView; @@ -1220,12 +1285,8 @@ class CalendarDataManager { props.calendarApi.currentDataManager = this; this.emitter.setThisContext(props.calendarApi); this.emitter.setOptions(currentViewData.options); - let currentDate = getInitialDate(optionsData.calendarOptions, optionsData.dateEnv); - let dateProfile = currentViewData.dateProfileGenerator.build(currentDate); - if (!rangeContainsMarker(dateProfile.activeRange, currentDate)) { - currentDate = dateProfile.currentRange.start; - } let calendarContext = { + nowManager: this.nowManager, dateEnv: optionsData.dateEnv, options: optionsData.calendarOptions, pluginHooks: optionsData.pluginHooks, @@ -1234,6 +1295,11 @@ class CalendarDataManager { emitter: this.emitter, getCurrentData: this.getCurrentData, }; + let currentDate = getInitialDate(optionsData.calendarOptions, optionsData.dateEnv, this.nowManager); + let dateProfile = currentViewData.dateProfileGenerator.build(currentDate); + if (!rangeContainsMarker(dateProfile.activeRange, currentDate)) { + currentDate = dateProfile.currentRange.start; + } // needs to be after setThisContext for (let callback of optionsData.pluginHooks.contextInit) { callback(calendarContext); @@ -1294,6 +1360,7 @@ class CalendarDataManager { emitter.setThisContext(props.calendarApi); emitter.setOptions(currentViewData.options); let calendarContext = { + nowManager: this.nowManager, dateEnv: optionsData.dateEnv, options: optionsData.calendarOptions, pluginHooks: optionsData.pluginHooks, @@ -1361,7 +1428,7 @@ class CalendarDataManager { let oldData = this.data; let optionsData = this.computeOptionsData(props.optionOverrides, state.dynamicOptionOverrides, props.calendarApi); let currentViewData = this.computeCurrentViewData(state.currentViewType, optionsData, props.optionOverrides, state.dynamicOptionOverrides); - let data = this.data = Object.assign(Object.assign(Object.assign({ viewTitle: this.buildTitle(state.dateProfile, currentViewData.options, optionsData.dateEnv), calendarApi: props.calendarApi, dispatch: this.dispatch, emitter: this.emitter, getCurrentData: this.getCurrentData }, optionsData), currentViewData), state); + let data = this.data = Object.assign(Object.assign(Object.assign({ nowManager: this.nowManager, viewTitle: this.buildTitle(state.dateProfile, currentViewData.options, optionsData.dateEnv), calendarApi: props.calendarApi, dispatch: this.dispatch, emitter: this.emitter, getCurrentData: this.getCurrentData }, optionsData), currentViewData), state); let changeHandlers = optionsData.pluginHooks.optionChangeHandlers; let oldCalendarOptions = oldData && oldData.calendarOptions; let newCalendarOptions = optionsData.calendarOptions; @@ -1469,8 +1536,10 @@ class CalendarDataManager { } let { refinedOptions, extra } = this.processRawViewOptions(viewSpec, optionsData.pluginHooks, optionsData.localeDefaults, optionOverrides, dynamicOptionOverrides); warnUnknownOptions(extra); + this.nowManager.handleInput(optionsData.dateEnv, refinedOptions.now); let dateProfileGenerator = this.buildDateProfileGenerator({ dateProfileGeneratorClass: viewSpec.optionDefaults.dateProfileGeneratorClass, + nowManager: this.nowManager, duration: viewSpec.duration, durationUnit: viewSpec.durationUnit, usesMinMaxTime: viewSpec.optionDefaults.usesMinMaxTime, @@ -1484,7 +1553,6 @@ class CalendarDataManager { dateIncrement: refinedOptions.dateIncrement, hiddenDays: refinedOptions.hiddenDays, weekends: refinedOptions.weekends, - nowInput: refinedOptions.now, validRangeInput: refinedOptions.validRange, visibleRangeInput: refinedOptions.visibleRange, fixedWeekCount: refinedOptions.fixedWeekCount, @@ -1888,8 +1956,6 @@ class CalendarContent extends PureComponent { render() { let { props } = this; let { toolbarConfig, options } = props; - let toolbarProps = this.buildToolbarProps(props.viewSpec, props.dateProfile, props.dateProfileGenerator, props.currentDate, getNow(props.options.now, props.dateEnv), // TODO: use NowTimer???? - props.viewTitle); let viewVGrow = false; let viewHeight = ''; let viewAspectRatio; @@ -1905,16 +1971,20 @@ class CalendarContent extends PureComponent { else { viewAspectRatio = Math.max(options.aspectRatio, 0.5); // prevent from getting too tall } - let viewContext = this.buildViewContext(props.viewSpec, props.viewApi, props.options, props.dateProfileGenerator, props.dateEnv, props.theme, props.pluginHooks, props.dispatch, props.getCurrentData, props.emitter, props.calendarApi, this.registerInteractiveComponent, this.unregisterInteractiveComponent); + let viewContext = this.buildViewContext(props.viewSpec, props.viewApi, props.options, props.dateProfileGenerator, props.dateEnv, props.nowManager, props.theme, props.pluginHooks, props.dispatch, props.getCurrentData, props.emitter, props.calendarApi, this.registerInteractiveComponent, this.unregisterInteractiveComponent); let viewLabelId = (toolbarConfig.header && toolbarConfig.header.hasTitle) ? this.state.viewLabelId : undefined; return (y(ViewContextType.Provider, { value: viewContext }, - toolbarConfig.header && (y(Toolbar, Object.assign({ ref: this.headerRef, extraClassName: "fc-header-toolbar", model: toolbarConfig.header, titleId: viewLabelId }, toolbarProps))), - y(ViewHarness, { liquid: viewVGrow, height: viewHeight, aspectRatio: viewAspectRatio, labeledById: viewLabelId }, - this.renderView(props), - this.buildAppendContent()), - toolbarConfig.footer && (y(Toolbar, Object.assign({ ref: this.footerRef, extraClassName: "fc-footer-toolbar", model: toolbarConfig.footer, titleId: "" }, toolbarProps))))); + y(NowTimer, { unit: "day" }, (nowDate) => { + let toolbarProps = this.buildToolbarProps(props.viewSpec, props.dateProfile, props.dateProfileGenerator, props.currentDate, nowDate, props.viewTitle); + return (y(_, null, + toolbarConfig.header && (y(Toolbar, Object.assign({ ref: this.headerRef, extraClassName: "fc-header-toolbar", model: toolbarConfig.header, titleId: viewLabelId }, toolbarProps))), + y(ViewHarness, { liquid: viewVGrow, height: viewHeight, aspectRatio: viewAspectRatio, labeledById: viewLabelId }, + this.renderView(props), + this.buildAppendContent()), + toolbarConfig.footer && (y(Toolbar, Object.assign({ ref: this.footerRef, extraClassName: "fc-footer-toolbar", model: toolbarConfig.footer, titleId: "" }, toolbarProps))))); + }))); } componentDidMount() { let { props } = this; diff --git a/platform-web-ui/src/main/resources/polymer/@fullcalendar/core/internal-common.js b/platform-web-ui/src/main/resources/polymer/@fullcalendar/core/internal-common.js index 4baf384b4ef..5e8fe260b78 100644 --- a/platform-web-ui/src/main/resources/polymer/@fullcalendar/core/internal-common.js +++ b/platform-web-ui/src/main/resources/polymer/@fullcalendar/core/internal-common.js @@ -81,7 +81,7 @@ if (typeof document !== 'undefined') { registerStylesRoot(document); } -var css_248z = ":root{--fc-small-font-size:.85em;--fc-page-bg-color:#fff;--fc-neutral-bg-color:hsla(0,0%,82%,.3);--fc-neutral-text-color:grey;--fc-border-color:#ddd;--fc-button-text-color:#fff;--fc-button-bg-color:#2c3e50;--fc-button-border-color:#2c3e50;--fc-button-hover-bg-color:#1e2b37;--fc-button-hover-border-color:#1a252f;--fc-button-active-bg-color:#1a252f;--fc-button-active-border-color:#151e27;--fc-event-bg-color:#3788d8;--fc-event-border-color:#3788d8;--fc-event-text-color:#fff;--fc-event-selected-overlay-color:rgba(0,0,0,.25);--fc-more-link-bg-color:#d0d0d0;--fc-more-link-text-color:inherit;--fc-event-resizer-thickness:8px;--fc-event-resizer-dot-total-width:8px;--fc-event-resizer-dot-border-width:1px;--fc-non-business-color:hsla(0,0%,84%,.3);--fc-bg-event-color:#8fdf82;--fc-bg-event-opacity:0.3;--fc-highlight-color:rgba(188,232,241,.3);--fc-today-bg-color:rgba(255,220,40,.15);--fc-now-indicator-color:red}.fc-not-allowed,.fc-not-allowed .fc-event{cursor:not-allowed}.fc{display:flex;flex-direction:column;font-size:1em}.fc,.fc *,.fc :after,.fc :before{box-sizing:border-box}.fc table{border-collapse:collapse;border-spacing:0;font-size:1em}.fc th{text-align:center}.fc td,.fc th{padding:0;vertical-align:top}.fc a[data-navlink]{cursor:pointer}.fc a[data-navlink]:hover{text-decoration:underline}.fc-direction-ltr{direction:ltr;text-align:left}.fc-direction-rtl{direction:rtl;text-align:right}.fc-theme-standard td,.fc-theme-standard th{border:1px solid var(--fc-border-color)}.fc-liquid-hack td,.fc-liquid-hack th{position:relative}@font-face{font-family:fcicons;font-style:normal;font-weight:400;src:url(\"data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SBfAAAAC8AAAAYGNtYXAXVtKNAAABHAAAAFRnYXNwAAAAEAAAAXAAAAAIZ2x5ZgYydxIAAAF4AAAFNGhlYWQUJ7cIAAAGrAAAADZoaGVhB20DzAAABuQAAAAkaG10eCIABhQAAAcIAAAALGxvY2ED4AU6AAAHNAAAABhtYXhwAA8AjAAAB0wAAAAgbmFtZXsr690AAAdsAAABhnBvc3QAAwAAAAAI9AAAACAAAwPAAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADpBgPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAOAAAAAoACAACAAIAAQAg6Qb//f//AAAAAAAg6QD//f//AAH/4xcEAAMAAQAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAWIAjQKeAskAEwAAJSc3NjQnJiIHAQYUFwEWMjc2NCcCnuLiDQ0MJAz/AA0NAQAMJAwNDcni4gwjDQwM/wANIwz/AA0NDCMNAAAAAQFiAI0CngLJABMAACUBNjQnASYiBwYUHwEHBhQXFjI3AZ4BAA0N/wAMJAwNDeLiDQ0MJAyNAQAMIw0BAAwMDSMM4uINIwwNDQAAAAIA4gC3Ax4CngATACcAACUnNzY0JyYiDwEGFB8BFjI3NjQnISc3NjQnJiIPAQYUHwEWMjc2NCcB87e3DQ0MIw3VDQ3VDSMMDQ0BK7e3DQ0MJAzVDQ3VDCQMDQ3zuLcMJAwNDdUNIwzWDAwNIwy4twwkDA0N1Q0jDNYMDA0jDAAAAgDiALcDHgKeABMAJwAAJTc2NC8BJiIHBhQfAQcGFBcWMjchNzY0LwEmIgcGFB8BBwYUFxYyNwJJ1Q0N1Q0jDA0Nt7cNDQwjDf7V1Q0N1QwkDA0Nt7cNDQwkDLfWDCMN1Q0NDCQMt7gMIw0MDNYMIw3VDQ0MJAy3uAwjDQwMAAADAFUAAAOrA1UAMwBoAHcAABMiBgcOAQcOAQcOARURFBYXHgEXHgEXHgEzITI2Nz4BNz4BNz4BNRE0JicuAScuAScuASMFITIWFx4BFx4BFx4BFREUBgcOAQcOAQcOASMhIiYnLgEnLgEnLgE1ETQ2Nz4BNz4BNz4BMxMhMjY1NCYjISIGFRQWM9UNGAwLFQkJDgUFBQUFBQ4JCRULDBgNAlYNGAwLFQkJDgUFBQUFBQ4JCRULDBgN/aoCVgQIBAQHAwMFAQIBAQIBBQMDBwQECAT9qgQIBAQHAwMFAQIBAQIBBQMDBwQECASAAVYRGRkR/qoRGRkRA1UFBAUOCQkVDAsZDf2rDRkLDBUJCA4FBQUFBQUOCQgVDAsZDQJVDRkLDBUJCQ4FBAVVAgECBQMCBwQECAX9qwQJAwQHAwMFAQICAgIBBQMDBwQDCQQCVQUIBAQHAgMFAgEC/oAZEhEZGRESGQAAAAADAFUAAAOrA1UAMwBoAIkAABMiBgcOAQcOAQcOARURFBYXHgEXHgEXHgEzITI2Nz4BNz4BNz4BNRE0JicuAScuAScuASMFITIWFx4BFx4BFx4BFREUBgcOAQcOAQcOASMhIiYnLgEnLgEnLgE1ETQ2Nz4BNz4BNz4BMxMzFRQWMzI2PQEzMjY1NCYrATU0JiMiBh0BIyIGFRQWM9UNGAwLFQkJDgUFBQUFBQ4JCRULDBgNAlYNGAwLFQkJDgUFBQUFBQ4JCRULDBgN/aoCVgQIBAQHAwMFAQIBAQIBBQMDBwQECAT9qgQIBAQHAwMFAQIBAQIBBQMDBwQECASAgBkSEhmAERkZEYAZEhIZgBEZGREDVQUEBQ4JCRUMCxkN/asNGQsMFQkIDgUFBQUFBQ4JCBUMCxkNAlUNGQsMFQkJDgUEBVUCAQIFAwIHBAQIBf2rBAkDBAcDAwUBAgICAgEFAwMHBAMJBAJVBQgEBAcCAwUCAQL+gIASGRkSgBkSERmAEhkZEoAZERIZAAABAOIAjQMeAskAIAAAExcHBhQXFjI/ARcWMjc2NC8BNzY0JyYiDwEnJiIHBhQX4uLiDQ0MJAzi4gwkDA0N4uINDQwkDOLiDCQMDQ0CjeLiDSMMDQ3h4Q0NDCMN4uIMIw0MDOLiDAwNIwwAAAABAAAAAQAAa5n0y18PPPUACwQAAAAAANivOVsAAAAA2K85WwAAAAADqwNVAAAACAACAAAAAAAAAAEAAAPA/8AAAAQAAAAAAAOrAAEAAAAAAAAAAAAAAAAAAAALBAAAAAAAAAAAAAAAAgAAAAQAAWIEAAFiBAAA4gQAAOIEAABVBAAAVQQAAOIAAAAAAAoAFAAeAEQAagCqAOoBngJkApoAAQAAAAsAigADAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA4ArgABAAAAAAABAAcAAAABAAAAAAACAAcAYAABAAAAAAADAAcANgABAAAAAAAEAAcAdQABAAAAAAAFAAsAFQABAAAAAAAGAAcASwABAAAAAAAKABoAigADAAEECQABAA4ABwADAAEECQACAA4AZwADAAEECQADAA4APQADAAEECQAEAA4AfAADAAEECQAFABYAIAADAAEECQAGAA4AUgADAAEECQAKADQApGZjaWNvbnMAZgBjAGkAYwBvAG4Ac1ZlcnNpb24gMS4wAFYAZQByAHMAaQBvAG4AIAAxAC4AMGZjaWNvbnMAZgBjAGkAYwBvAG4Ac2ZjaWNvbnMAZgBjAGkAYwBvAG4Ac1JlZ3VsYXIAUgBlAGcAdQBsAGEAcmZjaWNvbnMAZgBjAGkAYwBvAG4Ac0ZvbnQgZ2VuZXJhdGVkIGJ5IEljb01vb24uAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\") format(\"truetype\")}.fc-icon{speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:inline-block;font-family:fcicons!important;font-style:normal;font-variant:normal;font-weight:400;height:1em;line-height:1;text-align:center;text-transform:none;-moz-user-select:none;user-select:none;width:1em}.fc-icon-chevron-left:before{content:\"\\e900\"}.fc-icon-chevron-right:before{content:\"\\e901\"}.fc-icon-chevrons-left:before{content:\"\\e902\"}.fc-icon-chevrons-right:before{content:\"\\e903\"}.fc-icon-minus-square:before{content:\"\\e904\"}.fc-icon-plus-square:before{content:\"\\e905\"}.fc-icon-x:before{content:\"\\e906\"}.fc .fc-button{border-radius:0;font-family:inherit;font-size:inherit;line-height:inherit;margin:0;overflow:visible;text-transform:none}.fc .fc-button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}.fc .fc-button{-webkit-appearance:button}.fc .fc-button:not(:disabled){cursor:pointer}.fc .fc-button{background-color:transparent;border:1px solid transparent;border-radius:.25em;display:inline-block;font-size:1em;font-weight:400;line-height:1.5;padding:.4em .65em;text-align:center;-moz-user-select:none;user-select:none;vertical-align:middle}.fc .fc-button:hover{text-decoration:none}.fc .fc-button:focus{box-shadow:0 0 0 .2rem rgba(44,62,80,.25);outline:0}.fc .fc-button:disabled{opacity:.65}.fc .fc-button-primary{background-color:var(--fc-button-bg-color);border-color:var(--fc-button-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:hover{background-color:var(--fc-button-hover-bg-color);border-color:var(--fc-button-hover-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:disabled{background-color:var(--fc-button-bg-color);border-color:var(--fc-button-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:focus{box-shadow:0 0 0 .2rem rgba(76,91,106,.5)}.fc .fc-button-primary:not(:disabled).fc-button-active,.fc .fc-button-primary:not(:disabled):active{background-color:var(--fc-button-active-bg-color);border-color:var(--fc-button-active-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:not(:disabled).fc-button-active:focus,.fc .fc-button-primary:not(:disabled):active:focus{box-shadow:0 0 0 .2rem rgba(76,91,106,.5)}.fc .fc-button .fc-icon{font-size:1.5em;vertical-align:middle}.fc .fc-button-group{display:inline-flex;position:relative;vertical-align:middle}.fc .fc-button-group>.fc-button{flex:1 1 auto;position:relative}.fc .fc-button-group>.fc-button.fc-button-active,.fc .fc-button-group>.fc-button:active,.fc .fc-button-group>.fc-button:focus,.fc .fc-button-group>.fc-button:hover{z-index:1}.fc-direction-ltr .fc-button-group>.fc-button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-1px}.fc-direction-ltr .fc-button-group>.fc-button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.fc-direction-rtl .fc-button-group>.fc-button:not(:first-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.fc-direction-rtl .fc-button-group>.fc-button:not(:last-child){border-bottom-left-radius:0;border-top-left-radius:0}.fc .fc-toolbar{align-items:center;display:flex;justify-content:space-between}.fc .fc-toolbar.fc-header-toolbar{margin-bottom:1.5em}.fc .fc-toolbar.fc-footer-toolbar{margin-top:1.5em}.fc .fc-toolbar-title{font-size:1.75em;margin:0}.fc-direction-ltr .fc-toolbar>*>:not(:first-child){margin-left:.75em}.fc-direction-rtl .fc-toolbar>*>:not(:first-child){margin-right:.75em}.fc-direction-rtl .fc-toolbar-ltr{flex-direction:row-reverse}.fc .fc-scroller{-webkit-overflow-scrolling:touch;position:relative}.fc .fc-scroller-liquid{height:100%}.fc .fc-scroller-liquid-absolute{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-scroller-harness{direction:ltr;overflow:hidden;position:relative}.fc .fc-scroller-harness-liquid{height:100%}.fc-direction-rtl .fc-scroller-harness>.fc-scroller{direction:rtl}.fc-theme-standard .fc-scrollgrid{border:1px solid var(--fc-border-color)}.fc .fc-scrollgrid,.fc .fc-scrollgrid table{table-layout:fixed;width:100%}.fc .fc-scrollgrid table{border-left-style:hidden;border-right-style:hidden;border-top-style:hidden}.fc .fc-scrollgrid{border-bottom-width:0;border-collapse:separate;border-right-width:0}.fc .fc-scrollgrid-liquid{height:100%}.fc .fc-scrollgrid-section,.fc .fc-scrollgrid-section table,.fc .fc-scrollgrid-section>td{height:1px}.fc .fc-scrollgrid-section-liquid>td{height:100%}.fc .fc-scrollgrid-section>*{border-left-width:0;border-top-width:0}.fc .fc-scrollgrid-section-footer>*,.fc .fc-scrollgrid-section-header>*{border-bottom-width:0}.fc .fc-scrollgrid-section-body table,.fc .fc-scrollgrid-section-footer table{border-bottom-style:hidden}.fc .fc-scrollgrid-section-sticky>*{background:var(--fc-page-bg-color);position:sticky;z-index:3}.fc .fc-scrollgrid-section-header.fc-scrollgrid-section-sticky>*{top:0}.fc .fc-scrollgrid-section-footer.fc-scrollgrid-section-sticky>*{bottom:0}.fc .fc-scrollgrid-sticky-shim{height:1px;margin-bottom:-1px}.fc-sticky{position:sticky}.fc .fc-view-harness{flex-grow:1;position:relative}.fc .fc-view-harness-active>.fc-view{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-col-header-cell-cushion{display:inline-block;padding:2px 4px}.fc .fc-bg-event,.fc .fc-highlight,.fc .fc-non-business{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-non-business{background:var(--fc-non-business-color)}.fc .fc-bg-event{background:var(--fc-bg-event-color);opacity:var(--fc-bg-event-opacity)}.fc .fc-bg-event .fc-event-title{font-size:var(--fc-small-font-size);font-style:italic;margin:.5em}.fc .fc-highlight{background:var(--fc-highlight-color)}.fc .fc-cell-shaded,.fc .fc-day-disabled{background:var(--fc-neutral-bg-color)}a.fc-event,a.fc-event:hover{text-decoration:none}.fc-event.fc-event-draggable,.fc-event[href]{cursor:pointer}.fc-event .fc-event-main{position:relative;z-index:2}.fc-event-dragging:not(.fc-event-selected){opacity:.75}.fc-event-dragging.fc-event-selected{box-shadow:0 2px 7px rgba(0,0,0,.3)}.fc-event .fc-event-resizer{display:none;position:absolute;z-index:4}.fc-event-selected .fc-event-resizer,.fc-event:hover .fc-event-resizer{display:block}.fc-event-selected .fc-event-resizer{background:var(--fc-page-bg-color);border-color:inherit;border-radius:calc(var(--fc-event-resizer-dot-total-width)/2);border-style:solid;border-width:var(--fc-event-resizer-dot-border-width);height:var(--fc-event-resizer-dot-total-width);width:var(--fc-event-resizer-dot-total-width)}.fc-event-selected .fc-event-resizer:before{bottom:-20px;content:\"\";left:-20px;position:absolute;right:-20px;top:-20px}.fc-event-selected,.fc-event:focus{box-shadow:0 2px 5px rgba(0,0,0,.2)}.fc-event-selected:before,.fc-event:focus:before{bottom:0;content:\"\";left:0;position:absolute;right:0;top:0;z-index:3}.fc-event-selected:after,.fc-event:focus:after{background:var(--fc-event-selected-overlay-color);bottom:-1px;content:\"\";left:-1px;position:absolute;right:-1px;top:-1px;z-index:1}.fc-h-event{background-color:var(--fc-event-bg-color);border:1px solid var(--fc-event-border-color);display:block}.fc-h-event .fc-event-main{color:var(--fc-event-text-color)}.fc-h-event .fc-event-main-frame{display:flex}.fc-h-event .fc-event-time{max-width:100%;overflow:hidden}.fc-h-event .fc-event-title-container{flex-grow:1;flex-shrink:1;min-width:0}.fc-h-event .fc-event-title{display:inline-block;left:0;max-width:100%;overflow:hidden;right:0;vertical-align:top}.fc-h-event.fc-event-selected:before{bottom:-10px;top:-10px}.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-start),.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-end){border-bottom-left-radius:0;border-left-width:0;border-top-left-radius:0}.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-end),.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-start){border-bottom-right-radius:0;border-right-width:0;border-top-right-radius:0}.fc-h-event:not(.fc-event-selected) .fc-event-resizer{bottom:0;top:0;width:var(--fc-event-resizer-thickness)}.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start,.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end{cursor:w-resize;left:calc(var(--fc-event-resizer-thickness)*-.5)}.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end,.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start{cursor:e-resize;right:calc(var(--fc-event-resizer-thickness)*-.5)}.fc-h-event.fc-event-selected .fc-event-resizer{margin-top:calc(var(--fc-event-resizer-dot-total-width)*-.5);top:50%}.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-start,.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-end{left:calc(var(--fc-event-resizer-dot-total-width)*-.5)}.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-end,.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-start{right:calc(var(--fc-event-resizer-dot-total-width)*-.5)}.fc .fc-popover{box-shadow:0 2px 6px rgba(0,0,0,.15);position:absolute;z-index:9999}.fc .fc-popover-header{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding:3px 4px}.fc .fc-popover-title{margin:0 2px}.fc .fc-popover-close{cursor:pointer;font-size:1.1em;opacity:.65}.fc-theme-standard .fc-popover{background:var(--fc-page-bg-color);border:1px solid var(--fc-border-color)}.fc-theme-standard .fc-popover-header{background:var(--fc-neutral-bg-color)}"; +var css_248z = ":root{--fc-small-font-size:.85em;--fc-page-bg-color:#fff;--fc-neutral-bg-color:hsla(0,0%,82%,.3);--fc-neutral-text-color:grey;--fc-border-color:#ddd;--fc-button-text-color:#fff;--fc-button-bg-color:#2c3e50;--fc-button-border-color:#2c3e50;--fc-button-hover-bg-color:#1e2b37;--fc-button-hover-border-color:#1a252f;--fc-button-active-bg-color:#1a252f;--fc-button-active-border-color:#151e27;--fc-event-bg-color:#3788d8;--fc-event-border-color:#3788d8;--fc-event-text-color:#fff;--fc-event-selected-overlay-color:rgba(0,0,0,.25);--fc-more-link-bg-color:#d0d0d0;--fc-more-link-text-color:inherit;--fc-event-resizer-thickness:8px;--fc-event-resizer-dot-total-width:8px;--fc-event-resizer-dot-border-width:1px;--fc-non-business-color:hsla(0,0%,84%,.3);--fc-bg-event-color:#8fdf82;--fc-bg-event-opacity:0.3;--fc-highlight-color:rgba(188,232,241,.3);--fc-today-bg-color:rgba(255,220,40,.15);--fc-now-indicator-color:red}.fc-not-allowed,.fc-not-allowed .fc-event{cursor:not-allowed}.fc{display:flex;flex-direction:column;font-size:1em}.fc,.fc *,.fc :after,.fc :before{box-sizing:border-box}.fc table{border-collapse:collapse;border-spacing:0;font-size:1em}.fc th{text-align:center}.fc td,.fc th{padding:0;vertical-align:top}.fc a[data-navlink]{cursor:pointer}.fc a[data-navlink]:hover{text-decoration:underline}.fc-direction-ltr{direction:ltr;text-align:left}.fc-direction-rtl{direction:rtl;text-align:right}.fc-theme-standard td,.fc-theme-standard th{border:1px solid var(--fc-border-color)}.fc-liquid-hack td,.fc-liquid-hack th{position:relative}@font-face{font-family:fcicons;font-style:normal;font-weight:400;src:url(\"data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SBfAAAAC8AAAAYGNtYXAXVtKNAAABHAAAAFRnYXNwAAAAEAAAAXAAAAAIZ2x5ZgYydxIAAAF4AAAFNGhlYWQUJ7cIAAAGrAAAADZoaGVhB20DzAAABuQAAAAkaG10eCIABhQAAAcIAAAALGxvY2ED4AU6AAAHNAAAABhtYXhwAA8AjAAAB0wAAAAgbmFtZXsr690AAAdsAAABhnBvc3QAAwAAAAAI9AAAACAAAwPAAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADpBgPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAOAAAAAoACAACAAIAAQAg6Qb//f//AAAAAAAg6QD//f//AAH/4xcEAAMAAQAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAWIAjQKeAskAEwAAJSc3NjQnJiIHAQYUFwEWMjc2NCcCnuLiDQ0MJAz/AA0NAQAMJAwNDcni4gwjDQwM/wANIwz/AA0NDCMNAAAAAQFiAI0CngLJABMAACUBNjQnASYiBwYUHwEHBhQXFjI3AZ4BAA0N/wAMJAwNDeLiDQ0MJAyNAQAMIw0BAAwMDSMM4uINIwwNDQAAAAIA4gC3Ax4CngATACcAACUnNzY0JyYiDwEGFB8BFjI3NjQnISc3NjQnJiIPAQYUHwEWMjc2NCcB87e3DQ0MIw3VDQ3VDSMMDQ0BK7e3DQ0MJAzVDQ3VDCQMDQ3zuLcMJAwNDdUNIwzWDAwNIwy4twwkDA0N1Q0jDNYMDA0jDAAAAgDiALcDHgKeABMAJwAAJTc2NC8BJiIHBhQfAQcGFBcWMjchNzY0LwEmIgcGFB8BBwYUFxYyNwJJ1Q0N1Q0jDA0Nt7cNDQwjDf7V1Q0N1QwkDA0Nt7cNDQwkDLfWDCMN1Q0NDCQMt7gMIw0MDNYMIw3VDQ0MJAy3uAwjDQwMAAADAFUAAAOrA1UAMwBoAHcAABMiBgcOAQcOAQcOARURFBYXHgEXHgEXHgEzITI2Nz4BNz4BNz4BNRE0JicuAScuAScuASMFITIWFx4BFx4BFx4BFREUBgcOAQcOAQcOASMhIiYnLgEnLgEnLgE1ETQ2Nz4BNz4BNz4BMxMhMjY1NCYjISIGFRQWM9UNGAwLFQkJDgUFBQUFBQ4JCRULDBgNAlYNGAwLFQkJDgUFBQUFBQ4JCRULDBgN/aoCVgQIBAQHAwMFAQIBAQIBBQMDBwQECAT9qgQIBAQHAwMFAQIBAQIBBQMDBwQECASAAVYRGRkR/qoRGRkRA1UFBAUOCQkVDAsZDf2rDRkLDBUJCA4FBQUFBQUOCQgVDAsZDQJVDRkLDBUJCQ4FBAVVAgECBQMCBwQECAX9qwQJAwQHAwMFAQICAgIBBQMDBwQDCQQCVQUIBAQHAgMFAgEC/oAZEhEZGRESGQAAAAADAFUAAAOrA1UAMwBoAIkAABMiBgcOAQcOAQcOARURFBYXHgEXHgEXHgEzITI2Nz4BNz4BNz4BNRE0JicuAScuAScuASMFITIWFx4BFx4BFx4BFREUBgcOAQcOAQcOASMhIiYnLgEnLgEnLgE1ETQ2Nz4BNz4BNz4BMxMzFRQWMzI2PQEzMjY1NCYrATU0JiMiBh0BIyIGFRQWM9UNGAwLFQkJDgUFBQUFBQ4JCRULDBgNAlYNGAwLFQkJDgUFBQUFBQ4JCRULDBgN/aoCVgQIBAQHAwMFAQIBAQIBBQMDBwQECAT9qgQIBAQHAwMFAQIBAQIBBQMDBwQECASAgBkSEhmAERkZEYAZEhIZgBEZGREDVQUEBQ4JCRUMCxkN/asNGQsMFQkIDgUFBQUFBQ4JCBUMCxkNAlUNGQsMFQkJDgUEBVUCAQIFAwIHBAQIBf2rBAkDBAcDAwUBAgICAgEFAwMHBAMJBAJVBQgEBAcCAwUCAQL+gIASGRkSgBkSERmAEhkZEoAZERIZAAABAOIAjQMeAskAIAAAExcHBhQXFjI/ARcWMjc2NC8BNzY0JyYiDwEnJiIHBhQX4uLiDQ0MJAzi4gwkDA0N4uINDQwkDOLiDCQMDQ0CjeLiDSMMDQ3h4Q0NDCMN4uIMIw0MDOLiDAwNIwwAAAABAAAAAQAAa5n0y18PPPUACwQAAAAAANivOVsAAAAA2K85WwAAAAADqwNVAAAACAACAAAAAAAAAAEAAAPA/8AAAAQAAAAAAAOrAAEAAAAAAAAAAAAAAAAAAAALBAAAAAAAAAAAAAAAAgAAAAQAAWIEAAFiBAAA4gQAAOIEAABVBAAAVQQAAOIAAAAAAAoAFAAeAEQAagCqAOoBngJkApoAAQAAAAsAigADAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA4ArgABAAAAAAABAAcAAAABAAAAAAACAAcAYAABAAAAAAADAAcANgABAAAAAAAEAAcAdQABAAAAAAAFAAsAFQABAAAAAAAGAAcASwABAAAAAAAKABoAigADAAEECQABAA4ABwADAAEECQACAA4AZwADAAEECQADAA4APQADAAEECQAEAA4AfAADAAEECQAFABYAIAADAAEECQAGAA4AUgADAAEECQAKADQApGZjaWNvbnMAZgBjAGkAYwBvAG4Ac1ZlcnNpb24gMS4wAFYAZQByAHMAaQBvAG4AIAAxAC4AMGZjaWNvbnMAZgBjAGkAYwBvAG4Ac2ZjaWNvbnMAZgBjAGkAYwBvAG4Ac1JlZ3VsYXIAUgBlAGcAdQBsAGEAcmZjaWNvbnMAZgBjAGkAYwBvAG4Ac0ZvbnQgZ2VuZXJhdGVkIGJ5IEljb01vb24uAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\") format(\"truetype\")}.fc-icon{speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:inline-block;font-family:fcicons!important;font-style:normal;font-variant:normal;font-weight:400;height:1em;line-height:1;text-align:center;text-transform:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:1em}.fc-icon-chevron-left:before{content:\"\\e900\"}.fc-icon-chevron-right:before{content:\"\\e901\"}.fc-icon-chevrons-left:before{content:\"\\e902\"}.fc-icon-chevrons-right:before{content:\"\\e903\"}.fc-icon-minus-square:before{content:\"\\e904\"}.fc-icon-plus-square:before{content:\"\\e905\"}.fc-icon-x:before{content:\"\\e906\"}.fc .fc-button{border-radius:0;font-family:inherit;font-size:inherit;line-height:inherit;margin:0;overflow:visible;text-transform:none}.fc .fc-button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}.fc .fc-button{-webkit-appearance:button}.fc .fc-button:not(:disabled){cursor:pointer}.fc .fc-button{background-color:transparent;border:1px solid transparent;border-radius:.25em;display:inline-block;font-size:1em;font-weight:400;line-height:1.5;padding:.4em .65em;text-align:center;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle}.fc .fc-button:hover{text-decoration:none}.fc .fc-button:focus{box-shadow:0 0 0 .2rem rgba(44,62,80,.25);outline:0}.fc .fc-button:disabled{opacity:.65}.fc .fc-button-primary{background-color:var(--fc-button-bg-color);border-color:var(--fc-button-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:hover{background-color:var(--fc-button-hover-bg-color);border-color:var(--fc-button-hover-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:disabled{background-color:var(--fc-button-bg-color);border-color:var(--fc-button-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:focus{box-shadow:0 0 0 .2rem rgba(76,91,106,.5)}.fc .fc-button-primary:not(:disabled).fc-button-active,.fc .fc-button-primary:not(:disabled):active{background-color:var(--fc-button-active-bg-color);border-color:var(--fc-button-active-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:not(:disabled).fc-button-active:focus,.fc .fc-button-primary:not(:disabled):active:focus{box-shadow:0 0 0 .2rem rgba(76,91,106,.5)}.fc .fc-button .fc-icon{font-size:1.5em;vertical-align:middle}.fc .fc-button-group{display:inline-flex;position:relative;vertical-align:middle}.fc .fc-button-group>.fc-button{flex:1 1 auto;position:relative}.fc .fc-button-group>.fc-button.fc-button-active,.fc .fc-button-group>.fc-button:active,.fc .fc-button-group>.fc-button:focus,.fc .fc-button-group>.fc-button:hover{z-index:1}.fc-direction-ltr .fc-button-group>.fc-button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-1px}.fc-direction-ltr .fc-button-group>.fc-button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.fc-direction-rtl .fc-button-group>.fc-button:not(:first-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.fc-direction-rtl .fc-button-group>.fc-button:not(:last-child){border-bottom-left-radius:0;border-top-left-radius:0}.fc .fc-toolbar{align-items:center;display:flex;justify-content:space-between}.fc .fc-toolbar.fc-header-toolbar{margin-bottom:1.5em}.fc .fc-toolbar.fc-footer-toolbar{margin-top:1.5em}.fc .fc-toolbar-title{font-size:1.75em;margin:0}.fc-direction-ltr .fc-toolbar>*>:not(:first-child){margin-left:.75em}.fc-direction-rtl .fc-toolbar>*>:not(:first-child){margin-right:.75em}.fc-direction-rtl .fc-toolbar-ltr{flex-direction:row-reverse}.fc .fc-scroller{-webkit-overflow-scrolling:touch;position:relative}.fc .fc-scroller-liquid{height:100%}.fc .fc-scroller-liquid-absolute{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-scroller-harness{direction:ltr;overflow:hidden;position:relative}.fc .fc-scroller-harness-liquid{height:100%}.fc-direction-rtl .fc-scroller-harness>.fc-scroller{direction:rtl}.fc-theme-standard .fc-scrollgrid{border:1px solid var(--fc-border-color)}.fc .fc-scrollgrid,.fc .fc-scrollgrid table{table-layout:fixed;width:100%}.fc .fc-scrollgrid table{border-left-style:hidden;border-right-style:hidden;border-top-style:hidden}.fc .fc-scrollgrid{border-bottom-width:0;border-collapse:separate;border-right-width:0}.fc .fc-scrollgrid-liquid{height:100%}.fc .fc-scrollgrid-section,.fc .fc-scrollgrid-section table,.fc .fc-scrollgrid-section>td{height:1px}.fc .fc-scrollgrid-section-liquid>td{height:100%}.fc .fc-scrollgrid-section>*{border-left-width:0;border-top-width:0}.fc .fc-scrollgrid-section-footer>*,.fc .fc-scrollgrid-section-header>*{border-bottom-width:0}.fc .fc-scrollgrid-section-body table,.fc .fc-scrollgrid-section-footer table{border-bottom-style:hidden}.fc .fc-scrollgrid-section-sticky>*{background:var(--fc-page-bg-color);position:sticky;z-index:3}.fc .fc-scrollgrid-section-header.fc-scrollgrid-section-sticky>*{top:0}.fc .fc-scrollgrid-section-footer.fc-scrollgrid-section-sticky>*{bottom:0}.fc .fc-scrollgrid-sticky-shim{height:1px;margin-bottom:-1px}.fc-sticky{position:sticky}.fc .fc-view-harness{flex-grow:1;position:relative}.fc .fc-view-harness-active>.fc-view{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-col-header-cell-cushion{display:inline-block;padding:2px 4px}.fc .fc-bg-event,.fc .fc-highlight,.fc .fc-non-business{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-non-business{background:var(--fc-non-business-color)}.fc .fc-bg-event{background:var(--fc-bg-event-color);opacity:var(--fc-bg-event-opacity)}.fc .fc-bg-event .fc-event-title{font-size:var(--fc-small-font-size);font-style:italic;margin:.5em}.fc .fc-highlight{background:var(--fc-highlight-color)}.fc .fc-cell-shaded,.fc .fc-day-disabled{background:var(--fc-neutral-bg-color)}a.fc-event,a.fc-event:hover{text-decoration:none}.fc-event.fc-event-draggable,.fc-event[href]{cursor:pointer}.fc-event .fc-event-main{position:relative;z-index:2}.fc-event-dragging:not(.fc-event-selected){opacity:.75}.fc-event-dragging.fc-event-selected{box-shadow:0 2px 7px rgba(0,0,0,.3)}.fc-event .fc-event-resizer{display:none;position:absolute;z-index:4}.fc-event-selected .fc-event-resizer,.fc-event:hover .fc-event-resizer{display:block}.fc-event-selected .fc-event-resizer{background:var(--fc-page-bg-color);border-color:inherit;border-radius:calc(var(--fc-event-resizer-dot-total-width)/2);border-style:solid;border-width:var(--fc-event-resizer-dot-border-width);height:var(--fc-event-resizer-dot-total-width);width:var(--fc-event-resizer-dot-total-width)}.fc-event-selected .fc-event-resizer:before{bottom:-20px;content:\"\";left:-20px;position:absolute;right:-20px;top:-20px}.fc-event-selected,.fc-event:focus{box-shadow:0 2px 5px rgba(0,0,0,.2)}.fc-event-selected:before,.fc-event:focus:before{bottom:0;content:\"\";left:0;position:absolute;right:0;top:0;z-index:3}.fc-event-selected:after,.fc-event:focus:after{background:var(--fc-event-selected-overlay-color);bottom:-1px;content:\"\";left:-1px;position:absolute;right:-1px;top:-1px;z-index:1}.fc-h-event{background-color:var(--fc-event-bg-color);border:1px solid var(--fc-event-border-color);display:block}.fc-h-event .fc-event-main{color:var(--fc-event-text-color)}.fc-h-event .fc-event-main-frame{display:flex}.fc-h-event .fc-event-time{max-width:100%;overflow:hidden}.fc-h-event .fc-event-title-container{flex-grow:1;flex-shrink:1;min-width:0}.fc-h-event .fc-event-title{display:inline-block;left:0;max-width:100%;overflow:hidden;right:0;vertical-align:top}.fc-h-event.fc-event-selected:before{bottom:-10px;top:-10px}.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-start),.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-end){border-bottom-left-radius:0;border-left-width:0;border-top-left-radius:0}.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-end),.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-start){border-bottom-right-radius:0;border-right-width:0;border-top-right-radius:0}.fc-h-event:not(.fc-event-selected) .fc-event-resizer{bottom:0;top:0;width:var(--fc-event-resizer-thickness)}.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start,.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end{cursor:w-resize;left:calc(var(--fc-event-resizer-thickness)*-.5)}.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end,.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start{cursor:e-resize;right:calc(var(--fc-event-resizer-thickness)*-.5)}.fc-h-event.fc-event-selected .fc-event-resizer{margin-top:calc(var(--fc-event-resizer-dot-total-width)*-.5);top:50%}.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-start,.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-end{left:calc(var(--fc-event-resizer-dot-total-width)*-.5)}.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-end,.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-start{right:calc(var(--fc-event-resizer-dot-total-width)*-.5)}.fc .fc-popover{box-shadow:0 2px 6px rgba(0,0,0,.15);position:absolute;z-index:9999}.fc .fc-popover-header{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding:3px 4px}.fc .fc-popover-title{margin:0 2px}.fc .fc-popover-close{cursor:pointer;font-size:1.1em;opacity:.65}.fc-theme-standard .fc-popover{background:var(--fc-page-bg-color);border:1px solid var(--fc-border-color)}.fc-theme-standard .fc-popover-header{background:var(--fc-neutral-bg-color)}"; injectStyles(css_248z); class DelayedRunner { @@ -859,10 +859,10 @@ function memoizeObjArg(workerFunc, resEquality, teardownFunc) { const EXTENDED_SETTINGS_AND_SEVERITIES = { week: 3, - separator: 0, - omitZeroMinute: 0, - meridiem: 0, - omitCommas: 0, + separator: 9, + omitZeroMinute: 9, + meridiem: 9, + omitCommas: 9, }; const STANDARD_DATE_PROP_SEVERITIES = { timeZoneName: 7, @@ -884,22 +884,25 @@ class NativeFormatter { constructor(formatSettings) { let standardDateProps = {}; let extendedSettings = {}; - let severity = 0; + let smallestUnitNum = 9; // the smallest unit in the formatter (9 is a sentinel, beyond max) for (let name in formatSettings) { if (name in EXTENDED_SETTINGS_AND_SEVERITIES) { extendedSettings[name] = formatSettings[name]; - severity = Math.max(EXTENDED_SETTINGS_AND_SEVERITIES[name], severity); + const severity = EXTENDED_SETTINGS_AND_SEVERITIES[name]; + if (severity < 9) { + smallestUnitNum = Math.min(EXTENDED_SETTINGS_AND_SEVERITIES[name], smallestUnitNum); + } } else { standardDateProps[name] = formatSettings[name]; if (name in STANDARD_DATE_PROP_SEVERITIES) { // TODO: what about hour12? no severity - severity = Math.max(STANDARD_DATE_PROP_SEVERITIES[name], severity); + smallestUnitNum = Math.min(STANDARD_DATE_PROP_SEVERITIES[name], smallestUnitNum); } } } this.standardDateProps = standardDateProps; this.extendedSettings = extendedSettings; - this.severity = severity; + this.smallestUnitNum = smallestUnitNum; this.buildFormattingFunc = memoize(buildFormattingFunc); } format(date, context) { @@ -934,8 +937,8 @@ class NativeFormatter { } return full0 + separator + full1; } - getLargestUnit() { - switch (this.severity) { + getSmallestUnit() { + switch (this.smallestUnitNum) { case 7: case 6: case 5: @@ -2207,9 +2210,10 @@ class ScrollResponder { } const ViewContextType = createContext({}); // for Components -function buildViewContext(viewSpec, viewApi, viewOptions, dateProfileGenerator, dateEnv, theme, pluginHooks, dispatch, getCurrentData, emitter, calendarApi, registerInteractiveComponent, unregisterInteractiveComponent) { +function buildViewContext(viewSpec, viewApi, viewOptions, dateProfileGenerator, dateEnv, nowManager, theme, pluginHooks, dispatch, getCurrentData, emitter, calendarApi, registerInteractiveComponent, unregisterInteractiveComponent) { return { dateEnv, + nowManager, options: viewOptions, pluginHooks, emitter, @@ -2642,36 +2646,9 @@ function diffDates(date0, date1, dateEnv, largeUnit) { return diffDayAndTime(date0, date1); // returns a duration } -function reduceCurrentDate(currentDate, action) { - switch (action.type) { - case 'CHANGE_DATE': - return action.dateMarker; - default: - return currentDate; - } -} -function getInitialDate(options, dateEnv) { - let initialDateInput = options.initialDate; - // compute the initial ambig-timezone date - if (initialDateInput != null) { - return dateEnv.createMarker(initialDateInput); - } - return getNow(options.now, dateEnv); // getNow already returns unzoned -} -function getNow(nowInput, dateEnv) { - if (typeof nowInput === 'function') { - nowInput = nowInput(); - } - if (nowInput == null) { - return dateEnv.createNowMarker(); - } - return dateEnv.createMarker(nowInput); -} - class DateProfileGenerator { constructor(props) { this.props = props; - this.nowDate = getNow(props.nowInput, props.dateEnv); this.initHiddenDays(); } /* Date Range Computation @@ -2756,7 +2733,7 @@ class DateProfileGenerator { buildValidRange() { let input = this.props.validRangeInput; let simpleInput = typeof input === 'function' - ? input.call(this.props.calendarApi, this.nowDate) + ? input.call(this.props.calendarApi, this.props.dateEnv.toDate(this.props.nowManager.getDateMarker())) : input; return this.refineRange(simpleInput) || { start: null, end: null }; // completely open-ended @@ -4648,6 +4625,85 @@ function interactionSettingsToStore(settings) { // global state const interactionSettingsStore = {}; +class NowTimer extends x { + constructor(props, context) { + super(props, context); + this.handleRefresh = () => { + let timing = this.computeTiming(); + if (timing.state.nowDate.valueOf() !== this.state.nowDate.valueOf()) { + this.setState(timing.state); + } + this.clearTimeout(); + this.setTimeout(timing.waitMs); + }; + this.handleVisibilityChange = () => { + if (!document.hidden) { + this.handleRefresh(); + } + }; + this.state = this.computeTiming().state; + } + render() { + let { props, state } = this; + return props.children(state.nowDate, state.todayRange); + } + componentDidMount() { + this.setTimeout(); + this.context.nowManager.addResetListener(this.handleRefresh); + // fired tab becomes visible after being hidden + document.addEventListener('visibilitychange', this.handleVisibilityChange); + } + componentDidUpdate(prevProps) { + if (prevProps.unit !== this.props.unit) { + this.clearTimeout(); + this.setTimeout(); + } + } + componentWillUnmount() { + this.clearTimeout(); + this.context.nowManager.removeResetListener(this.handleRefresh); + document.removeEventListener('visibilitychange', this.handleVisibilityChange); + } + computeTiming() { + let { props, context } = this; + let unroundedNow = context.nowManager.getDateMarker(); + let currentUnitStart = context.dateEnv.startOf(unroundedNow, props.unit); + let nextUnitStart = context.dateEnv.add(currentUnitStart, createDuration(1, props.unit)); + let waitMs = nextUnitStart.valueOf() - unroundedNow.valueOf(); + // there is a max setTimeout ms value (https://stackoverflow.com/a/3468650/96342) + // ensure no longer than a day + waitMs = Math.min(1000 * 60 * 60 * 24, waitMs); + return { + state: { nowDate: currentUnitStart, todayRange: buildDayRange(currentUnitStart) }, + waitMs, + }; + } + setTimeout(waitMs = this.computeTiming().waitMs) { + // NOTE: timeout could take longer than expected if tab sleeps, + // which is why we listen to 'visibilitychange' + this.timeoutId = setTimeout(() => { + // NOTE: timeout could also return *earlier* than expected, and we need to wait 2 ms more + // This is why use use same waitMs from computeTiming, so we don't skip an interval while + // .setState() is executing + const timing = this.computeTiming(); + this.setState(timing.state, () => { + this.setTimeout(timing.waitMs); + }); + }, waitMs); + } + clearTimeout() { + if (this.timeoutId) { + clearTimeout(this.timeoutId); + } + } +} +NowTimer.contextType = ViewContextType; +function buildDayRange(date) { + let start = startOfDay(date); + let end = addDays(start, 1); + return { start, end }; +} + class CalendarImpl { getCurrentData() { return this.currentDataManager.getCurrentData(); @@ -4803,7 +4859,7 @@ class CalendarImpl { this.unselect(); this.dispatch({ type: 'CHANGE_DATE', - dateMarker: getNow(state.calendarOptions.now, state.dateEnv), + dateMarker: state.nowManager.getDateMarker(), }); } gotoDate(zonedDateInput) { @@ -5196,7 +5252,7 @@ function buildEventUiForKey(allUi, eventUiForKey, individualUi) { function getDateMeta(date, todayRange, nowDate, dateProfile) { return { dow: date.getUTCDay(), - isDisabled: Boolean(dateProfile && !rangeContainsMarker(dateProfile.activeRange, date)), + isDisabled: Boolean(dateProfile && (!dateProfile.activeRange || !rangeContainsMarker(dateProfile.activeRange, date))), isOther: Boolean(dateProfile && !rangeContainsMarker(dateProfile.currentRange, date)), isToday: Boolean(todayRange && rangeContainsMarker(todayRange, date)), isPast: Boolean(nowDate ? (date < nowDate) : todayRange ? (date < todayRange.start) : false), @@ -5918,7 +5974,13 @@ class TableDateCell extends BaseComponent { let navLinkAttrs = (!dayMeta.isDisabled && props.colCnt > 1) ? buildNavLinkAttrs(this.context, date) : {}; - let renderProps = Object.assign(Object.assign(Object.assign({ date: dateEnv.toDate(date), view: viewApi }, props.extraRenderProps), { text }), dayMeta); + let publicDate = dateEnv.toDate(date); + // workaround for Luxon (and maybe moment) returning prior-days when start-of-day + // in DST gap: https://github.com/fullcalendar/fullcalendar/issues/7633 + if (dateEnv.namedTimeZoneImpl) { + publicDate = addMs(publicDate, 3600000); // add an hour + } + let renderProps = Object.assign(Object.assign(Object.assign({ date: publicDate, view: viewApi }, props.extraRenderProps), { text }), dayMeta); return (y(ContentContainer, { elTag: "th", elClasses: classNames, elAttrs: Object.assign({ role: 'columnheader', colSpan: props.colSpan, 'data-date': !dayMeta.isDisabled ? formatDayString(date) : undefined }, props.extraDataAttrs), renderProps: renderProps, generatorName: "dayHeaderContent", customGenerator: options.dayHeaderContent, defaultGenerator: renderInner$1, classNameGenerator: options.dayHeaderClassNames, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, (InnerContainer) => (y("div", { className: "fc-scrollgrid-sync-inner" }, !dayMeta.isDisabled && (y(InnerContainer, { elTag: "a", elAttrs: navLinkAttrs, elClasses: [ 'fc-col-header-cell-cushion', props.isSticky && 'fc-sticky', @@ -5957,65 +6019,6 @@ class TableDowCell extends BaseComponent { } } -class NowTimer extends x { - constructor(props, context) { - super(props, context); - this.initialNowDate = getNow(context.options.now, context.dateEnv); - this.initialNowQueriedMs = new Date().valueOf(); - this.state = this.computeTiming().currentState; - } - render() { - let { props, state } = this; - return props.children(state.nowDate, state.todayRange); - } - componentDidMount() { - this.setTimeout(); - } - componentDidUpdate(prevProps) { - if (prevProps.unit !== this.props.unit) { - this.clearTimeout(); - this.setTimeout(); - } - } - componentWillUnmount() { - this.clearTimeout(); - } - computeTiming() { - let { props, context } = this; - let unroundedNow = addMs(this.initialNowDate, new Date().valueOf() - this.initialNowQueriedMs); - let currentUnitStart = context.dateEnv.startOf(unroundedNow, props.unit); - let nextUnitStart = context.dateEnv.add(currentUnitStart, createDuration(1, props.unit)); - let waitMs = nextUnitStart.valueOf() - unroundedNow.valueOf(); - // there is a max setTimeout ms value (https://stackoverflow.com/a/3468650/96342) - // ensure no longer than a day - waitMs = Math.min(1000 * 60 * 60 * 24, waitMs); - return { - currentState: { nowDate: currentUnitStart, todayRange: buildDayRange(currentUnitStart) }, - nextState: { nowDate: nextUnitStart, todayRange: buildDayRange(nextUnitStart) }, - waitMs, - }; - } - setTimeout() { - let { nextState, waitMs } = this.computeTiming(); - this.timeoutId = setTimeout(() => { - this.setState(nextState, () => { - this.setTimeout(); - }); - }, waitMs); - } - clearTimeout() { - if (this.timeoutId) { - clearTimeout(this.timeoutId); - } - } -} -NowTimer.contextType = ViewContextType; -function buildDayRange(date) { - let start = startOfDay(date); - let end = addDays(start, 1); - return { start, end }; -} - class DayHeader extends BaseComponent { constructor() { super(...arguments); @@ -7285,4 +7288,4 @@ function pickLatestEnd(seg0, seg1) { return seg0.eventRange.range.end > seg1.eventRange.range.end ? seg0 : seg1; } -export { elementClosest as $, memoizeObjArg as A, BaseComponent as B, ContentContainer as C, DelayedRunner as D, isPropsEqual as E, Emitter as F, getInitialDate as G, rangeContainsMarker as H, createEmptyEventStore as I, reduceCurrentDate as J, reduceEventStore as K, rezoneEventStoreDates as L, mergeRawOptions as M, BASE_OPTION_REFINERS as N, CALENDAR_LISTENER_REFINERS as O, CALENDAR_OPTION_REFINERS as P, COMPLEX_OPTION_COMPARATORS as Q, VIEW_OPTION_REFINERS as R, DateEnv as S, Theme as T, DateProfileGenerator as U, ViewContextType as V, createEventUi as W, parseBusinessHours as X, setRef as Y, Interaction as Z, getElSeg as _, mapHash as a, EventImpl as a0, listenBySelector as a1, listenToHoverBySelector as a2, PureComponent as a3, buildViewContext as a4, getUniqueDomId as a5, parseInteractionSettings as a6, interactionSettingsStore as a7, getNow as a8, CalendarImpl as a9, diffDates as aA, intersectRects as aE, pointInsideRect as aF, constrainPoint as aG, getRectCenter as aH, diffPoints as aI, compareObjs as aK, collectFromHash as aL, findElements as aM, removeElement as aO, applyStyle as aP, elementMatches as aQ, getEventTargetViaRoot as aR, parseClassNames as aS, getCanVGrowWithinCell as aT, mergeEventStores as aU, getRelevantEvents as aV, eventTupleToStore as aW, combineEventUis as aX, Splitter as aY, getDayClassNames as aZ, getDateMeta as a_, flushSync as aa, CalendarRoot as ab, RenderId as ac, ensureElHasStyles as ad, applyStyleProp as ae, sliceEventStore as af, JsonRequestError as ag, createContext as ah, refineProps as ai, createEventInstance as aj, parseEventDef as ak, refineEventDef as al, padStart as am, isInt as an, parseFieldSpecs as ao, compareByFieldSpecs as ap, flexibleCompare as aq, preventSelection as ar, allowSelection as as, preventContextMenu as at, allowContextMenu as au, compareNumbers as av, enableCursor as aw, disableCursor as ax, computeVisibleDayRange as ay, isMultiDayRange as az, buildViewClassNames as b, SimpleScrollGrid as b$, buildNavLinkAttrs as b0, preventDefault as b1, whenTransitionDone as b2, computeInnerRect as b3, computeEdges as b4, getClippingParents as b5, computeRect as b6, rangesEqual as b7, rangesIntersect as b8, rangeContainsRange as b9, SegHierarchy as bA, buildEntryKey as bB, getEntrySpanEnd as bC, binarySearch as bD, groupIntersectingEntries as bE, intersectSpans as bF, interactionSettingsToStore as bG, ElementDragging as bH, config as bI, DayHeader as bK, computeFallbackHeaderFormat as bL, TableDateCell as bM, TableDowCell as bN, DaySeriesModel as bO, hasBgRendering as bP, buildSegTimeText as bQ, sortEventSegs as bR, getSegMeta as bS, buildEventRangeKey as bT, getSegAnchorAttrs as bU, DayTableModel as bV, Slicer as bW, applyMutationToEventStore as bX, isPropsValid as bY, isInteractionValid as bZ, isDateSelectionValid as b_, PositionCache as ba, ScrollController as bb, ElementScrollController as bc, WindowScrollController as bd, DateComponent as be, isDateSpansEqual as bf, addMs as bg, addWeeks as bh, diffWeeks as bi, diffWholeWeeks as bj, diffDayAndTime as bk, diffDays as bl, isValidDate as bm, multiplyDuration as bo, addDurations as bp, asRoughMs as bs, wholeDivideDurations as bt, formatIsoTimeString as bu, formatDayString as bv, buildIsoString as bw, formatIsoMonthStr as bx, NamedTimeZoneImpl as by, parse as bz, greatestDurationDenominator as c, hasShrinkWidth as c0, renderMicroColGroup as c1, getScrollGridClassNames as c2, getSectionClassNames as c3, getSectionHasLiquidHeight as c4, getAllowYScrolling as c5, renderChunkContent as c6, computeShrinkWidth as c7, sanitizeShrinkWidth as c8, isColPropsEqual as c9, renderScrollShim as ca, getStickyFooterScrollbar as cb, getStickyHeaderDates as cc, Scroller as cd, getScrollbarWidths as ce, RefMap as cf, getIsRtlScrollbarOnLeft as cg, NowTimer as ch, ScrollResponder as ci, StandardEvent as cj, NowIndicatorContainer as ck, DayCellContainer as cl, hasCustomDayCellContent as cm, EventContainer as cn, renderFill as co, BgEvent as cp, WeekNumberContainer as cq, MoreLinkContainer as cr, computeEarliestSegStart as cs, ViewContainer as ct, triggerDateSelect as cu, getDefaultEventEnd as cv, injectStyles as cw, buildElAttrs as cx, createDuration as d, BASE_OPTION_DEFAULTS as e, arrayToHash as f, guid as g, filterHash as h, isArraysEqual as i, buildEventSourceRefiners as j, formatWithOrdinals as k, buildRangeApiWithTimeZone as l, mergeProps as m, identity as n, intersectRanges as o, parseEventSource as p, startOfDay as q, requestJson as r, subtractDurations as s, addDays as t, unpromisify as u, hashValuesToArray as v, buildEventApis as w, createFormatter as x, diffWholeDays as y, memoize as z }; +export { listenBySelector as $, memoizeObjArg as A, BaseComponent as B, ContentContainer as C, DelayedRunner as D, isPropsEqual as E, Emitter as F, rangeContainsMarker as G, createEmptyEventStore as H, reduceEventStore as I, rezoneEventStoreDates as J, mergeRawOptions as K, BASE_OPTION_REFINERS as L, CALENDAR_LISTENER_REFINERS as M, CALENDAR_OPTION_REFINERS as N, COMPLEX_OPTION_COMPARATORS as O, VIEW_OPTION_REFINERS as P, DateEnv as Q, DateProfileGenerator as R, createEventUi as S, Theme as T, parseBusinessHours as U, ViewContextType as V, setRef as W, Interaction as X, getElSeg as Y, elementClosest as Z, EventImpl as _, mapHash as a, preventDefault as a$, listenToHoverBySelector as a0, PureComponent as a1, buildViewContext as a2, getUniqueDomId as a3, parseInteractionSettings as a4, interactionSettingsStore as a5, NowTimer as a6, CalendarImpl as a7, flushSync as a8, CalendarRoot as a9, intersectRects as aC, pointInsideRect as aD, constrainPoint as aE, getRectCenter as aF, diffPoints as aG, compareObjs as aI, collectFromHash as aJ, findElements as aK, removeElement as aM, applyStyle as aN, elementMatches as aO, getEventTargetViaRoot as aP, parseClassNames as aQ, getCanVGrowWithinCell as aR, mergeEventStores as aS, getRelevantEvents as aT, eventTupleToStore as aU, combineEventUis as aV, Splitter as aW, getDayClassNames as aX, getDateMeta as aY, buildNavLinkAttrs as a_, RenderId as aa, ensureElHasStyles as ab, applyStyleProp as ac, sliceEventStore as ad, JsonRequestError as ae, createContext as af, refineProps as ag, createEventInstance as ah, parseEventDef as ai, refineEventDef as aj, padStart as ak, isInt as al, parseFieldSpecs as am, compareByFieldSpecs as an, flexibleCompare as ao, preventSelection as ap, allowSelection as aq, preventContextMenu as ar, allowContextMenu as as, compareNumbers as at, enableCursor as au, disableCursor as av, computeVisibleDayRange as aw, isMultiDayRange as ax, diffDates as ay, buildViewClassNames as b, renderMicroColGroup as b$, whenTransitionDone as b0, computeInnerRect as b1, computeEdges as b2, getClippingParents as b3, computeRect as b4, rangesEqual as b5, rangesIntersect as b6, rangeContainsRange as b7, PositionCache as b8, ScrollController as b9, getEntrySpanEnd as bA, binarySearch as bB, groupIntersectingEntries as bC, intersectSpans as bD, interactionSettingsToStore as bE, ElementDragging as bF, config as bG, DayHeader as bI, computeFallbackHeaderFormat as bJ, TableDateCell as bK, TableDowCell as bL, DaySeriesModel as bM, hasBgRendering as bN, buildSegTimeText as bO, sortEventSegs as bP, getSegMeta as bQ, buildEventRangeKey as bR, getSegAnchorAttrs as bS, DayTableModel as bT, Slicer as bU, applyMutationToEventStore as bV, isPropsValid as bW, isInteractionValid as bX, isDateSelectionValid as bY, SimpleScrollGrid as bZ, hasShrinkWidth as b_, ElementScrollController as ba, WindowScrollController as bb, DateComponent as bc, isDateSpansEqual as bd, addMs as be, addWeeks as bf, diffWeeks as bg, diffWholeWeeks as bh, diffDayAndTime as bi, diffDays as bj, isValidDate as bk, multiplyDuration as bm, addDurations as bn, asRoughMs as bq, wholeDivideDurations as br, formatIsoTimeString as bs, formatDayString as bt, buildIsoString as bu, formatIsoMonthStr as bv, NamedTimeZoneImpl as bw, parse as bx, SegHierarchy as by, buildEntryKey as bz, greatestDurationDenominator as c, getScrollGridClassNames as c0, getSectionClassNames as c1, getSectionHasLiquidHeight as c2, getAllowYScrolling as c3, renderChunkContent as c4, computeShrinkWidth as c5, sanitizeShrinkWidth as c6, isColPropsEqual as c7, renderScrollShim as c8, getStickyFooterScrollbar as c9, getStickyHeaderDates as ca, Scroller as cb, getScrollbarWidths as cc, RefMap as cd, getIsRtlScrollbarOnLeft as ce, ScrollResponder as cf, StandardEvent as cg, NowIndicatorContainer as ch, DayCellContainer as ci, hasCustomDayCellContent as cj, EventContainer as ck, renderFill as cl, BgEvent as cm, WeekNumberContainer as cn, MoreLinkContainer as co, computeEarliestSegStart as cp, ViewContainer as cq, triggerDateSelect as cr, getDefaultEventEnd as cs, injectStyles as ct, buildElAttrs as cu, createDuration as d, BASE_OPTION_DEFAULTS as e, arrayToHash as f, guid as g, filterHash as h, isArraysEqual as i, buildEventSourceRefiners as j, formatWithOrdinals as k, buildRangeApiWithTimeZone as l, mergeProps as m, identity as n, intersectRanges as o, parseEventSource as p, startOfDay as q, requestJson as r, subtractDurations as s, addDays as t, unpromisify as u, hashValuesToArray as v, buildEventApis as w, createFormatter as x, diffWholeDays as y, memoize as z }; diff --git a/platform-web-ui/src/main/resources/polymer/@fullcalendar/daygrid/internal.js b/platform-web-ui/src/main/resources/polymer/@fullcalendar/daygrid/internal.js index c9cbd3b7f2c..33298a0818e 100644 --- a/platform-web-ui/src/main/resources/polymer/@fullcalendar/daygrid/internal.js +++ b/platform-web-ui/src/main/resources/polymer/@fullcalendar/daygrid/internal.js @@ -1,4 +1,4 @@ -import { x as createFormatter, E as isPropsEqual, be as DateComponent, z as memoize, cf as RefMap, ch as NowTimer, ba as PositionCache, t as addDays, bW as Slicer, bK as DayHeader, bO as DaySeriesModel, bV as DayTableModel, U as DateProfileGenerator, bh as addWeeks, bi as diffWeeks, cw as injectStyles, cc as getStickyHeaderDates, b$ as SimpleScrollGrid, ct as ViewContainer, cb as getStickyFooterScrollbar, ca as renderScrollShim, bR as sortEventSegs, bS as getSegMeta, cp as BgEvent, co as renderFill, bT as buildEventRangeKey, B as BaseComponent, cj as StandardEvent, bQ as buildSegTimeText, bU as getSegAnchorAttrs, cn as EventContainer, a5 as getUniqueDomId, Y as setRef, cq as WeekNumberContainer, b0 as buildNavLinkAttrs, cm as hasCustomDayCellContent, cl as DayCellContainer, bg as addMs, o as intersectRanges, bA as SegHierarchy, bB as buildEntryKey, bF as intersectSpans, bx as formatIsoMonthStr, bv as formatDayString, cr as MoreLinkContainer } from '../core/internal-common.js'; +import { x as createFormatter, E as isPropsEqual, bc as DateComponent, z as memoize, cd as RefMap, a6 as NowTimer, b8 as PositionCache, t as addDays, bU as Slicer, bI as DayHeader, bM as DaySeriesModel, bT as DayTableModel, R as DateProfileGenerator, bf as addWeeks, bg as diffWeeks, ct as injectStyles, ca as getStickyHeaderDates, bZ as SimpleScrollGrid, cq as ViewContainer, c9 as getStickyFooterScrollbar, c8 as renderScrollShim, bP as sortEventSegs, bQ as getSegMeta, cm as BgEvent, cl as renderFill, bR as buildEventRangeKey, B as BaseComponent, cg as StandardEvent, bO as buildSegTimeText, bS as getSegAnchorAttrs, ck as EventContainer, a3 as getUniqueDomId, W as setRef, cn as WeekNumberContainer, a_ as buildNavLinkAttrs, cj as hasCustomDayCellContent, ci as DayCellContainer, be as addMs, o as intersectRanges, by as SegHierarchy, bz as buildEntryKey, bD as intersectSpans, bv as formatIsoMonthStr, bt as formatDayString, co as MoreLinkContainer } from '../core/internal-common.js'; import { createElement as y, Fragment as _, createRef as d } from '../../preact/dist/preact.module.js'; /* An abstract class for the daygrid views, as well as month view. Renders one or more rows of day cells. diff --git a/platform-web-ui/src/main/resources/polymer/@fullcalendar/interaction/index.js b/platform-web-ui/src/main/resources/polymer/@fullcalendar/interaction/index.js index 4d23c00a65a..e15590c0645 100644 --- a/platform-web-ui/src/main/resources/polymer/@fullcalendar/interaction/index.js +++ b/platform-web-ui/src/main/resources/polymer/@fullcalendar/interaction/index.js @@ -1,5 +1,5 @@ import { createPlugin } from '../core/index.js'; -import { bI as config, bb as ScrollController, bH as ElementDragging, ar as preventSelection, at as preventContextMenu, as as allowSelection, au as allowContextMenu, Z as Interaction, bG as interactionSettingsToStore, b_ as isDateSelectionValid, aw as enableCursor, ax as disableCursor, cu as triggerDateSelect, _ as getElSeg, aV as getRelevantEvents, $ as elementClosest, a0 as EventImpl, bX as applyMutationToEventStore, bZ as isInteractionValid, I as createEmptyEventStore, w as buildEventApis, a7 as interactionSettingsStore, q as startOfDay, aA as diffDates, d as createDuration, F as Emitter, aP as applyStyle, b2 as whenTransitionDone, aO as removeElement, b6 as computeRect, aG as constrainPoint, aE as intersectRects, aH as getRectCenter, aI as diffPoints, a as mapHash, b9 as rangeContainsRange, bf as isDateSpansEqual, av as compareNumbers, bc as ElementScrollController, b3 as computeInnerRect, bd as WindowScrollController, aR as getEventTargetViaRoot, n as identity, b5 as getClippingParents, aF as pointInsideRect } from '../core/internal-common.js'; +import { bG as config, b9 as ScrollController, bF as ElementDragging, ap as preventSelection, ar as preventContextMenu, aq as allowSelection, as as allowContextMenu, X as Interaction, bE as interactionSettingsToStore, bY as isDateSelectionValid, au as enableCursor, av as disableCursor, cr as triggerDateSelect, Y as getElSeg, aT as getRelevantEvents, Z as elementClosest, _ as EventImpl, bV as applyMutationToEventStore, bX as isInteractionValid, H as createEmptyEventStore, w as buildEventApis, a5 as interactionSettingsStore, q as startOfDay, ay as diffDates, d as createDuration, F as Emitter, aN as applyStyle, b0 as whenTransitionDone, aM as removeElement, b4 as computeRect, aE as constrainPoint, aC as intersectRects, aF as getRectCenter, aG as diffPoints, a as mapHash, b7 as rangeContainsRange, bd as isDateSpansEqual, at as compareNumbers, ba as ElementScrollController, b1 as computeInnerRect, bb as WindowScrollController, aP as getEventTargetViaRoot, n as identity, b3 as getClippingParents, aD as pointInsideRect } from '../core/internal-common.js'; config.touchMouseIgnoreWait = 500; let ignoreMouseDepth = 0; diff --git a/platform-web-ui/src/main/resources/polymer/@fullcalendar/list/internal.js b/platform-web-ui/src/main/resources/polymer/@fullcalendar/list/internal.js index 3830a62c035..3c04c7e3089 100644 --- a/platform-web-ui/src/main/resources/polymer/@fullcalendar/list/internal.js +++ b/platform-web-ui/src/main/resources/polymer/@fullcalendar/list/internal.js @@ -1,4 +1,4 @@ -import { x as createFormatter, be as DateComponent, z as memoize, a5 as getUniqueDomId, cd as Scroller, ct as ViewContainer, C as ContentContainer, bv as formatDayString, bR as sortEventSegs, bS as getSegMeta, ch as NowTimer, af as sliceEventStore, o as intersectRanges, q as startOfDay, t as addDays, cw as injectStyles, B as BaseComponent, a_ as getDateMeta, b0 as buildNavLinkAttrs, aZ as getDayClassNames, cn as EventContainer, bU as getSegAnchorAttrs, az as isMultiDayRange, bQ as buildSegTimeText } from '../core/internal-common.js'; +import { x as createFormatter, bc as DateComponent, z as memoize, a3 as getUniqueDomId, cb as Scroller, cq as ViewContainer, C as ContentContainer, bt as formatDayString, bP as sortEventSegs, bQ as getSegMeta, a6 as NowTimer, ad as sliceEventStore, o as intersectRanges, q as startOfDay, t as addDays, ct as injectStyles, B as BaseComponent, aY as getDateMeta, a_ as buildNavLinkAttrs, aX as getDayClassNames, ck as EventContainer, bS as getSegAnchorAttrs, ax as isMultiDayRange, bO as buildSegTimeText } from '../core/internal-common.js'; import { createElement as y, Fragment as _ } from '../../preact/dist/preact.module.js'; class ListViewHeaderRow extends BaseComponent { diff --git a/platform-web-ui/src/main/resources/polymer/@fullcalendar/moment-timezone/index.js b/platform-web-ui/src/main/resources/polymer/@fullcalendar/moment-timezone/index.js index eaa86e79df4..cee45831b29 100644 --- a/platform-web-ui/src/main/resources/polymer/@fullcalendar/moment-timezone/index.js +++ b/platform-web-ui/src/main/resources/polymer/@fullcalendar/moment-timezone/index.js @@ -1,6 +1,6 @@ import { createPlugin } from '../core/index.js'; import moment from '../../../_virtual/index.js'; -import { by as NamedTimeZoneImpl } from '../core/internal-common.js'; +import { bw as NamedTimeZoneImpl } from '../core/internal-common.js'; class MomentNamedTimeZone extends NamedTimeZoneImpl { offsetForArray(a) { diff --git a/platform-web-ui/src/main/resources/polymer/@fullcalendar/multimonth/index.js b/platform-web-ui/src/main/resources/polymer/@fullcalendar/multimonth/index.js index bdbf822b006..a52b8735a7a 100644 --- a/platform-web-ui/src/main/resources/polymer/@fullcalendar/multimonth/index.js +++ b/platform-web-ui/src/main/resources/polymer/@fullcalendar/multimonth/index.js @@ -1,6 +1,6 @@ import { createPlugin } from '../core/index.js'; import { DayTableSlicer, TableRows, TableDateProfileGenerator, buildDayTableModel, buildDayTableRenderRange } from '../daygrid/internal.js'; -import { be as DateComponent, z as memoize, a5 as getUniqueDomId, bK as DayHeader, bx as formatIsoMonthStr, ct as ViewContainer, E as isPropsEqual, d as createDuration, x as createFormatter, cw as injectStyles, o as intersectRanges } from '../core/internal-common.js'; +import { bc as DateComponent, z as memoize, a3 as getUniqueDomId, bI as DayHeader, bv as formatIsoMonthStr, cq as ViewContainer, E as isPropsEqual, d as createDuration, x as createFormatter, ct as injectStyles, o as intersectRanges } from '../core/internal-common.js'; import { createElement as y, createRef as d } from '../../preact/dist/preact.module.js'; class SingleMonth extends DateComponent { diff --git a/platform-web-ui/src/main/resources/polymer/@fullcalendar/timegrid/internal.js b/platform-web-ui/src/main/resources/polymer/@fullcalendar/timegrid/internal.js index 24b99623a04..6f8abf4af09 100644 --- a/platform-web-ui/src/main/resources/polymer/@fullcalendar/timegrid/internal.js +++ b/platform-web-ui/src/main/resources/polymer/@fullcalendar/timegrid/internal.js @@ -1,5 +1,5 @@ import { DayTable } from '../daygrid/internal.js'; -import { x as createFormatter, z as memoize, bK as DayHeader, bO as DaySeriesModel, bV as DayTableModel, cw as injectStyles, be as DateComponent, bl as diffDays, b0 as buildNavLinkAttrs, cq as WeekNumberContainer, C as ContentContainer, cc as getStickyHeaderDates, b$ as SimpleScrollGrid, ct as ViewContainer, cb as getStickyFooterScrollbar, ch as NowTimer, ck as NowIndicatorContainer, ca as renderScrollShim, d as createDuration, bs as asRoughMs, bt as wholeDivideDurations, bu as formatIsoTimeString, bp as addDurations, aY as Splitter, bP as hasBgRendering, B as BaseComponent, V as ViewContextType, bo as multiplyDuration, bW as Slicer, o as intersectRanges, cf as RefMap, ba as PositionCache, H as rangeContainsMarker, q as startOfDay, bR as sortEventSegs, cm as hasCustomDayCellContent, cl as DayCellContainer, bS as getSegMeta, bw as buildIsoString, cs as computeEarliestSegStart, cp as BgEvent, co as renderFill, bT as buildEventRangeKey, cr as MoreLinkContainer, cj as StandardEvent, bA as SegHierarchy, bE as groupIntersectingEntries, bD as binarySearch, bC as getEntrySpanEnd, bB as buildEntryKey } from '../core/internal-common.js'; +import { x as createFormatter, z as memoize, bI as DayHeader, bM as DaySeriesModel, bT as DayTableModel, ct as injectStyles, bc as DateComponent, bj as diffDays, a_ as buildNavLinkAttrs, cn as WeekNumberContainer, C as ContentContainer, ca as getStickyHeaderDates, bZ as SimpleScrollGrid, cq as ViewContainer, c9 as getStickyFooterScrollbar, a6 as NowTimer, ch as NowIndicatorContainer, c8 as renderScrollShim, d as createDuration, bq as asRoughMs, br as wholeDivideDurations, bs as formatIsoTimeString, bn as addDurations, aW as Splitter, bN as hasBgRendering, B as BaseComponent, V as ViewContextType, bm as multiplyDuration, bU as Slicer, o as intersectRanges, cd as RefMap, b8 as PositionCache, G as rangeContainsMarker, q as startOfDay, bP as sortEventSegs, cj as hasCustomDayCellContent, ci as DayCellContainer, bQ as getSegMeta, bu as buildIsoString, cp as computeEarliestSegStart, cm as BgEvent, cl as renderFill, bR as buildEventRangeKey, co as MoreLinkContainer, cg as StandardEvent, by as SegHierarchy, bC as groupIntersectingEntries, bB as binarySearch, bA as getEntrySpanEnd, bz as buildEntryKey } from '../core/internal-common.js'; import { createElement as y, createRef as d, Fragment as _ } from '../../preact/dist/preact.module.js'; class AllDaySplitter extends Splitter { diff --git a/platform-web-ui/src/main/resources/polymer/dompurify/dist/purify.es.mjs.js b/platform-web-ui/src/main/resources/polymer/dompurify/dist/purify.es.mjs.js index 8f895ff2408..0bfeeaaf86c 100644 --- a/platform-web-ui/src/main/resources/polymer/dompurify/dist/purify.es.mjs.js +++ b/platform-web-ui/src/main/resources/polymer/dompurify/dist/purify.es.mjs.js @@ -1,4 +1,4 @@ -/*! @license DOMPurify 3.2.4 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.4/LICENSE */ +/*! @license DOMPurify 3.2.5 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.5/LICENSE */ const { entries, @@ -58,6 +58,9 @@ const typeErrorCreate = unconstruct(TypeError); */ function unapply(func) { return function (thisArg) { + if (thisArg instanceof RegExp) { + thisArg.lastIndex = 0; + } for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } @@ -296,7 +299,7 @@ const _createHooksMap = function _createHooksMap() { function createDOMPurify() { let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal(); const DOMPurify = root => createDOMPurify(root); - DOMPurify.version = '3.2.4'; + DOMPurify.version = '3.2.5'; DOMPurify.removed = []; if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) { // Not running in a browser, provide a factory function @@ -901,7 +904,7 @@ function createDOMPurify() { allowedTags: ALLOWED_TAGS }); /* Detect mXSS attempts abusing namespace confusion */ - if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) { + if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) { _forceRemove(currentNode); return true; } diff --git a/platform-web-ui/src/main/resources/polymer/file-saver/dist/FileSaver.js b/platform-web-ui/src/main/resources/polymer/file-saver/dist/FileSaver.js new file mode 100644 index 00000000000..18f268d639d --- /dev/null +++ b/platform-web-ui/src/main/resources/polymer/file-saver/dist/FileSaver.js @@ -0,0 +1,195 @@ +import { commonjsGlobal } from '../../../_virtual/_commonjsHelpers.js'; +import { __module as FileSaver$1 } from '../../../_virtual/FileSaver2.js'; + +var FileSaver = FileSaver$1.exports; + +var hasRequiredFileSaver; + +function requireFileSaver () { + if (hasRequiredFileSaver) return FileSaver$1.exports; + hasRequiredFileSaver = 1; + (function (module, exports) { + (function (global, factory) { + { + factory(); + } + })(FileSaver, function () { + + /* + * FileSaver.js + * A saveAs() FileSaver implementation. + * + * By Eli Grey, http://eligrey.com + * + * License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT) + * source : http://purl.eligrey.com/github/FileSaver.js + */ + // The one and only way of getting global scope in all environments + // https://stackoverflow.com/q/3277182/1008999 + var _global = typeof window === 'object' && window.window === window ? window : typeof self === 'object' && self.self === self ? self : typeof commonjsGlobal === 'object' && commonjsGlobal.global === commonjsGlobal ? commonjsGlobal : void 0; + + function bom(blob, opts) { + if (typeof opts === 'undefined') opts = { + autoBom: false + };else if (typeof opts !== 'object') { + console.warn('Deprecated: Expected third argument to be a object'); + opts = { + autoBom: !opts + }; + } // prepend BOM for UTF-8 XML and text/* types (including HTML) + // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF + + if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { + return new Blob([String.fromCharCode(0xFEFF), blob], { + type: blob.type + }); + } + + return blob; + } + + function download(url, name, opts) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url); + xhr.responseType = 'blob'; + + xhr.onload = function () { + saveAs(xhr.response, name, opts); + }; + + xhr.onerror = function () { + console.error('could not download file'); + }; + + xhr.send(); + } + + function corsEnabled(url) { + var xhr = new XMLHttpRequest(); // use sync to avoid popup blocker + + xhr.open('HEAD', url, false); + + try { + xhr.send(); + } catch (e) {} + + return xhr.status >= 200 && xhr.status <= 299; + } // `a.click()` doesn't work for all browsers (#465) + + + function click(node) { + try { + node.dispatchEvent(new MouseEvent('click')); + } catch (e) { + var evt = document.createEvent('MouseEvents'); + evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null); + node.dispatchEvent(evt); + } + } // Detect WebView inside a native macOS app by ruling out all browsers + // We just need to check for 'Safari' because all other browsers (besides Firefox) include that too + // https://www.whatismybrowser.com/guides/the-latest-user-agent/macos + + + var isMacOSWebView = _global.navigator && /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent); + var saveAs = _global.saveAs || ( // probably in some web worker + typeof window !== 'object' || window !== _global ? function saveAs() {} + /* noop */ + // Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView + : 'download' in HTMLAnchorElement.prototype && !isMacOSWebView ? function saveAs(blob, name, opts) { + var URL = _global.URL || _global.webkitURL; + var a = document.createElement('a'); + name = name || blob.name || 'download'; + a.download = name; + a.rel = 'noopener'; // tabnabbing + // TODO: detect chrome extensions & packaged apps + // a.target = '_blank' + + if (typeof blob === 'string') { + // Support regular links + a.href = blob; + + if (a.origin !== location.origin) { + corsEnabled(a.href) ? download(blob, name, opts) : click(a, a.target = '_blank'); + } else { + click(a); + } + } else { + // Support blobs + a.href = URL.createObjectURL(blob); + setTimeout(function () { + URL.revokeObjectURL(a.href); + }, 4E4); // 40s + + setTimeout(function () { + click(a); + }, 0); + } + } // Use msSaveOrOpenBlob as a second approach + : 'msSaveOrOpenBlob' in navigator ? function saveAs(blob, name, opts) { + name = name || blob.name || 'download'; + + if (typeof blob === 'string') { + if (corsEnabled(blob)) { + download(blob, name, opts); + } else { + var a = document.createElement('a'); + a.href = blob; + a.target = '_blank'; + setTimeout(function () { + click(a); + }); + } + } else { + navigator.msSaveOrOpenBlob(bom(blob, opts), name); + } + } // Fallback to using FileReader and a popup + : function saveAs(blob, name, opts, popup) { + // Open a popup immediately do go around popup blocker + // Mostly only available on user interaction and the fileReader is async so... + popup = popup || open('', '_blank'); + + if (popup) { + popup.document.title = popup.document.body.innerText = 'downloading...'; + } + + if (typeof blob === 'string') return download(blob, name, opts); + var force = blob.type === 'application/octet-stream'; + + var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari; + + var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent); + + if ((isChromeIOS || force && isSafari || isMacOSWebView) && typeof FileReader !== 'undefined') { + // Safari doesn't allow downloading of blob URLs + var reader = new FileReader(); + + reader.onloadend = function () { + var url = reader.result; + url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;'); + if (popup) popup.location.href = url;else location = url; + popup = null; // reverse-tabnabbing #460 + }; + + reader.readAsDataURL(blob); + } else { + var URL = _global.URL || _global.webkitURL; + var url = URL.createObjectURL(blob); + if (popup) popup.location = url;else location.href = url; + popup = null; // reverse-tabnabbing #460 + + setTimeout(function () { + URL.revokeObjectURL(url); + }, 4E4); // 40s + } + }); + _global.saveAs = saveAs.saveAs = saveAs; + + { + module.exports = saveAs; + } + }); + } (FileSaver$1, FileSaver$1.exports)); + return FileSaver$1.exports; +} + +export { requireFileSaver as __require }; diff --git a/platform-web-ui/src/main/resources/polymer/lib/file-saver-lib.js b/platform-web-ui/src/main/resources/polymer/lib/file-saver-lib.js new file mode 100644 index 00000000000..e7e1af3a806 --- /dev/null +++ b/platform-web-ui/src/main/resources/polymer/lib/file-saver-lib.js @@ -0,0 +1,6 @@ +import { F as FileSaverExports } from '../../_virtual/FileSaver.js'; + + + +var saveAs = FileSaverExports.saveAs; +export { saveAs }; diff --git a/platform-web-ui/src/main/resources/polymer/moment-timezone/data/packed/latest.json.js b/platform-web-ui/src/main/resources/polymer/moment-timezone/data/packed/latest.json.js index 59772bbbc3a..f2b30a48d89 100644 --- a/platform-web-ui/src/main/resources/polymer/moment-timezone/data/packed/latest.json.js +++ b/platform-web-ui/src/main/resources/polymer/moment-timezone/data/packed/latest.json.js @@ -1,4 +1,4 @@ -var version = "2025a"; +var version = "2025b"; var zones = [ "Africa/Abidjan|LMT GMT|g.8 0|01|-2ldXH.Q|48e5", "Africa/Nairobi|LMT +0230 EAT +0245|-2r.g -2u -30 -2J|012132|-2ua2r.g N6nV.g 3Fbu h1cu dzbJ|47e5", @@ -54,6 +54,7 @@ var zones = [ "America/Chihuahua|LMT MST CST MDT CDT|74.k 70 60 60 50|0121312424231313131313131313131313131313131313131313131313132|-1UQF0 deo0 8lz0 16p0 11z0 1dd0 2zQN0 1lb0 14p0 1lb0 14q0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0|81e4", "America/Ciudad_Juarez|LMT MST CST MDT CDT|75.U 70 60 60 50|01213124242313131313131313131313131313131313131313131313131321313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131|-1UQF0 deo0 8lz0 16p0 11z0 1dd0 2zQN0 1lb0 14p0 1lb0 14q0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 U10 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1wn0 cm0 EP0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|", "America/Costa_Rica|LMT SJMT CST CDT|5A.d 5A.d 60 50|01232323232|-3eLun.L 1fyo0 2lu0n.L Db0 1Kp0 Db0 pRB0 15b0 1kp0 mL0|12e5", + "America/Coyhaique|LMT SMT -05 -04 -03|4M.g 4G.J 50 40 30|012131323232323232323434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434|-3eLvb.I MJbS.t fJAh.f 5knG.J 1Vzh.f jRAG.J 1pbh.f 11d0 1oL0 11d0 1oL0 11d0 1oL0 11d0 1pb0 11d0 nHX0 op0 blz0 ko0 Qeo0 WL0 1zd0 On0 1ip0 11z0 1o10 11z0 1qN0 WL0 1ld0 14n0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 1cL0 1cN0 11z0 1o10 11z0 1qN0 WL0 1fB0 19X0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1ip0 1fz0 1fB0 11z0 1qN0 WL0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1o10 19X0 1fB0 1nX0 G10 1EL0 Op0 1zb0 Rd0 1wn0 Rd0 46n0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1zb0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 11B0 1qL0 11B0|", "America/Phoenix|LMT MST MDT MWT|7s.i 70 60 60|012121313121|-3tFF0 1nEe0 1nX0 11B0 1nX0 SgN0 4Al1 Ap0 1db0 SWqX 1cL0|42e5", "America/Cuiaba|LMT -04 -03|3I.k 40 30|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2glwf.E HdLf.E 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 1EN0 Lz0 1C10 IL0 1HB0 Db0 1HB0 On0 1zd0 On0 1zd0 Lz0 1zd0 Rb0 1wN0 Wn0 1tB0 Rb0 1tB0 WL0 1tB0 Rb0 1zd0 On0 1HB0 FX0 4a10 HX0 1zd0 On0 1HB0 IL0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1HB0 FX0|54e4", "America/Danmarkshavn|LMT -03 -02 GMT|1e.E 30 20 0|01212121212121212121212121212121213|-2a5WJ.k 2z5fJ.k 19U0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 DC0|8", @@ -216,7 +217,7 @@ var zones = [ "Asia/Taipei|LMT CST JST CDT|-86 -80 -90 -90|012131313131313131313131313131313131313131|-30bk6 1FDc6 joM0 1yo0 Tz0 1ip0 1jX0 1cN0 11b0 1oN0 11b0 1oN0 11b0 1oN0 11b0 10N0 1BX0 10p0 1pz0 10p0 1pz0 10p0 1db0 1dd0 1db0 1cN0 1db0 1cN0 1db0 1cN0 1db0 1BB0 ML0 1Bd0 ML0 uq10 1db0 1cN0 1db0 97B0 AL0|74e5", "Asia/Tashkent|LMT +05 +06 +07|-4B.b -50 -60 -70|012323232323232323232321|-1Pc4B.b eUnB.b 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0|23e5", "Asia/Tbilisi|LMT TBMT +03 +04 +05|-2X.b -2X.b -30 -40 -50|01234343434343434343434323232343434343434343434323|-3D8OX.b 1LUM0 1jUnX.b WCL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 1cK0 1cL0 1cN0 1cL0 1cN0 2pz0 1cL0 1fB0 3Nz0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 An0 Os0 WM0|11e5", - "Asia/Tehran|LMT TMT +0330 +0430 +04 +05|-3p.I -3p.I -3u -4u -40 -50|012345423232323232323232323232323232323232323232323232323232323232323232|-2btDp.I Llc0 1FHaT.I 1pc0 120u Rc0 XA0 Wou JX0 1dB0 1en0 pNB0 UL0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 64p0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0|14e6", + "Asia/Tehran|LMT TMT +0330 +0430 +04 +05|-3p.I -3p.I -3u -4u -40 -50|012345423232323232323232323232323232323232323232323232323232323232323232|-2btDp.I Llc0 1FHaT.I 1pc0 120u Rc0 Dc0 1iMu JX0 1dB0 1en0 pNB0 UL0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 64p0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0|14e6", "Asia/Thimphu|LMT +0530 +06|-5W.A -5u -60|012|-Su5W.A 1BGMs.A|79e3", "Asia/Tokyo|LMT JST JDT|-9i.X -90 -a0|0121212121|-3jE90 2qSo0 Rc0 1lc0 14o0 1zc0 Oo0 1zc0 Oo0|38e6", "Asia/Tomsk|LMT +06 +07 +08|-5D.P -60 -70 -80|0123232323232323232323212323232323232323232323212121212121212121212|-21NhD.P pxzD.P 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 co0 1bB0 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0 3Qp0|10e5", @@ -644,7 +645,7 @@ var countries = [ "CH|Europe/Zurich", "CI|Africa/Abidjan", "CK|Pacific/Rarotonga", - "CL|America/Santiago America/Punta_Arenas Pacific/Easter", + "CL|America/Santiago America/Coyhaique America/Punta_Arenas Pacific/Easter", "CM|Africa/Lagos Africa/Douala", "CN|Asia/Shanghai Asia/Urumqi", "CO|America/Bogota", diff --git a/platform-web-ui/src/main/resources/polymer/moment-timezone/moment-timezone.js b/platform-web-ui/src/main/resources/polymer/moment-timezone/moment-timezone.js index 5b63b3b94d3..0d59c37b987 100644 --- a/platform-web-ui/src/main/resources/polymer/moment-timezone/moment-timezone.js +++ b/platform-web-ui/src/main/resources/polymer/moment-timezone/moment-timezone.js @@ -10,7 +10,7 @@ function requireMomentTimezone () { hasRequiredMomentTimezone = 1; (function (module) { //! moment-timezone.js - //! version : 0.5.47 + //! version : 0.5.48 //! Copyright (c) JS Foundation and other contributors //! license : MIT //! github.com/moment/moment-timezone @@ -36,7 +36,7 @@ function requireMomentTimezone () { // return moment; // } - var VERSION = "0.5.47", + var VERSION = "0.5.48", zones = {}, links = {}, countries = {}, diff --git a/platform-web-ui/src/main/resources/polymer/prosemirror-commands/dist/index.js b/platform-web-ui/src/main/resources/polymer/prosemirror-commands/dist/index.js index 765b9749502..1f38f62f84f 100644 --- a/platform-web-ui/src/main/resources/polymer/prosemirror-commands/dist/index.js +++ b/platform-web-ui/src/main/resources/polymer/prosemirror-commands/dist/index.js @@ -327,6 +327,8 @@ function splitBlockAs(splitNode) { types[0] = deflt ? { type: deflt } : null; can = canSplit(tr.doc, splitPos, types.length, types); } + if (!can) + return false; tr.split(splitPos, types.length, types); if (!atEnd && atStart && $from.node(splitDepth).type != deflt) { let first = tr.mapping.map($from.before(splitDepth)), $first = tr.doc.resolve(first); diff --git a/platform-web-ui/src/main/resources/polymer/prosemirror-model/dist/index.js b/platform-web-ui/src/main/resources/polymer/prosemirror-model/dist/index.js index f8e1948710c..e68f55656ec 100644 --- a/platform-web-ui/src/main/resources/polymer/prosemirror-model/dist/index.js +++ b/platform-web-ui/src/main/resources/polymer/prosemirror-model/dist/index.js @@ -2833,7 +2833,7 @@ class ParseContext { value = value.replace(/\r\n?/g, "\n"); } if (value) - this.insertNode(this.parser.schema.text(value), marks); + this.insertNode(this.parser.schema.text(value), marks, !/\S/.test(value)); this.findInText(dom); } else { @@ -2897,7 +2897,7 @@ class ParseContext { ignoreFallback(dom, marks) { // Ignored BR nodes should at least create an inline context if (dom.nodeName == "BR" && (!this.top.type || !this.top.type.inlineContent)) - this.findPlace(this.parser.schema.text("-"), marks); + this.findPlace(this.parser.schema.text("-"), marks, true); } // Run any style parser associated with the node's styles. Either // return an updated array of marks, or null to indicate some of the @@ -2945,7 +2945,7 @@ class ParseContext { marks = inner; } } - else if (!this.insertNode(nodeType.create(rule.attrs), marks)) { + else if (!this.insertNode(nodeType.create(rule.attrs), marks, dom.nodeName == "BR")) { this.leafFallback(dom, marks); } } @@ -2962,7 +2962,7 @@ class ParseContext { } else if (rule.getContent) { this.findInside(dom); - rule.getContent(dom, this.parser.schema).forEach(node => this.insertNode(node, marks)); + rule.getContent(dom, this.parser.schema).forEach(node => this.insertNode(node, marks, false)); } else { let contentDOM = dom; @@ -2993,19 +2993,22 @@ class ParseContext { // Try to find a way to fit the given node type into the current // context. May add intermediate wrappers and/or leave non-solid // nodes that we're in. - findPlace(node, marks) { + findPlace(node, marks, cautious) { let route, sync; - for (let depth = this.open; depth >= 0; depth--) { + for (let depth = this.open, penalty = 0; depth >= 0; depth--) { let cx = this.nodes[depth]; let found = cx.findWrapping(node); - if (found && (!route || route.length > found.length)) { + if (found && (!route || route.length > found.length + penalty)) { route = found; sync = cx; if (!found.length) break; } - if (cx.solid) - break; + if (cx.solid) { + if (cautious) + break; + penalty += 2; + } } if (!route) return null; @@ -3015,13 +3018,13 @@ class ParseContext { return marks; } // Try to insert the given node, adjusting the context when needed. - insertNode(node, marks) { + insertNode(node, marks, cautious) { if (node.isInline && this.needsBlock && !this.top.type) { let block = this.textblockFromContext(); if (block) marks = this.enterInner(block, null, marks); } - let innerMarks = this.findPlace(node, marks); + let innerMarks = this.findPlace(node, marks, cautious); if (innerMarks) { this.closeExtra(); let top = this.top; @@ -3039,7 +3042,7 @@ class ParseContext { // Try to start a node of the given type, adjusting the context when // necessary. enter(type, attrs, marks, preserveWS) { - let innerMarks = this.findPlace(type.create(attrs), marks); + let innerMarks = this.findPlace(type.create(attrs), marks, false); if (innerMarks) innerMarks = this.enterInner(type, attrs, marks, true, preserveWS); return innerMarks; diff --git a/platform-web-ui/src/main/resources/polymer/prosemirror-transform/dist/index.js b/platform-web-ui/src/main/resources/polymer/prosemirror-transform/dist/index.js index da9ab50a831..006abe7d27b 100644 --- a/platform-web-ui/src/main/resources/polymer/prosemirror-transform/dist/index.js +++ b/platform-web-ui/src/main/resources/polymer/prosemirror-transform/dist/index.js @@ -714,7 +714,7 @@ class ReplaceStep extends Step { let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1); if (from.deletedAcross && to.deletedAcross) return null; - return new ReplaceStep(from.pos, Math.max(from.pos, to.pos), this.slice); + return new ReplaceStep(from.pos, Math.max(from.pos, to.pos), this.slice, this.structure); } merge(other) { if (!(other instanceof ReplaceStep) || other.structure || this.structure) @@ -2061,19 +2061,26 @@ class Transform { return this; } /** - Remove a mark (or a mark of the given type) from the node at + Remove a mark (or all marks of the given type) from the node at position `pos`. */ removeNodeMark(pos, mark) { - if (!(mark instanceof Mark)) { - let node = this.doc.nodeAt(pos); - if (!node) - throw new RangeError("No node at position " + pos); - mark = mark.isInSet(node.marks); - if (!mark) - return this; + let node = this.doc.nodeAt(pos); + if (!node) + throw new RangeError("No node at position " + pos); + if (mark instanceof Mark) { + if (mark.isInSet(node.marks)) + this.step(new RemoveNodeMarkStep(pos, mark)); + } + else { + let set = node.marks, found, steps = []; + while (found = mark.isInSet(set)) { + steps.push(new RemoveNodeMarkStep(pos, found)); + set = found.removeFromSet(set); + } + for (let i = steps.length - 1; i >= 0; i--) + this.step(steps[i]); } - this.step(new RemoveNodeMarkStep(pos, mark)); return this; } /** diff --git a/platform-web-ui/src/main/resources/polymer/prosemirror-view/dist/index.js b/platform-web-ui/src/main/resources/polymer/prosemirror-view/dist/index.js index 6052ff88933..dda8fd8cd0a 100644 --- a/platform-web-ui/src/main/resources/polymer/prosemirror-view/dist/index.js +++ b/platform-web-ui/src/main/resources/polymer/prosemirror-view/dist/index.js @@ -2984,7 +2984,7 @@ function maybeWrapTrusted(html) { // innerHTML, even on a detached document. This wraps the string in // a way that makes the browser allow us to use its parser again. if (!_policy) - _policy = trustedTypes.createPolicy("ProseMirrorClipboard", { createHTML: (s) => s }); + _policy = trustedTypes.defaultPolicy || trustedTypes.createPolicy("ProseMirrorClipboard", { createHTML: (s) => s }); return _policy.createHTML(html); } function readHTML(html) { @@ -3676,6 +3676,10 @@ class Dragging { } } const dragCopyModifier = mac ? "altKey" : "ctrlKey"; +function dragMoves(view, event) { + let moves = view.someProp("dragCopies", test => !test(event)); + return moves != null ? moves : !event[dragCopyModifier]; +} handlers.dragstart = (view, _event) => { let event = _event; let mouseDown = view.input.mouseDown; @@ -3705,7 +3709,7 @@ handlers.dragstart = (view, _event) => { event.dataTransfer.effectAllowed = "copyMove"; if (!brokenClipboardAPI) event.dataTransfer.setData("text/plain", text); - view.dragging = new Dragging(slice, !event[dragCopyModifier], node); + view.dragging = new Dragging(slice, dragMoves(view, event), node); }; handlers.dragend = view => { let dragging = view.dragging; @@ -3732,7 +3736,7 @@ editHandlers.drop = (view, _event) => { else { slice = parseFromClipboard(view, getText(event.dataTransfer), brokenClipboardAPI ? null : event.dataTransfer.getData("text/html"), false, $mouse); } - let move = !!(dragging && !event[dragCopyModifier]); + let move = !!(dragging && dragMoves(view, event)); if (view.someProp("handleDrop", f => f(view, event, slice || Slice.empty, move))) { event.preventDefault(); return; @@ -5017,9 +5021,11 @@ function readDOMChange(view, from, to, typeOver, addedNodes) { // as being an iOS enter press), just dispatch an Enter key instead. if (((ios && view.input.lastIOSEnter > Date.now() - 225 && (!inlineChange || addedNodes.some(n => n.nodeName == "DIV" || n.nodeName == "P"))) || - (!inlineChange && $from.pos < parse.doc.content.size && !$from.sameParent($to) && + (!inlineChange && $from.pos < parse.doc.content.size && + (!$from.sameParent($to) || !$from.parent.inlineContent) && + !/\S/.test(parse.doc.textBetween($from.pos, $to.pos, "", "")) && (nextSel = Selection.findFrom(parse.doc.resolve($from.pos + 1), 1, true)) && - nextSel.head == $to.pos)) && + nextSel.head > $from.pos)) && view.someProp("handleKeyDown", f => f(view, keyEvent(13, "Enter")))) { view.input.lastIOSEnter = 0; return; diff --git a/platform-web-ui/src/main/resources/rollup.config.js b/platform-web-ui/src/main/resources/rollup.config.js index 7b19b29581e..03581cfa536 100644 --- a/platform-web-ui/src/main/resources/rollup.config.js +++ b/platform-web-ui/src/main/resources/rollup.config.js @@ -19,6 +19,7 @@ export default { // Our other libraries. 'node_modules/lib/antlr-lib.js', + 'node_modules/lib/file-saver-lib.js', 'node_modules/lib/fullcalendar-lib.js', 'node_modules/lib/moment-lib.js', 'node_modules/lib/toastui-editor-lib.js', diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/app/test/tg-menu-list-changes-test.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/app/test/tg-menu-list-changes-test.html index e09b943d68b..dbb371c8e9a 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/app/test/tg-menu-list-changes-test.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/app/test/tg-menu-list-changes-test.html @@ -8,7 +8,6 @@ - - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-entity-editing-with-collections.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-entity-editing-with-collections.html index 70daed6dbb0..8370a29d65f 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-entity-editing-with-collections.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-entity-editing-with-collections.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-filtering.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-filtering.html index 2c6e9cab80c..3478e4462ec 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-filtering.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-filtering.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-generation.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-generation.html index c394cb4cac9..865985d5e6e 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-generation.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-generation.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-performance.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-performance.html index 43c638893e8..c4022ee0657 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-performance.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-performance.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-query-param.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-query-param.html index 6b747bc4c9d..818c993ceec 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-query-param.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-query-param.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-refresh-for-complex-centre.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-refresh-for-complex-centre.html index 88f789e6bb0..021f631112e 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-refresh-for-complex-centre.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-refresh-for-complex-centre.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-running-for-complex-centre.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-running-for-complex-centre.html index 5a410121cd8..54305b895b4 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-running-for-complex-centre.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-running-for-complex-centre.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-selection.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-selection.html index c7292fa527f..ac848dca43c 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-selection.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-selection.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-test-delete-refresh-functionality.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-test-delete-refresh-functionality.html index 3d0ef2d1bf3..2154907414a 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-test-delete-refresh-functionality.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-test-delete-refresh-functionality.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-with-property-descriptor-criteria.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-with-property-descriptor-criteria.html index 965be1787ae..ddfc7404ffe 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-with-property-descriptor-criteria.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre-with-property-descriptor-criteria.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre.html index dda2090dee8..ae2594d1719 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre-template.js b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre-template.js index 31f4df0031b..4c655ed8f26 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre-template.js +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre-template.js @@ -27,7 +27,6 @@ import '/resources/centre/tg-entity-centre-styles.js'; import '/resources/centre/tg-selection-criteria-styles.js'; import { TgEntityCentreTemplateBehavior } from '/resources/centre/tg-entity-centre-template-behavior.js'; import '/resources/centre/tg-entity-centre-insertion-point.js'; -import { TgReflector } from '/app/tg-reflector.js'; const selectionCritTemplate = html` diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-generated-entity-centre-example.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-generated-entity-centre-example.html index 6d8d4dbeb1f..27ba900ecd3 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-generated-entity-centre-example.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-generated-entity-centre-example.html @@ -6,7 +6,6 @@ Generated Centre - - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/components/tg-drag-example.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/components/tg-drag-example.html index c473458d71e..50531dd9e25 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/components/tg-drag-example.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/components/tg-drag-example.html @@ -7,7 +7,6 @@ - - - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-approximations-for-DDMMYYYY-slash-24hours.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-approximations-for-DDMMYYYY-slash-24hours.html index f575a978955..17f5ac495bc 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-approximations-for-DDMMYYYY-slash-24hours.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-approximations-for-DDMMYYYY-slash-24hours.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-approximations-for-MMDDYYYY-slash-12hours.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-approximations-for-MMDDYYYY-slash-12hours.html index 0d7753c8d54..adee1fb3548 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-approximations-for-MMDDYYYY-slash-12hours.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-approximations-for-MMDDYYYY-slash-12hours.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-approximations-for-MMDDYYYY-slash-24hours.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-approximations-for-MMDDYYYY-slash-24hours.html index 513c3005646..5f165cbc605 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-approximations-for-MMDDYYYY-slash-24hours.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-approximations-for-MMDDYYYY-slash-24hours.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-approximations-for-YYYYMMDD-dash-24hours.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-approximations-for-YYYYMMDD-dash-24hours.html index 07cea20f2bd..25e32c20416 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-approximations-for-YYYYMMDD-dash-24hours.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-approximations-for-YYYYMMDD-dash-24hours.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-time-zones.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-time-zones.html index ab0caee3681..71886715013 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-time-zones.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-datetime-picker-time-zones.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-editor.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-editor.html index ee5cd947c54..06dbaad8cd0 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-editor.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-editor.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-entity-editor.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-entity-editor.html index c9c4e2b61b8..25d50bbbbca 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-entity-editor.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-entity-editor.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-entity-formatter.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-entity-formatter.html index 53bb04a3db1..113aa6a6c80 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-entity-formatter.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/editors/test/tg-entity-formatter.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/egi/egi-dynamic-columns-example.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/egi/egi-dynamic-columns-example.html index 3570a15e2ec..fde227087a0 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/egi/egi-dynamic-columns-example.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/egi/egi-dynamic-columns-example.html @@ -7,7 +7,6 @@ Dynamic egi column - - - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/egi/test/tg-egi-dynamic-rendering-hints-test.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/egi/test/tg-egi-dynamic-rendering-hints-test.html index 276ce383ce6..a6abf744ea7 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/egi/test/tg-egi-dynamic-rendering-hints-test.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/egi/test/tg-egi-dynamic-rendering-hints-test.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/element_loader/loader-example.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/element_loader/loader-example.html index 1ef4df1316f..8de768f19a0 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/element_loader/loader-example.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/element_loader/loader-example.html @@ -5,7 +5,6 @@ Element Loader Example - - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-conflict-resolution.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-conflict-resolution.html index bf9cabfc42b..2c137a1778b 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-conflict-resolution.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-conflict-resolution.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-editors-touching.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-editors-touching.html index 4fd7ac91cae..7ac67d23621 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-editors-touching.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-editors-touching.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-filtering.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-filtering.html index bdbb1448cb5..96bc70f9e9d 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-filtering.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-filtering.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-layout-binding.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-layout-binding.html index 4d8f7f4a47f..e0032bc01dd 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-layout-binding.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-layout-binding.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-with-rich-text.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-with-rich-text.html index 04cddc9eea3..d96233c3d3c 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-with-rich-text.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master-with-rich-text.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master.html index 168b78b7cee..6834c393940 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/test/tg-entity-master.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/tg-entity-master-template.js b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/tg-entity-master-template.js index 730372222d0..ba24e245a7a 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/tg-entity-master-template.js +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/tg-entity-master-template.js @@ -3,8 +3,6 @@ import '/resources/actions/tg-ui-action.js'; import { TgEntityMasterTemplateBehavior, Polymer, html } from '/resources/master/tg-entity-master-template-behavior.js'; -import { TgReflector } from '/app/tg-reflector.js'; -import { getParentAnd } from '/resources/reflection/tg-polymer-utils.js'; // required by BindSavedPropertyPostActionSuccess/Error handlers // diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/tg-entity-with-rich-text-prop-master-example.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/tg-entity-with-rich-text-prop-master-example.html index 9fb9b67e4af..5ad0e5854cb 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/tg-entity-with-rich-text-prop-master-example.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/tg-entity-with-rich-text-prop-master-example.html @@ -6,7 +6,6 @@ Generated Master - - - - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/reflection/test/tg-reflector.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/reflection/test/tg-reflector.html index 48fe11cace0..ae3d1842fff 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/reflection/test/tg-reflector.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/reflection/test/tg-reflector.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/serialisation/test/tg-serialiser.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/serialisation/test/tg-serialiser.html index 692b928af50..1f0fb97887b 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/serialisation/test/tg-serialiser.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/serialisation/test/tg-serialiser.html @@ -8,7 +8,6 @@ - diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/service-worker.js b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/service-worker.js index a072d0d9658..a81a33892e9 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/service-worker.js +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/service-worker.js @@ -1,141 +1,277 @@ /** * The name for resources cache. */ -const cacheName = 'tg-deployment-cache'; +const CACHE_NAME = 'tg-deployment-cache'; /** * The name for separate cache of resource checksums. */ -const checksumCacheName = 'tg-deployment-cache-checksums'; +const CHECKSUM_CACHE_NAME = 'tg-deployment-cache-checksums'; /** - * Determines whether request 'url' represents static resource, i.e. such resource that does not change between releases. + * Suffix for checksum request URL. + */ +const CHECKSUM_URL_SUFFIX = '?checksum=true'; +/** + * Suffix for resource paths request URL. + */ +const RESOURCES_URL_SUFFIX = '?resources=true'; +/** + * Delimiter for resource paths. + */ +const RESOURCES_DELIMITER = '\n'; + +/** + * Determines whether request 'pathName' represents static resource, i.e. such resource that does not change between releases. * - * Please note that for deployment mode only '/', '/logout', '/forgotten' and '/resources/...' are needed. + * Please note that for deployment mode only '/', '/forgotten' and '/resources/...' are needed. * However, we have listed all possible resources here to avoid the change to service worker later. * - * @param url + * @param pathName * @param method */ -const isStatic = function (url, method) { - const pathname = new URL(url).pathname; - return 'GET' === method && (pathname === '/' || - pathname === '/forgotten' || - pathname.startsWith('/resources/') || - pathname.startsWith('/app/') || - pathname.startsWith('/centre_ui/') || - pathname.startsWith('/master_ui/') || - pathname.startsWith('/custom_view/')); -}; +function isStatic(pathName, method) { + return 'GET' === method && (pathName === '/' || + pathName === '/forgotten' || + pathName.startsWith('/resources/') || + pathName.startsWith('/app/') || + pathName.startsWith('/centre_ui/') || + pathName.startsWith('/master_ui/') || + pathName.startsWith('/custom_view/')); +} /** - * Creates response indicating that client application is stale and is needed to be refreshed fully. + * Creates a response indicating that client application is stale and is needed to be refreshed fully. */ -const staleResponse = function () { +function staleResponse() { + console.info(`The client app is stale now.`); return new Response('STALE', {status: 412, statusText: 'BAD', headers: {'Content-Type': 'text/plain'}}); -}; +} /** - * Indicates whether response is successful. + * Indicates whether the 'response' is successful. */ -const isResponseSuccessful = function (response) { +function isResponseSuccessful(response) { return response && response.ok; -}; +} + +/** + * Creates an URL object from 'requestUrl' string. + */ +function createURL(requestUrl) { + return new URL(requestUrl); +} + +/** + * Creates GET Request object from 'url'. + */ +function createGETRequest(url) { + // GET is the default 'method', but make it a little bit more explicit. + return new Request(url, { method: 'GET' }); +} + +/** + * Creates a 'Promise' for 'cache' entry deletion by it's 'url'. + * Warns about unsuccessful deletion or if the resource was not found ('deleted' === false). + */ +function deleteCacheEntry(url, cache) { + return cache.delete(url).then( + deleted => { + if (!deleted) { + console.warn(`The cached resource at [${url}] was not deleted. It was likely deleted manually earlier.`); + } + return deleted; + }, + error => { + console.warn(`The cached resource at [${url}] was not deleted. Error:`, error); + // Preserve rejection as in original 'cache.delete' promise. + return Promise.reject(error); + } + ); +} + +/** + * Creates a 'Promise' for redundant 'url' resource deletion, assuming its presence in both 'cache' and 'checksumCache'. + * Warns about some unusual deletion problems and shows informational message for easier inspection. + * Use Chrome 'Default levels' (Info, Warnings, Errors) and unchecked 'Selected context only' and checked 'Preserve log'. + */ +function deleteRedundantResource(url, cache, checksumCache) { + // Shows informational message on 'url' resource deletion from a server and, consequently, from a Cache Storage. + console.info(`The resource at [${url}] has been deleted on the server. It will be removed from the cache.`); + return deleteCacheEntry(url, cache) + .then(_ => deleteCacheEntry(url + CHECKSUM_URL_SUFFIX, checksumCache)); +} + +/** + * Asynchronously cleans up Cache Storage by removing redundant entries, not present on a server. + * It does so by loading a set of present server resources and comparing it with Cache Storage entries. + * Missing server resources will be deleted from both 'cache' and 'checksumCache'. + */ +function cleanUp(url, cache, checksumCache) { + console.info(`Starting cleanup of redundant resources...`); + // Create special request against root '/' (aka 'index.html') to load paths of current resources. + const serverResourcesRequest = createGETRequest(url + RESOURCES_URL_SUFFIX); + // Fetch the request and get text from a response. + return fetch(serverResourcesRequest).then(serverResourcesResponse => { + return getTextFrom(serverResourcesResponse).then(serverResourcesStr => { + // Create a set of resource paths from a string, returned by a server. + const serverResources = new Set(serverResourcesStr.split(RESOURCES_DELIMITER)); + // Find all 'cache' entries... + return cache.keys().then(requests => { + return Promise.all( + // ... and filter out those not present on a server; + requests.filter(request => !serverResources.has(createURL(request.url).pathname)) + // Remove found entries from both caches. + .map(request => deleteRedundantResource(request.url, cache, checksumCache)) + ); + }); + }); + }); +} /** * Caches the specified 'response' and its checksum ('checksumResponse') in case where they are both successful. * Returns promise resolving to 'response'. + * + * Also initiates 'cleanUp' for changed '/' resource. */ -const cacheIfSuccessful = function (response, checksumRequest, checksumResponse, url, cache, checksumCache) { - if (isResponseSuccessful(response)) { // cache response if it is successful; 'checksumResponse' is successful at this stage - // IMPORTANT: Clone the response. A response is a stream and because we want the browser to consume the response - // as well as the cache consuming the response, we need to clone it so we have two streams. - return cache.put(url, response.clone()).then(function() { // cache response; it should not fail (otherwise bad response will be returned) - return checksumCache.put(checksumRequest, checksumResponse).then(function () { // cache checksum; it should not fail (otherwise bad response will be returned) +function cacheIfSuccessful(response, checksumRequest, checksumResponse, url, cache, checksumCache, urlObj, event) { + // Cache response if it is successful; 'checksumResponse' is successful at this stage. + if (isResponseSuccessful(response)) { + // IMPORTANT: Clone the response. We need to clone it so we have two streams. + // First stream is for the browser to consume the response. + // Second is for a cache consuming the response. + // Cache response; it should not fail (otherwise net::ERR_CACHE_* will be returned; see chrome://network-errors/): + return cache.put(url, response.clone()).then(() => { + // Cache checksum; it should not fail (otherwise - net::ERR_CACHE_*): + return checksumCache.put(checksumRequest, checksumResponse).then(() => { + if (urlObj.pathname === '/') { + // Main 'index.html' file has been re-cached after a change (or cached for the first time). + // Start cleaning up of Cache Storage asynchronously. + // Insist to keep service worker alive until 'cleanUp' promise completes: + event.waitUntil( + // Actual clean up of redundant resources: + cleanUp(url, cache, checksumCache).catch(error => { + console.warn(`Cleanup failed with error:`, error); + }) + ); + } + // Return response quite soon after 'checksumResponse' is inside the Cache Storage (no clean up blocking). return response; }); }); } - return Promise.resolve(response); // do not blow up response if for some reason response was not successful; just return it as if the request was not intercepted by service worker -}; + // Do not blow up response if for some reason it was not successful. + // Just return it as if the request was not intercepted by service worker. + return Promise.resolve(response); +} /** * Returns promise resolving to response text if successful, otherwise returns rejection promise containing unsuccessful response. * * @param response */ -const getTextFrom = function (response) { +function getTextFrom(response) { if (isResponseSuccessful(response)) { return response.clone().text(); // perform cloning here to leave original 'response' stream unaffected } else { return Promise.reject(response); } -}; +} + +addEventListener('install', event => { + // New updated service worker can be installed, but not yet activated until the page will be closed / opened again. + // Currently, even 'Hard reload' or 'Empty cache and hard reload' in Chrome does not insist on service worker update. + // Actually, these actions do nothing - not even installing an updated service worker (unlike Normal Reload, Ctrl+R). + // So, new updated service worker gets installed and keeps being in 'waiting to activate' state. + // This is because the previous service worker already controls 'index.html' and by default new service worker is not activated. + // We want to take control immediately for all pages, because every change to service worker are backward compatible. + // Practically skipWaiting() enforces control on every tab / window already opened. + // (See https://w3c.github.io/ServiceWorker/#activate 8.1 and 8.2). + // In case of some browser implementation deficiencies, clients.claim() should also additionally enforce that. + // But clients.claim() is not strictly required (see it's usage below for more details on the reason why it is needed). + skipWaiting(); // progressing service worker to 'activating' state and further - no need to 'waitUntil' here +}); -self.addEventListener('activate', event => { +addEventListener('activate', event => { // By default the page's fetches will not go through service worker if it was not fetched through service worker. - // This is the case for the very first time index.html loading. + // This is the case for the very first time 'index.html' loading. // However we can enforce service worker to take full control as soon as first activation performs. - // This makes [immediate caching of index.html dependencies] possible. - clients.claim(); + // This makes immediate caching of 'index.html' dependencies possible. + event.waitUntil(clients.claim()); // wait for the promise to settle and only then allow service worker to dispose }); -self.addEventListener('fetch', function (event) { +addEventListener('fetch', event => { const request = event.request; - const urlObj = new URL(request.url); - const url = urlObj.origin + urlObj.pathname; - if (isStatic(url, request.method)) { // only consider intercepting for static resources - event.respondWith(function() { - return caches.open(cacheName).then(function (cache) { // open main cache; it should not fail (otherwise bad response will be returned) - const serverChecksumRequest = new Request(url + '?checksum=true', { method: 'GET' }); - return fetch(serverChecksumRequest).then(function(serverChecksumResponse) { // fetch checksum for the intercepted resource; it should not fail (otherwise bad response will be returned) - return cache.match(url).then(function (cachedResponse) { // match resource in main cache; it should not fail (otherwise bad response will be returned) - return caches.open(checksumCacheName).then(function (checksumCache) { // open checksum cache; it should not fail (otherwise bad response will be returned) - return checksumCache.match(url + '?checksum=true').then(function (cachedChecksumResponse) { // match resource's checksum in checksum cache; it should not fail (otherwise bad response will be returned) - return getTextFrom(serverChecksumResponse).then(function (serverChecksum) { // get checksum text; checksum response should be successful (otherwise bad response will be returned) - if (cachedResponse && cachedChecksumResponse) { // cached entry exists and it has proper checksum too - return cachedChecksumResponse.text().then(function (cachedChecksum) { // cachedChecksumResponse always is successful, because only successful checksumResponse can be cached - if (!serverChecksum) { // resource has been deleted on server - console.warn(`Resource ${url} has been deleted on server.`); - return cache.delete(url).then(function (deleted) { - if (!deleted) { - console.warn(`Cached resource [${url}] was not deleted.`); // do not blow up response if for some reason deletion was not successful; but it should be successful - } - return staleResponse(); - }); - } else if (serverChecksum !== cachedChecksum) { // resource has been modified on server - console.warn(`Resource ${url} has been modified on server. CachedChecksum ${cachedChecksum} vs serverChecksum ${serverChecksum}. MODIFIED RESOURCE WILL BE RE-CACHED.`); - return fetch(url).then(function (fetchedResponse) { - return cacheIfSuccessful(fetchedResponse, serverChecksumRequest, serverChecksumResponse, url, cache, checksumCache); + const urlObj = createURL(request.url); + // Only consider intercepting of static resources. + if (isStatic(urlObj.pathname, request.method)) { + // 'respondWith' will insist on service worker to live until the promise will be resolved. + event.respondWith( + // Open the main cache; it should not fail (otherwise net::ERR_CACHE_* will be returned; see chrome://network-errors/). + caches.open(CACHE_NAME).then(cache => { + // 'request.url' may contain '#' / '?' parts -- use only 'origin' and 'pathname'. + const url = urlObj.origin + urlObj.pathname; + const serverChecksumRequest = createGETRequest(url + CHECKSUM_URL_SUFFIX); + // Fetch checksum for the intercepted resource; it should not fail (otherwise - net::ERR_*). + return fetch(serverChecksumRequest).then(serverChecksumResponse => { + // Match resource in the main cache; it should not fail (otherwise - net::ERR_*). + return cache.match(url).then(cachedResponse => { + // Open the checksum cache; it should not fail (otherwise - net::ERR_*). + return caches.open(CHECKSUM_CACHE_NAME).then(checksumCache => { + // Match resource's checksum in the checksum cache; it should not fail (otherwise - net::ERR_*). + return checksumCache.match(url + CHECKSUM_URL_SUFFIX).then(cachedChecksumResponse => { + // Get checksum text; checksum response should be successful (otherwise - net::ERR_*). + return getTextFrom(serverChecksumResponse).then(serverChecksum => { + if (cachedResponse && cachedChecksumResponse) { + // Cached entry exists and it has a proper checksum too. + // 'cachedChecksumResponse' is always successful, because only successful 'checksumResponse' can be cached. + return cachedChecksumResponse.text().then(cachedChecksum => { + if (!serverChecksum) { + return deleteRedundantResource(url, cache, checksumCache).then(_ => staleResponse()); + } else if (serverChecksum !== cachedChecksum) { + console.info(`The resource at [${url}] has been modified on the server. CachedChecksum ${cachedChecksum} vs serverChecksum ${serverChecksum}. The modified resource will be re-cached.`); + return fetch(url).then(fetchedResponse => { + return cacheIfSuccessful(fetchedResponse, serverChecksumRequest, serverChecksumResponse, url, cache, checksumCache, urlObj, event); }); - } else { // serverChecksum === cachedChecksum; resource is the same on server and in client cache + } else { + // 'serverChecksum' === 'cachedChecksum'. + // Resource is the same on the server and in the client cache. Just return it. return cachedResponse; } }); - } else { // there is no cached entry - if (!serverChecksum) { // resource has been deleted on server - console.warn(`Resource ${url} has been deleted on server.`); + } else { + // There is no cached entry (or for some reason it is incomplete, e.g. without a checksum). + if (!serverChecksum) { return staleResponse(); - } else { // resource exists on server - console.warn(`Resource ${url} exists on server. ServerChecksum ${serverChecksum}. NEW RESOURCE WILL BE CACHED.`); - return fetch(url).then(function (fetchedResponse) { - return cacheIfSuccessful(fetchedResponse, serverChecksumRequest, serverChecksumResponse, url, cache, checksumCache); + } else { + console.info(`The resource at [${url}] exists on the server. ServerChecksum ${serverChecksum}. The new resource will be cached.`); + return fetch(url).then(fetchedResponse => { + return cacheIfSuccessful(fetchedResponse, serverChecksumRequest, serverChecksumResponse, url, cache, checksumCache, urlObj, event); }); } } - }, function (serverChecksumResponseError) { // it is very important not to chain catch clause but to use onRejected callback; this is because we need to process errors only from getTextFrom(...) promise and not from getTextFrom(...).then(...) promise. + // It is very important not to chain catch clause but to use 'onRejected' callback. + // This is because we need to process errors only from getTextFrom(...) promise; + // (i.e. not from getTextFrom(...).then(...) promise). + }, serverChecksumResponseError => { if (serverChecksumResponseError instanceof Response && !isResponseSuccessful(serverChecksumResponseError) && (serverChecksumResponseError.status === 403 || serverChecksumResponseError.status === 503)) { - // If server checksum response is Forbidden (403) or Service Unavailable (503) then we need to respond with redirection response to a login resource. + // Server checksum response is Forbidden (403) or Service Unavailable (503). + // In this case we need to respond with redirection response to a login resource. return Response.redirect(url + 'login/'); } else { - throw serverChecksumResponseError; // rethrow the error in other cases as if there was no onRejected clause here; this would lead to promise rejection + // Re-throw the error in other cases as if there was no 'onRejected' clause here. + // This would lead to promise rejection. + throw serverChecksumResponseError; } }); }); }); }); }); - }); - }()); - } // all non-static resources should be bypassed by service worker, just ignoring them in 'fetch' event; this will trigger default logic + }) + ) + } + // Else: all non-static resources should be bypassed by service worker - just ignoring them in 'fetch' event. + // This will trigger the default logic. }); \ No newline at end of file diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/spikes/input-performance-spike.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/spikes/input-performance-spike.html index 492fa5916b8..567ff0d66a2 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/spikes/input-performance-spike.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/spikes/input-performance-spike.html @@ -7,7 +7,6 @@ Input performance spike with flex layout - - -