mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-11 01:32:08 +10:00
building blocks
This commit is contained in:
Binary file not shown.
@@ -38,7 +38,8 @@ public enum IconLoader {
|
||||
"cloud_wait.png"), Spinning("spinning.gif"), Megaphone("megaphone.png"), Download("download.png"), Eye("eye.png"), Messages(
|
||||
"messages.png"), SearchBox("search-box.png"), Screwdriver("screwdriver.png"), Hammer("hammer.png"), FlipHorizontal(
|
||||
"flip_horizontal.png"), FlipVertical("flip_vertical.png"), MagicWand("magic_wand.png"), PinGrey("pin_grey.png"), PinGreen(
|
||||
"pin_green.png");
|
||||
"pin_green.png"), CoffeebeanEdit("coffeebean_edit.png"), ApplicationEdit("application_edit.png"), ComponentAdd(
|
||||
"component_add.png");
|
||||
|
||||
protected String name;
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 929 B |
Binary file not shown.
|
After Width: | Height: | Size: 910 B |
Binary file not shown.
|
After Width: | Height: | Size: 910 B |
@@ -136,6 +136,10 @@ public class ActionFactory {
|
||||
public SaveAsTemplateAction createSaveAsTemplateAction(IPlugInPort plugInPort) {
|
||||
return new SaveAsTemplateAction(plugInPort);
|
||||
}
|
||||
|
||||
public SaveAsBlockAction createSaveAsBlockAction(IPlugInPort plugInPort) {
|
||||
return new SaveAsBlockAction(plugInPort);
|
||||
}
|
||||
|
||||
public ExpandSelectionAction createExpandSelectionAction(IPlugInPort plugInPort, ExpansionMode expansionMode) {
|
||||
return new ExpandSelectionAction(plugInPort, expansionMode);
|
||||
@@ -863,6 +867,30 @@ public class ActionFactory {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class SaveAsBlockAction extends AbstractAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private IPlugInPort plugInPort;
|
||||
|
||||
public SaveAsBlockAction(IPlugInPort plugInPort) {
|
||||
super();
|
||||
this.plugInPort = plugInPort;
|
||||
putValue(AbstractAction.NAME, "Save as Building Block");
|
||||
putValue(AbstractAction.SMALL_ICON, IconLoader.ComponentAdd.getIcon());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
LOG.info("Save as building block triggered");
|
||||
String templateName =
|
||||
JOptionPane.showInputDialog(null, "Block name:", "Save as Building Block", JOptionPane.PLAIN_MESSAGE);
|
||||
if (templateName != null && !templateName.trim().isEmpty()) {
|
||||
plugInPort.saveSelectionAsBlock(templateName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ExpandSelectionAction extends AbstractAction {
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ public class CanvasPlugin implements IPlugIn, ClipboardOwner {
|
||||
private ActionFactory.EditSelectionAction editSelectionAction;
|
||||
private ActionFactory.DeleteSelectionAction deleteSelectionAction;
|
||||
private ActionFactory.SaveAsTemplateAction saveAsTemplateAction;
|
||||
private ActionFactory.SaveAsBlockAction saveAsBlockAction;
|
||||
private ActionFactory.GroupAction groupAction;
|
||||
private ActionFactory.UngroupAction ungroupAction;
|
||||
private ActionFactory.SendToBackAction sendToBackAction;
|
||||
@@ -154,6 +155,7 @@ public class CanvasPlugin implements IPlugIn, ClipboardOwner {
|
||||
getMirrorVerticallyAction().setEnabled(enabled);
|
||||
|
||||
getSaveAsTemplateAction().setEnabled(plugInPort.getSelectedComponents().size() == 1);
|
||||
getSaveAsBlockAction().setEnabled(plugInPort.getSelectedComponents().size() > 1);
|
||||
|
||||
showPopupAt(e.getX(), e.getY());
|
||||
}
|
||||
@@ -265,6 +267,7 @@ public class CanvasPlugin implements IPlugIn, ClipboardOwner {
|
||||
popupMenu.add(getTransformMenu());
|
||||
popupMenu.add(getSaveAsTemplateAction());
|
||||
popupMenu.add(getApplyTemplateMenu());
|
||||
popupMenu.add(getSaveAsBlockAction());
|
||||
popupMenu.add(getExpandMenu());
|
||||
popupMenu.addSeparator();
|
||||
popupMenu.add(ActionFactory.getInstance().createEditProjectAction(plugInPort));
|
||||
@@ -439,6 +442,13 @@ public class CanvasPlugin implements IPlugIn, ClipboardOwner {
|
||||
return saveAsTemplateAction;
|
||||
}
|
||||
|
||||
public ActionFactory.SaveAsBlockAction getSaveAsBlockAction() {
|
||||
if (saveAsBlockAction == null) {
|
||||
saveAsBlockAction = ActionFactory.getInstance().createSaveAsBlockAction(plugInPort);
|
||||
}
|
||||
return saveAsBlockAction;
|
||||
}
|
||||
|
||||
public ActionFactory.GroupAction getGroupAction() {
|
||||
if (groupAction == null) {
|
||||
groupAction = ActionFactory.getInstance().createGroupAction(plugInPort);
|
||||
|
||||
@@ -47,6 +47,7 @@ public class EditMenuPlugin implements IPlugIn, ClipboardOwner {
|
||||
private ActionFactory.ExpandSelectionAction expandSelectionImmediateAction;
|
||||
private ActionFactory.ExpandSelectionAction expandSelectionSameTypeAction;
|
||||
private ActionFactory.SaveAsTemplateAction saveAsTemplateAction;
|
||||
private ActionFactory.SaveAsBlockAction saveAsBlockAction;
|
||||
private ActionFactory.RotateSelectionAction rotateClockwiseAction;
|
||||
private ActionFactory.RotateSelectionAction rotateCounterClockwiseAction;
|
||||
private ActionFactory.MirrorSelectionAction mirrorHorizontallyAction;
|
||||
@@ -187,6 +188,13 @@ public class EditMenuPlugin implements IPlugIn, ClipboardOwner {
|
||||
}
|
||||
return saveAsTemplateAction;
|
||||
}
|
||||
|
||||
public ActionFactory.SaveAsBlockAction getSaveAsBlockAction() {
|
||||
if (saveAsBlockAction == null) {
|
||||
saveAsBlockAction = ActionFactory.getInstance().createSaveAsBlockAction(plugInPort);
|
||||
}
|
||||
return saveAsBlockAction;
|
||||
}
|
||||
|
||||
public ActionFactory.RotateSelectionAction getRotateClockwiseAction() {
|
||||
if (rotateClockwiseAction == null) {
|
||||
@@ -306,6 +314,7 @@ public class EditMenuPlugin implements IPlugIn, ClipboardOwner {
|
||||
getMirrorHorizontallyAction().setEnabled(enabled);
|
||||
getMirrorVerticallyAction().setEnabled(enabled);
|
||||
getSaveAsTemplateAction().setEnabled(enabled);
|
||||
getSaveAsBlockAction().setEnabled(enabled);
|
||||
}
|
||||
|
||||
// ClipboardOwner
|
||||
|
||||
@@ -39,9 +39,9 @@ public class HelpMenuPlugin implements IPlugIn {
|
||||
public HelpMenuPlugin(ISwingUI swingUI) {
|
||||
swingUI.injectMenuAction(new NavigateURLAction("User Manual", IconLoader.Manual.getIcon(), MANUAL_URL), HELP_TITLE);
|
||||
swingUI.injectMenuAction(new NavigateURLAction("FAQ", IconLoader.Faq.getIcon(), FAQ_URL), HELP_TITLE);
|
||||
swingUI.injectMenuAction(new NavigateURLAction("Component API", IconLoader.Component.getIcon(), COMPONENT_URL),
|
||||
swingUI.injectMenuAction(new NavigateURLAction("Component API", IconLoader.CoffeebeanEdit.getIcon(), COMPONENT_URL),
|
||||
HELP_TITLE);
|
||||
swingUI.injectMenuAction(new NavigateURLAction("Plugin API", IconLoader.Plugin.getIcon(), PLUGIN_URL), HELP_TITLE);
|
||||
swingUI.injectMenuAction(new NavigateURLAction("Plugin API", IconLoader.ApplicationEdit.getIcon(), PLUGIN_URL), HELP_TITLE);
|
||||
swingUI.injectMenuAction(new NavigateURLAction("Submit a Bug", IconLoader.Bug.getIcon(), BUG_URL), HELP_TITLE);
|
||||
swingUI.injectMenuAction(null, HELP_TITLE);
|
||||
swingUI.injectMenuAction(new NavigateURLAction("Donate", IconLoader.Donate.getIcon(), DONATE_URL), HELP_TITLE);
|
||||
|
||||
@@ -13,7 +13,9 @@ import java.awt.event.FocusAdapter;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -43,6 +45,7 @@ import org.apache.log4j.Logger;
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.appframework.miscutils.IConfigListener;
|
||||
import org.diylc.common.ComponentType;
|
||||
import org.diylc.common.IBlockProcessor.InvalidBlockException;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.Template;
|
||||
@@ -61,7 +64,9 @@ public class TreePanel extends JPanel {
|
||||
private JScrollPane treeScroll;
|
||||
private JTextField searchField;
|
||||
private DefaultMutableTreeNode recentNode;
|
||||
private DefaultMutableTreeNode blocksNode;
|
||||
private List<String> recentComponents;
|
||||
private List<String> blocks;
|
||||
|
||||
private IPlugInPort plugInPort;
|
||||
|
||||
@@ -96,15 +101,35 @@ public class TreePanel extends JPanel {
|
||||
@Override
|
||||
public void valueChanged(String key, Object value) {
|
||||
List<String> newComponents = (List<String>) value;
|
||||
if (!new HashSet<String>(newComponents).equals(new HashSet<String>(TreePanel.this.recentComponents))) {
|
||||
LOG.info("Detected change");
|
||||
refreshRecentComponentsToolbar(newComponents);
|
||||
if (newComponents != null
|
||||
&& !new HashSet<String>(newComponents).equals(new HashSet<String>(TreePanel.this.recentComponents))) {
|
||||
LOG.info("Detected recent component change");
|
||||
refreshRecentComponents(newComponents);
|
||||
} else
|
||||
LOG.info("Detected no change");
|
||||
LOG.info("Detected no recent component change");
|
||||
TreePanel.this.recentComponents = new ArrayList<String>(newComponents);
|
||||
}
|
||||
});
|
||||
|
||||
ConfigurationManager.getInstance().addConfigListener(IPlugInPort.BLOCKS_KEY, new IConfigListener() {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void valueChanged(String key, Object value) {
|
||||
Map<String, Collection<IDIYComponent<?>>> newBlocks = (Map<String, Collection<IDIYComponent<?>>>) value;
|
||||
if (newBlocks != null) {
|
||||
List<String> blockNames = new ArrayList<String>(newBlocks.keySet());
|
||||
Collections.sort(blockNames);
|
||||
if (!blockNames.equals(TreePanel.this.blocks)) {
|
||||
LOG.info("Detected block change");
|
||||
refreshBlocks(blockNames);
|
||||
} else
|
||||
LOG.info("Detected no block change");
|
||||
} else
|
||||
LOG.info("Detected no block change");
|
||||
}
|
||||
});
|
||||
|
||||
getTree().expandRow(0);
|
||||
}
|
||||
|
||||
@@ -118,13 +143,13 @@ public class TreePanel extends JPanel {
|
||||
|
||||
public DefaultMutableTreeNode getRecentNode() {
|
||||
if (this.recentNode == null) {
|
||||
this.recentNode = new DefaultMutableTreeNode(new Payload("(Recently Used)"), true);
|
||||
this.recentNode = new DefaultMutableTreeNode(new Payload("(Recently Used)", null), true);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> recent =
|
||||
(List<String>) ConfigurationManager.getInstance().readObject(IPlugInPort.RECENT_COMPONENTS_KEY, null);
|
||||
if (recent != null) {
|
||||
this.recentComponents = new ArrayList<String>(recent);
|
||||
refreshRecentComponentsToolbar(recent);
|
||||
refreshRecentComponents(recent);
|
||||
} else {
|
||||
this.recentComponents = new ArrayList<String>();
|
||||
}
|
||||
@@ -132,16 +157,41 @@ public class TreePanel extends JPanel {
|
||||
return this.recentNode;
|
||||
}
|
||||
|
||||
public DefaultMutableTreeNode getBlocksNode() {
|
||||
if (this.blocksNode == null) {
|
||||
this.blocksNode = new DefaultMutableTreeNode(new Payload("(Building Blocks)", null), true);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Collection<IDIYComponent<?>>> newBlocks =
|
||||
(Map<String, Collection<IDIYComponent<?>>>) ConfigurationManager.getInstance().readObject(
|
||||
IPlugInPort.BLOCKS_KEY, null);
|
||||
if (newBlocks != null) {
|
||||
List<String> blockNames = new ArrayList<String>(newBlocks.keySet());
|
||||
Collections.sort(blockNames);
|
||||
refreshBlocks(blockNames);
|
||||
this.blocks = blockNames;
|
||||
} else {
|
||||
this.blocks = new ArrayList<String>();
|
||||
}
|
||||
}
|
||||
return this.blocksNode;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void refreshRecentComponentsToolbar(List<String> recentComponentClassList) {
|
||||
private void refreshRecentComponents(List<String> recentComponentClassList) {
|
||||
getRecentNode().removeAllChildren();
|
||||
for (String componentClassName : recentComponentClassList) {
|
||||
ComponentType componentType;
|
||||
final ComponentType componentType;
|
||||
try {
|
||||
componentType =
|
||||
ComponentProcessor.getInstance().extractComponentTypeFrom(
|
||||
(Class<? extends IDIYComponent<?>>) Class.forName(componentClassName));
|
||||
Payload payload = new Payload(componentType);
|
||||
Payload payload = new Payload(componentType, new MouseAdapter() {
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
plugInPort.setNewComponentTypeSlot(componentType, null);
|
||||
}
|
||||
});
|
||||
final DefaultMutableTreeNode componentNode = new DefaultMutableTreeNode(payload, false);
|
||||
String text = getSearchField().getText();
|
||||
boolean visible =
|
||||
@@ -157,18 +207,51 @@ public class TreePanel extends JPanel {
|
||||
getTreeModel().nodeStructureChanged(getRecentNode());
|
||||
}
|
||||
|
||||
private void refreshBlocks(List<String> blocks) {
|
||||
getBlocksNode().removeAllChildren();
|
||||
for (final String block : blocks) {
|
||||
Payload payload = new Payload(block, new MouseAdapter() {
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (SwingUtilities.isLeftMouseButton(e)) {
|
||||
try {
|
||||
plugInPort.loadBlock(block);
|
||||
} catch (InvalidBlockException e1) {
|
||||
// TODO Auto-generated catch block
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
final DefaultMutableTreeNode componentNode = new DefaultMutableTreeNode(payload, false);
|
||||
String text = getSearchField().getText();
|
||||
boolean visible = text.trim().length() == 0 || block.toLowerCase().contains(text.toLowerCase());
|
||||
payload.setVisible(visible);
|
||||
getBlocksNode().add(componentNode);
|
||||
}
|
||||
getTreeModel().nodeStructureChanged(getBlocksNode());
|
||||
}
|
||||
|
||||
public DefaultTreeModel getTreeModel() {
|
||||
if (treeModel == null) {
|
||||
final DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Components", true);
|
||||
rootNode.add(getRecentNode());
|
||||
rootNode.add(getBlocksNode());
|
||||
Map<String, List<ComponentType>> componentTypes = plugInPort.getComponentTypes();
|
||||
List<String> categories = new ArrayList<String>(componentTypes.keySet());
|
||||
Collections.sort(categories);
|
||||
for (String category : categories) {
|
||||
final DefaultMutableTreeNode categoryNode = new DefaultMutableTreeNode(new Payload(category));
|
||||
final DefaultMutableTreeNode categoryNode = new DefaultMutableTreeNode(new Payload(category, null));
|
||||
rootNode.add(categoryNode);
|
||||
for (ComponentType type : componentTypes.get(category)) {
|
||||
final DefaultMutableTreeNode componentNode = new DefaultMutableTreeNode(new Payload(type), false);
|
||||
for (final ComponentType type : componentTypes.get(category)) {
|
||||
final DefaultMutableTreeNode componentNode = new DefaultMutableTreeNode(new Payload(type, new MouseAdapter() {
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
plugInPort.setNewComponentTypeSlot(type, null);
|
||||
}
|
||||
}), false);
|
||||
categoryNode.add(componentNode);
|
||||
}
|
||||
}
|
||||
@@ -184,21 +267,6 @@ public class TreePanel extends JPanel {
|
||||
tree.setCellRenderer(new ComponentCellRenderer());
|
||||
tree.setRowHeight(0);
|
||||
ToolTipManager.sharedInstance().registerComponent(tree);
|
||||
// tree.addTreeSelectionListener(new TreeSelectionListener() {
|
||||
//
|
||||
// @Override
|
||||
// public void valueChanged(TreeSelectionEvent e) {
|
||||
// Object c = e.getPath().getLastPathComponent();
|
||||
// if (c != null && c.getClass().equals(DefaultMutableTreeNode.class)) {
|
||||
// DefaultMutableTreeNode node = (DefaultMutableTreeNode) c;
|
||||
// Object obj = node.getUserObject();
|
||||
// if (obj != null && obj.getClass().equals(Payload.class)) {
|
||||
// Payload payload = (Payload) obj;
|
||||
// plugInPort.setNewComponentTypeSlot(payload.getComponentType(), null);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
tree.addMouseListener(new MouseAdapter() {
|
||||
|
||||
@@ -208,8 +276,8 @@ public class TreePanel extends JPanel {
|
||||
DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
|
||||
if (node != null && node.getUserObject() != null) {
|
||||
Payload payload = (Payload) node.getUserObject();
|
||||
if (payload.getComponentType() != null) {
|
||||
plugInPort.setNewComponentTypeSlot(payload.getComponentType(), null);
|
||||
if (payload.getClickListener() != null) {
|
||||
payload.getClickListener().mouseClicked(e);
|
||||
}
|
||||
}
|
||||
if (SwingUtilities.isRightMouseButton(e)) {
|
||||
@@ -326,12 +394,14 @@ public class TreePanel extends JPanel {
|
||||
boolean visible = false;
|
||||
if (text.trim().length() == 0) {
|
||||
visible = true;
|
||||
} else {
|
||||
} else if (payload.getComponentType() != null) {
|
||||
ComponentType type = payload.getComponentType();
|
||||
visible =
|
||||
type.getName().toLowerCase().contains(text.toLowerCase())
|
||||
|| type.getDescription().toLowerCase().contains(text.toLowerCase())
|
||||
|| type.getCategory().toLowerCase().contains(text.toLowerCase());
|
||||
} else {
|
||||
visible = payload.toString().toLowerCase().contains(text.toLowerCase());
|
||||
}
|
||||
if (visible != payload.isVisible()) {
|
||||
payload.setVisible(visible);
|
||||
@@ -351,14 +421,14 @@ public class TreePanel extends JPanel {
|
||||
model.nodeStructureChanged(rootNode);
|
||||
}
|
||||
if (categoryVisible && text.trim().length() > 0) {
|
||||
tree.expandPath(new TreePath(categoryNode.getPath()));
|
||||
getTree().expandPath(new TreePath(categoryNode.getPath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < model.getChildCount(rootNode); i++) {
|
||||
DefaultMutableTreeNode categoryNode = (DefaultMutableTreeNode) model.getChild(rootNode, i);
|
||||
tree.expandPath(new TreePath(categoryNode.getPath()));
|
||||
getTree().expandPath(new TreePath(categoryNode.getPath()));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -378,18 +448,21 @@ public class TreePanel extends JPanel {
|
||||
|
||||
Object obj = ((DefaultMutableTreeNode) value).getUserObject();
|
||||
if (obj != null && obj.getClass().equals(Payload.class)) {
|
||||
Payload type = (Payload) obj;
|
||||
if (type.getComponentType() == null) {
|
||||
if (type.isVisible())
|
||||
Payload payload = (Payload) obj;
|
||||
if (payload.getComponentType() == null) {
|
||||
setToolTipText(null);
|
||||
if (leaf)
|
||||
setIcon(IconLoader.Component.getIcon());
|
||||
if (payload.isVisible())
|
||||
setPreferredSize(new Dimension(240, 20));
|
||||
else
|
||||
setPreferredSize(new Dimension(0, 0));
|
||||
} else {
|
||||
setToolTipText("<html><b>" + type.getComponentType().getName() + "</b><br>"
|
||||
+ type.getComponentType().getDescription() + "<br>Author: " + type.getComponentType().getAuthor()
|
||||
setToolTipText("<html><b>" + payload.getComponentType().getName() + "</b><br>"
|
||||
+ payload.getComponentType().getDescription() + "<br>Author: " + payload.getComponentType().getAuthor()
|
||||
+ "<br><br>Left click to instantiate this component, right click for more options" + "</html>");
|
||||
setIcon(type.getComponentType().getIcon());
|
||||
if (type.isVisible())
|
||||
setIcon(payload.getComponentType().getIcon());
|
||||
if (payload.isVisible())
|
||||
setPreferredSize(new Dimension(240, 32));
|
||||
else
|
||||
setPreferredSize(new Dimension(0, 0));
|
||||
@@ -404,16 +477,19 @@ public class TreePanel extends JPanel {
|
||||
private ComponentType componentType;
|
||||
private String category;
|
||||
private boolean isVisible;
|
||||
private MouseListener clickListener;
|
||||
|
||||
public Payload(ComponentType componentType) {
|
||||
public Payload(ComponentType componentType, MouseListener clickListener) {
|
||||
super();
|
||||
this.componentType = componentType;
|
||||
this.clickListener = clickListener;
|
||||
this.isVisible = true;
|
||||
}
|
||||
|
||||
public Payload(String category) {
|
||||
public Payload(String category, MouseListener clickListener) {
|
||||
super();
|
||||
this.category = category;
|
||||
this.clickListener = clickListener;
|
||||
this.isVisible = true;
|
||||
}
|
||||
|
||||
@@ -429,6 +505,10 @@ public class TreePanel extends JPanel {
|
||||
this.isVisible = isVisible;
|
||||
}
|
||||
|
||||
public MouseListener getClickListener() {
|
||||
return clickListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return componentType == null ? category : componentType.getName();
|
||||
|
||||
@@ -1554,6 +1554,10 @@
|
||||
<releaseDate>2017-01-05 20:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Ability to quickly save a group of components as a building block and use it later</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Show/hide rulers</description>
|
||||
|
||||
Reference in New Issue
Block a user