From 2b1a9cec2fb7577937949fc74982aa8d81f69f66 Mon Sep 17 00:00:00 2001 From: Diego Barreiro Date: Sun, 4 Jun 2023 18:25:14 +0200 Subject: [PATCH] feat(ui): Allow filtering components in source structure Introduces a dropdown which allows to select if the source structure box should display all components, only the visible ones or the non visible ones. Change-Id: I1121c78c82a9eddab1abc696d5af865460fcad50 --- .../appinventor/client/OdeMessages.java | 12 ++ .../client/boxes/ISourceStructureBox.java | 24 ++++ .../client/boxes/SourceStructureBox.java | 42 ++++--- .../boxes/SourceStructureBoxClassic.java | 31 +++++ .../client/boxes/SourceStructureBoxNew.java | 114 ++++++++++++++++++ .../simple/components/MockContainer.java | 16 ++- .../editor/simple/components/MockForm.java | 12 +- .../explorer/SourceStructureExplorer.java | 14 ++- .../appinventor/client/widgets/boxes/Box.java | 8 ++ appinventor/appengine/war/static/css/Ya.css | 6 + 10 files changed, 257 insertions(+), 22 deletions(-) create mode 100644 appinventor/appengine/src/com/google/appinventor/client/boxes/ISourceStructureBox.java create mode 100644 appinventor/appengine/src/com/google/appinventor/client/boxes/SourceStructureBoxClassic.java create mode 100644 appinventor/appengine/src/com/google/appinventor/client/boxes/SourceStructureBoxNew.java diff --git a/appinventor/appengine/src/com/google/appinventor/client/OdeMessages.java b/appinventor/appengine/src/com/google/appinventor/client/OdeMessages.java index e83d41dc214..812d94de0ae 100755 --- a/appinventor/appengine/src/com/google/appinventor/client/OdeMessages.java +++ b/appinventor/appengine/src/com/google/appinventor/client/OdeMessages.java @@ -908,6 +908,18 @@ public interface OdeMessages extends Messages, AutogeneratedOdeMessages { @Description("Caption for source structure box.") String sourceStructureBoxCaption(); + @DefaultMessage("All Components") + @Description("Caption for source structure box.") + String sourceStructureBoxCaptionAll(); + + @DefaultMessage("Visible Components") + @Description("Caption for source structure box.") + String sourceStructureBoxCaptionVisible(); + + @DefaultMessage("Non-visible Components") + @Description("Caption for source structure box.") + String sourceStructureBoxCaptionNonVisible(); + // Used in BlocksToolkit (SubsetJSONPropertyEditor) @DefaultMessage("Blocks Toolkit") diff --git a/appinventor/appengine/src/com/google/appinventor/client/boxes/ISourceStructureBox.java b/appinventor/appengine/src/com/google/appinventor/client/boxes/ISourceStructureBox.java new file mode 100644 index 00000000000..66bc739efac --- /dev/null +++ b/appinventor/appengine/src/com/google/appinventor/client/boxes/ISourceStructureBox.java @@ -0,0 +1,24 @@ +// -*- mode: java; c-basic-offset: 2; -*- +// Copyright 2009-2011 Google, All Rights reserved +// Copyright 2011-2012 MIT, All rights reserved +// Released under the Apache License, Version 2.0 +// http://www.apache.org/licenses/LICENSE-2.0 + +package com.google.appinventor.client.boxes; + +import com.google.appinventor.client.editor.simple.components.MockForm; +import com.google.appinventor.client.editor.youngandroid.YaFormEditor; +import com.google.appinventor.client.explorer.SourceStructureExplorer; + +public interface ISourceStructureBox { + /** + * Method to retrieve the rendered source structure explorer from the "child" boxes. + * @return SourceStructureExplorer + */ + SourceStructureExplorer getSourceStructureExplorer(); + + /** + * Method render the "child" boxes. + */ + void show(MockForm form); +} diff --git a/appinventor/appengine/src/com/google/appinventor/client/boxes/SourceStructureBox.java b/appinventor/appengine/src/com/google/appinventor/client/boxes/SourceStructureBox.java index 4edf2c48df1..0f92c2f14d9 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/boxes/SourceStructureBox.java +++ b/appinventor/appengine/src/com/google/appinventor/client/boxes/SourceStructureBox.java @@ -1,6 +1,6 @@ // -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved -// Copyright 2011-2012 MIT, All rights reserved +// Copyright 2011-2023 MIT, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 @@ -9,20 +9,19 @@ import static com.google.appinventor.client.Ode.MESSAGES; import com.google.appinventor.client.editor.simple.components.MockForm; -import com.google.appinventor.client.editor.youngandroid.YaFormEditor; import com.google.appinventor.client.explorer.SourceStructureExplorer; import com.google.appinventor.client.widgets.boxes.Box; +import com.google.appinventor.common.version.AppInventorFeatures; +import com.google.gwt.user.client.ui.DockPanel; /** * Box implementation for source structure explorer. - * */ -public final class SourceStructureBox extends Box { +public class SourceStructureBox extends Box implements ISourceStructureBox { // Singleton source structure explorer box instance private static final SourceStructureBox INSTANCE = new SourceStructureBox(); - - // Source structure explorer - private final SourceStructureExplorer sourceStructureExplorer; + // Singleton source structure explorer child instance + private static ISourceStructureBox SUBINSTANCE; /** * Return the singleton source structure explorer box. @@ -42,26 +41,39 @@ private SourceStructureBox() { false, // minimizable false); // removable - sourceStructureExplorer = new SourceStructureExplorer(); + // Creates the child instance according to the enabled features. + SUBINSTANCE = AppInventorFeatures.enableFutureFeatures() + ? new SourceStructureBoxNew(this) + : new SourceStructureBoxClassic(); - setContent(sourceStructureExplorer); + setContent(SUBINSTANCE.getSourceStructureExplorer()); } /** * Returns source structure explorer associated with box. * - * @return source structure explorer + * @return source structure explorer */ public SourceStructureExplorer getSourceStructureExplorer() { - return sourceStructureExplorer; + return SUBINSTANCE.getSourceStructureExplorer(); } + /** + * Calls the child box and renders it according to its behaviour. + * @param form current form + */ public void show(MockForm form) { - sourceStructureExplorer.updateTree(form.buildComponentsTree(), + getSourceStructureExplorer().updateTree(form.buildComponentsTree(), form.getLastSelectedComponent().getSourceStructureExplorerItem()); - sourceStructureExplorer.setVisible(true); - this.setVisible(true); - setContent(sourceStructureExplorer); + getSourceStructureBox().setVisible(true); + setContent(SUBINSTANCE.getSourceStructureExplorer()); } + /** + * Returns the header container for the source structure box (used by childs). + * @return DockPanel header container + */ + public DockPanel getHeaderContainer() { + return super.getHeaderContainer(); + } } diff --git a/appinventor/appengine/src/com/google/appinventor/client/boxes/SourceStructureBoxClassic.java b/appinventor/appengine/src/com/google/appinventor/client/boxes/SourceStructureBoxClassic.java new file mode 100644 index 00000000000..9974fc99cd6 --- /dev/null +++ b/appinventor/appengine/src/com/google/appinventor/client/boxes/SourceStructureBoxClassic.java @@ -0,0 +1,31 @@ +// -*- mode: java; c-basic-offset: 2; -*- +// Copyright 2009-2011 Google, All Rights reserved +// Copyright 2011-2023 MIT, All rights reserved +// Released under the Apache License, Version 2.0 +// http://www.apache.org/licenses/LICENSE-2.0 + +package com.google.appinventor.client.boxes; + +import com.google.appinventor.client.editor.simple.components.MockForm; +import com.google.appinventor.client.explorer.SourceStructureExplorer; + +/** + * Box implementation for source structure explorer (classic style). + */ +public final class SourceStructureBoxClassic implements ISourceStructureBox { + private final SourceStructureExplorer sourceStructureExplorer = new SourceStructureExplorer(); + + public SourceStructureBoxClassic() { + super(); + } + + public void show(MockForm form) { + sourceStructureExplorer.updateTree(form.buildComponentsTree(), + form.getLastSelectedComponent().getSourceStructureExplorerItem()); + sourceStructureExplorer.setVisible(true); + } + + public SourceStructureExplorer getSourceStructureExplorer() { + return sourceStructureExplorer; + } +} diff --git a/appinventor/appengine/src/com/google/appinventor/client/boxes/SourceStructureBoxNew.java b/appinventor/appengine/src/com/google/appinventor/client/boxes/SourceStructureBoxNew.java new file mode 100644 index 00000000000..463b73963e3 --- /dev/null +++ b/appinventor/appengine/src/com/google/appinventor/client/boxes/SourceStructureBoxNew.java @@ -0,0 +1,114 @@ +// -*- mode: java; c-basic-offset: 2; -*- +// Copyright 2009-2011 Google, All Rights reserved +// Copyright 2011-2023 MIT, All rights reserved +// Released under the Apache License, Version 2.0 +// http://www.apache.org/licenses/LICENSE-2.0 + +package com.google.appinventor.client.boxes; + +import com.google.appinventor.client.Ode; +import com.google.appinventor.client.editor.simple.components.MockForm; +import com.google.appinventor.client.editor.youngandroid.YaFormEditor; +import com.google.appinventor.client.explorer.SourceStructureExplorer; +import com.google.appinventor.client.widgets.DropDownButton; +import com.google.appinventor.client.widgets.DropDownItem; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.ui.DockPanel; + +import java.util.ArrayList; +import java.util.List; + +import static com.google.appinventor.client.Ode.MESSAGES; + +/** + * Box implementation for source structure explorer (new style, with filters). + */ +public final class SourceStructureBoxNew implements ISourceStructureBox { + private Integer view = 1; + + private final DropDownButton dropDownButton; + private final SourceStructureExplorer sourceStructureExplorer = new SourceStructureExplorer(false); + + /** + * Creates new source structure explorer box. + */ + public SourceStructureBoxNew(SourceStructureBox container) { + super(); + + List items = new ArrayList<>(); + items.add(new DropDownItem("AllComponents", MESSAGES.sourceStructureBoxCaptionAll(), new SelectSourceView(1))); + items.add(new DropDownItem("VisibleComponents", MESSAGES.sourceStructureBoxCaptionVisible(), new SelectSourceView(2))); + items.add(new DropDownItem("NonVisibleComponents", MESSAGES.sourceStructureBoxCaptionNonVisible(), new SelectSourceView(3))); + + dropDownButton = new DropDownButton("ComponentsTreeFilter", "", items, false); + dropDownButton.addStyleName("components-tree-filter"); + dropDownButton.setCaption(MESSAGES.sourceStructureBoxCaptionAll()); + + container.getHeaderContainer().clear(); + container.getHeaderContainer().add(dropDownButton, DockPanel.LINE_START); + } + + public void show(MockForm form) { + sourceStructureExplorer.updateTree(form.buildComponentsTree(view), + form.getLastSelectedComponent().getSourceStructureExplorerItem()); + updateSourceDropdownButtonCaption(); + + sourceStructureExplorer.setVisible(true); + } + + public SourceStructureExplorer getSourceStructureExplorer() { + return sourceStructureExplorer; + } + + public void setView(Integer view) { + this.view = view; + } + + public Integer getView() { + return view; + } + + private void updateSourceDropdownButtonCaption() { + String c; + switch (view) { + case 1: + c = MESSAGES.sourceStructureBoxCaptionAll(); + break; + case 2: + c = MESSAGES.sourceStructureBoxCaptionVisible(); + break; + case 3: + c = MESSAGES.sourceStructureBoxCaptionNonVisible(); + break; + default: + c = MESSAGES.sourceStructureBoxCaption(); + break; + } + + dropDownButton.setCaption(c); + } + + private final class SelectSourceView implements Command { + /* 1 - > All components + * 2 - > Visible components + * 3 - > Non-visible components + */ + private final Integer view; + + SelectSourceView(Integer view) { + super(); + this.view = view; + } + + @Override + public void execute() { + MockForm form = ((YaFormEditor) Ode.getInstance().getCurrentFileEditor()).getForm(); + sourceStructureExplorer.updateTree(form.buildComponentsTree(view), + form.getForm().getLastSelectedComponent().getSourceStructureExplorerItem()); + SourceStructureBoxNew.this.setView(view); + + updateSourceDropdownButtonCaption(); + } + } + +} diff --git a/appinventor/appengine/src/com/google/appinventor/client/editor/simple/components/MockContainer.java b/appinventor/appengine/src/com/google/appinventor/client/editor/simple/components/MockContainer.java index 25b3a2fc9df..f60292f2792 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/editor/simple/components/MockContainer.java +++ b/appinventor/appengine/src/com/google/appinventor/client/editor/simple/components/MockContainer.java @@ -87,13 +87,23 @@ public final MockLayout getLayout() { return layout; } - @Override - protected TreeItem buildTree() { + protected TreeItem buildTree(Integer view) { TreeItem itemNode = super.buildTree(); + //hide all containers except form if only nonvisible components are to be shown + //in such a case, we need only the form's treeItem because all non-visible components are attached to it + if (view == 3 && !isForm()) { + itemNode.setVisible(false); + } // Recursively build the tree for child components for (MockComponent child : children) { - itemNode.addItem(child.buildTree()); + TreeItem childNode = child.buildTree(); + if (view == 2 && child instanceof MockNonVisibleComponent) { + childNode.setVisible(false); + } else if (view == 3 && child instanceof MockVisibleComponent) { + childNode.setVisible(false); + } + itemNode.addItem(childNode); } itemNode.setState(expanded); diff --git a/appinventor/appengine/src/com/google/appinventor/client/editor/simple/components/MockForm.java b/appinventor/appengine/src/com/google/appinventor/client/editor/simple/components/MockForm.java index 8853b4eeff3..aa847ca2622 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/editor/simple/components/MockForm.java +++ b/appinventor/appengine/src/com/google/appinventor/client/editor/simple/components/MockForm.java @@ -1341,7 +1341,17 @@ public final void setPasteTarget(MockContainer target) { * @return tree showing the component hierarchy of the form */ public TreeItem buildComponentsTree() { - return buildTree(); + return buildComponentsTree(1); + } + + /** + * Builds a tree of the component hierarchy of the form for display in the + * {@code SourceStructureExplorer}. + * + * @return tree showing the component hierarchy of the form + */ + public TreeItem buildComponentsTree(Integer view) { + return buildTree(view); } // PropertyChangeListener implementation diff --git a/appinventor/appengine/src/com/google/appinventor/client/explorer/SourceStructureExplorer.java b/appinventor/appengine/src/com/google/appinventor/client/explorer/SourceStructureExplorer.java index 586fbdcc7fd..f30c0e721d1 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/explorer/SourceStructureExplorer.java +++ b/appinventor/appengine/src/com/google/appinventor/client/explorer/SourceStructureExplorer.java @@ -68,10 +68,14 @@ public void onBrowserEvent(Event event) { } } + public SourceStructureExplorer() { + this(true); + } + /** * Creates a new source structure explorer. */ - public SourceStructureExplorer() { + public SourceStructureExplorer(boolean includeButtonPanel) { // Initialize UI elements tree = new EventCaptureTree(Ode.getImageBundle()); tree.setAnimationEnabled(true); @@ -174,8 +178,12 @@ public void onClick(ClickEvent event) { VerticalPanel panel = new VerticalPanel(); panel.add(scrollPanel); - panel.add(new Label()); - panel.add(buttonPanel); + // TODO(diego@kodular.io): With App Inventor's current layout, as of now this is the only place to + // render these buttons... + // if (includeButtonPanel) { + panel.add(new Label()); + panel.add(buttonPanel); + // } panel.setCellHorizontalAlignment(buttonPanel, HorizontalPanel.ALIGN_CENTER); initWidget(panel); } diff --git a/appinventor/appengine/src/com/google/appinventor/client/widgets/boxes/Box.java b/appinventor/appengine/src/com/google/appinventor/client/widgets/boxes/Box.java index b8cfc80caf3..7eea0f5451f 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/widgets/boxes/Box.java +++ b/appinventor/appengine/src/com/google/appinventor/client/widgets/boxes/Box.java @@ -441,6 +441,14 @@ Widget getHeader() { return header; } + /** + * Returns the box header container. + * @return header container + */ + public DockPanel getHeaderContainer() { + return headerContainer; + } + /** * Invoked upon resizing of the box by the layout. Box height will remain * unmodified. diff --git a/appinventor/appengine/war/static/css/Ya.css b/appinventor/appengine/war/static/css/Ya.css index 1bee3f1f8c2..ee55a51c798 100755 --- a/appinventor/appengine/war/static/css/Ya.css +++ b/appinventor/appengine/war/static/css/Ya.css @@ -3072,3 +3072,9 @@ div.dropdiv p { .listItem:nth-child(2n + 0) { background: rgba(200, 200, 200, 0.1); } + +.components-tree-filter { + margin-top: 1px; + margin-left: 1px; + font-size: 11px; +}