Java tutorial
/* * Copyright 2015-2016 Red Hat, Inc, and individual contributors. * * 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 * * https://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. */ package org.jboss.hal.client.deployment; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import com.google.common.collect.Sets; import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.web.bindery.event.shared.EventBus; import elemental2.core.JsArray; import elemental2.dom.File; import elemental2.dom.File.ConstructorContentsArrayUnionType; import elemental2.dom.HTMLButtonElement; import elemental2.dom.HTMLElement; import elemental2.dom.HTMLImageElement; import org.jboss.gwt.elemento.core.Elements; import org.jboss.gwt.elemento.core.IsElement; import org.jboss.hal.ballroom.Attachable; import org.jboss.hal.ballroom.EmptyState; import org.jboss.hal.ballroom.Format; import org.jboss.hal.ballroom.LabelBuilder; import org.jboss.hal.ballroom.Search; import org.jboss.hal.ballroom.dialog.Dialog; import org.jboss.hal.ballroom.dialog.DialogFactory; import org.jboss.hal.ballroom.editor.AceEditor; import org.jboss.hal.ballroom.editor.Options; import org.jboss.hal.ballroom.form.FileItem; import org.jboss.hal.ballroom.form.Form; import org.jboss.hal.ballroom.form.TextBoxItem; import org.jboss.hal.ballroom.form.ValidationResult; import org.jboss.hal.ballroom.tree.Node; import org.jboss.hal.ballroom.tree.SelectionContext; import org.jboss.hal.ballroom.tree.Tree; import org.jboss.hal.config.Environment; import org.jboss.hal.core.deployment.Content; import org.jboss.hal.core.mbui.dialog.AddResourceDialog; import org.jboss.hal.core.mbui.form.ModelNodeForm; import org.jboss.hal.dmr.ModelNode; import org.jboss.hal.dmr.Operation; import org.jboss.hal.dmr.ResourceAddress; import org.jboss.hal.dmr.dispatch.Dispatcher; import org.jboss.hal.js.Browser; import org.jboss.hal.meta.AddressTemplate; import org.jboss.hal.meta.Metadata; import org.jboss.hal.meta.security.AuthorisationDecision; import org.jboss.hal.meta.security.Constraint; import org.jboss.hal.resources.CSS; import org.jboss.hal.resources.Icons; import org.jboss.hal.resources.Ids; import org.jboss.hal.resources.Names; import org.jboss.hal.resources.Resources; import org.jboss.hal.resources.Strings; import org.jboss.hal.resources.UIConstants; import org.jboss.hal.spi.Message; import org.jboss.hal.spi.MessageEvent; import rx.Completable; import rx.Single; import static com.google.common.base.Strings.nullToEmpty; import static elemental2.dom.DomGlobal.window; import static java.lang.Math.max; import static java.util.Collections.emptyList; import static org.jboss.gwt.elemento.core.Elements.*; import static org.jboss.gwt.elemento.core.EventType.click; import static org.jboss.hal.ballroom.LayoutBuilder.column; import static org.jboss.hal.ballroom.LayoutBuilder.row; import static org.jboss.hal.ballroom.Skeleton.MARGIN_BIG; import static org.jboss.hal.ballroom.Skeleton.MARGIN_SMALL; import static org.jboss.hal.ballroom.Skeleton.applicationHeight; import static org.jboss.hal.ballroom.Skeleton.applicationOffset; import static org.jboss.hal.client.deployment.ContentParser.NODE_ID; import static org.jboss.hal.dmr.ModelDescriptionConstants.*; import static org.jboss.hal.resources.CSS.*; /** UI element to browse and modify the content of an item from the content repository. */ // TODO Use metadata to show/hide buttons according to the security context @SuppressWarnings("OptionalUsedAsFieldOrParameterType") class BrowseContentElement implements IsElement, Attachable { @SuppressWarnings("HardCodedStringLiteral") private static final Set<String> EDITOR_FILE_TYPES = Sets.newHashSet("bash", "css", "htm", "html", "ini", "java", "js", "jsm", "jsx", "json", "jsf", "jsp", "jsx", "less", "md", "markdown", "MF", "php", "php", "php3", "php4", "php5", "phps", "phpt", "phtml", "properties", "rb", "ru", "sh", "sql", "txt", "ts", "typescript", "shtml", "xhtml", "xml"); @SuppressWarnings("HardCodedStringLiteral") private static final Set<String> IMAGE_FILE_TYPES = Sets.newHashSet("bmp", "gif", "ico", "img", "jpg", "jpeg", "png", "svg", "tiff", "webp"); private static final int MIN_HEIGHT = 70; private static final AddressTemplate CONTENT_TEMPLATE = AddressTemplate.of("/deployment=*"); private final Dispatcher dispatcher; private final EventBus eventBus; private final Resources resources; private final HTMLElement root; private final Search treeSearch; private Tree<ContentEntry> tree; private final EmptyState pleaseSelect; private final EmptyState deploymentPreview; private final EmptyState explodedPreview; private final EmptyState unsupportedFileType; private final AceEditor editor; private final HTMLButtonElement collapseButton; private final Optional<HTMLButtonElement> addContentButton; private final Optional<HTMLButtonElement> uploadContentButton; private final HTMLElement downloadContentLink; private final Optional<HTMLButtonElement> removeContentButton; private final HTMLElement treeContainer; private final HTMLElement editorControls; private final HTMLElement editorStatus; private final Optional<HTMLButtonElement> saveContentButton; private final HTMLElement previewContainer; private final HTMLElement previewHeader; private final HTMLElement previewImageContainer; private final HTMLImageElement previewImage; private Content content; private int surroundingHeight; // ------------------------------------------------------ ui setup @SuppressWarnings("ConstantConditions") BrowseContentElement(Dispatcher dispatcher, Environment environment, EventBus eventBus, Metadata metadata, Resources resources) { this.dispatcher = dispatcher; this.eventBus = eventBus; this.resources = resources; this.surroundingHeight = 0; treeSearch = new Search.Builder(Ids.CONTENT_TREE_SEARCH, query -> tree.search(query)) .onClear(() -> tree.clearSearch()).build(); treeSearch.element().classList.add(marginLeftSmall); Options editorOptions = new Options(); editorOptions.showGutter = true; editorOptions.showLineNumbers = true; editorOptions.showPrintMargin = false; editor = new AceEditor(Ids.CONTENT_EDITOR, editorOptions); Search contentSearch = new Search.Builder(Ids.CONTENT_SEARCH, query -> editor.getEditor().find(query)) .onPrevious(query -> editor.getEditor().findPrevious()) .onNext(query -> editor.getEditor().findNext()).build(); contentSearch.element().classList.add(marginRightSmall); pleaseSelect = new EmptyState.Builder(Ids.BROWSE_CONTENT_SELECT_EMPTY, resources.constants().nothingSelected()).icon(Icons.INFO) .description(resources.messages().noContentSelectedInDeployment()).build(); deploymentPreview = new EmptyState.Builder(Ids.BROWSE_CONTENT_DEPLOYMENT_EMPTY, Names.DEPLOYMENT) .icon(fontAwesome("archive")).description(resources.messages().deploymentPreview()).build(); explodedPreview = new EmptyState.Builder(Ids.BROWSE_CONTENT_EXPLODED_EMPTY, Names.DEPLOYMENT) .icon(fontAwesome("folder-open")).description(resources.messages().explodedPreview()).build(); unsupportedFileType = new EmptyState.Builder(Ids.BROWSE_CONTENT_UNSUPPORTED_EMPTY, resources.constants().unsupportedFileType()).icon(Icons.UNKNOWN) .description(resources.messages().unsupportedFileTypeDescription()) .primaryAction(resources.constants().download(), () -> window.location.assign(downloadUrl((tree.getSelected().data)))) .secondaryAction(resources.constants().viewInEditor(), () -> viewInEditor(tree.getSelected().data)) .build(); HTMLElement crudContainer; root = row().add(column(4).add(div().css(flexRow, marginTopLarge) .add(div().css(btnToolbar) .add(div().css(btnGroup).add(button().css(btn, btnDefault).on(click, event -> refresh()) .title(resources.constants().refresh()).add(i().css(fontAwesome(CSS.refresh)))) .add(collapseButton = button().css(btn, btnDefault).on(click, event -> { Node<ContentEntry> selection = tree.getSelected(); if (selection != null) { tree.selectNode(selection.id, true); } }).title(resources.constants().collapse()).add(i().css(fontAwesome("minus"))) .get())) .add(crudContainer = div().css(btnGroup) .add(downloadContentLink = a().css(btn, btnDefault) .title(resources.constants().download()).attr(UIConstants.TARGET, "_blank") //NON-NLS .attr(UIConstants.ROLE, UIConstants.BUTTON) .add(span().css(fontAwesome("download"))).get()) .get())) .add(treeSearch)).add(treeContainer = div().css(CSS.treeContainer).get())) .add(column(8).add(div().css(marginTopLarge, marginBottomLarge) .add(previewContainer = div() .add(previewHeader = h(1).textContent(resources.constants().preview()).get()) .add(previewImageContainer = div().style("overflow: scroll") //NON-NLS .add(previewImage = img().css(imgResponsive, imgThumbnail).get()).get()) .get()) .add(editorControls = div().css(CSS.editorControls, marginBottomSmall).add(contentSearch) .add(div().add(editorStatus = span() .textContent(resources.constants().nothingSelected()).get())) .get()) .add(editor).add(pleaseSelect).add(deploymentPreview).add(explodedPreview) .add(unsupportedFileType))) .get(); boolean supported = !(Browser.isEdge() || Browser.isIE()); AuthorisationDecision ad = AuthorisationDecision.from(environment, metadata.getSecurityContext()); if (supported && ad.isAllowed(Constraint.executable(CONTENT_TEMPLATE, ADD_CONTENT))) { addContentButton = Optional.of(button().css(btn, btnDefault).on(click, event -> addContent()) .title(resources.constants().newContent()).add(i().css(fontAwesome("file-o"))).get()); uploadContentButton = Optional.of(button().css(btn, btnDefault).on(click, event -> uploadContent()) .title(resources.constants().uploadContent()).title(resources.constants().addContent()) .add(i().css(fontAwesome("upload"))).get()); saveContentButton = Optional .of(button().css(btn, btnDefault, marginRightSmall).on(click, event -> saveContent()) .title(resources.constants().save()).add(span().css(fontAwesome("floppy-o"))).get()); crudContainer.insertBefore(addContentButton.get(), downloadContentLink); crudContainer.insertBefore(uploadContentButton.get(), downloadContentLink); editorControls.insertBefore(saveContentButton.get(), contentSearch.element()); } else { addContentButton = Optional.empty(); uploadContentButton = Optional.empty(); saveContentButton = Optional.empty(); } if (ad.isAllowed(Constraint.executable(CONTENT_TEMPLATE, REMOVE_CONTENT))) { removeContentButton = Optional.of(button().css(btn, btnDefault).on(click, event -> removeContent()) .title(resources.constants().removeContent()).add(i().css(pfIcon("remove"))).get()); crudContainer.appendChild(removeContentButton.get()); } else { removeContentButton = Optional.empty(); } saveContentButton.ifPresent(button -> button.disabled = true); setVisible(pleaseSelect.element(), true); setVisible(editorControls, false); setVisible(editor.element(), false); setVisible(deploymentPreview.element(), false); setVisible(explodedPreview.element(), false); setVisible(unsupportedFileType.element(), false); setVisible(previewContainer, false); } @Override public void attach() { editor.attach(); editor.getEditor().$blockScrolling = 1; adjustHeight(); adjustEditorHeight(); window.onresize = event -> { adjustEditorHeight(); return null; }; } @Override public void detach() { window.onresize = null; } @Override public HTMLElement element() { return root; } void setSurroundingHeight(int surroundingHeight) { this.surroundingHeight = surroundingHeight; adjustHeight(); adjustEditorHeight(); } private void adjustHeight() { int treeOffset = (int) (applicationOffset() + 2 * MARGIN_BIG + treeSearch.element().offsetHeight + MARGIN_SMALL + surroundingHeight); int previewHeaderHeight = (int) previewHeader.offsetHeight; int previewOffset = applicationOffset() + 2 * MARGIN_BIG + MARGIN_SMALL + previewHeaderHeight + surroundingHeight; treeContainer.style.height = vh(treeOffset); previewImageContainer.style.height = vh(previewOffset); } private void adjustEditorHeight() { int editorHeight = (int) (applicationHeight() - 2 * MARGIN_BIG - MARGIN_SMALL - editorControls.offsetHeight - surroundingHeight); if (Elements.isVisible(editor.element())) { editor.element().style.height = height(px(max(editorHeight, MIN_HEIGHT))); editor.getEditor().resize(); } } // ------------------------------------------------------ deployment methods private String downloadUrl(ContentEntry contentEntry) { ResourceAddress address = new ResourceAddress().add(DEPLOYMENT, content.getName()); Operation.Builder builder = new Operation.Builder(address, READ_CONTENT); if (contentEntry != null) { builder.param(PATH, contentEntry.path); } return dispatcher.downloadUrl(builder.build()); } private void refresh() { String selectedId = selectedId(); browseContent().andThen(awaitTreeReady()).subscribe(() -> { if (selectedId != null) { tree.selectNode(selectedId); } }); } // ------------------------------------------------------ CRUD content methods @SuppressWarnings("ConstantConditions") void setContent(Content content) { this.content = content; setVisible(addContentButton.orElse(null), content.isExploded()); setVisible(uploadContentButton.orElse(null), content.isExploded()); setVisible(removeContentButton.orElse(null), content.isExploded()); setVisible(saveContentButton.orElse(null), content.isExploded()); editor.getEditor().setReadOnly(!content.isExploded()); browseContent().subscribe(this::noSelection); } private void addContent() { TextBoxItem targetPathItem = new TextBoxItem(TARGET_PATH); targetPathItem.setRequired(true); Form<ModelNode> form = new ModelNodeForm.Builder<>(Ids.CONTENT_NEW, Metadata.empty()) .unboundFormItem(targetPathItem).addOnly().build(); AddResourceDialog dialog = new AddResourceDialog(resources.constants().newContent(), form, (name, model) -> { String path = targetPathItem.getValue(); ResourceAddress address = new ResourceAddress().add(DEPLOYMENT, content.getName()); ModelNode contentNode = new ModelNode(); contentNode.get(INPUT_STREAM_INDEX).set(0); contentNode.get(TARGET_PATH).set(path); Operation operation = new Operation.Builder(address, ADD_CONTENT) .param(CONTENT, new ModelNode().add(contentNode)).build(); dispatcher.upload(file(filename(path), ""), operation).toCompletable().andThen(browseContent()) .andThen(awaitTreeReady()).subscribe(() -> { MessageEvent.fire(eventBus, Message .success(resources.messages().newContentSuccess(content.getName(), path))); tree.selectNode(NODE_ID.apply(path)); }); }); targetPathItem.setValue(selectedPath()); dialog.show(); } private void uploadContent() { LabelBuilder labelBuilder = new LabelBuilder(); TextBoxItem targetPathItem = new TextBoxItem(TARGET_PATH); targetPathItem.setRequired(true); FileItem fileItem = new FileItem(FILE, labelBuilder.label(FILE)); fileItem.addValueChangeHandler( event -> targetPathItem.setValue(appendFilename(targetPathItem.getValue(), event.getValue().name))); TextBoxItem urlItem = new TextBoxItem(URL, labelBuilder.label(URL)); urlItem.addValueChangeHandler(event -> targetPathItem .setValue(appendFilename(targetPathItem.getValue(), filename(event.getValue())))); Form<ModelNode> form = new ModelNodeForm.Builder<>(Ids.CONTENT_NEW, Metadata.empty()) .unboundFormItem(fileItem).unboundFormItem(urlItem).unboundFormItem(targetPathItem).addOnly() .build(); form.addFormValidation(f -> { if (fileItem.isEmpty() && urlItem.isEmpty()) { return ValidationResult.invalid(resources.messages().uploadContentInvalid()); } return ValidationResult.OK; }); form.setSaveCallback((f, model) -> { String path = targetPathItem.getValue(); ResourceAddress address = new ResourceAddress().add(DEPLOYMENT, content.getName()); ModelNode contentNode = new ModelNode(); if (fileItem.isEmpty()) { contentNode.get(URL).set(urlItem.getValue()); } else { contentNode.get(INPUT_STREAM_INDEX).set(0); } contentNode.get(TARGET_PATH).set(path); Operation operation = new Operation.Builder(address, ADD_CONTENT) .param(CONTENT, new ModelNode().add(contentNode)).build(); Single<ModelNode> single = fileItem.isEmpty() ? dispatcher.execute(operation) : dispatcher.upload(fileItem.getValue(), operation); single.toCompletable().andThen(browseContent()).andThen(awaitTreeReady()).subscribe(() -> { MessageEvent.fire(eventBus, Message.success(resources.messages().newContentSuccess(content.getName(), path))); tree.selectNode(NODE_ID.apply(path)); }); }); Dialog dialog = new Dialog.Builder(resources.constants().uploadContent()) .add(p().innerHtml(resources.messages().uploadContentDescription()).get()).add(form.element()) .primary(resources.constants().upload(), form::save).size(Dialog.Size.MEDIUM).cancel().build(); dialog.registerAttachable(form); targetPathItem.setValue(selectedPath()); dialog.show(); form.edit(new ModelNode()); } private Completable browseContent() { ResourceAddress address = new ResourceAddress().add(DEPLOYMENT, content.getName()); Operation operation = new Operation.Builder(address, BROWSE_CONTENT).build(); return dispatcher.execute(operation).doOnSuccess(result -> { String contentName = SafeHtmlUtils.htmlEscapeAllowEntities(content.getName()); Node<ContentEntry> root = new Node.Builder<>(Ids.CONTENT_TREE_ROOT, contentName, new ContentEntry()) .root().folder().open().build(); JsArray<Node<ContentEntry>> nodes = new JsArray<>(); new ContentParser().parse(root, nodes, result.isDefined() ? result.asList() : emptyList()); if (tree != null) { tree.destroy(); tree = null; } tree = new Tree<>(Ids.CONTENT_TREE, nodes); Elements.removeChildrenFrom(treeContainer); treeContainer.appendChild(tree.element()); tree.attach(); tree.onSelectionChange((event, selectionContext) -> { if (!"ready".equals(selectionContext.action)) { //NON-NLS onNodeSelected(selectionContext); } }); }).toCompletable(); } private void loadContent(ContentEntry contentEntry, Consumer<String> successCallback) { if (!contentEntry.directory) { ResourceAddress address = new ResourceAddress().add(DEPLOYMENT, content.getName()); Operation operation = new Operation.Builder(address, READ_CONTENT).param(PATH, contentEntry.path) .build(); dispatcher.download(operation, successCallback); } } private void saveContent() { Node<ContentEntry> selection = tree.getSelected(); if (selection != null) { String filename = selection.data.path.contains("/") ? Strings.substringAfterLast(selection.data.path, "/") : selection.data.path; String editorContent = editor.getEditor().getSession().getValue(); ResourceAddress address = new ResourceAddress().add(DEPLOYMENT, content.getName()); ModelNode contentNode = new ModelNode(); contentNode.get(INPUT_STREAM_INDEX).set(0); contentNode.get(TARGET_PATH).set(selection.data.path); Operation operation = new Operation.Builder(address, ADD_CONTENT) .param(CONTENT, new ModelNode().add(contentNode)).build(); dispatcher.upload(file(filename, editorContent), operation) .doOnSuccess(result -> saveContentButton.ifPresent(button -> button.disabled = true)) .toCompletable().andThen(browseContent()).andThen(awaitTreeReady()).subscribe(() -> { MessageEvent.fire(eventBus, Message .success(resources.messages().saveContentSuccess(content.getName(), filename))); tree.selectNode(selection.id); }); } } private void removeContent() { Node<ContentEntry> selection = tree.getSelected(); if (selection != null) { String path = selection.data.path; DialogFactory.buildConfirmation(resources.constants().removeContent(), resources.messages().removeContentQuestion(content.getName(), path), null, Dialog.Size.MEDIUM, () -> { ResourceAddress address = new ResourceAddress().add(DEPLOYMENT, content.getName()); Operation operation = new Operation.Builder(address, REMOVE_CONTENT) .param(PATHS, new ModelNode().add(path)).build(); dispatcher.execute(operation).toCompletable().andThen(browseContent()) .andThen(awaitTreeReady()).subscribe(() -> { MessageEvent.fire(eventBus, Message.success( resources.messages().removeContentSuccess(content.getName(), path))); noSelection(); }); }).show(); } } // ------------------------------------------------------ UI state private void onNodeSelected(SelectionContext<ContentEntry> selection) { collapseButton.disabled = selection.selected.length == 0; if (selection.selected.length != 0) { if (selection.node.id.equals(Ids.CONTENT_TREE_ROOT)) { deploymentPreview(); } else { ContentEntry contentEntry = selection.node.data; if (contentEntry.directory) { directory(); } else { int index = contentEntry.name.lastIndexOf('.'); String extension = index != -1 && index < contentEntry.name.length() - 1 ? contentEntry.name.substring(index + 1) : ""; if (EDITOR_FILE_TYPES.contains(extension)) { viewInEditor(contentEntry); } else if (IMAGE_FILE_TYPES.contains(extension)) { viewInPreview(contentEntry); } else { unsupportedFileType(contentEntry); } } } } else { noSelection(); } } private void noSelection() { collapseButton.disabled = true; downloadContentLink.classList.add(disabled); removeContentButton.ifPresent(button -> button.disabled = true); setVisible(pleaseSelect.element(), true); setVisible(editorControls, false); setVisible(editor.element(), false); setVisible(deploymentPreview.element(), false); setVisible(explodedPreview.element(), false); setVisible(unsupportedFileType.element(), false); setVisible(previewContainer, false); } private void deploymentPreview() { if (content.isExploded()) { downloadContentLink.removeAttribute(UIConstants.HREF); downloadContentLink.removeAttribute(UIConstants.DOWNLOAD); downloadContentLink.classList.add(disabled); } else { downloadContentLink.setAttribute(UIConstants.HREF, downloadUrl(null)); downloadContentLink.setAttribute(UIConstants.DOWNLOAD, content.getName()); downloadContentLink.classList.remove(disabled); } removeContentButton.ifPresent(button -> button.disabled = true); setVisible(pleaseSelect.element(), false); setVisible(editorControls, false); setVisible(editor.element(), false); setVisible(deploymentPreview.element(), !content.isExploded()); setVisible(explodedPreview.element(), content.isExploded()); setVisible(unsupportedFileType.element(), false); setVisible(previewContainer, false); deploymentPreview.setHeader(content.getName()); deploymentPreview.setPrimaryAction(resources.constants().download(), () -> window.location.assign(downloadUrl(null))); } private void directory() { downloadContentLink.removeAttribute(UIConstants.HREF); downloadContentLink.removeAttribute(UIConstants.DOWNLOAD); downloadContentLink.classList.add(disabled); removeContentButton.ifPresent(button -> button.disabled = true); setVisible(pleaseSelect.element(), false); setVisible(editorControls, false); setVisible(editor.element(), false); setVisible(deploymentPreview.element(), false); setVisible(explodedPreview.element(), false); setVisible(unsupportedFileType.element(), false); setVisible(previewContainer, false); } private void viewInEditor(ContentEntry contentEntry) { downloadContentLink.setAttribute(UIConstants.HREF, downloadUrl(contentEntry)); downloadContentLink.setAttribute(UIConstants.DOWNLOAD, contentEntry.name); downloadContentLink.classList.remove(disabled); removeContentButton.ifPresent(button -> button.disabled = false); setVisible(pleaseSelect.element(), false); setVisible(editorControls, true); setVisible(editor.element(), true); setVisible(deploymentPreview.element(), false); setVisible(explodedPreview.element(), false); setVisible(unsupportedFileType.element(), false); setVisible(previewContainer, false); adjustEditorHeight(); editorStatus.textContent = contentEntry.name + " - " + Format.humanReadableFileSize(contentEntry.fileSize); loadContent(contentEntry, result -> { editor.setModeFromPath(contentEntry.name); editor.getEditor().getSession().setValue(result); editor.getEditor().getSession().on("change", //NON-NLS delta -> saveContentButton.ifPresent(button -> button.disabled = false)); saveContentButton.ifPresent(button -> button.disabled = true); }); } private void viewInPreview(ContentEntry contentEntry) { downloadContentLink.setAttribute(UIConstants.HREF, downloadUrl(contentEntry)); downloadContentLink.setAttribute(UIConstants.DOWNLOAD, contentEntry.name); downloadContentLink.classList.remove(disabled); removeContentButton.ifPresent(button -> button.disabled = false); previewImage.src = downloadUrl(contentEntry); setVisible(pleaseSelect.element(), false); setVisible(editorControls, false); setVisible(editor.element(), false); setVisible(deploymentPreview.element(), false); setVisible(explodedPreview.element(), false); setVisible(unsupportedFileType.element(), false); setVisible(previewContainer, true); } private void unsupportedFileType(ContentEntry contentEntry) { downloadContentLink.setAttribute(UIConstants.HREF, downloadUrl(contentEntry)); downloadContentLink.setAttribute(UIConstants.DOWNLOAD, contentEntry.name); downloadContentLink.classList.remove(disabled); removeContentButton.ifPresent(button -> button.disabled = false); setVisible(pleaseSelect.element(), false); setVisible(editorControls, false); setVisible(editor.element(), false); setVisible(deploymentPreview.element(), false); setVisible(explodedPreview.element(), false); setVisible(unsupportedFileType.element(), true); setVisible(previewContainer, false); } // ------------------------------------------------------ helper methods private String selectedId() { if (tree != null) { Node<ContentEntry> selection = tree.getSelected(); if (selection != null) { return selection.id; } } return null; } private String selectedPath() { String path = null; Node<ContentEntry> selection = tree.getSelected(); if (selection != null && !selection.id.equals(Ids.CONTENT_TREE_ROOT)) { path = Strings.strip(selection.data.path, "/"); if (!selection.data.directory) { path = Strings.getParent(path); } if (path != null) { path += "/"; } } return nullToEmpty(path); } private String appendFilename(String path, String file) { if (!com.google.common.base.Strings.isNullOrEmpty(path)) { if (path.endsWith("/")) { return path + file; } else { return Strings.getParent(path) + "/" + file; } } return file; } private String filename(String path) { if (path != null) { return path.contains("/") ? Strings.substringAfterLast(path, "/") : path; } return null; } private File file(String name, String content) { ConstructorContentsArrayUnionType contents = ConstructorContentsArrayUnionType.of(content); return new File(new ConstructorContentsArrayUnionType[] { contents }, name); } private Completable awaitTreeReady() { return Completable.fromEmitter(emitter -> tree.onReady((event, any) -> emitter.onCompleted())); } }