Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for proxy lists #22

Merged
merged 6 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/definitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ The __YAML__ structure follow the given hierarchy:
2. Property name: Unique string within current proxy representing an entry in your data model.
1. Property caracteristics:
1. size: How many values should be stored for that property. Skipping size attribute will imply a size of 1.
If size is superior to 1, or set to -1, the property will be interpreted as a list.
If size is set to -1, the size is dynamic, otherwise it is fixed.
For vue3, only lists of primitive types (uint, int, float, bool, string) are supported.
For vue2, proxy lists are also supported. `proxyType` must then be set to define the type of proxy listed
2. type: What kind of values that property is holding.
1. uint(8,16,32,64): Unsigned integer encoded on 8, 16, 32, 64 bytes.
2. int(8,16,32,64): Signed integer encoded on 8, 16, 32, 64 bytes.
Expand Down
38 changes: 38 additions & 0 deletions examples/07_ProxyList/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from pathlib import Path
from trame.app import get_server
from trame.ui.vuetify2 import SinglePageLayout
from trame.widgets import vuetify2 as vuetify, simput

from trame_simput import get_simput_manager


# -----------------------------------------------------------------------------
# Trame setup
# -----------------------------------------------------------------------------

server = get_server(client_type="vue2")
state, ctrl = server.state, server.controller

# -----------------------------------------------------------------------------
# Simput initialization
# -----------------------------------------------------------------------------

DEF_DIR = Path(__file__).with_name("definitions")

simput_manager = get_simput_manager()
simput_manager.load_model(yaml_file=DEF_DIR / "model.yaml")
simput_manager.load_ui(xml_file=DEF_DIR / "ui.xml")
simput_widget = simput.Simput(simput_manager, prefix="simput", trame_server=server)

address_book = simput_manager.proxymanager.create("AddressBook")


with SinglePageLayout(server) as layout:
simput_widget.register_layout(layout)
with layout.content:
with vuetify.VContainer(fluid=True):
simput.SimputItem(item_id=f"{address_book.id}")


if __name__ == "__main__":
server.start()
17 changes: 17 additions & 0 deletions examples/07_ProxyList/definitions/model.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Person:
_label: Personal Informations
FirstName:
_label: First Name
type: string
initial: John
LastName:
_label: Last Name
type: string
initial: Doe

AddressBook:
Persons:
type: proxy
size: -1
initial: []
proxyType: Person
20 changes: 20 additions & 0 deletions examples/07_ProxyList/definitions/ui.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<layouts>
<ui id="AddressBook">
<col>
<row>
<input name="Persons" sizeControl="true" />
</row>
<row>
<proxy name="Persons" sizeControl="true" />
</row>
</col>
</ui>
<ui id="Person">
<col>
<row>
<input name="FirstName" />
<input name="LastName" />
</row>
</col>
</ui>
</layouts>
15 changes: 9 additions & 6 deletions trame_simput/core/proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,12 @@ def set_property(self, name, value):
prop_type = definition.get("type", "string")
safe_value = value
if value is not None:
if prop_type == "proxy" and not isinstance(value, str):
safe_value = value.id
if prop_type == "proxy":
if isinstance(value, list):
if len(value) > 0 and not isinstance(value[0], str):
safe_value = [val.id for val in value]
elif not isinstance(value, str):
safe_value = value.id

# check if change
change_detected = False
Expand Down Expand Up @@ -253,6 +257,8 @@ def get_property(self, name, default=None):
"""Return a property value"""
value = self._properties.get(name, default)
if "proxy" == self.definition.get(name).get("type"):
if isinstance(value, list):
return [self._proxy_manager.get(proxy_id) for proxy_id in value]
return self._proxy_manager.get(self._properties.get(name))

return value
Expand Down Expand Up @@ -321,10 +327,7 @@ def __getitem__(self, name):
"""value = proxy[prop_name]"""

if self._properties and name in self._properties:
if "proxy" == self.definition.get(name).get("type"):
return self._proxy_manager.get(self._properties.get(name))

return self._properties[name]
return self.get_property(name)

logger.error("Proxy[%s] not found", name)

Expand Down
5 changes: 3 additions & 2 deletions trame_simput/core/ui/resolvers/vuetify.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ def __init__(self):
self._labels = None

def get_widget(self, elem):
attributes = {}
model = self._model.get(elem.get("name"), {})
attributes = {key: model[key] for key in model if not key.startswith("_")}
if elem.tag in VUETIFY_MAP:
return VUETIFY_MAP[elem.tag], attributes
elif elem.tag == "input":
domains = self._model.get(elem.get("name"), {}).get("domains", [])
domains = model.get("domains", [])
widget = "sw-text-field"
for domain in domains:
ctype = domain.get("type")
Expand Down
6 changes: 6 additions & 0 deletions trame_simput/module/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ def reset_cache(self):
logger.info("reset_cache")
self.net_cache_domains = {}

@exportRpc("simput.create_proxy")
def create_proxy(self, manager_id, proxy_type):
pxm = get_simput_manager(manager_id).proxymanager
proxy = pxm.create(proxy_type)
return proxy.id

@exportRpc("simput.push")
def push(self, manager_id, id=None, type=None):
logger.info("push")
Expand Down
26 changes: 26 additions & 0 deletions vue2-components/src/core/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,29 @@ export function getSimputManager(id, namespace, client) {
MANAGERS[id] = manager;
return manager;
}

export function getComponentProps(layout, index) {
if (layout === 'vertical') {
return { cols: 12 };
}
if (layout === 'l2') {
return { cols: 6 };
}
if (layout === 'l3') {
return { cols: 4 };
}
if (layout === 'l4') {
return { cols: 3 };
}
if (layout === 'm3-half') {
const props = { cols: 4 };
if (index === 3) {
props.offset = 4;
}
if (index === 5) {
props.offset = 8;
}
return props;
}
return {};
}
60 changes: 59 additions & 1 deletion vue2-components/src/widgets/Proxy/script.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,84 @@
import SimputInput from '../../components/SimputItem';
import { COMPUTED } from '../../core/utils';
import { COMPUTED, getComponentProps } from '../../core/utils';

export default {
name: 'swProxy',
props: {
name: {
type: String,
},
size: {
type: Number,
default: 1,
},
sizeControl: {
type: Boolean,
default: false,
},
proxyType: {
type: String,
},
mtime: {
type: Number,
},
},
components: {
SimputInput,
},
data() {
return {
dynamicSize: this.size,
};
},
computed: {
...COMPUTED.decorator,
model: {
get() {
/* eslint-disable no-unused-expressions */
this.mtime; // force refresh
this.dynamicSize;
const value = this.properties() && this.properties()[this.name];
if (!value && this.size > 1) {
const emptyArray = [];
emptyArray.length = this.size;
return emptyArray;
}
return value;
},
set(v) {
this.properties()[this.name] = v;
},
},
itemId() {
/* eslint-disable no-unused-expressions */
this.mtime; // force refresh
return this.properties()[this.name];
},
computedLayout() {
/* eslint-disable no-unused-expressions */
this.mtime; // force refresh
return this.layout || this.domains()[this.name]?.UI?.layout || 'vertical';
},
computedSize() {
if (Number(this.size) !== 1) {
return Math.max(this.size || 1, this.model?.length || 0);
}
return Number(this.size);
},
computedSizeControl() {
/* eslint-disable no-unused-expressions */
this.mtime; // force refresh
return this.sizeControl || this.domains()[this.name]?.UI?.sizeControl;
},
},
methods: {
getComponentProps(index) {
return getComponentProps(this.computedLayout, index);
},
deleteEntry(index) {
this.model.splice(index, 1);
this.dirty(this.name);
},
},
inject: ['data', 'properties', 'domains', 'dirty'],
};
25 changes: 24 additions & 1 deletion vue2-components/src/widgets/Proxy/template.html
Original file line number Diff line number Diff line change
@@ -1 +1,24 @@
<SimputInput :itemId="itemId" v-show="decorator.show" />
<template v-if="size==1">
<SimputInput :itemId="itemId" v-show="decorator.show" />
</template>
<template v-else>
<v-col>
<v-row
class="py-1"
v-for="i in computedSize"
:key="i"
v-bind="getComponentProps(i-1)"
>
<SimputInput :itemId="itemId[i-1]" v-show="decorator.show" />
<v-btn
v-if="computedSizeControl"
class="ml-2"
icon
x-small
@click="deleteEntry(i - 1)"
>
<v-icon>mdi-minus-circle-outline</v-icon>
</v-btn>
</v-row>
</v-col>
</template>
57 changes: 26 additions & 31 deletions vue2-components/src/widgets/TextField/script.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { COMPUTED } from '../../core/utils';
import { COMPUTED, getComponentProps } from '../../core/utils';

// Layouts: horizontal, vertical, l2, l3, l4
export default {
Expand Down Expand Up @@ -52,6 +52,9 @@ export default {
type: Boolean,
default: false,
},
proxyType: {
type: String,
},
},
data() {
return {
Expand Down Expand Up @@ -145,43 +148,35 @@ export default {
}
this.dynamicSize = this.model.length + 1;
this.model.length = this.dynamicSize;

if (this.newValue === 'null') {
this.model[this.model.length - 1] = null;
} else if (this.newValue === 'same') {
this.model[this.model.length - 1] = this.model[this.model.length - 2];
if (this.type == 'proxy') {
this.getSimput()
.wsClient.getConnection()
.getSession()
.call('simput.create_proxy', [
this.simputChannel.managerId,
this.proxyType,
])
.then((proxy_id) => {
if (proxy_id != undefined) {
this.model[this.dynamicSize - 1] = proxy_id;
this.validate(this.dynamicSize);
}
});
} else {
if (this.newValue === 'null') {
this.model[this.dynamicSize - 1] = null;
} else if (this.newValue === 'same') {
this.model[this.dynamicSize - 1] = this.model[this.dynamicSize - 2];
}
this.validate(this.dynamicSize);
}

this.validate(this.dynamicSize);
},
deleteEntry(index) {
this.model.splice(index, 1);
this.dirty(this.name);
},
getComponentProps(index) {
if (this.computedLayout === 'vertical') {
return { cols: 12 };
}
if (this.computedLayout === 'l2') {
return { cols: 6 };
}
if (this.computedLayout === 'l3') {
return { cols: 4 };
}
if (this.computedLayout === 'l4') {
return { cols: 3 };
}
if (this.computedLayout === 'm3-half') {
const props = { cols: 4 };
if (index === 3) {
props.offset = 4;
}
if (index === 5) {
props.offset = 8;
}
return props;
}
return {};
return getComponentProps(this.computedLayout, index);
},
color(component = 0) {
if (
Expand Down
2 changes: 1 addition & 1 deletion vue2-components/src/widgets/TextField/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</div>
</v-col>
</v-row>
<v-row>
<v-row v-if="type != 'proxy'">
<v-col v-if="size == 1" class="pt-0 pb-1">
<v-text-field
:name="`${data().type}:${name}:${i}`"
Expand Down
Loading