Skip to content

Content of file DefaultContentEditorPresentationControl.java

/*
 * #%L
 * *********************************************************************************************************************
 *
 * NorthernWind - lightweight CMS
 * http://northernwind.tidalwave.it - git clone git@bitbucket.org:tidalwave/northernwind-rca-src.git
 * %%
 * Copyright (C) 2013 - 2021 Tidalwave s.a.s. (http://tidalwave.it)
 * %%
 * *********************************************************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations under the License.
 *
 * *********************************************************************************************************************
 *
 *
 * *********************************************************************************************************************
 * #L%
 */
package it.tidalwave.northernwind.rca.ui.contenteditor.spi;

import javax.annotation.Nonnull;
import java.util.Optional;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import it.tidalwave.role.ui.BoundProperty;
import it.tidalwave.role.ui.UserAction;
import it.tidalwave.messagebus.annotation.ListensTo;
import it.tidalwave.messagebus.annotation.SimpleMessageSubscriber;
import it.tidalwave.northernwind.core.model.Content;
import it.tidalwave.northernwind.core.model.ResourceProperties;
import it.tidalwave.northernwind.rca.embeddedserver.EmbeddedServer;
import it.tidalwave.northernwind.rca.embeddedserver.EmbeddedServer.Document;
import it.tidalwave.northernwind.rca.ui.event.ContentSelectedEvent;
import it.tidalwave.northernwind.rca.ui.contenteditor.ContentEditorPresentation;
import it.tidalwave.northernwind.rca.ui.contenteditor.ContentEditorPresentation.Bindings;
import it.tidalwave.northernwind.rca.ui.contenteditor.ContentEditorPresentationControl;
import it.tidalwave.northernwind.rca.ui.contenteditor.impl.JSoupXhtmlNormalizer;
import it.tidalwave.northernwind.rca.ui.contenteditor.impl.ProcessExecutor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import static it.tidalwave.role.ui.Presentable.*;
import static it.tidalwave.northernwind.model.admin.Properties.*;
import static it.tidalwave.northernwind.model.admin.role.Saveable.Saveable;
import static it.tidalwave.northernwind.rca.ui.contenteditor.spi.PropertyBinder.*;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.joining;

/***********************************************************************************************************************
 *
 * A default implementation of the {@link ContentEditorPresentationControl}.
 *
 * @stereotype Control
 *
 * @author  Fabrizio Giudici
 *
 **********************************************************************************************************************/
@RequiredArgsConstructor @SimpleMessageSubscriber @Slf4j
public class DefaultContentEditorPresentationControl implements ContentEditorPresentationControl
  {
    @Nonnull
    private final EmbeddedServer documentServer;

    @Nonnull
    private final ContentEditorPresentation presentation;

    @Nonnull
    private Optional<Content> content = Optional.empty();

    @Nonnull
    private Optional<ResourceProperties> properties = Optional.empty();

    /* visible for testing */ final Bindings bindings = Bindings.builder()
            .openExternalEditorAction(UserAction.of(this::openExternalEditor))
            .openExternalEditorBrowserAction(UserAction.of(this::openExternalEditorBrowser))
            .title(new BoundProperty<>(""))
            .build();

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    /* visible for testing */ final UpdateCallback propertyUpdateCallback = (updatedProperties) ->
      {
        updatedProperties.as(Saveable).saveIn(content.get().getFile());
        unbindProperties();
        properties = content.map(Content::getProperties); // reload them
        // FIXME: properties have to be re-bound, since they have been reloaded - but this makes the HTML editor
        // to flicker and the caret in text editor to reset at position 0 - and to loop forever
        properties.ifPresent(this::bindProperties);
      };

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    public void initialize()
      {
        presentation.bind(bindings);
      }

    // FIXME: unbind at termination

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    /* visible for testing */ void onContentSelected (final @ListensTo @Nonnull ContentSelectedEvent selectionEvent)
      {
        log.debug("onContentSelected({})", selectionEvent);

        unbindProperties();
        content = selectionEvent.getContent();
        properties = content.map(Content::getProperties); // reload them

        if (!content.isPresent())
          {
            presentation.clear();
          }
        else
          {
            presentation.showUp();
          }

        properties.ifPresent(this::bindPropertiesAndLoadDocument);
      }

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    private void bindPropertiesAndLoadDocument (final @Nonnull ResourceProperties properties)
      {
        bindProperties(properties);
        final PropertyBinder propertyBinder = properties.as(PropertyBinder);
        final Document document = propertyBinder.createBoundDocument(PROPERTY_FULL_TEXT, propertyUpdateCallback);
        presentation.populateDocument(documentServer.putDocument("/", document));
      }

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    private void bindProperties (final @Nonnull ResourceProperties properties)
      {
        assert properties != null;
        presentation.bind(bindings); // FIXME: needed because of unbindAll()
needed because of unbindAll()
final PropertyBinder propertyBinder = properties.as(PropertyBinder); propertyBinder.bind(PROPERTY_TITLE, bindings.title, propertyUpdateCallback); presentation.populateProperties(properties.as(Presentable).createPresentationModel()); } /******************************************************************************************************************* * * * ******************************************************************************************************************/ private void unbindProperties() { bindings.title.unbindAll(); } /******************************************************************************************************************* * * * ******************************************************************************************************************/ private void openExternalEditorBrowser() { log.info("openExternalEditorBrowser"); // FIXME final String url = "http://localhost:12345/"; // Safari breaks Aloha editor ProcessExecutor.forExecutable("Google Chrome.app") .withArguments2(url) .withPostMortemTask(() -> properties.ifPresent(p -> bindPropertiesAndLoadDocument(p))) .execute(); } /******************************************************************************************************************* * * * ******************************************************************************************************************/ private void openExternalEditor() { log.info("openExternalEditor"); final String filePath = content.get().getFile().toFile().getAbsolutePath() + "/fullText_en.xhtml"; ProcessExecutor.forExecutable("BlueGriffon.app") // FIXME // .withArguments2(content.get().getFile().getPath().asString()) .withArguments2(filePath) .withPostMortemTask(() -> properties.ifPresent(p -> fix(p, filePath))) .execute(); } /******************************************************************************************************************* * * * ******************************************************************************************************************/ private void fix (final @Nonnull ResourceProperties properties, final @Nonnull String filePath) { try { final Path path = Paths.get(filePath); final String s = Files.lines(path, UTF_8).collect(joining("\n")) .replace("%27", "'") .replace("%28", "(") .replace("%29", ")") .replace("%7B", "{") .replace("%7D", "}") .replace("%20", " "); final JSoupXhtmlNormalizer normalizer = new JSoupXhtmlNormalizer(); Files.write(path, asList(normalizer.asNormalizedString(s)), UTF_8); bindPropertiesAndLoadDocument(properties); } catch (IOException e) { log.error("", e); } } }