-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
+
+ v-on:select_query="evt_select_query"
+ v-on:panel-update="evt_panel_update"/>
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
@@ -87,8 +108,9 @@
- -->
+
diff --git a/etc/js/app.js b/etc/js/app.js
index 01df4d4a..8f38652c 100644
--- a/etc/js/app.js
+++ b/etc/js/app.js
@@ -1,5 +1,4 @@
-
// Track state of connection to remote app
const ConnectionState = {
Initializing: Symbol('Initializing'),
@@ -7,7 +6,8 @@ const ConnectionState = {
Connecting: Symbol('Connecting'),
RetryConnecting: Symbol('RetryConnecting'),
Remote: Symbol('Remote'),
- ConnectionFailed: Symbol('ConnectionFailed')
+ ConnectionFailed: Symbol('ConnectionFailed'),
+ Disconnecting: Symbol('Disconnecting'),
};
// Short initial timeout to detect remote app. Should be long enough for
@@ -67,17 +67,36 @@ function getParameterByName(name, url = window.location.href) {
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
+function paramStr(params) {
+ let url_params = "";
+ if (params) {
+ for (var k in params) {
+ url_params += "&" + k + "=" + params[k];
+ }
+ }
+ return url_params;
+}
+
/*
- GLOBAL COMPONENT REGISTRATIOnS
+ GLOBAL COMPONENT REGISTRATIONS
*/
Vue.component('collapsible-panel', httpVueLoader('js/collapsible_panel.vue'));
Vue.component('detail-toggle-alt', httpVueLoader('js/detail_toggle_alt.vue'));
-// var icon_component = Vue.component('icon', httpVueLoader('js/components/icon.vue'));
-// Vue.component('icon-button', httpVueLoader('js/components/button.vue'));
var tooltip_component = Vue.component('tooltip', httpVueLoader('js/components/tooltip.vue'));
var popover_component = Vue.component('popover', httpVueLoader('js/components/popover.vue'));
Vue.component('url-popover', httpVueLoader('js/overlays/popovers/url-popover.vue'));
-// var entity_hierarchy_component = Vue.component('entity-hierarchy', httpVueLoader('js/components/entity_hierarchy.vue'));
+Vue.component('connection-popover', httpVueLoader('js/overlays/popovers/connection-popover.vue'));
+Vue.component('connection-status', httpVueLoader('js/widgets/connection_status.vue'));
+Vue.component('tabs', httpVueLoader('js/components/tabs.vue'));
+Vue.component('primary-button', httpVueLoader('js/components/button.vue'));
+
+Vue.component('panel-menu', httpVueLoader('js/components/panel_menu.vue'));
+Vue.component('panel-button', httpVueLoader('js/components/panel_button.vue'));
+Vue.component('stat', httpVueLoader('js/components/stat.vue'));
+Vue.component('stats-period', httpVueLoader('js/components/stats_period.vue'));
+Vue.component('stat-chart', httpVueLoader('js/components/stat_chart.vue'));
+Vue.component('stats-world', httpVueLoader('js/components/stats_world.vue'));
+Vue.component('stats-pipeline', httpVueLoader('js/components/stats_pipeline.vue'));
Vue.directive('tooltip', {
bind: function (el, binding, vnode) {
@@ -104,6 +123,15 @@ var app = new Vue({
el: '#app',
mounted: function() {
+
+ // Initialize title before watcher sets
+ document.title = this.title;
+
+ /*
+ Call Sequence:
+ Mounted -> Ready -> Connect ->
+ */
+
this.$nextTick(() => {
flecs_explorer.then(() => {
this.ready();
@@ -137,6 +165,11 @@ var app = new Vue({
if (Request.status == 0) {
this.retry_count ++;
+ // Disconnect after the 10th try
+ if (this.retry_count >= 10) {
+ this.disconnect();
+ }
+
// Retry if the server did not respond to request
if (retry_interval) {
retry_interval *= 1.3;
@@ -153,9 +186,12 @@ var app = new Vue({
"ensure app is running and REST is enabled " +
"(retried " + this.retry_count + " times)");
+ // Attempt reconnection loop
window.setTimeout(() => {
- this.http_request(method, host, path, recv, err,
- timeout, retry_interval);
+ if (this.connection != ConnectionState.Disconnecting && this.connection != ConnectionState.Local) {
+ this.http_request(method, host, path, recv, err,
+ timeout, retry_interval);
+ }
}, retry_interval);
} else {
if (err) err(Request.responseText);
@@ -238,14 +274,8 @@ var app = new Vue({
const reply = JSON.parse(r);
recv(reply);
} else if (this.is_remote()) {
- let url_params = "";
- if (params) {
- for (var k in params) {
- url_params += "&" + k + "=" + params[k];
- }
- }
this.request(id, "GET",
- "entity/" + path.replaceAll('.', '/') + url_params, recv, err);
+ "entity/" + path.replaceAll('.', '/') + paramStr(params), recv, err);
}
},
@@ -255,20 +285,23 @@ var app = new Vue({
const reply = JSON.parse(r);
recv(reply);
} else if (this.is_remote()) {
- let url_params = "";
- if (params) {
- for (var k in params) {
- url_params += "&" + k + "=" + params[k];
- }
- }
this.request(id,
- "GET", "query?q=" + encodeURIComponent(q) + url_params,
+ "GET", "query?q=" + encodeURIComponent(q) + paramStr(params),
recv, err);
} else {
err({error: "no connection"});
}
},
+ request_stats: function(id, category, recv, err, params) {
+ if (this.is_local()) {
+ return "{}";
+ } else if (this.is_remote()) {
+ this.request(id, "GET",
+ "stats/" + category + paramStr(params), recv, err);
+ }
+ },
+
insert_code: function(code, recv, timeout) {
if (this.is_local()) {
if (this.parse_timer) {
@@ -300,8 +333,9 @@ var app = new Vue({
}
if (selected) {
- this.selected_entity = selected;
+ this.set_entity(selected);
}
+
if (q) {
this.$refs.query.set_query(q);
}
@@ -318,17 +352,21 @@ var app = new Vue({
this.refresh_query();
this.refresh_entity();
this.refresh_tree();
+ this.refresh_stats();
// Refresh UI periodically
this.refresh_timer = window.setInterval(() => {
this.refresh_query();
this.refresh_entity();
this.refresh_tree();
+ this.refresh_stats();
}, REFRESH_INTERVAL);
+
+ this.evt_panel_update();
},
ready_local() {
- this.selected_entity = undefined;
+ this.set_entity();
const q_encoded = getParameterByName("q");
const p_encoded = getParameterByName("p");
@@ -367,6 +405,40 @@ var app = new Vue({
this.$refs.tree.update_expanded();
this.parse_interval = 150;
+
+ this.evt_panel_update();
+ },
+
+ disconnect() {
+ this.connection = ConnectionState.Disconnecting;
+
+ // Reset application connection status
+ this.retry_count = 0;
+
+ if (this.refresh_timer) {
+ window.clearInterval(this.refresh_timer);
+ }
+
+ // Clear URL params
+ const url = new URL(window.location);
+ url.searchParams.delete("host");
+ url.searchParams.delete("remote");
+ url.searchParams.delete("port");
+ window.history.replaceState({}, '', url);
+
+ // Clear stored params
+ this.params.host = undefined;
+ this.params.remote = undefined;
+ this.params.port = undefined;
+
+ // Reset
+ this.title = "Flecs";
+
+
+ setTimeout(() => {
+ this.connection = ConnectionState.Local;
+ this.ready_local();
+ }, 1)
},
// Connect to a remote host
@@ -499,16 +571,8 @@ var app = new Vue({
// Set inspector to entity by pathname
set_entity(path) {
- this.request_abort('inspector'); // Abort outstanding requests
- this.entity_result = undefined;
-
- this.selected_entity = path;
- if (!path) {
- return;
- }
-
- this.$refs.inspector.expand();
- this.refresh_entity();
+ this.$refs.inspector.set_entity(path);
+ this.$refs.tree.set_selected_entity(path);
},
set_entity_by_tree_item(item) {
@@ -524,23 +588,20 @@ var app = new Vue({
},
refresh_entity() {
- if (!this.selected_entity) {
- return;
- }
- this.request_entity('inspector', this.selected_entity, (reply) => {
- this.entity_error = reply.error;
- if (this.entity_error === undefined) {
- this.entity_result = reply;
- }
- }, () => {
- this.entity_error = "request for entity '" + this.selected_entity + "' failed";
- }, {type_info: true, label: true, brief: true, link: true, id_labels: true, values: true});
+ this.$refs.inspector.refresh();
},
refresh_tree() {
this.$refs.tree.update_expanded();
},
+ refresh_stats() {
+ if (this.$refs.stats_world) {
+ this.$refs.stats_world.refresh();
+ this.$refs.stats_pipeline.refresh();
+ }
+ },
+
// Code changed event
run_code(code, recv) {
this.insert_code(code, (reply) => {
@@ -565,16 +626,29 @@ var app = new Vue({
this.$refs.query.set_query(query);
},
+ evt_panel_update() {
+ this.$nextTick(() => {
+ if (this.$refs.panes) {
+ this.$refs.panes.resize();
+ }
+ if (this.$refs.panel_menu) {
+ this.$refs.panel_menu.refresh();
+ }
+ });
+ },
+
show_url_modal() {
const query = this.$refs.query.get_query();
let plecs;
let plecs_encoded;
- if (this.$refs.plecs) {
+ if (!this.remote_mode) {
plecs = this.$refs.plecs.get_code();
plecs_encoded = wq_encode(plecs);
}
+ let entity = this.$refs.inspector.get_entity();
+
const query_encoded = wq_encode(query);
let sep = "?";
@@ -617,15 +691,19 @@ var app = new Vue({
sep = "&";
}
- if (this.selected_entity) {
- this.url += sep + "s=" + this.selected_entity;
+ if (entity) {
+ this.url += sep + "s=" + entity;
sep = "&";
}
- // this.$refs.url.show();
this.$refs.share_url_popover.show();
},
+
+ show_connection_modal() {
+ this.$refs.connection_popover.show();
+ }
},
+
computed: {
valid: function() {
@@ -639,16 +717,21 @@ var app = new Vue({
(this.connection == ConnectionState.RetryConnecting) ||
this.params.remote || this.params.remote_self || this.params.host;
}
+
+ },
+
+ watch: {
+ title(new_title) {
+ // Watches for title data change, then updates page title
+ document.title = new_title;
+ },
},
data: {
title: "Flecs",
query_error: undefined,
- entity_error: undefined,
code_error: undefined,
query_result: undefined,
- entity_result: undefined,
- selected_entity: undefined,
selected_tree_item: undefined,
url: undefined,
params: {},
diff --git a/etc/js/app_title.js b/etc/js/app_title.js
index b38c5d9d..aad41ee3 100644
--- a/etc/js/app_title.js
+++ b/etc/js/app_title.js
@@ -34,7 +34,9 @@ Vue.component('app-title', {
this.connection == ConnectionState.Local ||
this.connection == ConnectionState.RetryConnecting)
{
- return this.value;
+ let str = this.value.replaceAll("_", " ");
+ str = str.charAt(0).toUpperCase() + str.slice(1);
+ return str;
} else if (this.connection == ConnectionState.Connecting ||
this.connection == ConnectionState.Initializing)
{
diff --git a/etc/js/components/button.vue b/etc/js/components/button.vue
index 6d59a9b4..8d6e1e00 100644
--- a/etc/js/components/button.vue
+++ b/etc/js/components/button.vue
@@ -1,53 +1,53 @@
-
-
-
+
+
+
+ {{ label }}
+
-
\ No newline at end of file
diff --git a/etc/js/components/entity_hierarchy.vue b/etc/js/components/entity_hierarchy.vue
index f5746e7a..ab551441 100644
--- a/etc/js/components/entity_hierarchy.vue
+++ b/etc/js/components/entity_hierarchy.vue
@@ -49,11 +49,6 @@ module.exports = {
},
parse() {
let entities = this.entity_path.split(".").slice(0, -1);
- // Path shortening is disabled temporarily
- // if (entities.length > 2) {
- // entities = [ entities[0], "…", entities[entities.length -1]];
- // }
-
this.entities = entities;
}
},
@@ -65,8 +60,8 @@ module.exports = {
\ No newline at end of file
diff --git a/etc/js/components/icon_button.vue b/etc/js/components/icon_button.vue
new file mode 100644
index 00000000..6d59a9b4
--- /dev/null
+++ b/etc/js/components/icon_button.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/etc/js/components/panel_button.vue b/etc/js/components/panel_button.vue
new file mode 100644
index 00000000..a95312f5
--- /dev/null
+++ b/etc/js/components/panel_button.vue
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
diff --git a/etc/js/components/panel_menu.vue b/etc/js/components/panel_menu.vue
new file mode 100644
index 00000000..c72cb3a2
--- /dev/null
+++ b/etc/js/components/panel_menu.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
diff --git a/etc/js/components/popover.vue b/etc/js/components/popover.vue
index f81d76d8..5768bec9 100644
--- a/etc/js/components/popover.vue
+++ b/etc/js/components/popover.vue
@@ -21,13 +21,18 @@ module.exports = {
this.position();
this.show_state = true;
setTimeout(() => {
- document.addEventListener("click", this.outside_click);
+ document.addEventListener("click", this.outside_click);
+ // close this popover when clicked outside
+ window.addEventListener("resize", this.position)
+ // relocate this popover when window is resized
}, 100);
},
hide() {
this.show_state = false;
setTimeout(() => {
+ // clean up listeners so they don't accumulate over time
document.removeEventListener("click", this.outside_click);
+ window.removeEventListener("resize", this.position)
}, 100);
},
position() {
@@ -46,6 +51,7 @@ module.exports = {
({x,y, placement, middlewareData}) => {
const {x: arrowX, y: arrowY} = middlewareData.arrow;
+ // position popover
Object.assign(this.$el.style, {
left: `${x}px`,
top: `${y}px`,
@@ -58,6 +64,7 @@ module.exports = {
left: 'right',
}[placement.split('-')[0]];
+ // position popover arrow
Object.assign(arrow_el.style, {
left: arrowX != null ? `${arrowX - middlewareData.shift.x}px` : '', // account for shifting
top: arrowY != null ? `${arrowY}px` : '',
@@ -97,6 +104,9 @@ module.exports = {
diff --git a/etc/js/components/stat_chart.vue b/etc/js/components/stat_chart.vue
new file mode 100644
index 00000000..e08529ea
--- /dev/null
+++ b/etc/js/components/stat_chart.vue
@@ -0,0 +1,238 @@
+
+
+
+
+
+
+
+
+
diff --git a/etc/js/components/stats_period.vue b/etc/js/components/stats_period.vue
new file mode 100644
index 00000000..fa6335da
--- /dev/null
+++ b/etc/js/components/stats_period.vue
@@ -0,0 +1,80 @@
+
+
+
+ 1 second
+
+ 1 minute
+
+ 1 hour
+
+ 1 day
+
+ 1 week
+
+
+
+
+
+
+
diff --git a/etc/js/components/stats_pipeline.vue b/etc/js/components/stats_pipeline.vue
new file mode 100644
index 00000000..3b371541
--- /dev/null
+++ b/etc/js/components/stats_pipeline.vue
@@ -0,0 +1,380 @@
+
+
+
+
+ Pipeline statistics
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Time spent
+ (
+ {{get_system_time_avg(i)}},
+ {{(system_time_pct.system_pct[i] * 100).toFixed(0)}}%
+ )
+
+
+
+
+
+
+ Matched entities
+ (
+ {{get_system_entities_avg(i)}}
+ )
+
+
+
+
+
+
+ Matched tables
+ (
+ {{get_system_tables_avg(i)}}
+ )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Could not request statistics from application, make sure to update
+ to the latest Flecs and import the flecs.monitor module!
+
+
+
+ In C:
+
+
+ ECS_IMPORT(world, FlecsMonitor);
+
+
+ In C++:
+
+
+ world.import<flecs::monitor>();
+
+
+
+
+
+
+
+ Connect to an application to see statistics.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/etc/js/components/stats_world.vue b/etc/js/components/stats_world.vue
new file mode 100644
index 00000000..6e10a2f8
--- /dev/null
+++ b/etc/js/components/stats_world.vue
@@ -0,0 +1,124 @@
+
+
+
+
+ World statistics
+
+
+
+
+
+
+
+
+
+
+
+ Could not request statistics from application, make sure to update
+ to the latest Flecs and import the flecs.monitor module!
+
+
+
+ In C:
+
+
+ ECS_IMPORT(world, FlecsMonitor);
+
+
+ In C++:
+
+
+ world.import<flecs::monitor>();
+
+
+
+
+
+
+
+ Connect to an application to see statistics.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/etc/js/components/tabs.vue b/etc/js/components/tabs.vue
new file mode 100644
index 00000000..354113aa
--- /dev/null
+++ b/etc/js/components/tabs.vue
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/etc/js/components/tooltip.vue b/etc/js/components/tooltip.vue
index 1b419823..dc342814 100644
--- a/etc/js/components/tooltip.vue
+++ b/etc/js/components/tooltip.vue
@@ -86,6 +86,4 @@ module.exports = {
transform: translateY(0px);
transition: opacity 0.20s 0.8s ease-in-out, transform 0.20s 0.8s ease-in-out;
}
-
-
-
\ No newline at end of file
+
diff --git a/etc/js/content_container.js b/etc/js/content_container.js
index e638406e..1a6d60ae 100644
--- a/etc/js/content_container.js
+++ b/etc/js/content_container.js
@@ -1,20 +1,58 @@
Vue.component('content-container', {
- props: ['hidden', 'disable', 'no_padding', 'closable'],
+ props: {
+ show_detail: { type: Boolean, required: false, default: true },
+ collapse: { type: Boolean, required: false, default: false },
+ no_padding: { type: Boolean, required: false },
+ closable: { type: Boolean, required: false }
+ },
+ data: function() {
+ return {
+ closed: false,
+ maximized: false
+ }
+ },
methods: {
+ open() {
+ this.closed = false;
+ this.$emit("panel-update");
+ },
+ close() {
+ this.closed = true;
+ this.maximized = false;
+ this.$emit("panel-update");
+ },
+ is_closed() {
+ return this.closed;
+ },
expand: function(arg) {
this.$refs.toggle.expand(arg);
},
- enable_toggle(e) {
- this.$refs.toggle.enable_toggle(e);
+ allow_toggle(e) {
+ this.$refs.toggle.allow_toggle(e);
},
evt_close: function() {
+ this.close();
+ this.maximized = false;
this.$emit('close');
+ },
+ evt_maximize: function() {
+ this.maximized = !this.maximized;
+ if (this.disable) {
+ this.maximized = false;
+ }
}
},
computed: {
wrapper_css: function() {
- return "content-container-wrapper content-container-wrapper-overflow";
+ let result = "content-container-wrapper content-container-wrapper-overflow";
+ if (this.closed) {
+ result += " disable";
+ }
+ if (this.maximized) {
+ result += " maximized";
+ }
+ return result;
},
detail_css: function() {
let result = "content-detail ";
@@ -22,15 +60,36 @@ Vue.component('content-container', {
result += " content-detail-padding";
}
return result;
+ },
+ maximize_icon: function() {
+ if (this.maximized) {
+ return "minimize";
+ } else {
+ return "maximize";
+ }
}
},
template: `
-
+
+
+
+
+
+
@@ -46,4 +105,3 @@ Vue.component('content-container', {
`
});
-
\ No newline at end of file
diff --git a/etc/js/detail_toggle.js b/etc/js/detail_toggle.js
index c453a2b1..f2eabd86 100644
--- a/etc/js/detail_toggle.js
+++ b/etc/js/detail_toggle.js
@@ -1,78 +1,64 @@
-
Vue.component('detail-toggle', {
- props: ['disable', 'collapse', 'hide_disabled', 'summary_toggle', 'show_divider'],
+ props: {
+ show_summary: { type: Boolean, required: false, default: true },
+ show_detail: { type: Boolean, required: false, default: true },
+ show_divider: { type: Boolean, required: false, default: false }
+ },
data: function() {
return {
- should_expand: true,
- can_toggle: true
+ expanded: true,
+ toggle_allowed: true
}
},
methods: {
toggle: function() {
- this.should_expand = !this.should_expand;
- },
- summary_clicked: function() {
- if (!this.can_toggle) {
- return;
- }
- if (this.summary_toggle && !this.disable) {
- this.should_expand = !this.should_expand;
+ if (this.toggle_allowed) {
+ this.expanded = !this.expanded;
}
},
expand: function(expand) {
if (expand === undefined) {
- this.should_expand = true;
+ this.expanded = true;
} else {
- this.should_expand = expand;
+ this.expanded = expand;
}
},
- enable_toggle: function(e) {
- this.can_toggle = e;
+ allow_toggle: function(allow) {
+ this.toggle_allowed = allow;
}
},
computed: {
- expanded: function() {
- return this.should_expand && (this.collapse === undefined || this.collapse === false);
- },
summary_css: function() {
- let result = "detail-toggle-summary";
- if (this.summary_toggle) {
- result += " noselect";
- }
- if (!this.disable) {
- result += " clickable";
- }
+ let result = "detail-toggle-summary noselect clickable";
return result;
},
detail_css: function() {
let result = "detail-toggle-detail"
- if (!this.expanded) {
+ if (!this.expanded || !this.show_detail) {
result += " detail-toggle-detail-hide";
}
- if (this.disable && !this.hide_disable) {
- result += " detail-toggle-detail-disable";
- }
return result;
}
},
template: `
-
-
+
diff --git a/etc/js/editor.js b/etc/js/editor.js
index 751b6441..371fc21c 100644
--- a/etc/js/editor.js
+++ b/etc/js/editor.js
@@ -73,9 +73,10 @@ Vue.component('editor', {
data: function() {
return {
status: undefined,
- status_kind: undefined,
+ status_kind: undefined
}
},
+ props: ["disable"],
methods: {
run() {
this.$refs.textarea.run();
@@ -97,10 +98,34 @@ Vue.component('editor', {
this.status = undefined;
this.status_kind = Status.Info;
}
+ },
+ open: function() {
+ this.$refs.container.open();
+ },
+ close: function() {
+ this.$refs.container.close();
+ },
+ evt_panel_update: function() {
+ this.$emit('panel-update');
+ },
+ evt_close() {
+ this.evt_panel_update();
+ }
+ },
+ watch: {
+ disable: function() {
+ this.$emit("panel-update");
}
},
template: `
-
+
+
Editor
diff --git a/etc/js/entity_inspector.js b/etc/js/entity_inspector.js
index 95f85db5..072efcdb 100644
--- a/etc/js/entity_inspector.js
+++ b/etc/js/entity_inspector.js
@@ -22,7 +22,7 @@ function fmt_float(value) {
if (num < 0) {
num = 0;
}
- return value.toFixed(num);
+ return Number.parseFloat(value.toFixed(num));
}
}
@@ -347,7 +347,7 @@ const inspector_component_component = Vue.component('inspector-component', {
template: `
-
+
:
-
+
@@ -425,20 +429,82 @@ const inspector_components_component = Vue.component('inspector-components', {
// Top level inspector
const inspector_component = Vue.component('inspector', {
- props: ['entity', 'entity_name', 'valid'],
+ props: ['valid'],
+ data: function() {
+ return {
+ entity: undefined,
+ entity_name: undefined,
+ error: undefined
+ }
+ },
+ mounted: function() {
+ if (this.entity_name == undefined) {
+ this.close();
+ }
+ },
methods: {
- expand: function() {
+ expand() {
this.$refs.container.expand();
},
- select_query: function() {
+ select_query() {
this.$emit('select-query', this.entity_name);
},
- evt_close: function() {
- this.$emit('select-entity');
+ refresh() {
+ if (!this.entity_name) {
+ return;
+ }
+
+ if (this.$refs.container.is_closed()) {
+ return;
+ }
+
+ app.request_entity('inspector', this.entity_name, (reply) => {
+ this.error = reply.error;
+ if (this.error === undefined) {
+ this.entity = reply;
+ }
+ }, () => {
+ this.error = "request for entity '" + this.entity_name + "' failed";
+ }, {type_info: true, label: true, brief: true, link: true, id_labels: true, values: true});
+ },
+ set_entity(path) {
+ if (path == this.entity_name) {
+ return;
+ }
+
+ app.request_abort('inspector');
+
+ this.entity = undefined;
+ this.entity_name = path;
+
+ if (path == undefined) {
+ this.close();
+ return;
+ } else {
+ this.open();
+ }
+
+ this.expand();
+ this.refresh();
},
- name_from_path: function(path) {
+ name_from_path(path) {
return name_from_path(path);
},
+ open() {
+ this.$refs.container.open();
+ },
+ close() {
+ this.$refs.container.close();
+ },
+ get_entity() {
+ return this.entity_name;
+ },
+ evt_panel_update() {
+ this.$emit('panel-update');
+ },
+ evt_close() {
+ this.$emit('select-entity');
+ }
},
computed: {
parent: function() {
@@ -474,11 +540,12 @@ const inspector_component = Vue.component('inspector', {
},
template: `
+ ref="container"
+ :no_padding="true"
+ :closable="true"
+ :show_detail="entity_name != undefined"
+ v-on:close="evt_close"
+ v-on:panel-update="evt_panel_update">
@@ -493,17 +560,6 @@ const inspector_component = Vue.component('inspector', {
Entity inspector
-
-
-
-
-
@@ -529,7 +585,7 @@ const inspector_component = Vue.component('inspector', {
diff --git a/etc/js/entity_tree.js b/etc/js/entity_tree.js
index 92f12a2e..2e7fde60 100644
--- a/etc/js/entity_tree.js
+++ b/etc/js/entity_tree.js
@@ -52,7 +52,7 @@ Vue.component('entity-tree-item', {
}
},
width_select_box: function() {
- return this.width - this.x - 29;
+ return Math.max(this.width - this.x - 29, 0);
},
css_text: function() {
if (this.entity_data.path == this.selected_entity) {
@@ -65,38 +65,41 @@ Vue.component('entity-tree-item', {
return this.entity_data.is_component && !this.entity_data.is_module;
},
search_icon_x: function() {
- return this.width - 20;
+ return Math.max(this.width - 20, 0);
+ },
+ xp: function() {
+ return this.x + 3;
}
},
template: `
`
});
@@ -267,7 +270,20 @@ Vue.component('entity-tree-list', {
});
Vue.component('entity-tree', {
- props: ['valid', 'selected_entity'],
+ props: ['valid'],
+ data: function() {
+ return {
+ width: 0,
+ disabled: false,
+ selected_entity: undefined,
+ root: {
+ path: "0",
+ entities: {},
+ expand: true,
+ selection: undefined
+ }
+ }
+ },
beforeUpdate: function() {
this.width = this.$el.clientWidth;
},
@@ -346,6 +362,10 @@ Vue.component('entity-tree', {
});
},
update_expanded: function(container) {
+ if (this.disabled) {
+ return;
+ }
+
if (!container) {
container = this.root;
}
@@ -388,7 +408,7 @@ Vue.component('entity-tree', {
this.update(cur, () => {
let next = cur.entities[elems[i]];
if (!next) {
- console.error("entity-tree: cannot navigate to entity " + elems[i]);
+ // console.error("entity-tree: cannot navigate to entity " + elems[i]);
this.collapse_all();
}
@@ -414,6 +434,9 @@ Vue.component('entity-tree', {
this.evt_select(item);
});
},
+ set_selected_entity(entity) {
+ this.selected_entity = entity;
+ },
evt_select: function(entity) {
if (entity) {
if (entity.path == this.selected_entity) {
@@ -429,17 +452,14 @@ Vue.component('entity-tree', {
this.$emit('select_query', entity);
},
evt_resize: function(elem) {
- }
- },
- data: function() {
- return {
- width: 0,
- root: {
- path: "0",
- entities: {},
- expand: true,
- selection: undefined
- }
+ },
+ open: function() {
+ this.disabled = false;
+ this.$emit("panel-update");
+ },
+ close: function() {
+ this.disabled = true;
+ this.$emit("panel-update");
}
},
computed: {
@@ -457,6 +477,9 @@ Vue.component('entity-tree', {
if (!this.valid) {
result += " invalid";
}
+ if (this.disabled) {
+ result += " disable";
+ }
return result;
}
},
diff --git a/etc/js/functional_components/functional_entity_hierarchy.js b/etc/js/functional_components/functional_entity_hierarchy.js
index 25383724..774b24ce 100644
--- a/etc/js/functional_components/functional_entity_hierarchy.js
+++ b/etc/js/functional_components/functional_entity_hierarchy.js
@@ -8,8 +8,7 @@ Vue.component('entity-hierarchy', {
return createElement(
'div',
- { class: ["entity-hierarchy"],
- on: { click: () => { console.log('clicked', context.props.entity_path) } } },
+ { class: ["entity-hierarchy"] },
entities.map(
(entity, index) => {
if (index != entities.length - 1) {
diff --git a/etc/js/functional_components/functional_icon.js b/etc/js/functional_components/functional_icon.js
index 0d963a48..7453d1e9 100644
--- a/etc/js/functional_components/functional_icon.js
+++ b/etc/js/functional_components/functional_icon.js
@@ -2,18 +2,27 @@ Vue.component('icon', {
functional: true,
props: {
icon: { type: String, required: true },
- size: { type: Number, required: false, default: 16 }
+ size: { type: Number, required: false, default: 16 },
+ rotate: { type: Number, required: false, default: 0 }
},
render: function (createElement, context) {
let [iconset, icon] = context.props.icon.split(":");
+ let css;
+
+ if (iconset == "codicons") {
+ css = "codicons-icon";
+ } else {
+ css = "feather-icon";
+ }
return createElement('svg', {
class: [
- "icon-obj",
+ css
],
style: {
width: `${context.props.size}px`,
height: `${context.props.size}px`,
+ transform: `rotate(${context.props.rotate}deg)`
}
}, [
createElement('use', {
diff --git a/etc/js/functional_components/functional_icon_button.js b/etc/js/functional_components/functional_icon_button.js
index c6e24830..52092419 100644
--- a/etc/js/functional_components/functional_icon_button.js
+++ b/etc/js/functional_components/functional_icon_button.js
@@ -2,19 +2,22 @@ Vue.component('icon-button', {
functional: true,
props: {
icon: { type: String, required: true },
- size: { type: Number, required: false, default: 16 }
+ size: { type: Number, required: false, default: 16 },
+ active: { type: Boolean, required: false, default: false }
},
render: function (createElement, context) {
let [iconset, icon] = context.props.icon.split(":");
+ let class_list = ["icon-button", "noselect", "clickable"];
+
+ if (context.props.active) {
+ class_list.push("icon-button-active");
+ }
+
return createElement(
'div',
{
- class: [
- "icon-button",
- "noselect",
- "clickable"
- ],
+ class: class_list,
style: {
width: `${context.props.size}px`,
height: `${context.props.size}px`,
diff --git a/etc/js/overlays/popovers/connection-popover.vue b/etc/js/overlays/popovers/connection-popover.vue
new file mode 100644
index 00000000..eed1f985
--- /dev/null
+++ b/etc/js/overlays/popovers/connection-popover.vue
@@ -0,0 +1,196 @@
+
+
+
+
+
+
+ Application connection
+
+
+ Connect to a Flecs application on localhost or remotely.
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/etc/js/overlays/popovers/url-popover.vue b/etc/js/overlays/popovers/url-popover.vue
index 4f029ce5..99778f3f 100644
--- a/etc/js/overlays/popovers/url-popover.vue
+++ b/etc/js/overlays/popovers/url-popover.vue
@@ -8,7 +8,7 @@
@@ -72,11 +72,8 @@ module.exports = {
border: 1px solid var(--grey-700);
border-radius: var(--br-2);
background-color: transparent;
-}
-
-.form-control:focus {
- border-color: var(--color-acent-emphasis);
- outline: 3px solid var(--color-acent-emphasis-translucent);
+ outline: 0px solid transparent;
+ transition: all 0.2s ease-in-out;
}
.popover-title {
@@ -88,4 +85,12 @@ module.exports = {
align-items: center;
gap: 4px;
}
+
+.popover-description {
+ font-size: var(--text-fs-sm);
+ line-height: var(--text-lh-sm);
+ font-weight: 400;
+ color: var(--secondary-text);
+ white-space:normal;
+}
\ No newline at end of file
diff --git a/etc/js/query.js b/etc/js/query.js
index 5cd2db3f..26d93a15 100644
--- a/etc/js/query.js
+++ b/etc/js/query.js
@@ -8,7 +8,7 @@ Vue.component('query', {
query_result: undefined,
error: false,
invalid_query: false,
- request: undefined
+ request: undefined,
}
},
methods: {
@@ -62,10 +62,13 @@ Vue.component('query', {
this.query_result = undefined;
}
},
- evt_enable_toggle(e) {
- this.$refs.container.enable_toggle(e);
+ evt_allow_toggle(e) {
+ this.$refs.container.allow_toggle(e);
},
refresh() {
+ if (this.$refs.container.is_closed()) {
+ return;
+ }
this.evt_query_changed(this.$refs.editor.get_query());
},
get_query() {
@@ -82,11 +85,20 @@ Vue.component('query', {
get_error() {
return this.query_error;
},
+ result_count() {
+ return this.$refs.results.count();
+ },
+ open() {
+ this.$refs.container.open();
+ },
+ close() {
+ this.$refs.container.close();
+ },
evt_close() {
this.set_query();
},
- result_count() {
- return this.$refs.results.count();
+ evt_panel_update() {
+ this.$emit("panel-update");
}
},
computed: {
@@ -110,30 +122,17 @@ Vue.component('query', {
template: `
+ v-on:close="evt_close"
+ v-on:panel-update="evt_panel_update">
-
-
-
-
-
-
-
+ v-on:allow-toggle="evt_allow_toggle"/>
diff --git a/etc/js/query_editor.js b/etc/js/query_editor.js
index 32f0488b..68d10114 100644
--- a/etc/js/query_editor.js
+++ b/etc/js/query_editor.js
@@ -52,8 +52,7 @@ Vue.component('query-editor', {
},
evt_focus(focus) {
this.focus = focus;
- this.$emit('enable_toggle', !focus);
-
+ this.$emit('allow-toggle', !focus);
if (focus) {
this.query = this.actual_query;
} else {
@@ -101,7 +100,9 @@ Vue.component('query-editor', {
Search
-
+
+
+
`
});
diff --git a/etc/js/query_results.js b/etc/js/query_results.js
index 489f61f3..7241a105 100644
--- a/etc/js/query_results.js
+++ b/etc/js/query_results.js
@@ -41,6 +41,7 @@ Vue.component('query-result', {
+
@@ -49,7 +50,7 @@ Vue.component('query-result', {
|
-
+
None
@@ -226,13 +227,14 @@ Vue.component('query-results', {
-
+
@@ -242,6 +244,7 @@ Vue.component('query-results', {
:result="result"
:index="0"
:show_terms="show_terms"
+ :key="entity"
v-on="$listeners"/>
diff --git a/etc/js/split_panes.js b/etc/js/split_panes.js
index 66a6574c..ec94dde8 100644
--- a/etc/js/split_panes.js
+++ b/etc/js/split_panes.js
@@ -15,6 +15,7 @@ const resize_handle = Vue.component('resize-handle', {
begin_drag_callback: Function,
dragging_callback: Function,
end_drag_callback: Function,
+ last: Boolean
},
data() {
return {
@@ -25,12 +26,6 @@ const resize_handle = Vue.component('resize-handle', {
x: 0,
}
},
- computed: {
- },
- watch: {
- },
- created() {
- },
mounted() {
this.$nextTick(() => {
this.start = this.$el.offsetLeft;
@@ -38,9 +33,6 @@ const resize_handle = Vue.component('resize-handle', {
})
},
methods: {
- recalibrate() {
-
- },
begin_drag(e) {
e.preventDefault();
this.moving = true;
@@ -78,8 +70,17 @@ const resize_handle = Vue.component('resize-handle', {
this.$parent.$el.style.cursor = "auto";
}
},
+ computed: {
+ css() {
+ let result = "handle";
+ if (this.last) {
+ result += " handle-last";
+ }
+ return result;
+ }
+ },
template: `
-
+
+
`
-})
+})
const frame_container = Vue.component('split-pane-container', {
data() {
@@ -186,7 +199,7 @@ const frame_container = Vue.component('split-pane-container', {
window.controller = this;
// Initialize frame dimensions
- this.resize()
+ this.resize();
// When window resizes, resize frames.
window.addEventListener("resize", () => {
@@ -196,6 +209,9 @@ const frame_container = Vue.component('split-pane-container', {
// Instantiate handles
for (let i = 0; i < this.frames.length - 1; i++) {
let frame = this.frames[i];
+ if (!frame.resizable) {
+ continue;
+ }
let handle_class = Vue.extend(resize_handle)
let handle_instance = new handle_class({
@@ -205,8 +221,9 @@ const frame_container = Vue.component('split-pane-container', {
begin_drag_callback: this.begin_adjust,
dragging_callback: this.adjust,
end_drag_callback: this.end_adjust,
+ last: i == (this.frames.length - 2)
}
- })
+ });
// Set ancestry
this.$children.push(handle_instance);
@@ -219,19 +236,54 @@ const frame_container = Vue.component('split-pane-container', {
},
methods: {
+ has_active_children(frame) {
+ let result = 0;
+
+ for (let i = 0; i < frame.$children.length; i ++) {
+ const child = frame.$children[i];
+ const css = child.$el.classList;
+ if (!css.contains("disable")) {
+ result ++;
+ }
+ }
+
+ return result != 0;
+ },
resize() {
+ let collapsed_width = 0;
+
+ for (const frame of this.frames) {
+ let active = true;
+ if (frame.collapseable) {
+ active = this.has_active_children(frame);
+ }
+
+ if (!active) {
+ collapsed_width += frame.width - 1;
+ frame.collapse();
+ } else {
+ frame.expand();
+ }
+ }
+
// Capture available and demanded space at moment
let application_width = this.$el.offsetWidth;
- let free_sp = application_width - (this.layout.fixed_fr_space + this.layout.handle_space);
+ let free_sp = application_width - (this.layout.fixed_fr_space + this.layout.handle_space) + collapsed_width;
let demanded_sp = this.layout.fluid_fr_space;
for (const frame of this.frames) {
- if (!frame.fixed) {
+ let active = true;
+ if (frame.collapseable) {
+ active = this.has_active_children(frame);
+ }
+
+ if (active && !frame.fixed) {
let r = frame.width / demanded_sp;
let resized_width = r * free_sp;
frame.width = resized_width >= frame.min_width ? resized_width : frame.min_width;
}
+
frame.save();
}
},
@@ -291,47 +343,6 @@ const frame_container = Vue.component('split-pane-container', {
}
},
- insert_frame(index) {
- // Insertion index must be within range
- if (index >= this.frames.length) return;
-
- // If no insertion index, then point to end
- if (!index) index = this.frames.length - 1;
-
- // Create frame
- let frame_class = Vue.extend(frame)
- let frame_instance = new frame_class()
- this.$children.push(frame_instance);
- this.frames.push(frame_instance);
- frame_instance.$parent = this;
- frame_instance.$mount();
- this.frames[index].$el.after(frame_instance.$el);
-
- // Create handle
- let handle_class = Vue.extend(resize_handle)
- let handle_instance = new handle_class({
- propsData: {
- left_frame: this.frames[index],
- right_frame: this.frames[index + 1],
- begin_drag_callback: this.begin_adjust,
- dragging_callback: this.adjust,
- end_drag_callback: this.end_adjust,
- }
- })
- this.$children.push(handle_instance);
- this.handles.push(handle_instance);
- handle_instance.$parent = this;
- handle_instance.$mount();
- this.frames[index].$el.after(handle_instance.$el);
-
- // Modify previous handle
- this.handles[index].left_frame = this.frames[index + 1];
- this.handles[index].right_frame = this.frames[index + 2];
-
- // Fit everything together
- this.resize();
- }
-
},
template: `
diff --git a/etc/js/status.js b/etc/js/status.js
index 00cacd0e..8b63b965 100644
--- a/etc/js/status.js
+++ b/etc/js/status.js
@@ -18,7 +18,7 @@ Vue.component('status', {
let result = "content-status";
if (this.has_status) {
result += " content-status-visible";
- if (this.kind == Status.Error) {
+ if (this.kind === undefined || this.kind == Status.Error) {
result += " content-status-error";
}
}
@@ -35,13 +35,13 @@ Vue.component('status', {
return;
}
let result = "content-status-text";
- if (this.kind == Status.Error) {
+ if (this.kind === undefined || this.kind == Status.Error) {
result += " content-status-text-error";
}
return result;
},
status_icon: function() {
- if (this.kind == Status.Error) {
+ if (this.kind === undefined || this.kind == Status.Error) {
return "error";
}
}
diff --git a/etc/js/widgets/connection_status.vue b/etc/js/widgets/connection_status.vue
new file mode 100644
index 00000000..0f3c0d59
--- /dev/null
+++ b/etc/js/widgets/connection_status.vue
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+ Initializing
+
+
+
+ Not connected
+
+
+
+ Connecting...
+
+
+
+ Connected
+
+ {{ value }}
+
+
+
+ Reconnecting
+
+
+
+ Connection failed
+
+
+
+ Disconnecting
+
+
+ Unknown error
+
+
+
+
+
+
+
+
\ No newline at end of file
|