Skip to content

Commit

Permalink
Only use BuilderDropZone when a container is empty.
Browse files Browse the repository at this point in the history
  • Loading branch information
mchilvers committed Mar 29, 2024
1 parent 65c24da commit eda2077
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 40 deletions.
4 changes: 2 additions & 2 deletions examples/calculator/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0">

<!-- PyScript -->
<link rel="stylesheet" href="https://pyscript.net/releases/2024.3.1/core.css">
<script type="module" src="https://pyscript.net/releases/2024.3.1/core.js"></script>
<link rel="stylesheet" href="https://pyscript.net/releases/2024.3.2/core.css">
<script type="module" src="https://pyscript.net/releases/2024.3.2/core.js"></script>

<!-- App CSS Styles -->
<link rel="stylesheet" href="https://unpkg.com/papercss@1.9.2/dist/paper.min.css">
Expand Down
1 change: 0 additions & 1 deletion src/invent/ui/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1200,7 +1200,6 @@ def _update_template_columns(self, element):
else:
template_columns.append("auto")

print("Template columns:", template_columns)
element.style.gridTemplateColumns = " ".join(template_columns)

def _wrap_child(self, child, index):
Expand Down
12 changes: 9 additions & 3 deletions src/invent/ui/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,12 @@ def _app_from_dict(app_dict):
_component_from_dict(component_dict) for component_dict in app_dict["content"]
]

app = App(**{**app_dict, "content": content})
app_dict["content"] = content

app = App(**app_dict)

# Sanity check!
assert app.as_dict() == app_dict
#assert app.as_dict() == app_dict

return app

Expand All @@ -189,7 +191,9 @@ def _component_from_dict(component_dict):
else:
content = None

return cls(**{**component_dict["properties"], "content": content})
component_dict["content"] = content

return cls(**component_dict)


# Internal ###################################################################
Expand Down Expand Up @@ -234,6 +238,8 @@ def _pretty_repr_component(component, lines, indent=""):
"""

if type(component).__name__ == "BuilderDropZone":
if type(component.parent).__name__ == "Grid":
component.parent.columns = component.parent.columns -1
return lines

# The first line of the component's constructor e.g. "Page(".
Expand Down
37 changes: 25 additions & 12 deletions src/tools/builder/public/css/page-editor.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.drop-zone {
padding: 0.05rem;
padding: 1rem;
width: 100%;
background-color: #f3f4f6;
border-width: 1px;
Expand All @@ -9,24 +9,37 @@
text-align: center;
}

.row-container {
border-color: rgb(255 0 0);
}

.column-container {
border-color: rgb(0 255 0);
}

.drop-zone span {
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 500;
}

.drop-zone-active {
background-color: #ede9fe;
border-color: #8b5cf6;
.drop-zone-active-above {
/* border-color: #8b5cf6;*/
/* background-color: #ede9fe;*/
border-top-color: red;
border-top-width: 4px;
border-top-style: solid;
}

.drop-zone-active-below {
border-bottom-color: red;
border-bottom-width: 4px;
border-bottom-style: solid;
}

.drop-zone-active-left {
border-left-color: red;
border-left-width: 4px;
border-left-style: solid;
}

.drop-zone-active-right {
border-right-color: red;
border-right-width: 4px;
border-right-style: solid;
}

.drop-zone-active span {
Expand Down
141 changes: 119 additions & 22 deletions src/tools/builder/src/python/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import invent
from invent.ui import export
from invent.ui.core import Container, Component, Widget, from_datastore
from invent.ui.core import Column, Container, Component, Grid, Row, Widget, from_datastore
from invent.ui import create_component, AVAILABLE_COMPONENTS


Expand All @@ -27,7 +27,7 @@ def __init__(self):
invent.ui.Page(
name="Page 1",
content=[
BuilderDropZone(self),
BuilderDropZone(self, invent.ui.Page),
]
)
]
Expand Down Expand Up @@ -67,29 +67,42 @@ def get_app_from_dict(self, app_dict):
app = export.from_dict({"app": json.loads(app_dict)})

for page in app.content:
self._inject_builder_drop_zones(page)
#self._inject_builder_drop_zones(page)
self._inject_click_handlers(page)

self._app = app

def _inject_builder_drop_zones(self, container):
def _inject_click_handlers(self, container):
"""
Inject a BuilderDropZone around each item in a container.
Recursively Inject JS event handlers onto all Widgets.
"""

self._add_click_handler(container)

for item in container.content[:]:
container.insert(
container.content.index(item), BuilderDropZone(self)
)

if isinstance(item, Container):
self._inject_builder_drop_zones(item)
self._inject_click_handlers(item)

else:
self._add_click_handler(item)

container.append(BuilderDropZone(self))
# def _inject_builder_drop_zones(self, container):
# """
# Inject a BuilderDropZone around each item in a container.
# """
#
# self._add_click_handler(container)
#
# for item in container.content[:]:
# container.insert(
# container.content.index(item), BuilderDropZone(self)
# )
#
# if isinstance(item, Container):
# self._inject_builder_drop_zones(item)
#
# else:
# self._add_click_handler(item)
#
# container.append(BuilderDropZone(self))

# Pages ############################################################################

Expand Down Expand Up @@ -159,20 +172,37 @@ def insert_component_after(self, after_component, component_type_name):
"""

parent = after_component.parent
after_component_index = parent.content.index(after_component)

component = create_component(component_type_name)

after_component_index = parent.content.index(after_component)
if after_component_index == len(parent.content) - 1:
parent.append(component)
parent.append(BuilderDropZone(self))

else:
parent.insert(after_component_index + 1, component)
parent.insert(after_component_index + 2, BuilderDropZone(self))

# If we are inserting a new (and hence *empty* container), give it a drop
# zone to give the user a place to put the first item in it.
if isinstance(component, Container):
component.append(BuilderDropZone(self))
component.append(BuilderDropZone(self, type(component)))

self._add_click_handler(component)

def insert_component_before(self, before_component, component_type_name):
"""
Create and insert a component before another (as a sibling).
"""

parent = before_component.parent
component = create_component(component_type_name)

before_component_index = parent.content.index(before_component)
parent.insert(before_component_index, component)

# If we are inserting a new (and hence *empty* container), give it a drop
# zone to give the user a place to put the first item in it.
if isinstance(component, Container):
component.append(BuilderDropZone(self, type(component)))

self._add_click_handler(component)

Expand Down Expand Up @@ -284,16 +314,75 @@ def on_click_on_component(event):

component.element.addEventListener("click", create_proxy(on_click_on_component))

def on_drop(event):
event.preventDefault()
event.stopPropagation()
component.element.parentNode.classList.remove(f"drop-zone-active-{self.mode}")
component_blueprint = json.loads(event.dataTransfer.getData("widget"))
component_type_name = component_blueprint["name"]

if self.mode in ["left", "above"]:
self.insert_component_before(
before_component=component, component_type_name=component_type_name
)

else:
self.insert_component_after(
after_component=component, component_type_name=component_type_name
)

def on_dragover(event):
"""
Handle a JS "dragover" event on a component.
"""

event.preventDefault()
event.stopPropagation()

pointer_offset_x = event.offsetX
pointer_offset_y = event.offsetY
component_width = component.element.offsetWidth
component_height = component.element.offsetHeight

if isinstance(component.parent, Column):
if pointer_offset_y < (component_height * .5):
self.mode = "above"

elif pointer_offset_y > (component_height * .5):
self.mode = "below"

elif isinstance(component.parent, Row):
if pointer_offset_x < (component_width * .5):
self.mode = "left"

elif pointer_offset_x > (component_width * .5):
self.mode = "right"

for class_name in component.element.parentNode.classList:
if class_name.startswith("drop-zone-active"):
component.element.parentNode.classList.remove(class_name)

component.element.parentNode.classList.add(f"drop-zone-active-{self.mode}")

def on_dragleave(event):
event.preventDefault()
event.stopPropagation()
component.element.parentNode.classList.remove(f"drop-zone-active-{self.mode}")

component.element.addEventListener("dragover", create_proxy(on_dragover))
component.element.addEventListener("dragleave", create_proxy(on_dragleave))
component.element.addEventListener("drop", create_proxy(on_drop))


class BuilderDropZone(Widget):
"""
A drop zone used ONLY in the builder to position components on a page.
"""

def __init__(self, builder, **kwargs):
super().__init__(**kwargs)
def __init__(self, builder, container_type, **kwargs):
self.container_type = container_type
self.builder = builder
super().__init__(**kwargs)

def on_dragover(self, event):
event.preventDefault()
Expand All @@ -311,16 +400,24 @@ def on_drop(self, event):
self.element.classList.remove("drop-zone-active")
component_blueprint = json.loads(event.dataTransfer.getData("widget"))

self.builder.insert_component_after(
after_component=self, component_type_name=component_blueprint["name"]
# Now that the container is no longer empty - we can get rid of the drop zone.
print("Drop Zone id", self.id)
parent_id = self.parent.id

self.builder.append_component(
parent_id, component_type_name=component_blueprint["name"]
)
self.builder.delete_component(self.id)


def render(self):
"""
Create the component's HTML element.
"""
element = document.createElement("div")
element.id = self.id

element.innerText = f"Drop yo' stuff on this here {self.container_type.__name__}!"
element.classList.add("drop-zone")
element.addEventListener("dragover", create_proxy(self.on_dragover))
element.addEventListener("dragleave", create_proxy(self.on_dragleave))
Expand Down

0 comments on commit eda2077

Please sign in to comment.