Commit 78ee15d4 authored by Eugene Shen's avatar Eugene Shen

Store contact information in ERP5 Persons

Store a jio_configuration string in ERP5 Persons to share contact info,
remove the entire concept of folders and replace it with rooms instead,
use emails to find contacts, replace ERP5 Python Scripts with pure jIO,
move query helpers to gadget_global and force names to be alphanumeric.
parent b9917283
......@@ -20,8 +20,6 @@
<form class="login-form">
<label>Name:</label>
<input type="text" name="name" required="required" />
<label>Folder:</label>
<input type="text" name="folder" required="required" />
<h3>Remote Storage</h3>
<label>
......@@ -44,7 +42,7 @@
<h3>Storage Configuration</h3>
<a class="dropbox-link">Dropbox OAuth</a>
<label>Dropbox folder:</label>
<input type="text" name="remote_dropbox_url" placeholder="/Apps/OfficeJS Chat/.jio_documents" />
<input type="text" name="remote_dropbox_folder" placeholder="/Apps/OfficeJS Chat/.jio_documents/" />
<label>ERP5 URL:</label>
<input type="text" name="remote_erp5_url" placeholder="https://softinst75770.host.vifib.net/erp5/web_site_module/" />
<label>WebDAV URL:</label>
......@@ -58,11 +56,9 @@
<label>Default storage:</label>
<input type="text" name="auth" placeholder="dropbox" />
<label>Dropbox folder:</label>
<input type="text" name="auth_dropbox_url" placeholder="/Apps/OfficeJS Chat" />
<input type="text" name="auth_dropbox_folder" placeholder="/Apps/OfficeJS Chat/" />
<label>ERP5 URL:</label>
<input type="text" name="auth_erp5_url" placeholder="https://softinst75770.host.vifib.net/erp5/webrtc_rooms_module/" />
<label>ERP5 Hateoas:</label>
<input type="text" name="auth_hateoas_url", placeholder="https://softinst75770.host.vifib.net/erp5/web_site_module/hateoas" />
<br />
<input type="submit" name="login" value="Login!" />
</form>
......
......@@ -2,12 +2,10 @@
/* URL Options:
* name: your chat username, e.g. eyqs
* folder: the root folder for your organization, e.g. nexedi;
* you can only chat with people with access to the same auth folder
* remote: the type of remote storage to use,
* either dav, dropbox, erp5, or local
* remote_dropbox_url: the remote Dropbox folder to synchronize with,
* e.g. /Apps/OfficeJS Chat/.jio_documents
* remote_dropbox_folder: the remote Dropbox folder to synchronize with,
* e.g. /Apps/OfficeJS Chat/.jio_documents/
* remote_erp5_url: the remote ERP5 instance to synchronize with,
* e.g. https://softinst75770.host.vifib.net/erp5/web_site_module/
* remote_dav_url: the remote WebDAV server to synchronize with,
......@@ -16,52 +14,20 @@
* remote_dav_pass: your WebDAV password, e.g. correct horse battery staple
* auth: the default type of authentication to use for WebRTC rooms,
* either dropbox or erp5
* auth_dropbox_url: the shared Dropbox folder to act as your auth folder,
* e.g. /Apps/OfficeJS Chat
* auth_erp5_url: the shared ERP5 instance to act as your auth folder,
* e.g. https://softinst75770.host.vifib.net/erp5/webrtc_rooms_module/
* auth_dropbox_folder: the shared Dropbox folder for WebRTC negotiations,
* e.g. /Apps/OfficeJS Chat/
* auth_erp5_url: the shared ERP5 instance to store WebRTC negotiations,
* e.g. https://softinst75770.host.vifib.net/erp5/web_site_module/
*/
function cleanId(input_id) {
const reserved = ["_", "&", "=", ",", ";", "\\"];
for (let i = 0, i_len = reserved.length; i < i_len; i++) {
input_id = input_id.replace(reserved[i], "-");
}
return input_id;
}
function getQueryValue(query_list, query_string) {
for (let i = 0, i_len = query_list.length; i < i_len; i++) {
const query = query_list[i];
if (query_string.indexOf(query + "=") !== -1) {
const start = query_string.indexOf(query + "=") + query.length + 1;
let end = query_string.indexOf("&", start);
if (end === -1) {
end = query_string.length;
}
return query_string.slice(start, end);
}
}
return null;
}
function setQueryValue(query_list, query_string, element) {
value = getQueryValue(query_list, query_string);
if (value) {
element.value = value;
}
}
rJS(window)
.ready(function (gadget) {
gadget.state_parameter_dict = {
element: null,
name: null,
folder: null,
auth: null,
dropbox_url: null,
dropbox_folder: null,
erp5_url: null,
hateoas_url: null,
// Dropbox OAuth only allows 500 characters in the state query
max_state_length: 500,
};
......@@ -105,6 +71,38 @@
.push(null, logError);
})
.allowPublicAcquisition('getContactByEmail', function (param_list) {
const gadget = this;
const email = param_list[0];
let storage_gadget;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("storage_gadget");
})
.push(function (jio_gadget) {
storage_gadget = jio_gadget;
return storage_gadget.allDocs({
limit: [0, 1000000],
query: 'portal_type: "Person"',
sort_on: [["last_modified", "descending"]],
select_list: ["default_email_coordinate_text", "jio_configuration"],
});
})
.push(function (person_list) {
if (person_list.data.rows.length) {
for (let i = 0, i_len = person_list.data.rows.length;
i < i_len; i++) {
if (person_list.data.rows[i].value
.default_email_coordinate_text === email) {
return person_list.data.rows[i].id;
}
}
}
return person_list.data.rows.length.toString();
})
.push(null, logError);
})
.allowPublicAcquisition('chooseRoom', function (param_list) {
const gadget = this;
const param_dict = param_list[0];
......@@ -122,12 +120,10 @@
.push(function (chat_gadget) {
const fields = webrtc_gadget.state_parameter_dict.element
.querySelector(".contact-form").elements;
fields.dropbox_url.value = param_dict.dropbox_url
|| gadget.state_parameter_dict.dropbox_url;
fields.dropbox_folder.value = param_dict.dropbox_folder
|| gadget.state_parameter_dict.dropbox_folder;
fields.erp5_url.value = param_dict.erp5_url
|| gadget.state_parameter_dict.erp5_url;
fields.hateoas_url.value = param_dict.hateoas_url
|| gadget.state_parameter_dict.hateoas_url;
webrtc_gadget.state_parameter_dict.element.querySelector(".auth-form")
.auth.value = param_dict.auth || gadget.state_parameter_dict.auth;
chat_gadget.state_parameter_dict.element
......@@ -136,7 +132,6 @@
chat_gadget.state_parameter_dict.element
.querySelector(".chat-right-panel-chat"));
webrtc_gadget.state_parameter_dict.login_dict = {
folder: gadget.state_parameter_dict.folder,
room: param_dict.room,
name: gadget.state_parameter_dict.name,
role: param_dict.role,
......@@ -192,10 +187,10 @@
})
.declareMethod('render', function () {
let url = decodeURIComponent(document.URL);
const gadget = this;
return new RSVP.Queue()
.push(function () {
let url = decodeURIComponent(document.URL);
const state_output = getQueryValue(["state"], url);
if (state_output) {
url += window.atob(state_output);
......@@ -204,12 +199,11 @@
.querySelector(".login-form").elements;
let state_input = "";
// A list of login-form fields and short keys in order of priority
const query_list = [["name", "m"], ["folder", "f"], ["remote", "r"],
["remote_dropbox_url", "rd"], ["remote_erp5_url", "re"],
const query_list = [["name", "m"], ["remote", "r"],
["remote_dropbox_folder", "rd"], ["remote_erp5_url", "re"],
["remote_dav_url", "rv"], ["remote_dav_user", "rvu"],
["remote_dav_pass", "rvp"], ["auth", "a"],
["auth_dropbox_url", "ad"], ["auth_erp5_url", "ae"],
["auth_hateoas_url", "ah"]]
["auth_dropbox_folder", "ad"], ["auth_erp5_url", "ae"]]
for (let i = 0, i_len = query_list.length; i < i_len; i++) {
const query = query_list[i];
setQueryValue(query, url, fields[query[0]]);
......@@ -246,27 +240,28 @@
const fields = gadget.state_parameter_dict.element
.querySelector(".login-form").elements;
gadget.state_parameter_dict.auth = fields.auth.value;
gadget.state_parameter_dict.dropbox_url =
fields.auth_dropbox_url.value;
gadget.state_parameter_dict.dropbox_folder =
fields.auth_dropbox_folder.value;
gadget.state_parameter_dict.erp5_url = fields.auth_erp5_url.value;
gadget.state_parameter_dict.hateoas_url =
fields.auth_hateoas_url.value;
gadget.state_parameter_dict.name = cleanId(fields.name.value);
gadget.state_parameter_dict.folder = cleanId(fields.folder.value);
gadget.state_parameter_dict.name =
fields.name.value.replace(/[^0-9a-z]/gi, '');
chat_gadget.state_parameter_dict.name =
gadget.state_parameter_dict.name;
chat_gadget.state_parameter_dict.folder =
gadget.state_parameter_dict.folder;
// XXX apply drivetojiomapping in custom dropbox_url directory
return gadget.createJio({
remote: fields.remote.value,
dropbox_url: fields.remote_dropbox_url.value,
dropbox_folder: fields.remote_dropbox_folder.value,
erp5_url: fields.remote_erp5_url.value,
dav_url: fields.remote_dav_url.value,
dav_user: fields.remote_dav_user.value,
dav_pass: fields.remote_dav_pass.value,
});
})
.push(function () {
return gadget.getDeclaredGadget("storage_gadget");
})
.push(function (storage_gadget) {
return storage_gadget.repair();
})
.push(function () {
return chat_gadget.render();
})
......@@ -346,8 +341,8 @@
use_remote_post: remote_post,
conflict_handling: 2,
query: {
query: 'portal_type: "Text Post"',
limit: [0, 1000000000],
query: 'portal_type: "Person" OR portal_type: "Text Post"',
},
local_sub_storage: {
type: "query",
......
......@@ -54,8 +54,8 @@
}
function isSameMessage(lhs, rhs) {
return lhs !== undefined && rhs !== undefined && lhs.name === rhs.name
&& lhs.content === rhs.content && lhs.folder === rhs.folder
return lhs !== undefined && rhs !== undefined
&& lhs.name === rhs.name && lhs.content === rhs.content
&& lhs.room === rhs.room && getTime(lhs) === getTime(rhs);
}
......@@ -144,15 +144,13 @@
return new RSVP.Queue()
.push(function () {
gadget.state_parameter_dict = {
folder: null,
room: null,
name: null,
element: null,
initialized: false,
// A set of names of rooms joined within this folder;
// as a guest, only notify that you have joined once
// you have received the host's remote archive, so that
// your notification doesn't override all previous messages
// A set of names of current rooms; as a guest, only notify that
// you have joined once you have received the host's remote archive,
// so that your notification doesn't override all previous messages
// room_set["room"] = false if joined, true if also notified
room_set: {},
// A dictionary of messages based on the current room, e.g.
......@@ -183,12 +181,12 @@
const gadget = this;
return new RSVP.Queue()
.push(function () {
gadget.state_parameter_dict.room = gadget.state_parameter_dict.name;
gadget.state_parameter_dict.element
.querySelector(".send-form input").onfocus = function () {
return logQueue(
gadget.notifyStatus(gadget.state_parameter_dict.room, false));
};
gadget.state_parameter_dict.room = gadget.state_parameter_dict.name;
})
.push(null, logError);
})
......@@ -199,32 +197,31 @@
const gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.wrapJioAccess('allDocs');
})
.push(function (document_list) {
const list = document_list.data.rows;
const promise_list = [];
for (let i = 0, i_len = list.length; i < i_len; i++) {
promise_list.push(gadget.wrapJioAccess('get', list[i].id));
}
return RSVP.all(promise_list);
return gadget.wrapJioAccess('allDocs', {
limit: [0, 1000000],
query: 'portal_type: "Text Post"',
sort_on: [["last_modified", "descending"]],
select_list: ["content"],
});
})
.push(function (message_list) {
const message_queue = new FastPriorityQueue(messageTimeCompare(true));
for (let i = 0, i_len = message_list.length; i < i_len; i++) {
if (message_list.data.rows.length) {
for (let i = 0, i_len = message_list.data.rows.length;
i < i_len; i++) {
try {
const message = JSON.parse(message_list[i].content);
const message = JSON.parse(list[i]);
if (message && typeof message === "object") {
message_queue.add(message);
}
} catch (error) {}
}
}
const promise_list = [];
const message_dict = gadget.state_parameter_dict.last_message_dict;
while (!message_queue.isEmpty()) {
const message = message_queue.poll();
if (message.folder === gadget.state_parameter_dict.folder
&& (message.room in gadget.state_parameter_dict.room_set)) {
if (message.room in gadget.state_parameter_dict.room_set) {
promise_list.push(gadget.storeList(message));
promise_list.push(gadget.appendMessage(message));
}
......@@ -346,7 +343,6 @@
return {
type: param_dict.type || "message",
name: param_dict.name || gadget.state_parameter_dict.name,
folder: param_dict.folder || gadget.state_parameter_dict.folder,
room: param_dict.room || gadget.state_parameter_dict.room,
time: param_dict.time || new Date(),
content: param_dict.content || "",
......@@ -421,20 +417,16 @@
// Store message in the archive
.declareMethod('storeArchive', function (message) {
const gadget = this;
return new RSVP.Queue()
.push(function () {
const id = message.folder + "_" + message.room + "_"
+ message.name + "_" + getTime(message).toString();
return gadget.wrapJioAccess('put', id, {
const id = message.room + "_" + message.name + "_"
+ getTime(message).toString();
return logQueue(gadget.wrapJioAccess('put', id, {
portal_type: "Text Post",
parent_relative_url: "post_text_module",
reference: id,
author: message.name,
date_ms: getTime(message),
content: JSON.stringify(message),
});
})
.push(null, logError);
}));
})
// Add message to the list
......@@ -514,7 +506,7 @@
class_name = "notify";
} else {
favicon_url = gadget.state_parameter_dict.default_icon;
class_name = "";
class_name = "current";
}
const link = document.querySelector("link[rel*='icon']")
|| document.createElement("link");
......@@ -524,16 +516,12 @@
document.head.appendChild(link);
const contact = gadget.state_parameter_dict.element
.querySelector("#chat-contact-" + room);
if (contact.className === "current") {
contact.className = class_name || "current";
} else {
contact.className = class_name;
}
})
.push(null, logError);
})
// Join a different room in the same folder
// Join a different room
.declareMethod('changeRoom', function (room) {
const gadget = this;
return new RSVP.Queue()
......@@ -570,7 +558,6 @@
.declareMethod('createContact', function (param_dict) {
const gadget = this;
return new RSVP.Queue()
// XXX: load params here too
.push(function () {
return gadget.appendContact(param_dict.room);
})
......@@ -736,9 +723,7 @@
return gadget.createContact({room: contact, role: "guest"});
case "make-form":
const room = resetInputValue(event.target.elements.content);
return gadget.createContact({room: room, role: "host"});
case "auth-form":
return gadget.connectContact();
return gadget.chooseRoom({room: room, role: "host"});
case "send-form":
const content = resetInputValue(event.target.elements.content);
if (content.indexOf("/") === 0) {
......
......@@ -15,20 +15,20 @@
<p class="status"></p>
<form class="contact-form">
<label>Name:</label>
<input type="text" name="name" />
<label>Folder:</label>
<input type="text" name="folder" />
<label>E-mail Address:</label>
<input type="text" name="email" />
<label>Dropbox folder:</label>
<input type="text" name="dropbox_url" placeholder="/Apps/OfficeJS Chat" />
<input type="text" name="dropbox_folder" placeholder="/Apps/OfficeJS Chat/" />
<label>ERP5 URL:</label>
<input type="text" name="erp5_url" placeholder="https://softinst75770.host.vifib.net/erp5/webrtc_rooms_module/" />
<label>Hateoas URL:</label>
<input type="text" name="hateoas_url" placeholder="https://softinst75770.host.vifib.net/erp5/web_site_module/hateoas" />
<input type="text" name="erp5_url" placeholder="https://softinst75770.host.vifib.net/erp5/web_site_module/" />
<br />
<input type="submit" value="Update information!" />
</form>
<form class="download-form">
<input type="submit" value="Download information from remote storage!" />
</form>
<form class="auth-form">
<label>
<input type="radio" name="auth" value="erp5" required="required" />
......
(function (window, document, rJS, RSVP) {
function webrtcError(gadget, error_string) {
return new RSVP.Queue()
.push(function () {
gadget.state_parameter_dict.element.querySelector(".error")
.textContent = error_string;
})
.push(function () {
return RSVP.delay(10000);
})
.push(function () {
return error_string;
});
}
// jIO utility functions
function createDropboxJio(gadget, folder) {
function createDropboxJio(gadget, param_dict) {
let dropbox_gadget;
return new RSVP.Queue()
.push(function () {
......@@ -21,10 +35,7 @@
});
})
.push(function () {
return dropbox_gadget.put(folder, {});
})
.push(null, function () {
return resetDropboxContent(dropbox_gadget, {folder: folder});
return dropbox_gadget.put(param_dict.dropbox_folder, {});
})
.push(null, logError);
}
......@@ -32,28 +43,31 @@
function resetDropboxContent(dropbox_gadget, param_dict) {
return new RSVP.Queue()
.push(function () {
return dropbox_gadget.allAttachments(param_dict.folder);
return dropbox_gadget.allAttachments(param_dict.dropbox_folder);
})
.push(function (attachment_list) {
.push(function (negotiation_list) {
const promise_list = [];
for (let file_name in attachment_list) {
if (attachment_list.hasOwnProperty(file_name)) {
for (let file_name in negotiation_list) {
if (negotiation_list.hasOwnProperty(file_name)
&& (file_name.indexOf("offer_" + param_dict.room + "_") === 0
|| file_name.indexOf("answer_" + param_dict.room + "_") === 0)) {
promise_list.push(dropbox_gadget
.removeAttachment(param_dict.folder, file_name));
.removeAttachment(param_dict.dropbox_folder, file_name));
}
}
return RSVP.all(promise_list);
});
})
.push(null, logError);
}
function getDropboxOffer(dropbox_gadget, param_dict) {
return new RSVP.Queue()
.push(function () {
return dropbox_gadget.allAttachments(param_dict.folder);
return dropbox_gadget.allAttachments(param_dict.dropbox_folder);
})
.push(function (attachment_list) {
for (let file_name in attachment_list) {
if (attachment_list.hasOwnProperty(file_name)
.push(function (negotiation_list) {
for (let file_name in negotiation_list) {
if (negotiation_list.hasOwnProperty(file_name)
&& file_name.indexOf("offer_" + param_dict.room + "_") === 0) {
return file_name.slice(6);
}
......@@ -67,7 +81,7 @@
return new RSVP.Queue()
.push(function () {
return dropbox_gadget.getAttachment(
param_dict.folder, param_dict.file_name);
param_dict.dropbox_folder, param_dict.file_name);
})
.push(function (attachment) {
return promiseReadAsText(attachment);
......@@ -79,37 +93,24 @@
function putDropboxContent(dropbox_gadget, param_dict) {
return logQueue(dropbox_gadget.putAttachment(
param_dict.folder, param_dict.file_name,
param_dict.dropbox_folder, param_dict.file_name,
new Blob([param_dict.content], {type: "text"})));
}
function removeDropboxContent(dropbox_gadget, param_dict) {
return logQueue(dropbox_gadget
.removeAttachment(param_dict.folder, param_dict.file_name));
return logQueue(dropbox_gadget.removeAttachment(
param_dict.dropbox_folder, param_dict.file_name));
}
function authenticateDropbox(gadget) {
if (document.URL.indexOf("access_token=") === -1) {
gadget.state_parameter_dict.element.querySelector(".error")
.textContent = "Please log in to Dropbox!";
return;
}
return new RSVP.Queue()
.push(function () {
return createDropboxJio(gadget,
gadget.state_parameter_dict.auth_dict.dropbox_folder);
})
.push(function () {
const dropbox_param_dict = {
gadget: gadget,
gadget_name: "dropbox_gadget",
function_param_dict: {
folder: gadget.state_parameter_dict.auth_dict.dropbox_folder,
room: gadget.state_parameter_dict.login_dict.room,
name: gadget.state_parameter_dict.login_dict.name,
file_name: null,
content: null,
},
dropbox_folder: gadget.state_parameter_dict.dropbox_folder,
function_dict: {
resetContent: resetDropboxContent,
getOffer: getDropboxOffer,
......@@ -118,6 +119,21 @@
removeContent: removeDropboxContent,
},
};
return new RSVP.Queue()
.push(function () {
if (document.URL.indexOf("access_token=") === -1) {
throw webrtcError(gadget, "Please log in to Dropbox!");
} else {
return createDropboxJio(gadget, dropbox_param_dict);
}
})
.push(function () {
return gadget.getDeclaredGadget("dropbox_gadget");
})
.push(function (dropbox_gadget) {
return resetDropboxContent(dropbox_gadget, dropbox_param_dict);
})
.push(function () {
if (gadget.state_parameter_dict.login_dict.role === "host") {
return authenticateHost(dropbox_param_dict);
} else if (gadget.state_parameter_dict.login_dict.role === "guest") {
......@@ -127,7 +143,7 @@
.push(null, logError);
}
function createErp5Jio(gadget, folder, url) {
function createErp5Jio(gadget, param_dict) {
let erp5_gadget;
return new RSVP.Queue()
.push(function () {
......@@ -138,7 +154,7 @@
erp5_gadget = jio_gadget;
return erp5_gadget.createJio({
type: "erp5",
url: url,
url: (new URI("hateoas")).absoluteTo(param_dict.erp5_url).toString(),
default_view_reference: "view",
});
})
......@@ -154,44 +170,57 @@
.push(function (room_list) {
if (room_list.data.rows.length) {
for (let i = 0, i_len = room_list.data.rows.length; i < i_len; i++) {
if (room_list.data.rows[i].value.title === folder) {
if (room_list.data.rows[i].value.title === param_dict.room) {
return room_list.data.rows[i].id;
}
}
}
return erp5_gadget.post({
title: folder,
portal_type: "Webrtc Room",
parent_relative_url: "webrtc_rooms_module",
title: param_dict.room,
negotiation_list: JSON.stringify({}),
});
})
.push(null, logError);
}
function resetErp5Content(erp5_gadget, param_dict) {
return logQueue(erp5_gadget.putAttachment(
"/", param_dict.url + "WebrtcRoom_resetContent",
new Blob([JSON.stringify({folder: param_dict.folder})])));
return logQueue(erp5_gadget.put(param_dict.id, {
portal_type: "Webrtc Room",
parent_relative_url: "webrtc_rooms_module",
title: param_dict.room,
negotiation_list: JSON.stringify({}),
}));
}
function getErp5Offer(erp5_gadget, param_dict) {
return new RSVP.Queue()
.push(function () {
return erp5_gadget.get(param_dict.id);
})
.push(function (webrtc_room) {
const negotiation_list = JSON.parse(webrtc_room.negotiation_list);
for (let file_name in negotiation_list) {
if (negotiation_list.hasOwnProperty(file_name)
&& file_name.indexOf("offer_" + param_dict.room + "_") === 0) {
return file_name.slice(6);
}
}
return null;
})
.push(null, logError);
}
function getErp5Content(erp5_gadget, param_dict) {
return new RSVP.Queue()
.push(function () {
return erp5_gadget.putAttachment(
"/", param_dict.url + param_dict.action,
new Blob([JSON.stringify({
folder: param_dict.folder,
room: param_dict.room,
name: param_dict.file_name,
})])
);
})
.push(function (response) {
return response.currentTarget.responseText;
})
.push(function (offer_name) {
if (offer_name) {
return offer_name;
return erp5_gadget.get(param_dict.id);
})
.push(function (webrtc_room) {
const negotiation_list = JSON.parse(webrtc_room.negotiation_list);
if (negotiation_list[param_dict.file_name]) {
return negotiation_list[param_dict.file_name];
} else {
return null;
}
......@@ -200,73 +229,71 @@
}
function putErp5Content(erp5_gadget, param_dict) {
return logQueue(erp5_gadget.putAttachment(
"/", param_dict.url + "WebrtcRoom_putContent",
new Blob([JSON.stringify({
folder: param_dict.folder,
name: param_dict.file_name,
content: param_dict.content,
})])
));
return new RSVP.Queue()
.push(function () {
return erp5_gadget.get(param_dict.id);
})
.push(function (webrtc_room) {
const negotiation_list = JSON.parse(webrtc_room.negotiation_list);
negotiation_list[param_dict.file_name] = param_dict.content;
return logQueue(erp5_gadget.put(param_dict.id, {
portal_type: "Webrtc Room",
parent_relative_url: "webrtc_rooms_module",
title: param_dict.room,
negotiation_list: JSON.stringify(negotiation_list),
}));
})
.push(null, logError);
}
function removeErp5Content(erp5_gadget, param_dict) {
return logQueue(erp5_gadget.putAttachment(
"/", param_dict.url + "WebrtcRoom_deleteContent",
new Blob([JSON.stringify({
folder: param_dict.folder,
name: param_dict.file_name,
})])
));
}
function authenticateErp5(gadget) {
return new RSVP.Queue()
.push(function () {
return createErp5Jio(
gadget, gadget.state_parameter_dict.login_dict.folder,
gadget.state_parameter_dict.auth_dict.hateoas_url);
})
.push(function (folder_id) {
gadget.state_parameter_dict.auth_dict.erp5_folder =
folder_id.slice(folder_id.indexOf("/") + 1);
gadget.state_parameter_dict.auth_dict.erp5_folder_url =
gadget.state_parameter_dict.auth_dict.erp5_url
+ gadget.state_parameter_dict.auth_dict.erp5_folder + "/";
return gadget.getDeclaredGadget("erp5_gadget");
return erp5_gadget.get(param_dict.id);
})
.push(function (erp5_gadget) {
return resetErp5Content(erp5_gadget, {
folder: gadget.state_parameter_dict.auth_dict.erp5_folder,
url: gadget.state_parameter_dict.auth_dict.erp5_folder_url,
});
.push(function (webrtc_room) {
const negotiation_list = JSON.parse(webrtc_room.negotiation_list);
delete negotiation_list[param_dict.file_name];
return logQueue(erp5_gadget.put(param_dict.id, {
portal_type: "Webrtc Room",
parent_relative_url: "webrtc_rooms_module",
title: param_dict.room,
negotiation_list: JSON.stringify(negotiation_list),
}));
})
.push(function () {
.push(null, logError);
}
function authenticateErp5(gadget) {
const erp5_param_dict = {
gadget: gadget,
gadget_name: "erp5_gadget",
function_param_dict: {
folder: gadget.state_parameter_dict.auth_dict.erp5_folder,
room: gadget.state_parameter_dict.login_dict.room,
name: gadget.state_parameter_dict.login_dict.name,
url: gadget.state_parameter_dict.auth_dict.erp5_folder_url,
file_name: null,
content: null,
},
id: null,
erp5_url: gadget.state_parameter_dict.erp5_url,
function_dict: {
resetContent: resetErp5Content,
getOffer: function (erp5_gadget, param_dict) {
param_dict.action = "WebrtcRoom_getOffer";
return getErp5Content(erp5_gadget, param_dict);
},
getContent: function (erp5_gadget, param_dict) {
param_dict.action = "WebrtcRoom_getContent";
return getErp5Content(erp5_gadget, param_dict);
},
getOffer: getErp5Offer,
getContent: getErp5Content,
putContent: putErp5Content,
removeContent: removeErp5Content,
},
};
return new RSVP.Queue()
.push(function () {
return createErp5Jio(gadget, erp5_param_dict);
})
.push(function (id) {
erp5_param_dict.id = id;
return gadget.getDeclaredGadget("erp5_gadget");
})
.push(function (erp5_gadget) {
return resetErp5Content(erp5_gadget, erp5_param_dict);
})
.push(function () {
if (gadget.state_parameter_dict.login_dict.role === "host") {
return authenticateHost(erp5_param_dict);
} else if (gadget.state_parameter_dict.login_dict.role === "guest") {
......@@ -282,15 +309,12 @@
let jio_gadget;
return new RSVP.Queue()
.push(function () {
gadget.state_parameter_dict.element
.querySelector(".error").textContent = "";
return gadget.getDeclaredGadget(param_dict.gadget_name);
})
.push(function (storage_gadget) {
jio_gadget = storage_gadget;
return pollUntilNotNull(gadget, 2000, 86400000, function () {
return param_dict.function_dict.getOffer(
jio_gadget, param_dict.function_param_dict);
return param_dict.function_dict.getOffer(jio_gadget, param_dict);
}, function (offer_name) {
file_name = offer_name;
});
......@@ -305,14 +329,12 @@
}
});
}, function () {
throw param_dict.function_dict.resetContent(
jio_gadget, param_dict.function_param_dict);
throw param_dict.function_dict.resetContent(jio_gadget, param_dict);
})
.push(function () {
param_dict.function_param_dict.file_name = "offer_" + file_name;
param_dict.file_name = "offer_" + file_name;
return pollUntilNotNull(gadget, 500, 10000, function () {
return param_dict.function_dict.getContent(
jio_gadget, param_dict.function_param_dict);
return param_dict.function_dict.getContent(jio_gadget, param_dict);
}, function (guest_offer) {
return sendOffer(gadget, guest_offer);
});
......@@ -328,18 +350,16 @@
return gadget.state_parameter_dict.candidate;
}, function (host_answer) {
gadget.state_parameter_dict.candidate = null;
param_dict.function_param_dict.file_name = "answer_" + file_name;
param_dict.function_param_dict.content = host_answer;
return param_dict.function_dict.putContent(
jio_gadget, param_dict.function_param_dict);
param_dict.file_name = "answer_" + file_name;
param_dict.content = host_answer;
return param_dict.function_dict.putContent(jio_gadget, param_dict);
});
})
.push(null, logError)
.push(function () {
if (file_name) {
param_dict.function_param_dict.file_name = "offer_" + file_name;
return param_dict.function_dict.removeContent(
jio_gadget, param_dict.function_param_dict);
param_dict.file_name = "offer_" + file_name;
return param_dict.function_dict.removeContent(jio_gadget, param_dict);
} else {
return;
}
......@@ -356,8 +376,6 @@
let jio_gadget;
return new RSVP.Queue()
.push(function () {
gadget.state_parameter_dict.element
.querySelector(".error").textContent = "";
return gadget.getDeclaredGadget(param_dict.gadget_name);
})
.push(function (storage_gadget) {
......@@ -365,29 +383,28 @@
return pollUntilNotNull(gadget, 500, 30000, function () {
return gadget.state_parameter_dict.candidate;
}, function (guest_offer) {
file_name = param_dict.function_param_dict.room + "_"
+ param_dict.function_param_dict.name + ".txt";
param_dict.function_param_dict.file_name = "offer_" + file_name;
param_dict.function_param_dict.content = guest_offer;
return param_dict.function_dict.putContent(
jio_gadget, param_dict.function_param_dict);
file_name = param_dict.room + "_" + param_dict.name + ".txt";
param_dict.file_name = "offer_" + file_name;
param_dict.content = guest_offer;
return param_dict.function_dict.putContent(jio_gadget, param_dict);
});
})
.push(function () {
return pollUntilNotNull(gadget, 500, 30000, function () {
param_dict.function_param_dict.file_name = "answer_" + file_name;
return param_dict.function_dict.getContent(
jio_gadget, param_dict.function_param_dict);
param_dict.file_name = "answer_" + file_name;
return param_dict.function_dict.getContent(jio_gadget, param_dict);
}, function (host_answer) {
return sendAnswer(gadget, host_answer);
});
})
.push(null, logError)
.push(function () {
// XXX pause to avoid data races, use locks later
return RSVP.delay(1000);
}, logError)
.push(function () {
if (file_name) {
param_dict.function_param_dict.file_name = "answer_" + file_name;
return param_dict.function_dict.removeContent(
jio_gadget, param_dict.function_param_dict);
param_dict.file_name = "answer_" + file_name;
return param_dict.function_dict.removeContent(jio_gadget, param_dict);
} else {
return;
}
......@@ -484,13 +501,9 @@
"WebRTC connection status: "
+ peer_connection.iceConnectionState + ".";
if (peer_connection.iceConnectionState === "failed") {
gadget.state_parameter_dict.element
.querySelector(".error").textContent =
"WebRTC connection failed!";
throw webrtcError(gadget, "WebRTC connection failed!");
} else if (peer_connection.iceConnectionState === "failed") {
gadget.state_parameter_dict.element
.querySelector(".error").textContent =
"WebRTC connection disconnected!";
throw webrtcError(gadget, "WebRTC connection disconnected!");
}
};
}
......@@ -566,18 +579,14 @@
gadget.state_parameter_dict = {
element: null,
login_dict: {
folder: null,
room: null,
name: null,
role: null,
},
auth_dict: {
dropbox_url: null,
dropbox_folder: null,
erp5_url: null,
erp5_folder: null,
erp5_folder_url: null,
},
dataChannelOnopen: null,
dataChannelOnmessage: null,
candidate: null,
connected: false,
offer_ready: false,
......@@ -617,6 +626,9 @@
.push(null, logError);
})
.declareAcquiredMethod('wrapJioAccess', 'wrapJioAccess')
.declareAcquiredMethod('getContactByEmail', 'getContactByEmail')
.declareMethod('render', function () {
const gadget = this;
return new RSVP.Queue()
......@@ -670,32 +682,15 @@
.push(null, logError);
})
.declareMethod('authenticate', function () {
.declareMethod('authenticate', function (auth) {
const gadget = this;
const auth = gadget.state_parameter_dict.element
.querySelector(".auth-form").elements.auth.value;
const fields = gadget.state_parameter_dict.element
.querySelector(".contact-form").elements;
const dropbox_url = fields.dropbox_url.value;
const erp5_url = fields.erp5_url.value;
const hateoas_url = fields.hateoas_url.value;
return new RSVP.Queue()
.push(function () {
let folder = gadget.state_parameter_dict.login_dict.folder;
if (folder[0] !== "/") {
folder = "/" + folder;
}
if (folder[folder.length - 1] !== "/") {
folder = folder + "/";
}
gadget.state_parameter_dict.auth_dict = {
dropbox_url: dropbox_url,
dropbox_folder: dropbox_url + folder,
erp5_url: erp5_url,
erp5_folder: null,
erp5_folder_url: null,
hateoas_url: hateoas_url,
};
gadget.state_parameter_dict.dropbox_folder =
fields.dropbox_folder.value;
gadget.state_parameter_dict.erp5_url = fields.erp5_url.value;
switch (auth) {
case "dropbox":
return authenticateDropbox(gadget);
......@@ -707,11 +702,78 @@
});
})
.declareMethod('uploadContact', function (event) {
const gadget = this;
const fields = event.target.elements;
return new RSVP.Queue()
.push(function () {
return gadget.getContactByEmail(fields.email.value);
})
.push(function (id) {
const param_dict = {
dropbox_folder: fields.dropbox_folder.value,
erp5_url: fields.erp5_url.value,
auth: gadget.state_parameter_dict.element
.querySelector(".auth-form").elements.auth.value,
}
let jio_configuration = "";
for (let key in param_dict) {
if (param_dict.hasOwnProperty(key) && param_dict[key]) {
jio_configuration += "&" + key + "=" + param_dict[key];
}
}
return gadget.wrapJioAccess('put', id, {
portal_type: "Person",
parent_relative_url: "person_module",
default_email_coordinate_text: fields.email.value,
jio_configuration: jio_configuration,
});
})
.push(function () {
return gadget.wrapJioAccess('repair');
})
.push(null, logError);
})
.declareMethod('downloadContact', function () {
const gadget = this;
const fields = gadget.state_parameter_dict.element
.querySelector(".contact-form").elements;
return new RSVP.Queue()
.push(function () {
return gadget.getContactByEmail(fields.email.value);
})
.push(function (id) {
return gadget.wrapJioAccess('get', id);
})
.push(function (contact) {
if (!contact) {
throw webrtcError(gadget, "Email not found!");
} else {
const param_dict = {};
const query_list = [["email", "e"],
["dropbox_folder", "d"], ["erp5_url", "e"]];
for (let i = 0, i_len = query_list.length; i < i_len; i++) {
const query = query_list[i];
setQueryValue(query, contact.jio_configuration, fields[query[0]]);
}
setQueryValue(["auth", "a"], contact.jio_configuration, gadget
.state_parameter_dict.element.querySelector(".auth-form").auth);
return;
}
})
.push(null, logError);
})
.onEvent('submit', function (event) {
const gadget = this;
switch (event.target.className) {
case "auth-form":
return gadget.authenticate();
return gadget.authenticate(event.target.elements.auth.value);
case "contact-form":
return gadget.uploadContact(event);
case "download-form":
return gadget.downloadContact();
case "host-offer-form":
styleElementByQuery(gadget, ".host-offer-form", "none");
styleElementByQuery(gadget, ".host-answer-form", "block");
......
......@@ -149,6 +149,28 @@
return value;
};
window.getQueryValue = function (query_list, query_string) {
for (let i = 0, i_len = query_list.length; i < i_len; i++) {
const query = query_list[i];
if (query_string.indexOf(query + "=") !== -1) {
const start = query_string.indexOf(query + "=") + query.length + 1;
let end = query_string.indexOf("&", start);
if (end === -1) {
end = query_string.length;
}
return query_string.slice(start, end);
}
}
return null;
}
window.setQueryValue = function (query_list, query_string, element) {
const value = getQueryValue(query_list, query_string);
if (value) {
element.value = value;
}
}
window.pollUntilNotNull = function (
gadget, delay_ms, timeout_ms,
nullableFunction, callbackFunction) {
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Folder" module="OFS.Folder"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_Transient_Objects_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Access_arbitrary_user_session_data_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_events_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_folders_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_member_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_topics_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
</tuple>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>erp5_webrtc_room</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
import json
module = context.getPortalObject().getDefaultModule(portal_type="Webrtc Room")
webrtc_folder = module[folder]
negotiation_list = json.loads(webrtc_folder.negotiation_list)
negotiation_list.pop(name, None)
webrtc_folder.edit(
negotiation_list = json.dumps(negotiation_list)
)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>folder, name</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>WebrtcRoom_deleteContent</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
import json
module = context.getPortalObject().getDefaultModule(portal_type="Webrtc Room")
webrtc_folder = module[folder]
negotiation_list = json.loads(webrtc_folder.negotiation_list)
return negotiation_list.get(name, "")
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>folder, name</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>WebrtcRoom_getContent</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
import json
module = context.getPortalObject().getDefaultModule(portal_type="Webrtc Room")
webrtc_folder = module[folder]
negotiation_list = json.loads(webrtc_folder.negotiation_list)
for name in negotiation_list:
if name.startswith("offer_" + room):
return name[6:]
return ""
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>folder, room</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>WebrtcRoom_getOffer</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
import json
module = context.getPortalObject().getDefaultModule(portal_type="Webrtc Room")
webrtc_folder = module[folder]
negotiation_list = json.loads(webrtc_folder.negotiation_list)
negotiation_list[name] = content
webrtc_folder.edit(
negotiation_list = json.dumps(negotiation_list)
)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>folder, name, content</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>WebrtcRoom_putContent</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
import json
module = context.getPortalObject().getDefaultModule(portal_type="Webrtc Room")
webrtc_folder = module[folder]
webrtc_folder.edit(
negotiation_list = json.dumps({})
)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>folder</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>WebrtcRoom_resetContent</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5 Form" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>action</string> </key>
<value> <string>Base_edit</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>edit_order</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<list>
<string>left</string>
<string>right</string>
<string>center</string>
<string>bottom</string>
<string>hidden</string>
</list>
</value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>bottom</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>center</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>hidden</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list>
<string>my_title</string>
<string>my_negotiation_list</string>
</list>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>WebrtcRoom_view</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>WebrtcRoom_view</string> </value>
</item>
<item>
<key> <string>pt</string> </key>
<value> <string>form_view</string> </value>
</item>
<item>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Webrtc Room</string> </value>
</item>
<item>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>update_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>update_action_title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StringField" module="Products.Formulator.StandardFields"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>my_negotiation_list</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
<item>
<key> <string>required_not_found</string> </key>
<value> <string>Input is required but no input given.</string> </value>
</item>
<item>
<key> <string>too_long</string> </key>
<value> <string>Too much input was given.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <int>20</int> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string>text</string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>WebRTC Negotiation List</string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StringField" module="Products.Formulator.StandardFields"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>my_title</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
<item>
<key> <string>required_not_found</string> </key>
<value> <string>Input is required but no input given.</string> </value>
</item>
<item>
<key> <string>too_long</string> </key>
<value> <string>Too much input was given.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <int>20</int> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string>text</string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Title</string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5 Form" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>action</string> </key>
<value> <string>WebrtcRoomsModule_addWebrtcRoom</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>use this dialog to create new rooms</string> </value>
</item>
<item>
<key> <string>edit_order</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<list>
<string>left</string>
<string>right</string>
<string>center</string>
<string>bottom</string>
<string>hidden</string>
</list>
</value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>bottom</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>center</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>hidden</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list>
<string>your_title</string>
</list>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>WebrtcRoomsModule_viewAddWebrtcRoom</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>WebrtcRoomsModule_viewAddWebrtcRoom</string> </value>
</item>
<item>
<key> <string>pt</string> </key>
<value> <string>form_dialog</string> </value>
</item>
<item>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>New Rooms</string> </value>
</item>
<item>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>update_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>update_action_title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StringField" module="Products.Formulator.StandardFields"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>your_title</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
<item>
<key> <string>required_not_found</string> </key>
<value> <string>Input is required but no input given.</string> </value>
</item>
<item>
<key> <string>too_long</string> </key>
<value> <string>Too much input was given.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <int>20</int> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string>text</string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Title</string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5 Form" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>action</string> </key>
<value> <string>Base_doSelect</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>edit_order</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<list>
<string>left</string>
<string>right</string>
<string>center</string>
<string>bottom</string>
<string>hidden</string>
</list>
</value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>bottom</string> </key>
<value>
<list>
<string>listbox</string>
</list>
</value>
</item>
<item>
<key> <string>center</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>hidden</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>WebrtcRoomsModule_viewWebrtcRoomList</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>WebrtcRoomsModule_viewWebrtcRoomList</string> </value>
</item>
<item>
<key> <string>pt</string> </key>
<value> <string>form_list</string> </value>
</item>
<item>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Webrtc Rooms</string> </value>
</item>
<item>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>update_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>update_action_title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>columns</string>
<string>selection_name</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>columns</string> </key>
<value>
<list>
<tuple>
<string>title</string>
<string>Title</string>
</tuple>
<tuple>
<string>negotiation_list</string>
<string>WebRTC Negotiation List</string>
</tuple>
</list>
</value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_list_mode_listbox</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value>
<list>
<tuple>
<string>Webrtc Room</string>
<string>Webrtc Room</string>
</tuple>
</list>
</value>
</item>
<item>
<key> <string>selection_name</string> </key>
<value> <string>webrtc_rooms_module_selection</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>WebRTC Rooms</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
erp5_webrtc_room
\ No newline at end of file
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