Unverified Commit 6f9b348a authored by Justus's avatar Justus Committed by Jeffrey I. Schiller

Support upgrading of extension components

You can now load a new version of an extension, provided it has the same
package name, over a previous version. Any incompatible block changes
between the old and new version will result in the blocks being marked
“bad”. The MIT App Inventor programmer then has to do some cleanup, but
this is way better then having to delete the old component, which
removes all associated blocks.

Change-Id: I05ad39e16dcb8ea298091fa01338d5cf6c16c23e
parent a932c83d
......@@ -544,9 +544,9 @@ public interface OdeMessages extends Messages {
@Description("Error message reported when the component import failed due to unknown url")
String componentImportUnknownURLError();
@DefaultMessage("This Extension is already imported! Use ")
@Description("Error message reported when the component import due to already imported extension")
String componentAlreadyImportedError();
@DefaultMessage("Extension Upgraded : ")
@Description("Alert message reported when the component import upgraded an already imported extension")
String componentUpgradedAlert();
@DefaultMessage("The selected file is not a component file!\n" +
"Component files are aix files.")
......
......@@ -10,6 +10,7 @@ import static com.google.appinventor.client.Ode.MESSAGES;
import com.google.appinventor.client.editor.simple.components.MockComponent;
import com.google.appinventor.client.editor.simple.components.MockForm;
import com.google.appinventor.client.editor.simple.palette.SimplePaletteItem;
import com.google.appinventor.client.explorer.project.ComponentDatabaseChangeListener;
import com.google.appinventor.client.widgets.dnd.DragSource;
import com.google.appinventor.client.widgets.dnd.DropTarget;
import com.google.gwt.user.client.ui.Composite;
......@@ -18,11 +19,14 @@ import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import java.util.List;
import java.util.Map;
/**
* Panel in the Simple design editor holding non-visible Simple components.
*
*/
public final class SimpleNonVisibleComponentsPanel extends Composite implements DropTarget {
public final class SimpleNonVisibleComponentsPanel extends Composite implements DropTarget, ComponentDatabaseChangeListener {
// UI elements
private final Label heading;
......@@ -124,4 +128,24 @@ public final class SimpleNonVisibleComponentsPanel extends Composite implements
addComponent(sourceComponent);
sourceComponent.select();
}
@Override
public void onComponentTypeAdded(List<String> componentTypes) {
}
@Override
public boolean beforeComponentTypeRemoved(List<String> componentTypes) {
return true;
}
@Override
public void onComponentTypeRemoved(Map<String, String> componentTypes) {
}
@Override
public void onResetDatabase() {
}
}
......@@ -10,6 +10,7 @@ import static com.google.appinventor.client.Ode.MESSAGES;
import com.google.appinventor.client.editor.ProjectEditor;
import com.google.appinventor.client.editor.simple.components.MockForm;
import com.google.appinventor.client.editor.simple.palette.SimplePaletteItem;
import com.google.appinventor.client.explorer.project.ComponentDatabaseChangeListener;
import com.google.appinventor.client.widgets.dnd.DragSource;
import com.google.appinventor.client.widgets.dnd.DropTarget;
import com.google.appinventor.shared.settings.SettingsConstants;
......@@ -20,11 +21,14 @@ import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import java.util.List;
import java.util.Map;
/**
* Panel in the Simple design editor holding visible Simple components.
*
*/
public final class SimpleVisibleComponentsPanel extends Composite implements DropTarget {
public final class SimpleVisibleComponentsPanel extends Composite implements DropTarget, ComponentDatabaseChangeListener {
// UI elements
private final VerticalPanel phoneScreen;
private final CheckBox checkboxShowHiddenComponents;
......@@ -173,4 +177,24 @@ public final class SimpleVisibleComponentsPanel extends Composite implements Dro
public void onDrop(DragSource source, int x, int y, int offsetX, int offsetY) {
nonVisibleComponentsPanel.onDrop(source, -1, -1, offsetX, offsetY);
}
@Override
public void onComponentTypeAdded(List<String> componentTypes) {
}
@Override
public boolean beforeComponentTypeRemoved(List<String> componentTypes) {
return true;
}
@Override
public void onComponentTypeRemoved(Map<String, String> componentTypes) {
}
@Override
public void onResetDatabase() {
}
}
......@@ -14,7 +14,9 @@ import com.google.appinventor.client.Images;
import com.google.appinventor.client.Ode;
import com.google.appinventor.client.TranslationDesignerPallete;
import com.google.appinventor.client.editor.simple.SimpleEditor;
import com.google.appinventor.client.editor.simple.components.utils.PropertiesUtil;
import com.google.appinventor.client.editor.youngandroid.YaBlocksEditor;
import com.google.appinventor.client.editor.youngandroid.YaFormEditor;
import com.google.appinventor.client.explorer.SourceStructureExplorerItem;
import com.google.appinventor.client.explorer.project.Project;
import com.google.appinventor.client.output.OdeLog;
......@@ -34,6 +36,7 @@ import com.google.appinventor.shared.rpc.project.ProjectNode;
import com.google.appinventor.shared.rpc.project.youngandroid.YoungAndroidAssetsFolder;
import com.google.appinventor.shared.rpc.project.youngandroid.YoungAndroidProjectNode;
import com.google.appinventor.shared.settings.SettingsConstants;
import com.google.appinventor.shared.simple.ComponentDatabaseInterface;
import com.google.appinventor.shared.storage.StorageUtil;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
......@@ -59,6 +62,8 @@ import com.google.gwt.user.client.ui.TreeItem;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.impl.ClippedImagePrototype;
import com.google.appinventor.shared.simple.ComponentDatabaseInterface.ComponentDefinition;
import com.google.appinventor.shared.simple.ComponentDatabaseInterface.PropertyDefinition;
import java.util.ArrayList;
import java.util.Arrays;
......@@ -209,6 +214,9 @@ public abstract class MockComponent extends Composite implements PropertyChangeL
}
}
// Component database: information about components (including their properties and events)
private final SimpleComponentDatabase COMPONENT_DATABASE;
// Image bundle
protected static final Images images = Ode.getImageBundle();
......@@ -220,7 +228,8 @@ public abstract class MockComponent extends Composite implements PropertyChangeL
protected final SimpleEditor editor;
private final String type;
private final Image iconImage;
private ComponentDefinition componentDefinition;
private Image iconImage;
private final SourceStructureExplorerItem sourceStructureExplorerItem;
/**
......@@ -250,6 +259,8 @@ public abstract class MockComponent extends Composite implements PropertyChangeL
this.editor = editor;
this.type = type;
this.iconImage = iconImage;
COMPONENT_DATABASE = SimpleComponentDatabase.getInstance(editor.getProjectId());
componentDefinition = COMPONENT_DATABASE.getComponentDefinition(type);
sourceStructureExplorerItem = new SourceStructureExplorerItem() {
@Override
......@@ -976,4 +987,86 @@ public abstract class MockComponent extends Composite implements PropertyChangeL
}
};
}
/** Upgrading MockComponent
*
* When extensions are upgraded, the MockComponents might need to undergo changes.
* These changes can be produced inside this function.
* All subclasses overriding this method must call super.upgrade()!
*/
public void upgrade() {
//Upgrade Icon
//We copy all compatible properties values
List<PropertyDefinition> newProperties = COMPONENT_DATABASE.getPropertyDefinitions(this.type);
List<PropertyDefinition> oldProperties = componentDefinition.getProperties();
EditableProperties currentProperties = getProperties();
//Operations
List<String> toBeRemoved = new ArrayList<String>();
List<String> toBeAdded = new ArrayList<String>();
//Plan operations
for (EditableProperty property : currentProperties) {
boolean presentInNewProperties = false;
boolean presentInOldProperties = false;
String oldType = "";
String newType = "";
for (PropertyDefinition prop : newProperties) {
if (prop.getName() == property.getName()) {
presentInNewProperties = true;
newType = prop.getEditorType();
}
}
for (PropertyDefinition prop : oldProperties) {
if (prop.getName() == property.getName()) {
presentInOldProperties = true;
oldType = prop.getEditorType();
}
}
// deprecated property
if (!presentInNewProperties && presentInOldProperties) {
toBeRemoved.add(property.getName());
}
// new property, less likely to happen here
else if (presentInNewProperties && !presentInOldProperties) {
toBeAdded.add(property.getName());
}
// existing property
else if (presentInNewProperties && presentInOldProperties) {
if (newType != oldType) { // type change detected
toBeRemoved.add(property.getName());
toBeAdded.add(property.getName());
}
}
}
//New property
for (PropertyDefinition property : newProperties) {
if (!toBeAdded.contains(property.getName()) && !currentProperties.hasProperty(property.getName())) {
toBeAdded.add(property.getName());
}
}
//Execute operations
for (String prop : toBeRemoved) {
currentProperties.removeProperty(prop);
}
for (PropertyDefinition property : newProperties) {
if (toBeAdded.contains(property.getName())) {
PropertyEditor propertyEditor = PropertiesUtil.createPropertyEditor(property.getEditorType(), (YaFormEditor) editor);
addProperty(property.getName(), property.getDefaultValue(), property.getCaption(), propertyEditor);
}
}
}
/**
* upgradeComplete()
* Mark a MockComponent upgrade complete.
* This MUST be called manually after calling upgrade()!
* All subclasses overriding this method must call super.upgradeComplete()!
*/
public void upgradeComplete() {
this.componentDefinition = COMPONENT_DATABASE.getComponentDefinition(this.type); //Update ComponentDefinition
}
}
......@@ -619,6 +619,10 @@ public class BlocklyPanel extends HTMLPanel implements ComponentDatabaseChangeLi
doHardReset(formName);
}
public void verifyAllBlocks() {
doVerifyAllBlocks(formName);
}
public static boolean checkIsAdmin() {
return Ode.getInstance().getUser().getIsAdmin();
}
......@@ -813,6 +817,8 @@ public class BlocklyPanel extends HTMLPanel implements ComponentDatabaseChangeLi
@Override
public void onComponentTypeAdded(List<String> componentTypes) {
populateComponentTypes(formName);
verifyAllBlocks();
}
@Override
......@@ -945,6 +951,7 @@ public class BlocklyPanel extends HTMLPanel implements ComponentDatabaseChangeLi
// [lyn, 2014/10/27] added formJson for upgrading
public static native void doLoadBlocksContent(String formName, String formJson, String blocksContent) /*-{
$wnd.Blocklies[formName].SaveFile.load(formJson, blocksContent);
$wnd.Blocklies[formName].Component.verifyAllBlocks();
}-*/;
public static native String doGetBlocksContent(String formName) /*-{
......@@ -1043,4 +1050,11 @@ public class BlocklyPanel extends HTMLPanel implements ComponentDatabaseChangeLi
public static native void populateComponentTypes(String formName) /*-{
$wnd.Blocklies[formName].ComponentTypes.populateTypes();
}-*/;
/*
* Update Component Types in Blockly ComponentTypes
*/
public static native void doVerifyAllBlocks(String formName) /*-{
$wnd.Blocklies[formName].Component.verifyAllBlocks();
}-*/;
}
......@@ -138,7 +138,9 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen
// Create UI elements for the designer panels.
nonVisibleComponentsPanel = new SimpleNonVisibleComponentsPanel();
addComponentDatabaseChangeListener(nonVisibleComponentsPanel);
visibleComponentsPanel = new SimpleVisibleComponentsPanel(this, nonVisibleComponentsPanel);
addComponentDatabaseChangeListener(visibleComponentsPanel);
DockPanel componentsPanel = new DockPanel();
componentsPanel.setHorizontalAlignment(DockPanel.ALIGN_CENTER);
componentsPanel.add(visibleComponentsPanel, DockPanel.NORTH);
......@@ -719,6 +721,20 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen
propertiesBox.setVisible(false);
}
/**
* Runs through all the Mock Components and upgrades if its corresponding Component was Upgraded
* @param componentTypes the Component Types that got upgraded
*/
private void updateMockComponents(List<String> componentTypes) {
Map<String, MockComponent> componentMap = getComponents();
for (MockComponent mockComponent : componentMap.values()) {
if (componentTypes.contains(mockComponent.getType())) {
mockComponent.upgrade();
mockComponent.upgradeComplete();
}
}
}
/*
* Push changes to a connected phone (or emulator).
*/
......@@ -746,6 +762,10 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen
for (ComponentDatabaseChangeListener cdbChangeListener : componentDatabaseChangeListeners) {
cdbChangeListener.onComponentTypeAdded(componentTypes);
}
//Update Mock Components
updateMockComponents(componentTypes);
//Update the Properties Panel
updatePropertiesPanel(form.getSelectedComponent());
}
@Override
......
......@@ -477,7 +477,9 @@ public final class YaProjectEditor extends ProjectEditor implements ProjectChang
COMPONENT_DATABASE.addComponentDatabaseListener(projectEditor);
COMPONENT_DATABASE.addComponent(new ClientJsonParser().parse(
jsonFileContent).asObject());
externalComponents.add(compNode.getName());
if (!externalComponents.contains(compNode.getName())) { // In case of upgrade, we do not need to add entry
externalComponents.add(compNode.getName());
}
if (afterComponentAdded != null) {
afterComponentAdded.execute();
}
......
......@@ -15,7 +15,7 @@ import java.util.Map;
public interface ComponentDatabaseChangeListener {
/**
* Invoked after one or more components are added
* Invoked after one or more components are added or upgraded
*/
void onComponentTypeAdded(List<String> componentTypes);
......
......@@ -159,10 +159,9 @@ public class Properties<T extends Property> implements Iterable<T> {
/**
* Adds a new property to the collection.
* A property of the same name must not already exist.
* A property of the same name may already exist.
*
* @param property property to be added
* @throws IllegalStateException if a same-named property already exists
*/
protected void addProperty(T property) {
T oldProperty = propertiesMap.put(property.getName(), property);
......@@ -172,6 +171,18 @@ public class Properties<T extends Property> implements Iterable<T> {
}
}
/**
* Removes a property from the collection
* The property may not exist in the collection
*
* @param propertyName name of the property to be removed
*/
protected void removeProperty(String propertyName) {
if (propertiesMap.containsKey(propertyName)) {
propertiesMap.remove(propertyName);
}
}
/**
* Deletes all properties.
*/
......@@ -196,6 +207,10 @@ public class Properties<T extends Property> implements Iterable<T> {
}
}
public final boolean hasProperty(String name) {
return propertiesMap.containsKey(name);
}
/**
* Returns the value of an existing property.
*
......
......@@ -63,6 +63,11 @@ public class EditableProperties extends Properties<EditableProperty> {
}
}
public void removeProperty(String propertyName) {
super.removeProperty(propertyName);
}
/**
* Adds a {@link PropertyChangeListener} to the listener list.
*
......
......@@ -6,15 +6,20 @@
package com.google.appinventor.client.widgets.properties;
import com.google.appinventor.client.editor.simple.components.MockComponent;
import com.google.appinventor.client.explorer.project.ComponentDatabaseChangeListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import java.util.List;
import java.util.Map;
/**
* Panel to display properties.
*
*/
public class PropertiesPanel extends Composite {
public class PropertiesPanel extends Composite implements ComponentDatabaseChangeListener {
// UI elements
private final VerticalPanel panel;
......@@ -85,4 +90,24 @@ public class PropertiesPanel extends Composite {
public void setPropertiesCaption(String name) {
componentName.setText(name);
}
@Override
public void onComponentTypeAdded(List<String> componentTypes) {
}
@Override
public boolean beforeComponentTypeRemoved(List<String> componentTypes) {
return true;
}
@Override
public void onComponentTypeRemoved(Map<String, String> componentTypes) {
}
@Override
public void onResetDatabase() {
}
}
......@@ -56,30 +56,57 @@ public class ComponentImportWizard extends Wizard {
Window.alert(MESSAGES.componentImportError());
return;
}
else if (response.getStatus() == ComponentImportResponse.Status.ALREADY_IMPORTED) {
String componentName = SimpleComponentDatabase.getInstance().getComponentName(response.getComponentType());
Window.alert(MESSAGES.componentAlreadyImportedError() + componentName + ".");
else if (response.getStatus() != ComponentImportResponse.Status.IMPORTED &&
response.getStatus() != ComponentImportResponse.Status.UPGRADED) {
Window.alert(MESSAGES.componentImportError());
return;
}
else if (response.getStatus() == ComponentImportResponse.Status.UNKNOWN_URL) {
Window.alert(MESSAGES.componentImportUnknownURLError());
}
else if (response.getStatus() != ComponentImportResponse.Status.SUCCESS) {
Window.alert(MESSAGES.componentImportError());
return;
else if (response.getStatus() == ComponentImportResponse.Status.UPGRADED) {
String componentName = SimpleComponentDatabase.getInstance().getComponentName(response.getComponentType());
Window.alert(MESSAGES.componentUpgradedAlert() + componentName + " !");
}
List<ProjectNode> compNodes = response.getNodes();
for (ProjectNode node : compNodes) {
if (node.getName().equals("component.json") && StringUtils.countMatches(node.getFileId(), "/") == 3) {
String fileId = node.getFileId();
int start = fileId.indexOf(external_components) + external_components.length();
int end = fileId.indexOf('/', start);
String typeName = fileId.substring(start, end);
new ComponentRenameWizard(typeName, compNodes).center();
long destinationProjectId = response.getProjectId();
long currentProjectId = ode.getCurrentYoungAndroidProjectId();
if (currentProjectId != destinationProjectId) {
return; // User switched project early!
}
Project project = ode.getProjectManager().getProject(destinationProjectId);
if (project == null) {
return; // Project does not exist!
}
if (response.getStatus() == ComponentImportResponse.Status.UPGRADED) {
YoungAndroidComponentsFolder componentsFolder = ((YoungAndroidProjectNode) project.getRootNode()).getComponentsFolder();
YaProjectEditor projectEditor = (YaProjectEditor) ode.getEditorManager().getOpenProjectEditor(destinationProjectId);
if (projectEditor == null) {
return; // Project is not open!
}
for (ProjectNode node : compNodes) {
project.addNode(componentsFolder, node);
if (node.getName().equals("component.json") && StringUtils.countMatches(node.getFileId(), "/") == 3) {
projectEditor.addComponent(node, null);
}
}
} else if (response.getStatus() == ComponentImportResponse.Status.IMPORTED) {
for (ProjectNode node : compNodes) {
if (node.getName().equals("component.json") && StringUtils.countMatches(node.getFileId(), "/") == 3) {
String fileId = node.getFileId();
int start = fileId.indexOf(external_components) + external_components.length();
int end = fileId.indexOf('/', start);
String typeName = fileId.substring(start, end);
new ComponentRenameWizard(typeName, destinationProjectId, compNodes).center();
}
}
}
}
}
......
......@@ -37,20 +37,23 @@ public class ComponentRenameWizard extends Wizard{
private String defaultTypeName;
private String defaultName;
private long projectId;
private long destinationProjectId;
private class RenameComponentCallback extends OdeAsyncCallback<Void> {
@Override
public void onSuccess(Void result) {
if (nodes.isEmpty()) return;
Project project = ode.getProjectManager().getProject(projectId);
Project project = ode.getProjectManager().getProject(destinationProjectId);
if (project == null) {
return;
}
YoungAndroidComponentsFolder componentsFolder = ((YoungAndroidProjectNode) project.getRootNode()).getComponentsFolder();
YaProjectEditor projectEditor = (YaProjectEditor) ode.getEditorManager().getOpenProjectEditor(projectId);
YaProjectEditor projectEditor = (YaProjectEditor) ode.getEditorManager().getOpenProjectEditor(destinationProjectId);
if (projectEditor == null) {
return;
}
for (ProjectNode node : nodes) {
project.addNode(componentsFolder,node);
project.addNode(componentsFolder, node);
if (node.getName().equals("component.json") && StringUtils.countMatches(node.getFileId(), "/") == 3) {
projectEditor.addComponent(node, null);
}
......@@ -61,12 +64,12 @@ public class ComponentRenameWizard extends Wizard{
protected ComponentRenameWizard(final String defaultTypeName, final List<ProjectNode> compNodes) {
protected ComponentRenameWizard(final String defaultTypeName, long projectId, final List<ProjectNode> compNodes) {
super(MESSAGES.componentRenameWizardCaption(), true, false);
this.defaultTypeName = defaultTypeName;
this.defaultName = getDefaultName(defaultTypeName);
this.projectId = ode.getCurrentYoungAndroidProjectId();
this.destinationProjectId = projectId;
this.nodes = compNodes;
setStylePrimaryName("ode-DialogBox");
......@@ -98,7 +101,7 @@ public class ComponentRenameWizard extends Wizard{
String newName = renameTextBox.getText();
if (TextValidators.checkNewComponentName(newName)) {
ode.getComponentService().renameImportedComponent(defaultTypeName, newName, projectId, new RenameComponentCallback());
ode.getComponentService().renameImportedComponent(defaultTypeName, newName, destinationProjectId, new RenameComponentCallback());
} else {
show();
......@@ -113,7 +116,7 @@ public class ComponentRenameWizard extends Wizard{
initCancelCommand(new Command() {
@Override
public void execute() {
ode.getComponentService().deleteImportedComponent(defaultTypeName, projectId, new AsyncCallback<Void>() {
ode.getComponentService().deleteImportedComponent(defaultTypeName, destinationProjectId, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable throwable) {
......
......@@ -134,12 +134,26 @@ public class ComponentServiceImpl extends OdeRemoteServiceServlet
private void importToProject(Map<String, byte[]> contents, long projectId,
String folderPath, ComponentImportResponse response) throws FileImporterException, IOException {
List<ProjectNode> compNodes = new ArrayList<ProjectNode>();
response.setProjectId(projectId);
List<String> sourceFiles = storageIo.getProjectSourceFiles(userInfoProvider.getUserId(), projectId);
boolean extensionUpgrade = false;
String oldCompName; // name with which extension is already imported, we have to grab from the old component files
for (String name : contents.keySet()) {
String destination = folderPath + "/external_comps/" + name;
if (sourceFiles.contains(destination)) { // Check if source File already contains component files
response.setStatus(ComponentImportResponse.Status.ALREADY_IMPORTED);
return; // Fail the Import!!
// This is an upgrade, if it replaces old component files
extensionUpgrade = true;
if (StorageUtil.basename(name).equals("component.json")) { // TODO : we need a more secure check
// We modify the name property of the new component.json to match that of the already imported component.json
JSONObject oldCompJson = new JSONObject(storageIo.downloadFile(
userInfoProvider.getUserId(), projectId, destination, StorageUtil.DEFAULT_CHARSET));
oldCompName = oldCompJson.getString("name");
String componentJSONString = new String(contents.get(name), StorageUtil.DEFAULT_CHARSET);
JSONObject newCompJSon = new JSONObject(componentJSONString);
newCompJSon.put("name", oldCompName); //change the name to the same as that of already imported component
componentJSONString = newCompJSon.toString(1); // 1 is the indent factor, let it look beautiful
contents.put(name, componentJSONString.getBytes(StorageUtil.DEFAULT_CHARSET));
}
}
FileNode fileNode = new YoungAndroidComponentNode(StorageUtil.basename(name), destination);
fileImporter.importFile(userInfoProvider.getUserId(), projectId,
......@@ -147,10 +161,13 @@ public class ComponentServiceImpl extends OdeRemoteServiceServlet
compNodes.add(fileNode);
}
if (extensionUpgrade) {
response.setStatus(ComponentImportResponse.Status.UPGRADED);
} else {
response.setStatus(ComponentImportResponse.Status.IMPORTED);
}
String type = contents.keySet().iterator().next(); // get an element
type = type.substring(0, type.indexOf('/')); // get the type
response.setStatus(ComponentImportResponse.Status.SUCCESS);
response.setComponentType(type);
response.setNodes(compNodes);
return;
......
......@@ -17,25 +17,27 @@ import java.util.List;
public class ComponentImportResponse implements IsSerializable{
public enum Status {
SUCCESS,
ALREADY_IMPORTED,
IMPORTED,
UPGRADED, //when the old component files were replaced with newer ones
NOT_GCS,
UNKNOWN_URL,
FAILED
}
private Status status;
private long projectId; // necessary to ensure right project
private String componentType; // Type of Component
private List<ProjectNode> nodes; // Added Nodes
public ComponentImportResponse(Status status, String componentType, List<ProjectNode> nodes) {
public ComponentImportResponse(Status status, long projectId, String componentType, List<ProjectNode> nodes) {
this.status = status;
this.projectId = projectId;
this.componentType = componentType;
this.nodes = nodes;
}
public ComponentImportResponse(Status status) {
this(status, "", null);
this(status, 0, "", null);
}
private ComponentImportResponse() {
......@@ -49,6 +51,14 @@ public class ComponentImportResponse implements IsSerializable{
this.status = status;
}
public long getProjectId() {
return projectId;
}
public void setProjectId(long projectId){
this.projectId = projectId;
}
public String getComponentType() {
return componentType;
}
......
......@@ -20,7 +20,7 @@ public interface ComponentService extends RemoteService {
* Import the component to the project in the server and
* return a list of ProjectNode that can be added to the client
*
* @param url the url of the componenet file or filename of temp file
* @param fileOrUrl the url of the component file or filename of temp file
* @param projectId id of the project to which the component will be added
* @param folderPath folder to which the component will be stored
* @return a list of ProjectNode created from the component
......
......@@ -6,6 +6,10 @@
package com.google.appinventor.shared.simple;
import com.google.appinventor.components.common.ComponentCategory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
......@@ -16,6 +20,131 @@ import java.util.Set;
* @author lizlooney@google.com (lizlooney)
*/
public interface ComponentDatabaseInterface {
/**
* Simple component information: component name, its properties
*/
public static class ComponentDefinition {
private final String name;
private final int version;
private final String type;
private final boolean external;
private final String categoryString;
private final String helpString;
private final boolean showOnPalette;
private final String categoryDocUrlString;
private final List<PropertyDefinition> properties;
private final List<BlockPropertyDefinition> blockProperties;
private final List<EventDefinition> events;
private final List<MethodDefinition> methods;
private final Map<String, String> propertiesTypesByName;
private final boolean nonVisible;
private final String iconName;
private final String typeDescription;
public ComponentDefinition(String name, int version, String type, boolean external, String categoryString, String helpString,
boolean showOnPalette, boolean nonVisible, String iconName, String typeDescription) {
this.name = name;
this.version = version;
this.type = type;
this.external = external;
this.categoryString = categoryString;
this.helpString = helpString;
this.showOnPalette = showOnPalette;
this.categoryDocUrlString = ComponentCategory.valueOf(categoryString).getDocName();
this.properties = new ArrayList<PropertyDefinition>();
this.blockProperties = new ArrayList<BlockPropertyDefinition>();
this.events = new ArrayList<EventDefinition>();
this.methods = new ArrayList<MethodDefinition>();
this.propertiesTypesByName = new HashMap<String, String>();
this.nonVisible = nonVisible;
this.iconName = iconName;
this.typeDescription = typeDescription;
}
public void add(PropertyDefinition property) {
properties.add(property);
propertiesTypesByName.put(property.getName(), property.getEditorType());
}
public void add(BlockPropertyDefinition blockProperty) {
blockProperties.add(blockProperty);
}
public void add(EventDefinition event) {
events.add(event);
}
public void add(MethodDefinition method) {
methods.add(method);
}
public String getName() {
return name;
}
public int getVersion() {
return version;
}
public String getType() {
return type;
}
public boolean isExternal() {
return external;
}
public String getCategoryString() {
return categoryString;
}
public String getHelpString() {
return helpString;
}
public boolean isShowOnPalette() {
return showOnPalette;
}
public String getCategoryDocUrlString() {
return categoryDocUrlString;
}
public List<PropertyDefinition> getProperties() {
return properties;
}
public List<BlockPropertyDefinition> getBlockProperties() {
return blockProperties;
}
public List<EventDefinition> getEvents() {
return events;
}
public List<MethodDefinition> getMethods() {
return methods;
}
public Map<String, String> getPropertiesTypesByName() {
return propertiesTypesByName;
}
public boolean isNonVisible() {
return nonVisible;
}
public String getIconName() {
return iconName;
}
public String getTypeDescription() {
return typeDescription;
}
}
/**
* Property definition: property name, property editor type and property
* default value.
......
......@@ -161,6 +161,19 @@ Blockly.Component.buildComponentMap = function(warnings, errors, forRepl, compil
return map;
};
/**
* Verify all blocks after a Component upgrade
*/
Blockly.Component.verifyAllBlocks = function () {
var allBlocks = Blockly.mainWorkspace.getAllBlocks();
for (var x = 0, block; block = allBlocks[x]; ++x) {
if (block.category != 'Component') {
continue;
}
block.verify();
}
}
/**
* Blockly.ComponentTypes
*
......@@ -175,7 +188,9 @@ Blockly.Component.buildComponentMap = function(warnings, errors, forRepl, compil
*
* The componentInfo has the following format (where upper-case strings are
* non-terminals and lower-case strings are literals):
* { "name": "COMPONENT-TYPE-NAME",
* { "type": "COMPONENT-TYPE",
* "name": "COMPONENT-TYPE-NAME",
* "external": "true"|"false",
* "version": "VERSION",
* "categoryString": "PALETTE-CATEGORY",
* "helpString": "DESCRIPTION",
......@@ -228,6 +243,7 @@ Blockly.ComponentTypes.populateTypes = function() {
var typeName = componentInfo.name;
Blockly.ComponentTypes[typeName] = {};
Blockly.ComponentTypes[typeName].type = componentInfo.type;
Blockly.ComponentTypes[typeName].external = componentInfo.external;
Blockly.ComponentTypes[typeName].componentInfo = componentInfo;
Blockly.ComponentTypes[typeName].eventDictionary = {};
Blockly.ComponentTypes[typeName].methodDictionary = {};
......
......@@ -610,12 +610,12 @@ Blockly.Yail.blockToCode1 = function(block) {
var code = func.call(block);
if (code instanceof Array) {
// Value blocks return tuples of code and operator order.
if (block.disabled) {
if (block.disabled || block.isBadBlock()) {
code[0] = '';
}
return [this.scrub_(block, code[0], true), code[1]];
} else {
if (block.disabled) {
if (block.disabled || block.isBadBlock()) {
code = '';
}
return this.scrub_(block, code, true);
......
......@@ -95,6 +95,7 @@ Blockly.Msg.en.switch_language_to_english = {
Blockly.ERROR_SELECT_VALID_ITEM_FROM_DROPDOWN = "Select a valid item in the drop down.";
Blockly.ERROR_DUPLICATE_EVENT_HANDLER = "This is a duplicate event handler for this component.";
Blockly.ERROR_COMPONENT_DOES_NOT_EXIST = "Component does not exist";
Blockly.ERROR_BLOCK_IS_NOT_DEFINED = "This block is not defined. Delete this block!";
// Colour Blocks.
Blockly.Msg.LANG_COLOUR_PICKER_HELPURL = 'http://appinventor.mit.edu/explore/ai2/support/blocks/colors#basic';
......@@ -979,6 +980,8 @@ Blockly.Msg.en.switch_language_to_english = {
Blockly.Msg.LANG_PROCEDURES_MUTATORARG_TOOLTIP = '';
// Components Blocks.
Blockly.Msg.UNDEFINED_BLOCK_TOOLTIP = "This block is not defined. Delete this block!";
Blockly.Msg.LANG_COMPONENT_BLOCK_HELPURL = '';
Blockly.Msg.LANG_COMPONENT_BLOCK_TITLE_WHEN = 'when ';
Blockly.Msg.LANG_COMPONENT_BLOCK_TITLE_DO = 'do';
......
......@@ -214,6 +214,27 @@ Blockly.WarningHandler.checkIsInDefinition = function(){
}
// Check if block is undefined and unplug
Blockly.WarningHandler.checkIfUndefinedBlock = function() {
if (this.isBadBlock() === true) {
var errorMessage = Blockly.ERROR_BLOCK_IS_NOT_DEFINED;
var healStack = true;
if (this.type == "component_event") {
healStack = false; // unplug all blocks inside
}
this.isolate(healStack, true);
if(this.errorIcon){
this.errorIcon.setText(errorMessage);
} else {
this.setErrorIconText(errorMessage);
}
return true;
} else {
return false;
}
}
//Check if the block has an invalid drop down value, if so, create an error
Blockly.WarningHandler.checkDropDownContainsValidValue = function(params){
for(var i=0;i<params.dropDowns.length;i++){
......
......@@ -353,6 +353,14 @@ Blockly.Block.prototype.badBlock = function() {
this.svg_.addBadBlock();
};
/**
* Unmark this block as Bad.
*/
Blockly.Block.prototype.notBadBlock = function() {
goog.asserts.assertObject(this.svg_, 'Block is not rendered.');
this.svg_.removeBadBlock();
};
/**
* Check to see if this block is bad.
*/
......@@ -442,6 +450,16 @@ Blockly.Block.prototype.dispose = function(healStack, animate,
Blockly.WarningHandler.checkDisposedBlock.call(this);
};
/**
Unplug this block from every block connected to it.
*/
Blockly.Block.prototype.isolate = function(healStack, bump) {
this.unplug(healStack, bump);
for (var x = this.childBlocks_.length - 1; x >= 0; x--) {
this.childBlocks_[x].unplug(healStack, bump);
}
}
/**
* Unplug this block from its superior block. If this block is a statement,
* optionally reconnect the block underneath with the block on top.
......@@ -1564,6 +1582,7 @@ Blockly.Block.prototype.setDisabled = function(disabled) {
this.workspace.fireChangeEvent();
};
/**
* Get whether the block is disabled or not due to parents.
* The block's own disabled property is not considered.
......
......@@ -442,6 +442,7 @@ Blockly.BlockSvg.prototype.updateDisabled = function() {
}
};
/**
* Select this block. Highlight it visually.
*/
......@@ -470,6 +471,16 @@ Blockly.BlockSvg.prototype.addBadBlock = function() {
this.svgGroup_.parentNode.appendChild(this.svgGroup_);
};
/**
* Unmark this block as bad.
*/
Blockly.BlockSvg.prototype.removeBadBlock = function() {
Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
'badBlock');
// Move the selected block to the top of the stack.
this.svgGroup_.parentNode.appendChild(this.svgGroup_);
};
/**
* Check to see if the block is marked as bad.
*/
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment