diff --git a/apps/hello/main.py b/apps/hello/main.py
index 4d582a8c8..fb11524dc 100644
--- a/apps/hello/main.py
+++ b/apps/hello/main.py
@@ -20,7 +20,10 @@ def update(state, session):
main_df = _get_main_df()
main_df = main_df[main_df['length_cm'] >= state["filter"]["min_length"]]
main_df = main_df[main_df['weight_g'] >= state["filter"]["min_weight"]]
- state["main_df"] = main_df
+ state['main_df'] = main_df
+
+ paginated_members = _get_paginated_members(state['paginated_members_page'] - 1, state['paginated_members_page_size'])
+ state['paginated_members'] = paginated_members
state["session"] = session
_update_metrics(state)
_update_role_chart(state)
@@ -33,6 +36,22 @@ def handle_story_download(state):
state.file_download(data, file_name)
+def handle_paginated_members_page_change(state, payload):
+ page = payload
+ maxpage = int(state["paginated_members_total_items"] / state["paginated_members_page_size"]) + 1
+ if page > maxpage:
+ state["paginated_members_page"] = maxpage - 2
+ else:
+ state["paginated_members_page"] = page
+
+ update(state, None)
+
+
+def handle_paginated_members_page_size_change(state, payload):
+ state['paginated_members_page_size'] = payload
+ update(state, None)
+
+
# LOAD / GENERATE DATA
@@ -47,12 +66,15 @@ def _get_main_df():
main_df = pd.read_csv("assets/main_df.csv")
return main_df
-
def _get_highlighted_members():
sample_df = _get_main_df().sample(3).set_index("name", drop=False)
sample = sample_df.to_dict("index")
return sample
+def _get_paginated_members(offset: int, limit: int):
+ paginated_df = _get_main_df()[offset:offset + limit].set_index("name", drop=False)
+ paginated = paginated_df.to_dict("index")
+ return paginated
def _get_story_text():
with open("assets/story.txt", "r") as f:
@@ -117,6 +139,10 @@ def _update_scatter_chart(state):
"highlighted_members": _get_highlighted_members(),
"random_df": _generate_random_df(),
"hue_rotation": 26,
+ "paginated_members": _get_paginated_members(0, 2),
+ "paginated_members_page": 1,
+ "paginated_members_total_items": len(_get_main_df()),
+ "paginated_members_page_size": 2,
"story": {
"text": _get_story_text(), # For display
},
diff --git a/apps/hello/ui.json b/apps/hello/ui.json
index 6c184f207..b67253f77 100644
--- a/apps/hello/ui.json
+++ b/apps/hello/ui.json
@@ -270,7 +270,7 @@
"width": "1",
"isCollapsible": "",
"startCollapsed": "",
- "horizontalAlignment": "center"
+ "contentHAlign": "center"
},
"parentId": "fb22acfc-cdb5-44b6-9e97-76c3a51a8fff",
"position": 2
@@ -351,7 +351,7 @@
"name": "Timer"
},
"parentId": "ee919cd6-8153-4f34-8c6a-bfc1153df360",
- "position": 3
+ "position": 4
},
"db4c66d6-1eb7-44d3-a2d4-65d0b3e5cf12": {
"id": "db4c66d6-1eb7-44d3-a2d4-65d0b3e5cf12",
@@ -367,7 +367,7 @@
"id": "09ddb2da-6fa3-4157-8da3-4d5d44a6a58d",
"type": "horizontalstack",
"content": {
- "alignment": "left"
+ "contentHAlign": "start"
},
"parentId": "85120b55-69c6-4b50-853a-bbbf73ff8121",
"position": 0
@@ -591,7 +591,7 @@
"content": {
"width": "1",
"verticalAlignment": "",
- "horizontalAlignment": ""
+ "contentHAlign": ""
},
"parentId": "b9cb10e5-1ead-448b-afcc-909e23afb72a",
"position": 0
@@ -843,7 +843,7 @@
"id": "9bb8a686-7013-4af7-a89e-d89c7754120d",
"type": "horizontalstack",
"content": {
- "alignment": "left"
+ "contentHAlign": "start"
},
"parentId": "771dc336-69b2-400e-9ea3-e881e2332c9d",
"position": 2,
@@ -911,6 +911,70 @@
"click": "handle_story_download"
},
"visible": true
+ },
+ "e1ax8ctt8lrao0e4": {
+ "id": "e1ax8ctt8lrao0e4",
+ "type": "tab",
+ "content": {
+ "name": "Pagination"
+ },
+ "parentId": "ee919cd6-8153-4f34-8c6a-bfc1153df360",
+ "position": 3,
+ "handlers": {},
+ "visible": true
+ },
+ "j3jkho6tb97u0onr": {
+ "id": "j3jkho6tb97u0onr",
+ "type": "repeater",
+ "content": {
+ "keyVariable": "itemId",
+ "valueVariable": "item",
+ "repeaterObject": "@{paginated_members}"
+ },
+ "parentId": "e1ax8ctt8lrao0e4",
+ "position": 0,
+ "handlers": {},
+ "visible": true
+ },
+ "4wzaubf275w17gac": {
+ "id": "4wzaubf275w17gac",
+ "type": "section",
+ "content": {
+ "title": "@{item.name} \u2b50\ufe0f"
+ },
+ "parentId": "j3jkho6tb97u0onr",
+ "position": 0,
+ "handlers": {},
+ "visible": true
+ },
+ "19binb4yi70gesho": {
+ "id": "19binb4yi70gesho",
+ "type": "text",
+ "content": {
+ "text": "**Role:** @{item.role}\n",
+ "useMarkdown": "yes"
+ },
+ "parentId": "4wzaubf275w17gac",
+ "position": 0
+ },
+ "zfp1koasiuleygmz": {
+ "id": "zfp1koasiuleygmz",
+ "type": "pagination",
+ "content": {
+ "page": "@{paginated_members_page}",
+ "pageSize": "@{paginated_members_page_size}",
+ "totalItems": "@{paginated_members_total_items}",
+ "pageSizeOptions": "1,2,5",
+ "pageSizeShowAll": "no",
+ "jumpTo": "no"
+ },
+ "parentId": "e1ax8ctt8lrao0e4",
+ "position": 1,
+ "handlers": {
+ "ss-change-page": "handle_paginated_members_page_change",
+ "ss-change-page-size": "handle_paginated_members_page_size_change"
+ },
+ "visible": true
}
}
}
\ No newline at end of file
diff --git a/docs/docs/frontend-scripts.md b/docs/docs/frontend-scripts.md
index 7ca609e8e..425f9981f 100644
--- a/docs/docs/frontend-scripts.md
+++ b/docs/docs/frontend-scripts.md
@@ -71,6 +71,20 @@ initial_state.import_script("my_script", "/static/script.js")
Importing scripts is useful to import libraries that don't support ES6 modules. When possible, use ES6 modules. The `import_script` syntax is only used for side effects; you'll only be able to call functions from the backend using modules that have been previously imported via `import_frontend_module`.
:::
+## Importing a script or stylesheet from a URL
+
+Streamsync can also import scripts and stylesheets from URLs. This is useful for importing libraries from CDNs. The `import_script` and `import_stylesheet` methods take a `url` argument, which is the URL to the script or stylesheet.
+
+```python
+initial_state = ss.init_state({
+ "my_app": {
+ "title": "My App"
+ },
+})
+
+initial_state.import_script("lodash", "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.js")
+```
+
## Frontend core
You can access Streamsync's frontend core via `globalThis.core`, unlocking all sorts of functionality. Notably, you can use `getUserState()` to get values from state.
diff --git a/src/streamsync/__init__.py b/src/streamsync/__init__.py
index 71eb1a9aa..d982e2a37 100644
--- a/src/streamsync/__init__.py
+++ b/src/streamsync/__init__.py
@@ -2,7 +2,7 @@
from streamsync.core import Readable, FileWrapper, BytesWrapper, Config
from streamsync.core import initial_state, component_manager, session_manager, session_verifier
-VERSION = "0.2.8"
+VERSION = "0.3.0"
component_manager
session_manager
diff --git a/src/streamsync/app_runner.py b/src/streamsync/app_runner.py
index 2d43c9be0..af2d3aa5b 100644
--- a/src/streamsync/app_runner.py
+++ b/src/streamsync/app_runner.py
@@ -25,6 +25,8 @@
import watchdog.events
from streamsync import VERSION
+logging.basicConfig(level=logging.INFO, format='%(message)s')
+
class MessageHandlingException(Exception):
pass
@@ -631,7 +633,7 @@ async def dispatch_message(self, session_id: Optional[str], request: AppProcessS
def _load_persisted_script(self) -> str:
try:
contents = None
- with open(os.path.join(self.app_path, "main.py"), "r") as f:
+ with open(os.path.join(self.app_path, "main.py"), "r", encoding='utf-8') as f:
contents = f.read()
return contents
except FileNotFoundError:
@@ -799,4 +801,4 @@ async def notify_of_code_update(self):
try:
self.code_update_condition.notify_all()
finally:
- self.code_update_condition.release()
\ No newline at end of file
+ self.code_update_condition.release()
diff --git a/src/streamsync/app_templates/hello/main.py b/src/streamsync/app_templates/hello/main.py
index 4d582a8c8..fb11524dc 100644
--- a/src/streamsync/app_templates/hello/main.py
+++ b/src/streamsync/app_templates/hello/main.py
@@ -20,7 +20,10 @@ def update(state, session):
main_df = _get_main_df()
main_df = main_df[main_df['length_cm'] >= state["filter"]["min_length"]]
main_df = main_df[main_df['weight_g'] >= state["filter"]["min_weight"]]
- state["main_df"] = main_df
+ state['main_df'] = main_df
+
+ paginated_members = _get_paginated_members(state['paginated_members_page'] - 1, state['paginated_members_page_size'])
+ state['paginated_members'] = paginated_members
state["session"] = session
_update_metrics(state)
_update_role_chart(state)
@@ -33,6 +36,22 @@ def handle_story_download(state):
state.file_download(data, file_name)
+def handle_paginated_members_page_change(state, payload):
+ page = payload
+ maxpage = int(state["paginated_members_total_items"] / state["paginated_members_page_size"]) + 1
+ if page > maxpage:
+ state["paginated_members_page"] = maxpage - 2
+ else:
+ state["paginated_members_page"] = page
+
+ update(state, None)
+
+
+def handle_paginated_members_page_size_change(state, payload):
+ state['paginated_members_page_size'] = payload
+ update(state, None)
+
+
# LOAD / GENERATE DATA
@@ -47,12 +66,15 @@ def _get_main_df():
main_df = pd.read_csv("assets/main_df.csv")
return main_df
-
def _get_highlighted_members():
sample_df = _get_main_df().sample(3).set_index("name", drop=False)
sample = sample_df.to_dict("index")
return sample
+def _get_paginated_members(offset: int, limit: int):
+ paginated_df = _get_main_df()[offset:offset + limit].set_index("name", drop=False)
+ paginated = paginated_df.to_dict("index")
+ return paginated
def _get_story_text():
with open("assets/story.txt", "r") as f:
@@ -117,6 +139,10 @@ def _update_scatter_chart(state):
"highlighted_members": _get_highlighted_members(),
"random_df": _generate_random_df(),
"hue_rotation": 26,
+ "paginated_members": _get_paginated_members(0, 2),
+ "paginated_members_page": 1,
+ "paginated_members_total_items": len(_get_main_df()),
+ "paginated_members_page_size": 2,
"story": {
"text": _get_story_text(), # For display
},
diff --git a/src/streamsync/app_templates/hello/ui.json b/src/streamsync/app_templates/hello/ui.json
index 6c184f207..f68ce177e 100644
--- a/src/streamsync/app_templates/hello/ui.json
+++ b/src/streamsync/app_templates/hello/ui.json
@@ -270,7 +270,7 @@
"width": "1",
"isCollapsible": "",
"startCollapsed": "",
- "horizontalAlignment": "center"
+ "contentHAlign": "center"
},
"parentId": "fb22acfc-cdb5-44b6-9e97-76c3a51a8fff",
"position": 2
@@ -351,7 +351,7 @@
"name": "Timer"
},
"parentId": "ee919cd6-8153-4f34-8c6a-bfc1153df360",
- "position": 3
+ "position": 4
},
"db4c66d6-1eb7-44d3-a2d4-65d0b3e5cf12": {
"id": "db4c66d6-1eb7-44d3-a2d4-65d0b3e5cf12",
@@ -367,7 +367,7 @@
"id": "09ddb2da-6fa3-4157-8da3-4d5d44a6a58d",
"type": "horizontalstack",
"content": {
- "alignment": "left"
+ "contentHAlign": "start"
},
"parentId": "85120b55-69c6-4b50-853a-bbbf73ff8121",
"position": 0
@@ -591,7 +591,7 @@
"content": {
"width": "1",
"verticalAlignment": "",
- "horizontalAlignment": ""
+ "contentHAlign": ""
},
"parentId": "b9cb10e5-1ead-448b-afcc-909e23afb72a",
"position": 0
@@ -843,7 +843,7 @@
"id": "9bb8a686-7013-4af7-a89e-d89c7754120d",
"type": "horizontalstack",
"content": {
- "alignment": "left"
+ "contentHAlign": "start"
},
"parentId": "771dc336-69b2-400e-9ea3-e881e2332c9d",
"position": 2,
@@ -911,6 +911,71 @@
"click": "handle_story_download"
},
"visible": true
+ },
+ "e1ax8ctt8lrao0e4": {
+ "id": "e1ax8ctt8lrao0e4",
+ "type": "tab",
+ "content": {
+ "name": "Pagination"
+ },
+ "parentId": "ee919cd6-8153-4f34-8c6a-bfc1153df360",
+ "position": 3,
+ "handlers": {},
+ "visible": true
+ },
+ "j3jkho6tb97u0onr": {
+ "id": "j3jkho6tb97u0onr",
+ "type": "repeater",
+ "content": {
+ "keyVariable": "itemId",
+ "valueVariable": "item",
+ "repeaterObject": "@{paginated_members}"
+ },
+ "parentId": "e1ax8ctt8lrao0e4",
+ "position": 0,
+ "handlers": {},
+ "visible": true
+ },
+ "4wzaubf275w17gac": {
+ "id": "4wzaubf275w17gac",
+ "type": "section",
+ "content": {
+ "title": "@{item.name} \u2b50\ufe0f"
+ },
+ "parentId": "j3jkho6tb97u0onr",
+ "position": 0,
+ "handlers": {},
+ "visible": true
+ },
+ "19binb4yi70gesho": {
+ "id": "19binb4yi70gesho",
+ "type": "text",
+ "content": {
+ "text": "**Role:** @{item.role}\n",
+ "useMarkdown": "yes"
+ },
+ "parentId": "4wzaubf275w17gac",
+ "position": 0
+ },
+ "zfp1koasiuleygmz": {
+ "id": "zfp1koasiuleygmz",
+ "type": "pagination",
+ "content": {
+ "page": "@{paginated_members_page}",
+ "pageSize": "@{paginated_members_page_size}",
+ "totalItems": "@{paginated_members_total_items}",
+ "pageSizeOptions": "1,2,5",
+ "pageSizeShowAll": "no",
+ "jumpTo": "no",
+ "urlParam": "no"
+ },
+ "parentId": "e1ax8ctt8lrao0e4",
+ "position": 1,
+ "handlers": {
+ "ss-change-page": "handle_paginated_members_page_change",
+ "ss-change-page-size": "handle_paginated_members_page_size_change"
+ },
+ "visible": true
}
}
}
\ No newline at end of file
diff --git a/src/streamsync/core.py b/src/streamsync/core.py
index 4e6b20b76..2c1353915 100644
--- a/src/streamsync/core.py
+++ b/src/streamsync/core.py
@@ -120,6 +120,8 @@ def serialise(self, v: Any) -> Union[Dict, List, str, bool, int, float, None]:
if "matplotlib.figure.Figure" in v_mro:
return self._serialise_matplotlib_fig(v)
+ if "plotly.graph_objs._figure.Figure" in v_mro:
+ return v.to_json()
if "numpy.float64" in v_mro:
return float(v)
if "numpy.ndarray" in v_mro:
@@ -229,7 +231,7 @@ def apply(self, key) -> None:
def get_mutations_as_dict(self) -> Dict[str, Any]:
serialised_mutations: Dict[str, Union[Dict,
List, str, bool, int, float, None]] = {}
- for key, value in self.state.items():
+ for key, value in list(self.state.items()):
if key.startswith("_"):
continue
escaped_key = key.replace(".", "\.")
@@ -237,7 +239,7 @@ def get_mutations_as_dict(self) -> Dict[str, Any]:
serialised_value = None
if isinstance(value, StateProxy):
if value.initial_assignment:
- serialised_mutations[key] = serialised_value
+ serialised_mutations[escaped_key] = serialised_value
value.initial_assignment = False
child_mutations = value.get_mutations_as_dict()
if child_mutations is None:
@@ -330,19 +332,38 @@ def add_notification(self, type: Literal["info", "success", "warning", "error"],
"message": message,
})
- def _log_entry_in_logger(self, type: Literal["info", "error"], title: str, message: str, code: Optional[str] = None) -> None:
+ def _log_entry_in_logger(self, type: Literal["debug", "info", "warning", "error", "critical"], title: str, message: str, code: Optional[str] = None) -> None:
if not Config.logger:
return
log_args: Tuple[str, ...] = ()
+
if code:
log_args = (title, message, code)
else:
log_args = (title, message)
+
+ log_colors = {
+ "debug": "\x1b[36;20m", # Cyan for debug
+ "info": "\x1b[34;20m", # Blue for info
+ "warning": "\x1b[33;20m", # Yellow for warning
+ "error": "\x1b[31;20m", # Red for error
+ "critical": "\x1b[35;20m" # Magenta for critical
+ }
+
+ log_methods = {
+ "debug": Config.logger.debug,
+ "info": Config.logger.info,
+ "warning": Config.logger.warning,
+ "error": Config.logger.error,
+ "critical": Config.logger.critical
+ }
+
log_message = "From app log: " + ("\n%s" * len(log_args))
- if type == "info":
- Config.logger.info(f"\x1b[34;20m{log_message}\x1b[0m", *log_args)
- elif type == "error":
- Config.logger.error(f"\x1b[31;20m{log_message}\x1b[0m", *log_args)
+
+ color = log_colors.get(type, "\x1b[0m") # Default to no color if type not found
+ log_method = log_methods.get(type, Config.logger.info) # Default to info level if type not found
+
+ log_method(f"{color}{log_message}\x1b[0m", *log_args)
def add_log_entry(self, type: Literal["info", "error"], title: str, message: str, code: Optional[str] = None) -> None:
self._log_entry_in_logger(type, title, message, code)
@@ -643,6 +664,19 @@ def _transform_date_change(self, ev) -> str:
return payload
+ def _transform_change_page_size(self, ev) -> Optional[int]:
+ try:
+ return int(ev.payload)
+ except ValueError:
+ return None
+
+ def _transform_change_page(self, ev) -> Optional[int]:
+ try:
+ return int(ev.payload)
+ except ValueError:
+ return None
+
+
class Evaluator:
diff --git a/src/streamsync/serve.py b/src/streamsync/serve.py
index 0bbe480e6..0bedcd9e4 100644
--- a/src/streamsync/serve.py
+++ b/src/streamsync/serve.py
@@ -2,6 +2,7 @@
import mimetypes
from contextlib import asynccontextmanager
import sys
+import textwrap
from typing import Any, Callable, Dict, List, Optional, Set, Union
import typing
from fastapi import FastAPI, Request, HTTPException
@@ -41,7 +42,9 @@ async def lifespan(app: FastAPI):
app_runner.hook_to_running_event_loop()
app_runner.load()
- if on_load is not None:
+ if on_load is not None \
+ and hasattr(app.state, 'is_server_static_mounted') \
+ and app.state.is_server_static_mounted:
on_load()
try:
@@ -359,8 +362,23 @@ async def stream(websocket: WebSocket):
server_path = os.path.dirname(__file__)
server_static_path = pathlib.Path(server_path) / "static"
- asgi_app.mount(
- "/", StaticFiles(directory=str(server_static_path), html=True), name="server_static")
+ if server_static_path.exists():
+ asgi_app.mount(
+ "/", StaticFiles(directory=str(server_static_path), html=True), name="server_static")
+ asgi_app.state.is_server_static_mounted = True
+ else:
+ logging.error(
+ textwrap.dedent(
+ """\
+ \x1b[31;20mError: Failed to acquire server static path. Streamsync may not be properly built.
+
+ To resolve this issue, try the following steps:
+ 1. Run the 'npm run build' script in the 'ui' directory and then restart the app.
+ 2. Alternatively, launch a UI instance by running 'npm run dev' in the 'ui' directory.
+
+ Please refer to the CONTRIBUTING.md for detailed instructions.\x1b[0m"""
+ )
+ )
# Return
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 000000000..3892a47f8
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,3 @@
+from pathlib import Path
+
+test_app_dir = Path(__file__).resolve().parent / 'testapp'
diff --git a/tests/test_app_runner.py b/tests/test_app_runner.py
index 6bb7d4d9d..39b9d3a2b 100644
--- a/tests/test_app_runner.py
+++ b/tests/test_app_runner.py
@@ -7,6 +7,8 @@
from streamsync.ss_types import EventRequest, InitSessionRequest, InitSessionRequestPayload, StreamsyncEvent
import asyncio
+from tests import test_app_dir
+
class TestAppRunner:
numberinput_instance_path = [
@@ -26,11 +28,11 @@ def test_init_wrong_path(self) -> None:
def test_init_wrong_mode(self) -> None:
with pytest.raises(ValueError):
- AppRunner("./testapp", "virus")
+ AppRunner(test_app_dir, "virus")
@pytest.mark.asyncio
async def test_pre_session(self) -> None:
- ar = AppRunner("./testapp", "run")
+ ar = AppRunner(test_app_dir, "run")
er = EventRequest(
type="event",
payload=StreamsyncEvent(
@@ -48,7 +50,7 @@ async def test_pre_session(self) -> None:
@pytest.mark.asyncio
async def test_valid_session_invalid_event(self) -> None:
- ar = AppRunner("./testapp", "run")
+ ar = AppRunner(test_app_dir, "run")
ar.load()
si = InitSessionRequest(
type="sessionInit",
@@ -74,7 +76,7 @@ async def test_valid_session_invalid_event(self) -> None:
@pytest.mark.asyncio
async def test_valid_event(self) -> None:
- ar = AppRunner("./testapp", "run")
+ ar = AppRunner(test_app_dir, "run")
ar.load()
si = InitSessionRequest(
type="sessionInit",
@@ -103,7 +105,7 @@ async def test_valid_event(self) -> None:
@pytest.mark.asyncio
async def test_bad_event_handler(self) -> None:
- ar = AppRunner("./testapp", "run")
+ ar = AppRunner(test_app_dir, "run")
ar.load()
si = InitSessionRequest(
type="sessionInit",
@@ -131,7 +133,7 @@ async def test_bad_event_handler(self) -> None:
ar.shut_down()
def test_run_code_edit(self) -> None:
- ar = AppRunner("./testapp", "run")
+ ar = AppRunner(test_app_dir, "run")
with pytest.raises(PermissionError):
ar.update_code(None, "exec(virus)")
with pytest.raises(PermissionError):
@@ -151,7 +153,7 @@ async def wait_for_code_update(self, app_runner: AppRunner) -> None:
@pytest.mark.asyncio
async def test_code_update(self) -> None:
- ar = AppRunner("./testapp", "edit")
+ ar = AppRunner(test_app_dir, "edit")
ar.hook_to_running_event_loop()
ar.load()
wait_update_task = asyncio.create_task(self.wait_for_code_update(ar))
diff --git a/tests/test_core.py b/tests/test_core.py
index ce60c4cb7..33fd53845 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -8,11 +8,15 @@
import streamsync as ss
from streamsync.ss_types import StreamsyncEvent
import pandas as pd
+import plotly.express as px
import pytest
import altair
import pyarrow as pa
import urllib
+from pathlib import Path
+from tests import test_app_dir
+
raw_state_dict = {
"name": "Robert",
"age": 1,
@@ -33,7 +37,7 @@
}
sc = None
-with open("testapp/ui.json", "r") as f:
+with open(test_app_dir / "ui.json", "r") as f:
sc = json.load(f).get("components")
ss.Config.is_mail_enabled_for_log = True
@@ -364,7 +368,7 @@ def test_date_change(self) -> None:
class TestFileWrapper():
- file_path = "testapp/assets/myfile.csv"
+ file_path = str(test_app_dir / "assets/myfile.csv")
def test_get_as_dataurl(self) -> None:
fw = FileWrapper(self.file_path, "text/plain")
@@ -381,8 +385,8 @@ def test_get_as_dataurl(self) -> None:
class TestStateSerialiser():
sts = StateSerialiser()
- file_path = "testapp/assets/myfile.csv"
- df_path = "testapp/assets/main_df.csv"
+ file_path = str(test_app_dir / "assets/myfile.csv")
+ df_path = str(test_app_dir / "assets/main_df.csv")
def test_nested_dict(self) -> None:
d = {
@@ -487,6 +491,26 @@ def test_unserialisable_altair(self) -> None:
with pytest.warns(UserWarning):
with pytest.raises(ValueError):
self.sts.serialise(d)
+
+ def test_plotly_should_be_serialize_to_json(self) -> None:
+ """
+ Test that plotly figure should be serialised to json string directly. Serializing the json directly allows you
+ to display datasets that exceed 10,000 records.
+
+ With the default json serializer, a dataset like this blows up memory. Plotly is using internaly orjson as serializer.
+ """
+ # Arrange
+ df = px.data.iris()
+ fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species", symbol="species")
+
+ # Acts
+ json_code = self.sts.serialise(fig)
+
+ # Assert
+ assert isinstance(json_code, str)
+ o = json.loads(json_code)
+ assert 'data' in o
+ assert 'layout' in o
def test_pandas_df(self) -> None:
d = {
diff --git a/tests/test_serve.py b/tests/test_serve.py
index 1f5f528b6..9665590ff 100644
--- a/tests/test_serve.py
+++ b/tests/test_serve.py
@@ -6,12 +6,14 @@
import fastapi.testclient
import pytest
+from tests import test_app_dir
+
class TestServe:
def test_valid(self) -> None:
asgi_app: fastapi.FastAPI = streamsync.serve.get_asgi_app(
- "./testapp", "run")
+ test_app_dir, "run")
with fastapi.testclient.TestClient(asgi_app) as client:
res = client.post("/api/init", json={
"proposedSessionId": None
@@ -46,7 +48,7 @@ def test_valid(self) -> None:
def test_bad_session(self) -> None:
asgi_app: fastapi.FastAPI = streamsync.serve.get_asgi_app(
- "./testapp", "run")
+ test_app_dir, "run")
with fastapi.testclient.TestClient(asgi_app) as client:
with client.websocket_connect("/api/stream") as websocket:
websocket.send_json({
@@ -61,7 +63,7 @@ def test_bad_session(self) -> None:
def test_session_verifier_header(self) -> None:
asgi_app: fastapi.FastAPI = streamsync.serve.get_asgi_app(
- "./testapp", "run")
+ test_app_dir, "run")
with fastapi.testclient.TestClient(asgi_app) as client:
res = client.post("/api/init", json={
"proposedSessionId": None
@@ -73,7 +75,7 @@ def test_session_verifier_header(self) -> None:
def test_session_verifier_cookies(self) -> None:
asgi_app: fastapi.FastAPI = streamsync.serve.get_asgi_app(
- "./testapp", "run")
+ test_app_dir, "run")
with fastapi.testclient.TestClient(asgi_app, cookies={
"fail_cookie": "yes"
}) as client:
@@ -86,7 +88,7 @@ def test_session_verifier_cookies(self) -> None:
def test_session_verifier_pass(self) -> None:
asgi_app: fastapi.FastAPI = streamsync.serve.get_asgi_app(
- "./testapp", "run")
+ test_app_dir, "run")
with fastapi.testclient.TestClient(asgi_app, cookies={
"another_cookie": "yes"
}) as client:
@@ -102,7 +104,7 @@ def test_serve_javascript_file_with_a_valid_content_type(self) -> None:
# Arrange
mimetypes.add_type("text/plain", ".js")
- asgi_app: fastapi.FastAPI = streamsync.serve.get_asgi_app("./testapp", "run")
+ asgi_app: fastapi.FastAPI = streamsync.serve.get_asgi_app(test_app_dir, "run")
with fastapi.testclient.TestClient(asgi_app) as client:
# Acts
res = client.get("/static/file.js")
diff --git a/tests/testapp/main.py b/tests/testapp/main.py
index 2567ac5b0..4efd171b4 100644
--- a/tests/testapp/main.py
+++ b/tests/testapp/main.py
@@ -217,7 +217,7 @@ def _get_altair_chart():
"b": {
"pet_count": 8
},
- "utfࠀ": 23,
+ "utfࠀ": "ثعلب كلب",
"prog_languages": {
"c": {"name": "C"},
"ts": {"name": "TypeScript"},
diff --git a/tests/testapp/ui.json b/tests/testapp/ui.json
index 35736852c..11d36bee7 100644
--- a/tests/testapp/ui.json
+++ b/tests/testapp/ui.json
@@ -1,6 +1,6 @@
{
"metadata": {
- "streamsync_version": "0.2.5"
+ "streamsync_version": "0.3.0a1"
},
"components": {
"root": {
@@ -64,8 +64,7 @@
"id": "9c30af6d-4ee5-4782-9169-0f361d67fa76",
"type": "section",
"content": {
- "title": "",
- "snapMode": ""
+ "title": ""
},
"parentId": "d1e01ce1-fab1-4a6e-91a1-1f45f9e57aa5",
"position": 0
@@ -137,8 +136,7 @@
"id": "7e625201-20c2-4b05-951c-d825de28b216",
"type": "section",
"content": {
- "title": "Filter data",
- "snapMode": "no"
+ "title": "Filter data"
},
"parentId": "fbad9feb-5c88-4425-bb17-0d138286a875",
"position": 0
@@ -185,8 +183,7 @@
"id": "70d82458-a08f-4005-8f96-dc8d3ba92fad",
"type": "section",
"content": {
- "title": "About this app",
- "snapMode": "no"
+ "title": "About this app"
},
"parentId": "fbad9feb-5c88-4425-bb17-0d138286a875",
"position": 1
@@ -264,7 +261,7 @@
"type": "column",
"content": {
"width": "1",
- "verticalAlignment": ""
+ "contentVAlign": ""
},
"parentId": "fb22acfc-cdb5-44b6-9e97-76c3a51a8fff",
"position": 0
@@ -337,8 +334,7 @@
"type": "section",
"content": {
"title": "",
- "containerBackgroundColor": "#ebfcff",
- "snapMode": "no"
+ "containerBackgroundColor": "#ebfcff"
},
"parentId": "3cc9c5e9-6c77-401d-ab82-7805d9df760c",
"position": 1
@@ -347,7 +343,7 @@
"id": "919b0d26-ea9b-4364-b5af-865236b3fc3a",
"type": "horizontalstack",
"content": {
- "alignment": "left"
+ "contentHAlign": "start"
},
"parentId": "ec5bc32e-1456-4abd-8d3e-97c640e32339",
"position": 0
@@ -402,7 +398,7 @@
"id": "09ddb2da-6fa3-4157-8da3-4d5d44a6a58d",
"type": "horizontalstack",
"content": {
- "alignment": "left"
+ "contentHAlign": "start"
},
"parentId": "85120b55-69c6-4b50-853a-bbbf73ff8121",
"position": 0
@@ -580,7 +576,6 @@
"type": "section",
"content": {
"title": "@{item.name}",
- "snapMode": "no",
"containerBackgroundColor": "#eff4f6"
},
"parentId": "0dd29423-3867-478a-997e-eeaafb6b811e",
@@ -657,8 +652,8 @@
"content": {
"width": "1",
"not-a-real-field": "not-a-real-value",
- "verticalAlignment": "",
- "horizontalAlignment": ""
+ "contentHAlign": "",
+ "contentVAlign": ""
},
"parentId": "b9cb10e5-1ead-448b-afcc-909e23afb72a",
"position": 0
@@ -1025,8 +1020,7 @@
"id": "2ab19af7-1efa-4012-af35-f01c3d39a409",
"type": "section",
"content": {
- "title": "Payload",
- "snapMode": "no"
+ "title": "Payload"
},
"parentId": "7730df5b-8731-4123-bacc-898e7347b124",
"position": 3,
@@ -1079,8 +1073,7 @@
"id": "3eb28922-ef5c-47de-88aa-d100c503a2f5",
"type": "section",
"content": {
- "title": "Bindings",
- "snapMode": "no"
+ "title": "Bindings"
},
"parentId": "7730df5b-8731-4123-bacc-898e7347b124",
"position": 5,
@@ -1338,8 +1331,7 @@
"id": "ca8f9355-c26b-44a6-85c1-46d2182a576e",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "c55caf0b-39c1-4fe5-8756-7506b3e3a19a",
"position": 0,
@@ -1361,8 +1353,7 @@
"id": "ee91f05a-6cac-4cf0-96dd-090a7bf09bd6",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "d4d9fd0d-b347-42d9-9786-a9873cdd0912",
"position": 0,
@@ -1404,8 +1395,7 @@
"id": "04acdbfb-2e72-45b3-9fad-367cda5164b5",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "c937bbfd-a510-432c-8e58-c50eecd5c67a",
"position": 0,
@@ -1458,8 +1448,7 @@
"id": "c72a3364-03d9-4fb1-ac1f-13a1785e18c0",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "ad8bd11b-3f89-4839-b2d3-4c7f52c267bd",
"position": 0,
@@ -1470,8 +1459,7 @@
"id": "065e4f07-c707-4cc7-8cbe-143be5ab2486",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "e702f506-8ec1-495e-9d8e-e488120b9a7b",
"position": 0,
@@ -1482,8 +1470,7 @@
"id": "511bedb8-f856-451f-8ee4-a123d934b744",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "065e4f07-c707-4cc7-8cbe-143be5ab2486",
"position": 0,
@@ -1494,8 +1481,7 @@
"id": "5ab397b2-4283-44ab-ba9f-4cde3b422641",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "e702f506-8ec1-495e-9d8e-e488120b9a7b",
"position": 1,
@@ -1506,7 +1492,7 @@
"id": "ba15571d-4baa-4c65-b600-45af1c32ca75",
"type": "horizontalstack",
"content": {
- "alignment": "left"
+ "contentHAlign": "start"
},
"parentId": "88ea37a5-eb07-4740-ae42-a3eeeacca310",
"position": 4,
@@ -1594,8 +1580,7 @@
"id": "1b3c8d83-36c1-4c74-9dcd-110160c16081",
"type": "section",
"content": {
- "title": "iframe",
- "snapMode": "no"
+ "title": "iframe"
},
"parentId": "f2c445a4-9d3c-4c38-9fe3-cda02126d5d0",
"position": 0,
@@ -1870,8 +1855,7 @@
"id": "9f589947-0ea9-4a70-9d57-4ae52543be42",
"type": "section",
"content": {
- "title": "",
- "snapMode": "no"
+ "title": ""
},
"parentId": "d15b9cf1-7e79-4ee5-9f0d-7c38c6b6c070",
"position": 0,
@@ -2098,8 +2082,7 @@
"id": "575542f1-bb18-429c-9d6a-706edc7512be",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "35986d56-3a1a-4ded-bb5c-b60c2046756f",
"position": 7,
@@ -2119,8 +2102,7 @@
"id": "35f579dd-4205-413a-9c4f-1caf015a598a",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 1,
@@ -2131,8 +2113,7 @@
"id": "4c65268c-b536-4671-8081-c69be3e84161",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 2,
@@ -2143,8 +2124,7 @@
"id": "436ba1be-1194-4da9-9eb3-a10e9ec019f1",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 5,
@@ -2155,8 +2135,7 @@
"id": "7a89f965-8c1d-473c-a565-947be62faa43",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 45,
@@ -2167,8 +2146,7 @@
"id": "e2d3c1be-c43e-4b8a-84a8-90649da0b55e",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 6,
@@ -2179,8 +2157,7 @@
"id": "ddbedb50-5fbe-43a3-b221-8f7d59c617da",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 7,
@@ -2191,8 +2168,7 @@
"id": "21dda1e9-a972-4f74-972d-d13f5e2941de",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 8,
@@ -2203,8 +2179,7 @@
"id": "6ee3de2e-7333-469f-9ced-1e08d9231117",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 9,
@@ -2215,8 +2190,7 @@
"id": "f0659dad-aa37-457e-80fe-02b900f38694",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 10,
@@ -2227,8 +2201,7 @@
"id": "380f8a0b-38f3-414a-ab9f-7034629f5e8e",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 11,
@@ -2239,8 +2212,7 @@
"id": "e43471a7-2e58-437f-8daf-489b221b464a",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 12,
@@ -2262,8 +2234,7 @@
"id": "71500567-d3db-4d67-89aa-86ea8fecc4ed",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 14,
@@ -2274,8 +2245,7 @@
"id": "0e8d0885-954a-4b95-9cfd-0a8ed2ed8683",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 15,
@@ -2286,8 +2256,7 @@
"id": "a039b488-a523-42bc-84d0-f9b3f5185052",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 16,
@@ -2298,8 +2267,7 @@
"id": "8582c124-474c-4da5-80c5-16bca90f4d98",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 17,
@@ -2310,8 +2278,7 @@
"id": "d88e0ff3-4acb-474c-b37c-9cd7ea51fd26",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 18,
@@ -2322,8 +2289,7 @@
"id": "de7a750f-6c88-4fbb-8962-93714b6be485",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 19,
@@ -2334,8 +2300,7 @@
"id": "110625bc-bad4-4b44-b20f-51339554d77b",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 20,
@@ -2346,8 +2311,7 @@
"id": "eb568d28-e6de-4deb-978b-a10d6cb3c454",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 21,
@@ -2358,8 +2322,7 @@
"id": "4a94898f-01b2-4424-8db9-9ed75e788611",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 22,
@@ -2370,8 +2333,7 @@
"id": "f8136c45-f868-4858-928a-f007f9a7dfbe",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 23,
@@ -2382,8 +2344,7 @@
"id": "1a8ca9b4-afc5-4055-9709-bd27cd7fd7aa",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 24,
@@ -2394,8 +2355,7 @@
"id": "6a308e3d-c5a0-400e-9036-6183aecac714",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 25,
@@ -2406,8 +2366,7 @@
"id": "a71a65fc-8710-40fb-8410-6c676072dab5",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 26,
@@ -2418,8 +2377,7 @@
"id": "ff213e0d-4e33-494d-8765-6738010ea932",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 27,
@@ -2430,8 +2388,7 @@
"id": "25f8e06e-5a86-4e5c-afbc-6191c4146bd8",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 28,
@@ -2442,8 +2399,7 @@
"id": "48cb52fa-bb6a-43e7-aa7a-cbc0b30156b1",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 29,
@@ -2454,8 +2410,7 @@
"id": "7d914a22-e7aa-4340-9b88-36423a5566a3",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 30,
@@ -2466,8 +2421,7 @@
"id": "bcbae34f-72fd-40b3-8678-d4b986b47cd5",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 31,
@@ -2478,8 +2432,7 @@
"id": "574ec17c-ed69-49a6-b991-29354b8a8a76",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 32,
@@ -2490,8 +2443,7 @@
"id": "27a21b92-0ed0-4044-8eb4-69979a285e2a",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 33,
@@ -2502,8 +2454,7 @@
"id": "c759135c-72ed-43b8-ab91-8bce748dcd58",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 35,
@@ -2514,8 +2465,7 @@
"id": "210f45a4-c8da-48b2-a2ff-bca06ca7f8a3",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 36,
@@ -2526,8 +2476,7 @@
"id": "0bdf78ac-2824-4116-8e14-af2c363c3b45",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 37,
@@ -2538,8 +2487,7 @@
"id": "a4a6316d-5fb1-47e2-976b-97265533fc97",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 38,
@@ -2550,8 +2498,7 @@
"id": "aa8ab024-25a5-4d33-82cf-f20146e9cf28",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "4c65268c-b536-4671-8081-c69be3e84161",
"position": 0,
@@ -2562,8 +2509,7 @@
"id": "1c5b7db1-f8fa-4c7b-9daf-db60045fd16e",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 39,
@@ -2585,8 +2531,7 @@
"id": "857dc6fc-d6f7-4dd1-bbc3-6f0dea011d54",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 42,
@@ -2597,8 +2542,7 @@
"id": "aac79665-3341-4784-a105-433baf45dde3",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 43,
@@ -2609,8 +2553,7 @@
"id": "d0f010ac-2048-49f3-91ff-e17e145298ed",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 44,
@@ -2621,8 +2564,7 @@
"id": "b3aa5f2f-d952-4021-a506-7a4515388580",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 46,
@@ -2633,8 +2575,7 @@
"id": "d07b8382-dca7-4061-b0ca-22edd43e06b1",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 47,
@@ -2645,8 +2586,7 @@
"id": "9005a1fc-c560-4ca4-9626-b660031c9c8e",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 48,
@@ -2657,8 +2597,7 @@
"id": "440de4a5-b901-4626-a282-3aee83a007c5",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 49,
@@ -2669,8 +2608,7 @@
"id": "2e8069dd-0017-42f3-a13b-44e19f132c4f",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 50,
@@ -2681,8 +2619,7 @@
"id": "cab6894d-98e1-426f-876c-ca1c81bb7a30",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 51,
@@ -2693,8 +2630,7 @@
"id": "8a937df7-c974-455e-b2bc-40445621ef40",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 52,
@@ -2792,8 +2728,7 @@
"id": "1faaa29d-96d3-404a-b8f5-7d392cd55177",
"type": "section",
"content": {
- "title": "Section Title",
- "snapMode": "no"
+ "title": "Section Title"
},
"parentId": "aade8074-13be-4e11-a405-a71b8138e6ae",
"position": 40,
@@ -2874,33 +2809,33 @@
"c9e4e0d9-771e-47c2-863a-55ce8a98bfa5": {
"id": "c9e4e0d9-771e-47c2-863a-55ce8a98bfa5",
"type": "multiselectinput",
- "parentId": "6010765e-9ac3-4570-84bf-913ae404e03a",
"content": {
"label": "Default checkbox mirrored in multiselect"
},
- "handlers": {},
+ "parentId": "6010765e-9ac3-4570-84bf-913ae404e03a",
"position": 6,
- "visible": true,
+ "handlers": {},
"binding": {
"eventType": "ss-options-change",
"stateRef": "b.default_checkbox"
- }
+ },
+ "visible": true
},
"c1d1f478-3dbb-4009-94a0-9b92404348cd": {
"id": "c1d1f478-3dbb-4009-94a0-9b92404348cd",
"type": "selectinput",
- "parentId": "6010765e-9ac3-4570-84bf-913ae404e03a",
"content": {
"label": "Language",
"options": "{\n \"en\": \"English\",\n \"sp\": \"Spanish\",\n \"pl\": \"Polish\"\n}"
},
- "handlers": {},
+ "parentId": "6010765e-9ac3-4570-84bf-913ae404e03a",
"position": 4,
- "visible": true,
+ "handlers": {},
"binding": {
"eventType": "ss-option-change",
"stateRef": "b.language"
- }
+ },
+ "visible": true
}
}
}
\ No newline at end of file
diff --git a/ui/src/assets/padding-4-side.svg b/ui/src/assets/padding-4-side.svg
new file mode 100644
index 000000000..5e9978938
--- /dev/null
+++ b/ui/src/assets/padding-4-side.svg
@@ -0,0 +1,7 @@
+
diff --git a/ui/src/assets/padding-bottom-side.svg b/ui/src/assets/padding-bottom-side.svg
new file mode 100644
index 000000000..ea2213d05
--- /dev/null
+++ b/ui/src/assets/padding-bottom-side.svg
@@ -0,0 +1,4 @@
+
diff --git a/ui/src/assets/padding-left-side.svg b/ui/src/assets/padding-left-side.svg
new file mode 100644
index 000000000..77fd70dab
--- /dev/null
+++ b/ui/src/assets/padding-left-side.svg
@@ -0,0 +1,4 @@
+
diff --git a/ui/src/assets/padding-right-side.svg b/ui/src/assets/padding-right-side.svg
new file mode 100644
index 000000000..a3893d6b7
--- /dev/null
+++ b/ui/src/assets/padding-right-side.svg
@@ -0,0 +1,4 @@
+
diff --git a/ui/src/assets/padding-top-side.svg b/ui/src/assets/padding-top-side.svg
new file mode 100644
index 000000000..a02038512
--- /dev/null
+++ b/ui/src/assets/padding-top-side.svg
@@ -0,0 +1,4 @@
+
diff --git a/ui/src/assets/padding-x-side.svg b/ui/src/assets/padding-x-side.svg
new file mode 100644
index 000000000..855629910
--- /dev/null
+++ b/ui/src/assets/padding-x-side.svg
@@ -0,0 +1,5 @@
+
diff --git a/ui/src/assets/padding-y-side.svg b/ui/src/assets/padding-y-side.svg
new file mode 100644
index 000000000..19f6981e9
--- /dev/null
+++ b/ui/src/assets/padding-y-side.svg
@@ -0,0 +1,5 @@
+
diff --git a/ui/src/builder/BuilderFieldsAlign.vue b/ui/src/builder/BuilderFieldsAlign.vue
new file mode 100644
index 000000000..6b21edd45
--- /dev/null
+++ b/ui/src/builder/BuilderFieldsAlign.vue
@@ -0,0 +1,266 @@
+
+