mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 05:37:17 +02:00
merge 'master' into 'workload'
This commit is contained in:
commit
1247f88d83
43 changed files with 3981 additions and 2902 deletions
|
@ -1,12 +1,6 @@
|
||||||
{
|
{
|
||||||
"version": 2.1,
|
"version": 2.1,
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
|
||||||
"name": "label",
|
|
||||||
"label": "Label",
|
|
||||||
"settings": [
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "metaverse",
|
"name": "metaverse",
|
||||||
"label": "Metaverse / Networking",
|
"label": "Metaverse / Networking",
|
||||||
|
@ -15,7 +9,8 @@
|
||||||
"name": "access_token",
|
"name": "access_token",
|
||||||
"label": "Access Token",
|
"label": "Access Token",
|
||||||
"help": "This is your OAuth access token to connect this domain-server with your High Fidelity account. <br/>It can be generated by clicking the 'Connect Account' button above.<br/>You can also go to the <a href='https://metaverse.highfidelity.com/user/security' target='_blank'>My Security</a> page of your account and generate a token with the 'domains' scope and paste it here.",
|
"help": "This is your OAuth access token to connect this domain-server with your High Fidelity account. <br/>It can be generated by clicking the 'Connect Account' button above.<br/>You can also go to the <a href='https://metaverse.highfidelity.com/user/security' target='_blank'>My Security</a> page of your account and generate a token with the 'domains' scope and paste it here.",
|
||||||
"advanced": true
|
"advanced": true,
|
||||||
|
"backup": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "id",
|
"name": "id",
|
||||||
|
@ -55,8 +50,8 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Places / Paths",
|
"label": "Paths",
|
||||||
"html_id": "places_paths",
|
"html_id": "paths",
|
||||||
"restart": false,
|
"restart": false,
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
|
@ -64,6 +59,7 @@
|
||||||
"label": "Paths",
|
"label": "Paths",
|
||||||
"help": "Clients can enter a path to reach an exact viewpoint in your domain.<br/>Add rows to the table below to map a path to a viewpoint.<br/>The index path ( / ) is where clients will enter if they do not enter an explicit path.",
|
"help": "Clients can enter a path to reach an exact viewpoint in your domain.<br/>Add rows to the table below to map a path to a viewpoint.<br/>The index path ( / ) is where clients will enter if they do not enter an explicit path.",
|
||||||
"type": "table",
|
"type": "table",
|
||||||
|
"content_setting": true,
|
||||||
"can_add_new_rows": true,
|
"can_add_new_rows": true,
|
||||||
"key": {
|
"key": {
|
||||||
"name": "path",
|
"name": "path",
|
||||||
|
@ -164,7 +160,8 @@
|
||||||
{
|
{
|
||||||
"name": "http_username",
|
"name": "http_username",
|
||||||
"label": "HTTP Username",
|
"label": "HTTP Username",
|
||||||
"help": "Username used for basic HTTP authentication."
|
"help": "Username used for basic HTTP authentication.",
|
||||||
|
"backup": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "http_password",
|
"name": "http_password",
|
||||||
|
@ -172,7 +169,8 @@
|
||||||
"type": "password",
|
"type": "password",
|
||||||
"help": "Password used for basic HTTP authentication. Leave this alone if you do not want to change it.",
|
"help": "Password used for basic HTTP authentication. Leave this alone if you do not want to change it.",
|
||||||
"password_placeholder": "******",
|
"password_placeholder": "******",
|
||||||
"value-hidden": true
|
"value-hidden": true,
|
||||||
|
"backup": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "verify_http_password",
|
"name": "verify_http_password",
|
||||||
|
@ -935,6 +933,7 @@
|
||||||
"name": "persistent_scripts",
|
"name": "persistent_scripts",
|
||||||
"type": "table",
|
"type": "table",
|
||||||
"label": "Persistent Scripts",
|
"label": "Persistent Scripts",
|
||||||
|
"content_setting": true,
|
||||||
"help": "Add the URLs for scripts that you would like to ensure are always running in your domain.",
|
"help": "Add the URLs for scripts that you would like to ensure are always running in your domain.",
|
||||||
"can_add_new_rows": true,
|
"can_add_new_rows": true,
|
||||||
"columns": [
|
"columns": [
|
||||||
|
@ -955,99 +954,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "asset_server",
|
|
||||||
"label": "Asset Server (ATP)",
|
|
||||||
"assignment-types": [ 3 ],
|
|
||||||
"settings": [
|
|
||||||
{
|
|
||||||
"name": "enabled",
|
|
||||||
"type": "checkbox",
|
|
||||||
"label": "Enabled",
|
|
||||||
"help": "Assigns an asset-server in your domain to serve files to clients via the ATP protocol (over UDP)",
|
|
||||||
"default": true,
|
|
||||||
"advanced": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "assets_path",
|
|
||||||
"type": "string",
|
|
||||||
"label": "Assets Path",
|
|
||||||
"help": "The path to the directory assets are stored in.<br/>If this path is relative, it will be relative to the application data directory.<br/>If you change this path you will need to manually copy any existing assets from the previous directory.",
|
|
||||||
"default": "",
|
|
||||||
"advanced": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "assets_filesize_limit",
|
|
||||||
"type": "int",
|
|
||||||
"label": "File Size Limit",
|
|
||||||
"help": "The file size limit of an asset that can be imported into the asset server in MBytes. 0 (default) means no limit on file size.",
|
|
||||||
"default": 0,
|
|
||||||
"advanced": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "entity_script_server",
|
|
||||||
"label": "Entity Script Server (ESS)",
|
|
||||||
"assignment-types": [ 5 ],
|
|
||||||
"settings": [
|
|
||||||
{
|
|
||||||
"name": "entity_pps_per_script",
|
|
||||||
"label": "Entity PPS per script",
|
|
||||||
"help": "The number of packets per second (PPS) that can be sent to the entity server for each server entity script. This contributes to a total overall amount.<br/>Example: 1000 PPS with 5 entites gives a total PPS of 5000 that is shared among the entity scripts. A single could use 4000 PPS, leaving 1000 for the rest, for example.",
|
|
||||||
"default": 900,
|
|
||||||
"type": "int",
|
|
||||||
"advanced": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "max_total_entity_pps",
|
|
||||||
"label": "Maximum Total Entity PPS",
|
|
||||||
"help": "The maximum total packets per seconds (PPS) that can be sent to the entity server.<br/>Example: 5 scripts @ 1000 PPS per script = 5000 total PPS. A maximum total PPS of 4000 would cap this 5000 total PPS to 4000.",
|
|
||||||
"default": 9000,
|
|
||||||
"type": "int",
|
|
||||||
"advanced": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "avatars",
|
|
||||||
"label": "Avatars",
|
|
||||||
"assignment-types": [ 1, 2 ],
|
|
||||||
"settings": [
|
|
||||||
{
|
|
||||||
"name": "min_avatar_height",
|
|
||||||
"type": "double",
|
|
||||||
"label": "Minimum Avatar Height (meters)",
|
|
||||||
"help": "Limits the height of avatars in your domain. Must be at least 0.009.",
|
|
||||||
"placeholder": 0.4,
|
|
||||||
"default": 0.4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "max_avatar_height",
|
|
||||||
"type": "double",
|
|
||||||
"label": "Maximum Avatar Height (meters)",
|
|
||||||
"help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.",
|
|
||||||
"placeholder": 5.2,
|
|
||||||
"default": 5.2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "avatar_whitelist",
|
|
||||||
"label": "Avatars Allowed from:",
|
|
||||||
"help": "Comma separated list of URLs (with optional paths) that avatar .fst files are allowed from. If someone attempts to use an avatar with a different domain, it will be rejected and the replacement avatar will be used. If left blank, any domain is allowed.",
|
|
||||||
"placeholder": "",
|
|
||||||
"default": "",
|
|
||||||
"advanced": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "replacement_avatar",
|
|
||||||
"label": "Replacement Avatar for disallowed avatars",
|
|
||||||
"help": "A URL for an avatar .fst to be used when someone tries to use an avatar that is not allowed. If left blank, the generic default avatar is used.",
|
|
||||||
"placeholder": "",
|
|
||||||
"default": "",
|
|
||||||
"advanced": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "audio_threading",
|
"name": "audio_threading",
|
||||||
"label": "Audio Threading",
|
"label": "Audio Threading",
|
||||||
|
@ -1080,6 +986,7 @@
|
||||||
"name": "attenuation_per_doubling_in_distance",
|
"name": "attenuation_per_doubling_in_distance",
|
||||||
"label": "Default Domain Attenuation",
|
"label": "Default Domain Attenuation",
|
||||||
"help": "Factor between 0 and 1.0 (0: No attenuation, 1.0: extreme attenuation)",
|
"help": "Factor between 0 and 1.0 (0: No attenuation, 1.0: extreme attenuation)",
|
||||||
|
"content_setting": true,
|
||||||
"placeholder": "0.5",
|
"placeholder": "0.5",
|
||||||
"default": "0.5",
|
"default": "0.5",
|
||||||
"advanced": false
|
"advanced": false
|
||||||
|
@ -1105,6 +1012,7 @@
|
||||||
"label": "Zones",
|
"label": "Zones",
|
||||||
"help": "In this table you can define a set of zones in which you can specify various audio properties.",
|
"help": "In this table you can define a set of zones in which you can specify various audio properties.",
|
||||||
"numbered": false,
|
"numbered": false,
|
||||||
|
"content_setting": true,
|
||||||
"can_add_new_rows": true,
|
"can_add_new_rows": true,
|
||||||
"key": {
|
"key": {
|
||||||
"name": "name",
|
"name": "name",
|
||||||
|
@ -1155,6 +1063,7 @@
|
||||||
"type": "table",
|
"type": "table",
|
||||||
"label": "Attenuation Coefficients",
|
"label": "Attenuation Coefficients",
|
||||||
"help": "In this table you can set custom attenuation coefficients between audio zones",
|
"help": "In this table you can set custom attenuation coefficients between audio zones",
|
||||||
|
"content_setting": true,
|
||||||
"numbered": true,
|
"numbered": true,
|
||||||
"can_order": true,
|
"can_order": true,
|
||||||
"can_add_new_rows": true,
|
"can_add_new_rows": true,
|
||||||
|
@ -1185,6 +1094,7 @@
|
||||||
"label": "Reverb Settings",
|
"label": "Reverb Settings",
|
||||||
"help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.",
|
"help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.",
|
||||||
"numbered": true,
|
"numbered": true,
|
||||||
|
"content_setting": true,
|
||||||
"can_add_new_rows": true,
|
"can_add_new_rows": true,
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
|
@ -1266,9 +1176,82 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "avatars",
|
||||||
|
"label": "Avatars",
|
||||||
|
"assignment-types": [ 1, 2 ],
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"name": "min_avatar_height",
|
||||||
|
"type": "double",
|
||||||
|
"label": "Minimum Avatar Height (meters)",
|
||||||
|
"help": "Limits the height of avatars in your domain. Must be at least 0.009.",
|
||||||
|
"placeholder": 0.4,
|
||||||
|
"default": 0.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "max_avatar_height",
|
||||||
|
"type": "double",
|
||||||
|
"label": "Maximum Avatar Height (meters)",
|
||||||
|
"help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.",
|
||||||
|
"placeholder": 5.2,
|
||||||
|
"default": 5.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "avatar_whitelist",
|
||||||
|
"label": "Avatars Allowed from:",
|
||||||
|
"help": "Comma separated list of URLs (with optional paths) that avatar .fst files are allowed from. If someone attempts to use an avatar with a different domain, it will be rejected and the replacement avatar will be used. If left blank, any domain is allowed.",
|
||||||
|
"placeholder": "",
|
||||||
|
"default": "",
|
||||||
|
"advanced": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "replacement_avatar",
|
||||||
|
"label": "Replacement Avatar for disallowed avatars",
|
||||||
|
"help": "A URL for an avatar .fst to be used when someone tries to use an avatar that is not allowed. If left blank, the generic default avatar is used.",
|
||||||
|
"placeholder": "",
|
||||||
|
"default": "",
|
||||||
|
"advanced": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "avatar_mixer",
|
||||||
|
"label": "Avatar Mixer",
|
||||||
|
"assignment-types": [
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"name": "max_node_send_bandwidth",
|
||||||
|
"type": "double",
|
||||||
|
"label": "Per-Node Bandwidth",
|
||||||
|
"help": "Desired maximum send bandwidth (in Megabits per second) to each node",
|
||||||
|
"placeholder": 5.0,
|
||||||
|
"default": 5.0,
|
||||||
|
"advanced": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "auto_threads",
|
||||||
|
"label": "Automatically determine thread count",
|
||||||
|
"type": "checkbox",
|
||||||
|
"help": "Allow system to determine number of threads (recommended)",
|
||||||
|
"default": false,
|
||||||
|
"advanced": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "num_threads",
|
||||||
|
"label": "Number of Threads",
|
||||||
|
"help": "Threads to spin up for avatar mixing (if not automatically set)",
|
||||||
|
"placeholder": "1",
|
||||||
|
"default": "1",
|
||||||
|
"advanced": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "entity_server_settings",
|
"name": "entity_server_settings",
|
||||||
"label": "Entity Server Settings",
|
"label": "Entities",
|
||||||
"assignment-types": [
|
"assignment-types": [
|
||||||
6
|
6
|
||||||
],
|
],
|
||||||
|
@ -1309,6 +1292,7 @@
|
||||||
"name": "entityEditFilter",
|
"name": "entityEditFilter",
|
||||||
"label": "Filter Entity Edits",
|
"label": "Filter Entity Edits",
|
||||||
"help": "Check all entity edits against this filter function.",
|
"help": "Check all entity edits against this filter function.",
|
||||||
|
"content_setting": true,
|
||||||
"placeholder": "url whose content is like: function filter(properties) { return properties; }",
|
"placeholder": "url whose content is like: function filter(properties) { return properties; }",
|
||||||
"default": "",
|
"default": "",
|
||||||
"advanced": true
|
"advanced": true
|
||||||
|
@ -1503,35 +1487,55 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "avatar_mixer",
|
"name": "asset_server",
|
||||||
"label": "Avatar Mixer",
|
"label": "Asset Server (ATP)",
|
||||||
"assignment-types": [
|
"assignment-types": [ 3 ],
|
||||||
1
|
|
||||||
],
|
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"name": "max_node_send_bandwidth",
|
"name": "enabled",
|
||||||
"type": "double",
|
|
||||||
"label": "Per-Node Bandwidth",
|
|
||||||
"help": "Desired maximum send bandwidth (in Megabits per second) to each node",
|
|
||||||
"placeholder": 5.0,
|
|
||||||
"default": 5.0,
|
|
||||||
"advanced": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "auto_threads",
|
|
||||||
"label": "Automatically determine thread count",
|
|
||||||
"type": "checkbox",
|
"type": "checkbox",
|
||||||
"help": "Allow system to determine number of threads (recommended)",
|
"label": "Enabled",
|
||||||
"default": false,
|
"help": "Assigns an asset-server in your domain to serve files to clients via the ATP protocol (over UDP)",
|
||||||
|
"default": true,
|
||||||
"advanced": true
|
"advanced": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "num_threads",
|
"name": "assets_path",
|
||||||
"label": "Number of Threads",
|
"type": "string",
|
||||||
"help": "Threads to spin up for avatar mixing (if not automatically set)",
|
"label": "Assets Path",
|
||||||
"placeholder": "1",
|
"help": "The path to the directory assets are stored in.<br/>If this path is relative, it will be relative to the application data directory.<br/>If you change this path you will need to manually copy any existing assets from the previous directory.",
|
||||||
"default": "1",
|
"default": "",
|
||||||
|
"advanced": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "assets_filesize_limit",
|
||||||
|
"type": "int",
|
||||||
|
"label": "File Size Limit",
|
||||||
|
"help": "The file size limit of an asset that can be imported into the asset server in MBytes. 0 (default) means no limit on file size.",
|
||||||
|
"default": 0,
|
||||||
|
"advanced": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "entity_script_server",
|
||||||
|
"label": "Entity Script Server (ESS)",
|
||||||
|
"assignment-types": [ 5 ],
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"name": "entity_pps_per_script",
|
||||||
|
"label": "Entity PPS per script",
|
||||||
|
"help": "The number of packets per second (PPS) that can be sent to the entity server for each server entity script. This contributes to a total overall amount.<br/>Example: 1000 PPS with 5 entites gives a total PPS of 5000 that is shared among the entity scripts. A single could use 4000 PPS, leaving 1000 for the rest, for example.",
|
||||||
|
"default": 900,
|
||||||
|
"type": "int",
|
||||||
|
"advanced": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "max_total_entity_pps",
|
||||||
|
"label": "Maximum Total Entity PPS",
|
||||||
|
"help": "The maximum total packets per seconds (PPS) that can be sent to the entity server.<br/>Example: 5 scripts @ 1000 PPS per script = 5000 total PPS. A maximum total PPS of 4000 would cap this 5000 total PPS to 4000.",
|
||||||
|
"default": 9000,
|
||||||
|
"type": "int",
|
||||||
"advanced": true
|
"advanced": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
7
domain-server/resources/web/base-settings-scripts.html
Normal file
7
domain-server/resources/web/base-settings-scripts.html
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<script src='/js/underscore-min.js'></script>
|
||||||
|
<script src='/js/underscore-keypath.min.js'></script>
|
||||||
|
<script src='/js/bootbox.min.js'></script>
|
||||||
|
<script src='/js/form2js.min.js'></script>
|
||||||
|
<script src='/js/bootstrap-switch.min.js'></script>
|
||||||
|
<script src='/js/shared.js'></script>
|
||||||
|
<script src='/js/base-settings.js'></script>
|
59
domain-server/resources/web/base-settings.html
Normal file
59
domain-server/resources/web/base-settings.html
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<div class="col-md-10 col-md-offset-1 col-xs-12">
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<div id="cloud-domains-alert" class="alert alert-info alert-dismissible" role="alert" style="display: none;">
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<span class="alert-link">
|
||||||
|
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Visit Cloud Hosted Domains</a> to manage all your cloud domains
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="settings-form" role="form">
|
||||||
|
<script id="panels-template" type="text/template">
|
||||||
|
<% _.each(descriptions, function(group){ %>
|
||||||
|
<% if (!group.hidden) { %>
|
||||||
|
|
||||||
|
<% var settings = _.partition(group.settings, function(value, index) { %>
|
||||||
|
<% return !value.deprecated %>
|
||||||
|
<% })[0] %>
|
||||||
|
|
||||||
|
<% var split_settings = _.partition(group.settings, function(value, index) { %>
|
||||||
|
<% return !value.advanced %>
|
||||||
|
<% }) %>
|
||||||
|
|
||||||
|
<% isGrouped = !!group.name %>
|
||||||
|
<% panelID = isGrouped ? group.name : group.html_id %>
|
||||||
|
|
||||||
|
<div id="<%- panelID %>_group" class="anchor"></div>
|
||||||
|
<div class="panel panel-default<%- (isGrouped) ? ' grouped' : '' %>"
|
||||||
|
id="<%- panelID %>">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title"><%- group.label %></h3>
|
||||||
|
<span class="badge"></span>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<% _.each(split_settings[0], function(setting) { %>
|
||||||
|
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||||
|
<%= getFormGroup(keypath, setting, values, false) %>
|
||||||
|
<% }); %>
|
||||||
|
|
||||||
|
<% if (split_settings[1].length > 0) { %>
|
||||||
|
<button type="button" class="btn btn-default" data-toggle="collapse" data-target="#<%- panelID %>-advanced">Advanced Settings <span class="caret"></span></button>
|
||||||
|
<div id="<%- panelID %>-advanced" class="collapse advanced-settings-section">
|
||||||
|
<% _.each(split_settings[1], function(setting) { %>
|
||||||
|
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||||
|
<%= getFormGroup(keypath, setting, values, true) %>
|
||||||
|
<% }); %>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
<% }); %>
|
||||||
|
</script>
|
||||||
|
<div id="panels"></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,45 +1,19 @@
|
||||||
<!--#include virtual="header.html"-->
|
<!--#include virtual="header.html"-->
|
||||||
|
|
||||||
<div class="col-md-10 col-md-offset-1">
|
<script type="text/javascript">
|
||||||
<div class="row">
|
var Settings = {
|
||||||
<div class="col-xs-12">
|
content_settings: true,
|
||||||
<div class="alert" style="display:none;"></div>
|
endpoint: "/content-settings.json",
|
||||||
</div>
|
path: "/content/"
|
||||||
</div>
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<div class="row">
|
<!--#include virtual="base-settings.html"-->
|
||||||
<div class="col-xs-12">
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h3 class="panel-title">Upload Entities File</h3>
|
|
||||||
</div>
|
|
||||||
<form id="upload-form" action="upload" enctype="multipart/form-data" method="post">
|
|
||||||
<div class="panel-body">
|
|
||||||
<p>
|
|
||||||
Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.<br>
|
|
||||||
Note: <strong>Your domain's content will be replaced by the content you upload</strong>, but the backup files of your domain's content will not immediately be changed.
|
|
||||||
</p>
|
|
||||||
<p>If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:</p>
|
|
||||||
<label class="control-label">Windows</label>
|
|
||||||
<pre>C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
|
||||||
<label class="control-label">OSX</label>
|
|
||||||
<pre>/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
|
||||||
<label class="control-label">Linux</label>
|
|
||||||
<pre>/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
|
||||||
<br>
|
|
||||||
<input type="file" name="entities-file" class="form-control-file" accept=".json, .gz">
|
|
||||||
<br>
|
|
||||||
</div>
|
|
||||||
<div class="panel-footer">
|
|
||||||
<input type="submit" class="btn btn-info" value="Upload">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--#include virtual="footer.html"-->
|
<!--#include virtual="footer.html"-->
|
||||||
<script src='js/content.js'></script>
|
|
||||||
<script src='/js/sweetalert.min.js'></script>
|
<!--#include virtual="base-settings-scripts.html"-->
|
||||||
|
|
||||||
|
<script src="js/content.js"></script>
|
||||||
|
|
||||||
<!--#include virtual="page-end.html"-->
|
<!--#include virtual="page-end.html"-->
|
||||||
|
|
|
@ -1,14 +1,6 @@
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
|
|
||||||
function showSpinnerAlert(title) {
|
Settings.afterReloadActions = function() {};
|
||||||
swal({
|
|
||||||
title: title,
|
|
||||||
text: '<div class="spinner" style="color:black;"><div class="bounce1"></div><div class="bounce2"></div><div class="bounce3"></div></div>',
|
|
||||||
html: true,
|
|
||||||
showConfirmButton: false,
|
|
||||||
allowEscapeKey: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var frm = $('#upload-form');
|
var frm = $('#upload-form');
|
||||||
frm.submit(function (ev) {
|
frm.submit(function (ev) {
|
||||||
|
|
|
@ -18,6 +18,14 @@ body {
|
||||||
margin-top: 70px;
|
margin-top: 70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* unfortunate hack so that anchors go to the right place with fixed navbar */
|
||||||
|
:target:before {
|
||||||
|
content: " ";
|
||||||
|
display: block;
|
||||||
|
height: 70px;
|
||||||
|
margin-top: -70px;
|
||||||
|
}
|
||||||
|
|
||||||
[hidden] {
|
[hidden] {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
@ -118,11 +126,6 @@ span.port {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#small-save-button {
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
td.buttons {
|
td.buttons {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
}
|
}
|
||||||
|
@ -345,3 +348,89 @@ table .headers + .headers td {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
ul.nav li.dropdown-on-hover:hover ul.dropdown-menu {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.nav li.dropdown ul.dropdown-menu {
|
||||||
|
padding: 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.nav li.dropdown li a {
|
||||||
|
padding-top: 7px;
|
||||||
|
padding-bottom: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.nav li.dropdown li a:hover {
|
||||||
|
color: white;
|
||||||
|
background-color: #337ab7;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.nav li.dropdown ul.dropdown-menu .divider {
|
||||||
|
margin: 0px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#visit-domain-link {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-btn {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#save-settings-xs-button {
|
||||||
|
float: right;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#button-bars {
|
||||||
|
display: inline-block;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hamburger-badge {
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
float: left;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#restart-server {
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#restart-server:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
margin-left: 5px;
|
||||||
|
background-color: #00B4EF !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#visit-hmd-icon {
|
||||||
|
width: 25px;
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advanced-settings-section {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#restore-settings-button {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fix for https://bugs.webkit.org/show_bug.cgi?id=39620 */
|
||||||
|
.save-button-text {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
|
@ -17,30 +17,47 @@
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<!-- Brand and toggle get grouped for better mobile display -->
|
<!-- Brand and toggle get grouped for better mobile display -->
|
||||||
<div class="navbar-header">
|
<div class="navbar-header">
|
||||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
|
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#collapsed-navbar">
|
||||||
<span class="sr-only">Toggle navigation</span>
|
<span class="sr-only">Toggle navigation</span>
|
||||||
<span class="icon-bar"></span>
|
<span id="hamburger-badge" class="badge"></span>
|
||||||
<span class="icon-bar"></span>
|
|
||||||
<span class="icon-bar"></span>
|
<div id="button-bars">
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button id="save-settings-xs-button" class="save-button btn btn-success navbar-btn hidden-sm hidden-md hidden-lg" disabled="true"><span class="save-button-text">Save</span></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
<div class="collapse navbar-collapse" id="collapsed-navbar">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
<li><a href="/">Nodes</a></li>
|
<li><a href="/">Nodes</a></li>
|
||||||
<li class="dropdown">
|
<li><a href="/assignment">Assignment</a></li>
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Assignments <span class="caret"></span></a>
|
|
||||||
<ul class="dropdown-menu" role="menu">
|
<li class="dropdown dropdown-on-hover">
|
||||||
<li><a href="/assignment">New Assignment</a></li>
|
<a href="/content/" class="hidden-xs">Content <span class="content-settings-badge badge"></span> <span class="caret"></span></a>
|
||||||
</ul>
|
<a href="#" class="dropdown-toggle hidden-sm hidden-md hidden-lg" data-toggle="dropdown">Content <span class="content-badge badge"></span> <span class="caret"></span></a>
|
||||||
|
<ul id="content-settings-nav-dropdown" class="dropdown-menu" role="menu">
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dropdown dropdown-on-hover">
|
||||||
|
<a href="/settings/" class="hidden-xs">Settings <span class="domain-settings-badge badge"></span> <span class="caret"></span></a>
|
||||||
|
<a href="#" class="dropdown-toggle hidden-sm hidden-md hidden-lg" data-toggle="dropdown">Settings <span class="domain-settings-badge badge"></span> <span class="caret"></span></a>
|
||||||
|
<ul id="domain-settings-nav-dropdown" class="dropdown-menu" role="menu">
|
||||||
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="/content/">Content</a></li>
|
|
||||||
<li><a href="/settings/">Settings</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav navbar-right navbar-nav">
|
<ul class="nav navbar-right navbar-nav">
|
||||||
<li><a id="visit-domain-link" class="blue-link" target="_blank" style="display: none;">Visit domain in VR</a></li>
|
<a id="visit-domain-link" class="btn btn-default navbar-btn" role="button" target="_blank" style="display: none;">
|
||||||
<li><a href="#" id="restart-server"><span class="glyphicon glyphicon-refresh"></span> Restart</a></li>
|
<img id="visit-hmd-icon" src="/images/hmd-w-eyes.svg" alt="Head-mounted display" />
|
||||||
|
Visit in VR
|
||||||
|
</a>
|
||||||
|
<button id="save-settings-button" class="save-button btn btn-success navbar-btn hidden-xs" disabled="true"><span class="save-button-text">Save</span></button>
|
||||||
|
<a href="#" id="restart-server" class="navbar-btn btn btn-link"><span class="glyphicon glyphicon-refresh"></span> Restart</a>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- /.container-fluid -->
|
</div><!-- /.container-fluid -->
|
||||||
|
@ -50,7 +67,7 @@
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 class="modal-title">domain-server is restarting</h4>
|
<h4 class="modal-title">Domain Server is restarting</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<h5>This page will automatically refresh in <span id="refresh-time">3 seconds</span>.</h5>
|
<h5>This page will automatically refresh in <span id="refresh-time">3 seconds</span>.</h5>
|
||||||
|
|
13
domain-server/resources/web/images/hmd-w-eyes.svg
Normal file
13
domain-server/resources/web/images/hmd-w-eyes.svg
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 456 244" style="enable-background:new 0 0 456 244;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#666666;}
|
||||||
|
</style>
|
||||||
|
<path class="st0" d="M352.8,3.4h-250C49.6,3.4,6.3,46.2,6.3,98.9v44.9c0,52.7,43.3,95.5,96.5,95.5h67.6c19.4,0,31.9-17.9,42.9-33.6
|
||||||
|
c4.2-6.1,11.1-15.9,14.8-17.9c3.6,2.1,10.4,12.2,14.5,18.4c10.4,15.5,22.1,33.1,40.3,33.1h69.9c53.3,0,96.5-42.9,96.5-95.5V98.9
|
||||||
|
C449.3,46.2,406,3.4,352.8,3.4z M129.6,157.6c-22.4,0-40.6-18.2-40.6-40.6s18.2-40.6,40.6-40.6c22.4,0,40.6,18.2,40.6,40.6
|
||||||
|
S151.9,157.6,129.6,157.6z M328.4,157.6c-22.4,0-40.6-18.2-40.6-40.6s18.2-40.6,40.6-40.6c22.4,0,40.6,18.2,40.6,40.6
|
||||||
|
S350.8,157.6,328.4,157.6z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 928 B |
1152
domain-server/resources/web/js/base-settings.js
Normal file
1152
domain-server/resources/web/js/base-settings.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -23,16 +23,22 @@ function showRestartModal() {
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function settingsGroupAnchor(base, html_id) {
|
||||||
|
return base + "#" + html_id + "_group"
|
||||||
|
}
|
||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
var url = window.location;
|
var url = window.location;
|
||||||
|
|
||||||
// Will only work if string in href matches with location
|
// Will only work if string in href matches with location
|
||||||
$('ul.nav a[href="'+ url +'"]').parent().addClass('active');
|
$('ul.nav a[href="'+ url +'"]').parent().addClass('active');
|
||||||
|
|
||||||
// Will also work for relative and absolute hrefs
|
// Will also work for relative and absolute hrefs
|
||||||
$('ul.nav a').filter(function() {
|
$('ul.nav a').filter(function() {
|
||||||
return this.href == url;
|
return this.href == url;
|
||||||
}).parent().addClass('active');
|
}).parent().addClass('active');
|
||||||
$('body').on('click', '#restart-server', function(e) {
|
|
||||||
|
$('body').on('click', '#restart-server', function(e) {
|
||||||
swal( {
|
swal( {
|
||||||
title: "Are you sure?",
|
title: "Are you sure?",
|
||||||
text: "This will restart your domain server, causing your domain to be briefly offline.",
|
text: "This will restart your domain server, causing your domain to be briefly offline.",
|
||||||
|
@ -45,4 +51,47 @@ $(document).ready(function(){
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var $contentDropdown = $('#content-settings-nav-dropdown');
|
||||||
|
var $settingsDropdown = $('#domain-settings-nav-dropdown');
|
||||||
|
|
||||||
|
// for pages that have the settings dropdowns
|
||||||
|
if ($contentDropdown.length && $settingsDropdown.length) {
|
||||||
|
// make a JSON request to get the dropdown menus for content and settings
|
||||||
|
// we don't error handle here because the top level menu is still clickable and usables if this fails
|
||||||
|
$.getJSON('/settings-menu-groups.json', function(data){
|
||||||
|
function makeGroupDropdownElement(group, base) {
|
||||||
|
var html_id = group.html_id ? group.html_id : group.name;
|
||||||
|
return "<li class='setting-group'><a href='" + settingsGroupAnchor(base, html_id) + "'>" + group.label + "<span class='badge'></span></a></li>";
|
||||||
|
}
|
||||||
|
|
||||||
|
$.each(data.content_settings, function(index, group){
|
||||||
|
if (index > 0) {
|
||||||
|
$contentDropdown.append("<li role='separator' class='divider'></li>");
|
||||||
|
}
|
||||||
|
|
||||||
|
$contentDropdown.append(makeGroupDropdownElement(group, "/content/"));
|
||||||
|
});
|
||||||
|
|
||||||
|
$.each(data.domain_settings, function(index, group){
|
||||||
|
if (index > 0) {
|
||||||
|
$settingsDropdown.append("<li role='separator' class='divider'></li>");
|
||||||
|
}
|
||||||
|
|
||||||
|
$settingsDropdown.append(makeGroupDropdownElement(group, "/settings/"));
|
||||||
|
|
||||||
|
// for domain settings, we add a dummy "Places" group that we fill
|
||||||
|
// via the API - add it to the dropdown menu in the right spot
|
||||||
|
// which is after "Metaverse / Networking"
|
||||||
|
if (group.name == "metaverse") {
|
||||||
|
$settingsDropdown.append("<li role='separator' class='divider'></li>");
|
||||||
|
$settingsDropdown.append(makeGroupDropdownElement({ html_id: 'places', label: 'Places' }, "/settings/"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// append a link for the "Settings Backup" panel
|
||||||
|
$settingsDropdown.append("<li role='separator' class='divider'></li>");
|
||||||
|
$settingsDropdown.append(makeGroupDropdownElement({ html_id: 'settings_backup', label: 'Settings Backup'}, "/settings"));
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
var Settings = {
|
if (typeof Settings === "undefined") {
|
||||||
showAdvanced: false,
|
Settings = {};
|
||||||
ADVANCED_CLASS: 'advanced-setting',
|
}
|
||||||
|
|
||||||
|
Object.assign(Settings, {
|
||||||
DEPRECATED_CLASS: 'deprecated-setting',
|
DEPRECATED_CLASS: 'deprecated-setting',
|
||||||
TRIGGER_CHANGE_CLASS: 'trigger-change',
|
TRIGGER_CHANGE_CLASS: 'trigger-change',
|
||||||
DATA_ROW_CLASS: 'value-row',
|
DATA_ROW_CLASS: 'value-row',
|
||||||
|
@ -41,7 +43,7 @@ var Settings = {
|
||||||
FORM_ID: 'settings-form',
|
FORM_ID: 'settings-form',
|
||||||
INVALID_ROW_CLASS: 'invalid-input',
|
INVALID_ROW_CLASS: 'invalid-input',
|
||||||
DATA_ROW_INDEX: 'data-row-index'
|
DATA_ROW_INDEX: 'data-row-index'
|
||||||
};
|
});
|
||||||
|
|
||||||
var URLs = {
|
var URLs = {
|
||||||
// STABLE METAVERSE_URL: https://metaverse.highfidelity.com
|
// STABLE METAVERSE_URL: https://metaverse.highfidelity.com
|
||||||
|
@ -95,7 +97,13 @@ var DOMAIN_ID_TYPE_FULL = 2;
|
||||||
var DOMAIN_ID_TYPE_UNKNOWN = 3;
|
var DOMAIN_ID_TYPE_UNKNOWN = 3;
|
||||||
|
|
||||||
function domainIDIsSet() {
|
function domainIDIsSet() {
|
||||||
return Settings.data.values.metaverse.id.length > 0;
|
if (typeof Settings.data.values.metaverse !== 'undefined' &&
|
||||||
|
typeof Settings.data.values.metaverse.id !== 'undefined') {
|
||||||
|
|
||||||
|
return Settings.data.values.metaverse.id.length > 0;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentDomainIDType() {
|
function getCurrentDomainIDType() {
|
||||||
|
@ -156,11 +164,12 @@ function getDomainFromAPI(callback) {
|
||||||
if (callback === undefined) {
|
if (callback === undefined) {
|
||||||
callback = function() {};
|
callback = function() {};
|
||||||
}
|
}
|
||||||
|
|
||||||
var domainID = Settings.data.values.metaverse.id;
|
if (!domainIDIsSet()) {
|
||||||
if (domainID === null || domainID === undefined || domainID === '') {
|
|
||||||
callback({ status: 'fail' });
|
callback({ status: 'fail' });
|
||||||
return null;
|
return null;
|
||||||
|
} else {
|
||||||
|
var domainID = Settings.data.values.metaverse.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingDomainRequest = $.ajax({
|
pendingDomainRequest = $.ajax({
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,104 +1,29 @@
|
||||||
<!--#include virtual="header.html"-->
|
<!--#include virtual="header.html"-->
|
||||||
|
|
||||||
<div class="col-md-10 col-md-offset-1">
|
<script type="text/javascript">
|
||||||
<div class="row">
|
var Settings = {
|
||||||
<div class="col-md-12">
|
content_settings: false,
|
||||||
<div class="alert" style="display:none;"></div>
|
endpoint: "/settings.json",
|
||||||
</div>
|
path: "/settings/"
|
||||||
</div>
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<div class="row">
|
<div class="col-md-10 col-md-offset-1 col-xs-12">
|
||||||
<div class="col-md-3 col-sm-3" id="setup-sidebar-col">
|
<div class="row">
|
||||||
<div id="setup-sidebar" data-clampedwidth="#setup-sidebar-col">
|
|
||||||
<script id="list-group-template" type="text/template">
|
|
||||||
<% _.each(descriptions, function(group){ %>
|
|
||||||
<% if (!group.hidden) { %>
|
|
||||||
<% panelID = group.name ? group.name : group.html_id %>
|
|
||||||
<li>
|
|
||||||
<a href="#<%- panelID %>" class="list-group-item">
|
|
||||||
<span class="badge"></span>
|
|
||||||
<%- group.label %>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<% } %>
|
|
||||||
<% }); %>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<ul class="nav nav-pills nav-stacked">
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<button id="advanced-toggle-button" class="btn btn-info advanced-toggle">Show advanced</button>
|
|
||||||
<button class="btn btn-success save-button" disabled>Save</button>
|
|
||||||
<div id="manage-cloud-domains-link" style="display: none;">
|
|
||||||
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Manage Cloud Hosted Domains</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-9 col-sm-9 col-xs-12">
|
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
|
<div class="alert alert-info">Your domain content settings are now available in <a href='/content/'>Content</a></div>
|
||||||
<div id="cloud-domains-alert" class="alert alert-info alert-dismissible" role="alert" style="display: none;">
|
</div>
|
||||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
</div>
|
||||||
<span class="alert-link">
|
|
||||||
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Visit Cloud Hosted Domains</a> to manage all your cloud domains
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form id="settings-form" role="form">
|
|
||||||
|
|
||||||
<script id="panels-template" type="text/template">
|
|
||||||
<% _.each(descriptions, function(group){ %>
|
|
||||||
<% if (!group.hidden) { %>
|
|
||||||
<% var settings = _.partition(group.settings, function(value, index) { return !value.deprecated })[0] %>
|
|
||||||
<% split_settings = _.partition(settings, function(value, index) { return !value.advanced }) %>
|
|
||||||
<% isAdvanced = _.isEmpty(split_settings[0]) && !_.isEmpty(split_settings[1]) %>
|
|
||||||
<% if (isAdvanced) { %>
|
|
||||||
<% $("a[href=#" + group.name + "]").addClass('advanced-setting').hide() %>
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
<% isGrouped = !!group.name %>
|
|
||||||
<% panelID = isGrouped ? group.name : group.html_id %>
|
|
||||||
|
|
||||||
<div class="panel panel-default<%- (isAdvanced) ? ' advanced-setting' : '' %><%- (isGrouped) ? ' grouped' : '' %>"
|
|
||||||
id="<%- panelID %>">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h3 class="panel-title"><%- group.label %></h3>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<% _.each(split_settings[0], function(setting) { %>
|
|
||||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
|
||||||
<%= getFormGroup(keypath, setting, values, false) %>
|
|
||||||
<% }); %>
|
|
||||||
<% if (!_.isEmpty(split_settings[1])) { %>
|
|
||||||
<% $("#advanced-toggle-button").show() %>
|
|
||||||
<% _.each(split_settings[1], function(setting) { %>
|
|
||||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
|
||||||
<%= getFormGroup(keypath, setting, values, true) %>
|
|
||||||
<% }); %>
|
|
||||||
<% }%>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% } %>
|
|
||||||
<% }); %>
|
|
||||||
</script>
|
|
||||||
<div id="panels"></div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-xs-12 hidden-sm hidden-md hidden-lg">
|
|
||||||
<button class="btn btn-success save-button" id="small-save-button">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!--#include virtual="base-settings.html"-->
|
||||||
|
|
||||||
<!--#include virtual="footer.html"-->
|
<!--#include virtual="footer.html"-->
|
||||||
<script src='/js/underscore-min.js'></script>
|
|
||||||
<script src='/js/underscore-keypath.min.js'></script>
|
|
||||||
<script src='/js/bootbox.min.js'></script>
|
|
||||||
<script src='/js/sha256.js'></script>
|
<script src='/js/sha256.js'></script>
|
||||||
<script src='js/form2js.min.js'></script>
|
|
||||||
<script src='js/bootstrap-switch.min.js'></script>
|
<!--#include virtual="base-settings-scripts.html"-->
|
||||||
<script src='/js/shared.js'></script>
|
|
||||||
<script src='js/settings.js'></script>
|
<script src="js/settings.js"></script>
|
||||||
|
|
||||||
<!--#include virtual="page-end.html"-->
|
<!--#include virtual="page-end.html"-->
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -501,7 +501,7 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
|
||||||
// store the new domain ID and auto network setting immediately
|
// store the new domain ID and auto network setting immediately
|
||||||
QString newSettingsJSON = QString("{\"metaverse\": { \"id\": \"%1\", \"automatic_networking\": \"full\"}}").arg(id);
|
QString newSettingsJSON = QString("{\"metaverse\": { \"id\": \"%1\", \"automatic_networking\": \"full\"}}").arg(id);
|
||||||
auto settingsDocument = QJsonDocument::fromJson(newSettingsJSON.toUtf8());
|
auto settingsDocument = QJsonDocument::fromJson(newSettingsJSON.toUtf8());
|
||||||
_settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object());
|
_settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object(), DomainSettings);
|
||||||
|
|
||||||
// store the new ID and auto networking setting on disk
|
// store the new ID and auto networking setting on disk
|
||||||
_settingsManager.persistToFile();
|
_settingsManager.persistToFile();
|
||||||
|
|
|
@ -39,8 +39,11 @@ const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings
|
||||||
const QString DESCRIPTION_SETTINGS_KEY = "settings";
|
const QString DESCRIPTION_SETTINGS_KEY = "settings";
|
||||||
const QString SETTING_DEFAULT_KEY = "default";
|
const QString SETTING_DEFAULT_KEY = "default";
|
||||||
const QString DESCRIPTION_NAME_KEY = "name";
|
const QString DESCRIPTION_NAME_KEY = "name";
|
||||||
|
const QString DESCRIPTION_GROUP_LABEL_KEY = "label";
|
||||||
|
const QString DESCRIPTION_BACKUP_FLAG_KEY = "backup";
|
||||||
const QString SETTING_DESCRIPTION_TYPE_KEY = "type";
|
const QString SETTING_DESCRIPTION_TYPE_KEY = "type";
|
||||||
const QString DESCRIPTION_COLUMNS_KEY = "columns";
|
const QString DESCRIPTION_COLUMNS_KEY = "columns";
|
||||||
|
const QString CONTENT_SETTING_FLAG_KEY = "content_setting";
|
||||||
|
|
||||||
const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
|
const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
|
||||||
|
|
||||||
|
@ -63,6 +66,8 @@ DomainServerSettingsManager::DomainServerSettingsManager() {
|
||||||
|
|
||||||
if (descriptionObject.contains(DESCRIPTION_SETTINGS_KEY)) {
|
if (descriptionObject.contains(DESCRIPTION_SETTINGS_KEY)) {
|
||||||
_descriptionArray = descriptionDocument.object()[DESCRIPTION_SETTINGS_KEY].toArray();
|
_descriptionArray = descriptionDocument.object()[DESCRIPTION_SETTINGS_KEY].toArray();
|
||||||
|
splitSettingsDescription();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,11 +83,99 @@ DomainServerSettingsManager::DomainServerSettingsManager() {
|
||||||
Q_ARG(int, MISSING_SETTINGS_DESC_ERROR_CODE));
|
Q_ARG(int, MISSING_SETTINGS_DESC_ERROR_CODE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DomainServerSettingsManager::splitSettingsDescription() {
|
||||||
|
// construct separate description arrays for domain settings and content settings
|
||||||
|
// since they are displayed on different pages
|
||||||
|
|
||||||
|
// along the way we also construct one object that holds the groups separated by domain settings
|
||||||
|
// and content settings, so that the DS can setup dropdown menus below "Content" and "Settings"
|
||||||
|
// headers to jump directly to a settings group on the page of either
|
||||||
|
QJsonArray domainSettingsMenuGroups;
|
||||||
|
QJsonArray contentSettingsMenuGroups;
|
||||||
|
|
||||||
|
foreach(const QJsonValue& group, _descriptionArray) {
|
||||||
|
QJsonObject groupObject = group.toObject();
|
||||||
|
|
||||||
|
static const QString HIDDEN_GROUP_KEY = "hidden";
|
||||||
|
bool groupHidden = groupObject.contains(HIDDEN_GROUP_KEY) && groupObject[HIDDEN_GROUP_KEY].toBool();
|
||||||
|
|
||||||
|
QJsonArray domainSettingArray;
|
||||||
|
QJsonArray contentSettingArray;
|
||||||
|
|
||||||
|
foreach(const QJsonValue& settingDescription, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) {
|
||||||
|
QJsonObject settingDescriptionObject = settingDescription.toObject();
|
||||||
|
|
||||||
|
bool isContentSetting = settingDescriptionObject.contains(CONTENT_SETTING_FLAG_KEY)
|
||||||
|
&& settingDescriptionObject[CONTENT_SETTING_FLAG_KEY].toBool();
|
||||||
|
|
||||||
|
if (isContentSetting) {
|
||||||
|
// push the setting description to the pending content setting array
|
||||||
|
contentSettingArray.push_back(settingDescriptionObject);
|
||||||
|
} else {
|
||||||
|
// push the setting description to the pending domain setting array
|
||||||
|
domainSettingArray.push_back(settingDescriptionObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!domainSettingArray.isEmpty() || !contentSettingArray.isEmpty()) {
|
||||||
|
|
||||||
|
// we know for sure we'll have something to add to our settings menu groups
|
||||||
|
// so setup that object for the group now, as long as the group isn't hidden alltogether
|
||||||
|
QJsonObject settingsDropdownGroup;
|
||||||
|
|
||||||
|
if (!groupHidden) {
|
||||||
|
if (groupObject.contains(DESCRIPTION_NAME_KEY)) {
|
||||||
|
settingsDropdownGroup[DESCRIPTION_NAME_KEY] = groupObject[DESCRIPTION_NAME_KEY];
|
||||||
|
}
|
||||||
|
|
||||||
|
settingsDropdownGroup[DESCRIPTION_GROUP_LABEL_KEY] = groupObject[DESCRIPTION_GROUP_LABEL_KEY];
|
||||||
|
|
||||||
|
static const QString DESCRIPTION_GROUP_HTML_ID_KEY = "html_id";
|
||||||
|
if (groupObject.contains(DESCRIPTION_GROUP_HTML_ID_KEY)) {
|
||||||
|
settingsDropdownGroup[DESCRIPTION_GROUP_HTML_ID_KEY] = groupObject[DESCRIPTION_GROUP_HTML_ID_KEY];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!domainSettingArray.isEmpty()) {
|
||||||
|
// we have some domain settings from this group, add the group with the filtered settings
|
||||||
|
QJsonObject filteredGroupObject = groupObject;
|
||||||
|
filteredGroupObject[DESCRIPTION_SETTINGS_KEY] = domainSettingArray;
|
||||||
|
_domainSettingsDescription.push_back(filteredGroupObject);
|
||||||
|
|
||||||
|
// if the group isn't hidden, add its information to the domain settings menu groups
|
||||||
|
if (!groupHidden) {
|
||||||
|
domainSettingsMenuGroups.push_back(settingsDropdownGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!contentSettingArray.isEmpty()) {
|
||||||
|
// we have some content settings from this group, add the group with the filtered settings
|
||||||
|
QJsonObject filteredGroupObject = groupObject;
|
||||||
|
filteredGroupObject[DESCRIPTION_SETTINGS_KEY] = contentSettingArray;
|
||||||
|
_contentSettingsDescription.push_back(filteredGroupObject);
|
||||||
|
|
||||||
|
// if the group isn't hidden, add its information to the content settings menu groups
|
||||||
|
if (!groupHidden) {
|
||||||
|
contentSettingsMenuGroups.push_back(settingsDropdownGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate the settings menu groups with what we've collected
|
||||||
|
|
||||||
|
static const QString SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY = "domain_settings";
|
||||||
|
static const QString SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY = "content_settings";
|
||||||
|
|
||||||
|
_settingsMenuGroups[SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY] = domainSettingsMenuGroups;
|
||||||
|
_settingsMenuGroups[SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY] = contentSettingsMenuGroups;
|
||||||
|
}
|
||||||
|
|
||||||
void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message) {
|
void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message) {
|
||||||
Assignment::Type type;
|
Assignment::Type type;
|
||||||
message->readPrimitive(&type);
|
message->readPrimitive(&type);
|
||||||
|
|
||||||
QJsonObject responseObject = responseObjectForType(QString::number(type));
|
QJsonObject responseObject = settingsResponseObjectForType(QString::number(type));
|
||||||
auto json = QJsonDocument(responseObject).toJson();
|
auto json = QJsonDocument(responseObject).toJson();
|
||||||
|
|
||||||
auto packetList = NLPacketList::create(PacketType::DomainSettings, QByteArray(), true, true);
|
auto packetList = NLPacketList::create(PacketType::DomainSettings, QByteArray(), true, true);
|
||||||
|
@ -314,14 +407,14 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
||||||
|
|
||||||
QVariant* avatarMinScale = _configMap.valueForKeyPath(AVATAR_MIN_SCALE_KEYPATH);
|
QVariant* avatarMinScale = _configMap.valueForKeyPath(AVATAR_MIN_SCALE_KEYPATH);
|
||||||
if (avatarMinScale) {
|
if (avatarMinScale) {
|
||||||
float scale = avatarMinScale->toFloat();
|
auto newMinScaleVariant = _configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, true);
|
||||||
_configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT);
|
*newMinScaleVariant = avatarMinScale->toFloat() * DEFAULT_AVATAR_HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant* avatarMaxScale = _configMap.valueForKeyPath(AVATAR_MAX_SCALE_KEYPATH);
|
QVariant* avatarMaxScale = _configMap.valueForKeyPath(AVATAR_MAX_SCALE_KEYPATH);
|
||||||
if (avatarMaxScale) {
|
if (avatarMaxScale) {
|
||||||
float scale = avatarMaxScale->toFloat();
|
auto newMaxScaleVariant = _configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, true);
|
||||||
_configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT);
|
*newMaxScaleVariant = avatarMaxScale->toFloat() * DEFAULT_AVATAR_HEIGHT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -986,48 +1079,246 @@ QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QStrin
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection *connection, const QUrl &url) {
|
bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection *connection, const QUrl &url) {
|
||||||
if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == SETTINGS_PATH_JSON) {
|
if (connection->requestOperation() == QNetworkAccessManager::PostOperation) {
|
||||||
// this is a POST operation to change one or more settings
|
static const QString SETTINGS_RESTORE_PATH = "/settings/restore";
|
||||||
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
|
|
||||||
QJsonObject postedObject = postedDocument.object();
|
|
||||||
|
|
||||||
// we recurse one level deep below each group for the appropriate setting
|
if (url.path() == SETTINGS_PATH_JSON || url.path() == CONTENT_SETTINGS_PATH_JSON) {
|
||||||
bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject);
|
// this is a POST operation to change one or more settings
|
||||||
|
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
|
||||||
|
QJsonObject postedObject = postedDocument.object();
|
||||||
|
|
||||||
// store whatever the current _settingsMap is to file
|
SettingsType endpointType = url.path() == SETTINGS_PATH_JSON ? DomainSettings : ContentSettings;
|
||||||
persistToFile();
|
|
||||||
|
|
||||||
// return success to the caller
|
// we recurse one level deep below each group for the appropriate setting
|
||||||
QString jsonSuccess = "{\"status\": \"success\"}";
|
bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject, endpointType);
|
||||||
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
|
|
||||||
|
|
||||||
// defer a restart to the domain-server, this gives our HTTPConnection enough time to respond
|
// store whatever the current _settingsMap is to file
|
||||||
if (restartRequired) {
|
persistToFile();
|
||||||
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
|
|
||||||
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
|
// return success to the caller
|
||||||
} else {
|
QString jsonSuccess = "{\"status\": \"success\"}";
|
||||||
unpackPermissions();
|
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
|
||||||
apiRefreshGroupInformation();
|
|
||||||
emit updateNodePermissions();
|
// defer a restart to the domain-server, this gives our HTTPConnection enough time to respond
|
||||||
emit settingsUpdated();
|
if (restartRequired) {
|
||||||
|
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
|
||||||
|
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
|
||||||
|
} else {
|
||||||
|
unpackPermissions();
|
||||||
|
apiRefreshGroupInformation();
|
||||||
|
emit updateNodePermissions();
|
||||||
|
emit settingsUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (url.path() == SETTINGS_RESTORE_PATH) {
|
||||||
|
// this is an JSON settings file restore, ask the HTTPConnection to parse the data
|
||||||
|
QList<FormData> formData = connection->parseFormData();
|
||||||
|
|
||||||
|
bool wasRestoreSuccessful = false;
|
||||||
|
|
||||||
|
if (formData.size() > 0 && formData[0].second.size() > 0) {
|
||||||
|
// take the posted file and convert it to a QJsonObject
|
||||||
|
auto postedDocument = QJsonDocument::fromJson(formData[0].second);
|
||||||
|
if (postedDocument.isObject()) {
|
||||||
|
wasRestoreSuccessful = restoreSettingsFromObject(postedDocument.object(), DomainSettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wasRestoreSuccessful) {
|
||||||
|
// respond with a 200 for success
|
||||||
|
QString jsonSuccess = "{\"status\": \"success\"}";
|
||||||
|
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
|
||||||
|
|
||||||
|
// defer a restart to the domain-server, this gives our HTTPConnection enough time to respond
|
||||||
|
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
|
||||||
|
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
|
||||||
|
} else {
|
||||||
|
// respond with a 400 for failure
|
||||||
|
connection->respond(HTTPConnection::StatusCode400);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
|
||||||
|
static const QString SETTINGS_MENU_GROUPS_PATH = "/settings-menu-groups.json";
|
||||||
|
static const QString SETTINGS_BACKUP_PATH = "/settings/backup.json";
|
||||||
|
|
||||||
return true;
|
if (url.path() == SETTINGS_PATH_JSON || url.path() == CONTENT_SETTINGS_PATH_JSON) {
|
||||||
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH_JSON) {
|
|
||||||
// setup a JSON Object with descriptions and non-omitted settings
|
|
||||||
const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions";
|
|
||||||
const QString SETTINGS_RESPONSE_VALUE_KEY = "values";
|
|
||||||
|
|
||||||
QJsonObject rootObject;
|
// setup a JSON Object with descriptions and non-omitted settings
|
||||||
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray;
|
const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions";
|
||||||
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
|
const QString SETTINGS_RESPONSE_VALUE_KEY = "values";
|
||||||
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
|
|
||||||
|
QJsonObject rootObject;
|
||||||
|
|
||||||
|
bool forDomainSettings = (url.path() == SETTINGS_PATH_JSON);
|
||||||
|
bool forContentSettings = (url.path() == CONTENT_SETTINGS_PATH_JSON);;
|
||||||
|
|
||||||
|
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = forDomainSettings
|
||||||
|
? _domainSettingsDescription : _contentSettingsDescription;
|
||||||
|
|
||||||
|
// grab a domain settings object for all types, filtered for the right class of settings
|
||||||
|
// and exclude default values
|
||||||
|
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = settingsResponseObjectForType("", true,
|
||||||
|
forDomainSettings, forContentSettings,
|
||||||
|
true);
|
||||||
|
|
||||||
|
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (url.path() == SETTINGS_MENU_GROUPS_PATH) {
|
||||||
|
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(_settingsMenuGroups).toJson(), "application/json");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (url.path() == SETTINGS_BACKUP_PATH) {
|
||||||
|
// grab the settings backup as an authenticated user
|
||||||
|
// for the domain settings type only, excluding hidden and default values
|
||||||
|
auto currentDomainSettingsJSON = settingsResponseObjectForType("", true, true, false, false, true);
|
||||||
|
|
||||||
|
// setup headers that tell the client to download the file wth a special name
|
||||||
|
Headers downloadHeaders;
|
||||||
|
downloadHeaders.insert("Content-Transfer-Encoding", "binary");
|
||||||
|
|
||||||
|
// create a timestamped filename for the backup
|
||||||
|
const QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" };
|
||||||
|
auto backupFilename = "domain-settings_" + QDateTime::currentDateTime().toString(DATETIME_FORMAT) + ".json";
|
||||||
|
|
||||||
|
downloadHeaders.insert("Content-Disposition",
|
||||||
|
QString("attachment; filename=\"%1\"").arg(backupFilename).toLocal8Bit());
|
||||||
|
|
||||||
|
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(currentDomainSettingsJSON).toJson(),
|
||||||
|
"application/force-download", downloadHeaders);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& typeValue, bool isAuthenticated) {
|
bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType) {
|
||||||
|
QJsonArray& filteredDescriptionArray = settingsType == DomainSettings
|
||||||
|
? _domainSettingsDescription : _contentSettingsDescription;
|
||||||
|
|
||||||
|
// grab a copy of the current config before restore, so that we can back out if something bad happens during
|
||||||
|
QVariantMap preRestoreConfig = _configMap.getConfig();
|
||||||
|
|
||||||
|
bool shouldCancelRestore = false;
|
||||||
|
|
||||||
|
// enumerate through the settings in the description
|
||||||
|
// if we have one in the restore then use it, otherwise clear it from current settings
|
||||||
|
foreach(const QJsonValue& descriptionGroupValue, filteredDescriptionArray) {
|
||||||
|
QJsonObject descriptionGroupObject = descriptionGroupValue.toObject();
|
||||||
|
QString groupKey = descriptionGroupObject[DESCRIPTION_NAME_KEY].toString();
|
||||||
|
QJsonArray descriptionGroupSettings = descriptionGroupObject[DESCRIPTION_SETTINGS_KEY].toArray();
|
||||||
|
|
||||||
|
// grab the matching group from the restore so we can look at its settings
|
||||||
|
QJsonObject restoreGroup;
|
||||||
|
QVariantMap* configGroupMap = nullptr;
|
||||||
|
|
||||||
|
if (groupKey.isEmpty()) {
|
||||||
|
// this is for a setting at the root, use the full object as our restore group
|
||||||
|
restoreGroup = settingsToRestore;
|
||||||
|
|
||||||
|
// the variant map for this "group" is just the config map since there's no group
|
||||||
|
configGroupMap = &_configMap.getConfig();
|
||||||
|
} else {
|
||||||
|
if (settingsToRestore.contains(groupKey)) {
|
||||||
|
restoreGroup = settingsToRestore[groupKey].toObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// grab the variant for the group
|
||||||
|
auto groupMapVariant = _configMap.valueForKeyPath(groupKey);
|
||||||
|
|
||||||
|
// if it existed, double check that it is a map - any other value is unexpected and should cancel a restore
|
||||||
|
if (groupMapVariant) {
|
||||||
|
if (groupMapVariant->canConvert<QVariantMap>()) {
|
||||||
|
configGroupMap = static_cast<QVariantMap*>(groupMapVariant->data());
|
||||||
|
} else {
|
||||||
|
shouldCancelRestore = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(const QJsonValue& descriptionSettingValue, descriptionGroupSettings) {
|
||||||
|
|
||||||
|
QJsonObject descriptionSettingObject = descriptionSettingValue.toObject();
|
||||||
|
|
||||||
|
// we'll override this setting with the default or what is in the restore as long as
|
||||||
|
// it isn't specifically excluded from backups
|
||||||
|
bool isBackedUpSetting = !descriptionSettingObject.contains(DESCRIPTION_BACKUP_FLAG_KEY)
|
||||||
|
|| descriptionSettingObject[DESCRIPTION_BACKUP_FLAG_KEY].toBool();
|
||||||
|
|
||||||
|
if (isBackedUpSetting) {
|
||||||
|
QString settingName = descriptionSettingObject[DESCRIPTION_NAME_KEY].toString();
|
||||||
|
|
||||||
|
// check if we have a matching setting for this in the restore
|
||||||
|
QJsonValue restoreValue;
|
||||||
|
if (restoreGroup.contains(settingName)) {
|
||||||
|
restoreValue = restoreGroup[settingName];
|
||||||
|
}
|
||||||
|
|
||||||
|
// we should create the value for this key path in our current config map
|
||||||
|
// if we had value in the restore file
|
||||||
|
bool shouldCreateIfMissing = !restoreValue.isNull();
|
||||||
|
|
||||||
|
// get a QVariant pointer to this setting in our config map
|
||||||
|
QString fullSettingKey = !groupKey.isEmpty()
|
||||||
|
? groupKey + "." + settingName : settingName;
|
||||||
|
|
||||||
|
QVariant* variantValue = _configMap.valueForKeyPath(fullSettingKey, shouldCreateIfMissing);
|
||||||
|
|
||||||
|
if (restoreValue.isNull()) {
|
||||||
|
if (variantValue && !variantValue->isNull() && configGroupMap) {
|
||||||
|
// we didn't have a value to restore, but there might be a value in the config map
|
||||||
|
// so we need to remove the value in the config map which will set it back to the default
|
||||||
|
qDebug() << "Removing" << fullSettingKey << "from settings since it is not in the restored JSON";
|
||||||
|
configGroupMap->remove(settingName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we have a value to restore, use update setting to set it
|
||||||
|
|
||||||
|
// we might need to re-grab config group map in case it didn't exist when we looked for it before
|
||||||
|
// but was created by the call to valueForKeyPath before
|
||||||
|
if (!configGroupMap) {
|
||||||
|
auto groupMapVariant = _configMap.valueForKeyPath(groupKey);
|
||||||
|
if (groupMapVariant && groupMapVariant->canConvert<QVariantMap>()) {
|
||||||
|
configGroupMap = static_cast<QVariantMap*>(groupMapVariant->data());
|
||||||
|
} else {
|
||||||
|
shouldCancelRestore = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Updating setting" << fullSettingKey << "from restored JSON";
|
||||||
|
|
||||||
|
updateSetting(settingName, restoreValue, *configGroupMap, descriptionSettingObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldCancelRestore) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldCancelRestore) {
|
||||||
|
// if we cancelled the restore, go back to our state before and return false
|
||||||
|
qDebug() << "Restore cancelled, settings have not been changed";
|
||||||
|
_configMap.getConfig() = preRestoreConfig;
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// restore completed, persist the new settings
|
||||||
|
qDebug() << "Restore completed, persisting restored settings to file";
|
||||||
|
persistToFile();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QString& typeValue, bool isAuthenticated,
|
||||||
|
bool includeDomainSettings,
|
||||||
|
bool includeContentSettings,
|
||||||
|
bool includeDefaults, bool isForBackup) {
|
||||||
QJsonObject responseObject;
|
QJsonObject responseObject;
|
||||||
|
|
||||||
if (!typeValue.isEmpty() || isAuthenticated) {
|
if (!typeValue.isEmpty() || isAuthenticated) {
|
||||||
|
@ -1036,8 +1327,16 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
|
||||||
|
|
||||||
const QString AFFECTED_TYPES_JSON_KEY = "assignment-types";
|
const QString AFFECTED_TYPES_JSON_KEY = "assignment-types";
|
||||||
|
|
||||||
// enumerate the groups in the description object to find which settings to pass
|
// only enumerate the requested settings type (domain setting or content setting)
|
||||||
foreach(const QJsonValue& groupValue, _descriptionArray) {
|
QJsonArray& filteredDescriptionArray = _descriptionArray;
|
||||||
|
if (includeDomainSettings && !includeContentSettings) {
|
||||||
|
filteredDescriptionArray = _domainSettingsDescription;
|
||||||
|
} else if (includeContentSettings && !includeDomainSettings) {
|
||||||
|
filteredDescriptionArray = _contentSettingsDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
// enumerate the groups in the potentially filtered object to find which settings to pass
|
||||||
|
foreach(const QJsonValue& groupValue, filteredDescriptionArray) {
|
||||||
QJsonObject groupObject = groupValue.toObject();
|
QJsonObject groupObject = groupValue.toObject();
|
||||||
QString groupKey = groupObject[DESCRIPTION_NAME_KEY].toString();
|
QString groupKey = groupObject[DESCRIPTION_NAME_KEY].toString();
|
||||||
QJsonArray groupSettingsArray = groupObject[DESCRIPTION_SETTINGS_KEY].toArray();
|
QJsonArray groupSettingsArray = groupObject[DESCRIPTION_SETTINGS_KEY].toArray();
|
||||||
|
@ -1045,11 +1344,17 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
|
||||||
QJsonObject groupResponseObject;
|
QJsonObject groupResponseObject;
|
||||||
|
|
||||||
foreach(const QJsonValue& settingValue, groupSettingsArray) {
|
foreach(const QJsonValue& settingValue, groupSettingsArray) {
|
||||||
|
|
||||||
const QString VALUE_HIDDEN_FLAG_KEY = "value-hidden";
|
const QString VALUE_HIDDEN_FLAG_KEY = "value-hidden";
|
||||||
|
|
||||||
QJsonObject settingObject = settingValue.toObject();
|
QJsonObject settingObject = settingValue.toObject();
|
||||||
|
|
||||||
if (!settingObject[VALUE_HIDDEN_FLAG_KEY].toBool()) {
|
// consider this setting as long as it isn't hidden
|
||||||
|
// and either this isn't for a backup or it's a value included in backups
|
||||||
|
bool includedInBackups = !settingObject.contains(DESCRIPTION_BACKUP_FLAG_KEY)
|
||||||
|
|| settingObject[DESCRIPTION_BACKUP_FLAG_KEY].toBool();
|
||||||
|
|
||||||
|
if (!settingObject[VALUE_HIDDEN_FLAG_KEY].toBool() && (!isForBackup || includedInBackups)) {
|
||||||
QJsonArray affectedTypesArray = settingObject[AFFECTED_TYPES_JSON_KEY].toArray();
|
QJsonArray affectedTypesArray = settingObject[AFFECTED_TYPES_JSON_KEY].toArray();
|
||||||
if (affectedTypesArray.isEmpty()) {
|
if (affectedTypesArray.isEmpty()) {
|
||||||
affectedTypesArray = groupObject[AFFECTED_TYPES_JSON_KEY].toArray();
|
affectedTypesArray = groupObject[AFFECTED_TYPES_JSON_KEY].toArray();
|
||||||
|
@ -1057,8 +1362,6 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
|
||||||
|
|
||||||
if (affectedTypesArray.contains(queryType) ||
|
if (affectedTypesArray.contains(queryType) ||
|
||||||
(queryType.isNull() && isAuthenticated)) {
|
(queryType.isNull() && isAuthenticated)) {
|
||||||
// this is a setting we should include in the responseObject
|
|
||||||
|
|
||||||
QString settingName = settingObject[DESCRIPTION_NAME_KEY].toString();
|
QString settingName = settingObject[DESCRIPTION_NAME_KEY].toString();
|
||||||
|
|
||||||
// we need to check if the settings map has a value for this setting
|
// we need to check if the settings map has a value for this setting
|
||||||
|
@ -1074,28 +1377,31 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
|
||||||
variantValue = _configMap.value(settingName);
|
variantValue = _configMap.value(settingName);
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonValue result;
|
// final check for inclusion
|
||||||
|
// either we include default values or we don't but this isn't a default value
|
||||||
|
if (includeDefaults || !variantValue.isNull()) {
|
||||||
|
QJsonValue result;
|
||||||
|
|
||||||
if (variantValue.isNull()) {
|
if (variantValue.isNull()) {
|
||||||
// no value for this setting, pass the default
|
// no value for this setting, pass the default
|
||||||
if (settingObject.contains(SETTING_DEFAULT_KEY)) {
|
if (settingObject.contains(SETTING_DEFAULT_KEY)) {
|
||||||
result = settingObject[SETTING_DEFAULT_KEY];
|
result = settingObject[SETTING_DEFAULT_KEY];
|
||||||
|
} else {
|
||||||
|
// users are allowed not to provide a default for string values
|
||||||
|
// if so we set to the empty string
|
||||||
|
result = QString("");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// users are allowed not to provide a default for string values
|
result = QJsonValue::fromVariant(variantValue);
|
||||||
// if so we set to the empty string
|
|
||||||
result = QString("");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
if (!groupKey.isEmpty()) {
|
||||||
result = QJsonValue::fromVariant(variantValue);
|
// this belongs in the group object
|
||||||
}
|
groupResponseObject[settingName] = result;
|
||||||
|
} else {
|
||||||
if (!groupKey.isEmpty()) {
|
// this is a value that should be at the root
|
||||||
// this belongs in the group object
|
responseObject[settingName] = result;
|
||||||
groupResponseObject[settingName] = result;
|
}
|
||||||
} else {
|
|
||||||
// this is a value that should be at the root
|
|
||||||
responseObject[settingName] = result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1108,7 +1414,6 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return responseObject;
|
return responseObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1140,6 +1445,8 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
|
||||||
settingMap[key] = sanitizedValue;
|
settingMap[key] = sanitizedValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (newValue.isDouble()) {
|
||||||
|
settingMap[key] = newValue.toDouble();
|
||||||
} else if (newValue.isBool()) {
|
} else if (newValue.isBool()) {
|
||||||
settingMap[key] = newValue.toBool();
|
settingMap[key] = newValue.toBool();
|
||||||
} else if (newValue.isObject()) {
|
} else if (newValue.isObject()) {
|
||||||
|
@ -1212,7 +1519,8 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson
|
||||||
return QJsonObject();
|
return QJsonObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) {
|
bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
|
||||||
|
SettingsType settingsType) {
|
||||||
static const QString SECURITY_ROOT_KEY = "security";
|
static const QString SECURITY_ROOT_KEY = "security";
|
||||||
static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist";
|
static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist";
|
||||||
static const QString BROADCASTING_KEY = "broadcasting";
|
static const QString BROADCASTING_KEY = "broadcasting";
|
||||||
|
@ -1222,6 +1530,8 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
||||||
auto& settingsVariant = _configMap.getConfig();
|
auto& settingsVariant = _configMap.getConfig();
|
||||||
bool needRestart = false;
|
bool needRestart = false;
|
||||||
|
|
||||||
|
auto& filteredDescriptionArray = settingsType == DomainSettings ? _domainSettingsDescription : _contentSettingsDescription;
|
||||||
|
|
||||||
// Iterate on the setting groups
|
// Iterate on the setting groups
|
||||||
foreach(const QString& rootKey, postedObject.keys()) {
|
foreach(const QString& rootKey, postedObject.keys()) {
|
||||||
const QJsonValue& rootValue = postedObject[rootKey];
|
const QJsonValue& rootValue = postedObject[rootKey];
|
||||||
|
@ -1236,7 +1546,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
||||||
QJsonObject groupDescriptionObject;
|
QJsonObject groupDescriptionObject;
|
||||||
|
|
||||||
// we need to check the description array to see if this is a root setting or a group setting
|
// we need to check the description array to see if this is a root setting or a group setting
|
||||||
foreach(const QJsonValue& groupValue, _descriptionArray) {
|
foreach(const QJsonValue& groupValue, filteredDescriptionArray) {
|
||||||
if (groupValue.toObject()[DESCRIPTION_NAME_KEY] == rootKey) {
|
if (groupValue.toObject()[DESCRIPTION_NAME_KEY] == rootKey) {
|
||||||
// we matched a group - keep this since we'll use it below to update the settings
|
// we matched a group - keep this since we'll use it below to update the settings
|
||||||
groupDescriptionObject = groupValue.toObject();
|
groupDescriptionObject = groupValue.toObject();
|
||||||
|
@ -1257,7 +1567,9 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
||||||
foreach(const QJsonValue& groupValue, _descriptionArray) {
|
foreach(const QJsonValue& groupValue, _descriptionArray) {
|
||||||
// find groups with root values (they don't have a group name)
|
// find groups with root values (they don't have a group name)
|
||||||
QJsonObject groupObject = groupValue.toObject();
|
QJsonObject groupObject = groupValue.toObject();
|
||||||
|
|
||||||
if (!groupObject.contains(DESCRIPTION_NAME_KEY)) {
|
if (!groupObject.contains(DESCRIPTION_NAME_KEY)) {
|
||||||
|
|
||||||
// this is a group with root values - check if our setting is in here
|
// this is a group with root values - check if our setting is in here
|
||||||
matchingDescriptionObject = settingDescriptionFromGroup(groupObject, rootKey);
|
matchingDescriptionObject = settingDescriptionFromGroup(groupObject, rootKey);
|
||||||
|
|
||||||
|
@ -1269,6 +1581,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
||||||
|
|
||||||
if (!matchingDescriptionObject.isEmpty()) {
|
if (!matchingDescriptionObject.isEmpty()) {
|
||||||
updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject);
|
updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject);
|
||||||
|
|
||||||
if (rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY &&
|
if (rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY &&
|
||||||
rootKey != SETTINGS_PATHS_KEY && rootKey != WIZARD_KEY) {
|
rootKey != SETTINGS_PATHS_KEY && rootKey != WIZARD_KEY) {
|
||||||
needRestart = true;
|
needRestart = true;
|
||||||
|
@ -1286,6 +1599,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
||||||
if (!matchingDescriptionObject.isEmpty()) {
|
if (!matchingDescriptionObject.isEmpty()) {
|
||||||
const QJsonValue& settingValue = rootValue.toObject()[settingKey];
|
const QJsonValue& settingValue = rootValue.toObject()[settingKey];
|
||||||
updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject);
|
updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject);
|
||||||
|
|
||||||
if ((rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY &&
|
if ((rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY &&
|
||||||
rootKey != DESCRIPTION_ROOT_KEY && rootKey != WIZARD_KEY) ||
|
rootKey != DESCRIPTION_ROOT_KEY && rootKey != WIZARD_KEY) ||
|
||||||
settingKey == AC_SUBNET_WHITELIST_KEY) {
|
settingKey == AC_SUBNET_WHITELIST_KEY) {
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#define hifi_DomainServerSettingsManager_h
|
#define hifi_DomainServerSettingsManager_h
|
||||||
|
|
||||||
#include <QtCore/QJsonArray>
|
#include <QtCore/QJsonArray>
|
||||||
|
#include <QtCore/QJsonObject>
|
||||||
#include <QtCore/QJsonDocument>
|
#include <QtCore/QJsonDocument>
|
||||||
#include <QtNetwork/QNetworkReply>
|
#include <QtNetwork/QNetworkReply>
|
||||||
|
|
||||||
|
@ -28,6 +29,7 @@ const QString SETTINGS_PATHS_KEY = "paths";
|
||||||
|
|
||||||
const QString SETTINGS_PATH = "/settings";
|
const QString SETTINGS_PATH = "/settings";
|
||||||
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
|
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
|
||||||
|
const QString CONTENT_SETTINGS_PATH_JSON = "/content-settings.json";
|
||||||
const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions";
|
const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions";
|
||||||
const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions";
|
const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions";
|
||||||
const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions";
|
const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions";
|
||||||
|
@ -38,6 +40,10 @@ const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens";
|
||||||
|
|
||||||
using GroupByUUIDKey = QPair<QUuid, QUuid>; // groupID, rankID
|
using GroupByUUIDKey = QPair<QUuid, QUuid>; // groupID, rankID
|
||||||
|
|
||||||
|
enum SettingsType {
|
||||||
|
DomainSettings,
|
||||||
|
ContentSettings
|
||||||
|
};
|
||||||
|
|
||||||
class DomainServerSettingsManager : public QObject {
|
class DomainServerSettingsManager : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -123,8 +129,11 @@ private slots:
|
||||||
private:
|
private:
|
||||||
QStringList _argumentList;
|
QStringList _argumentList;
|
||||||
|
|
||||||
QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false);
|
QJsonArray filteredDescriptionArray(bool isContentSettings);
|
||||||
bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject);
|
QJsonObject settingsResponseObjectForType(const QString& typeValue, bool isAuthenticated = false,
|
||||||
|
bool includeDomainSettings = true, bool includeContentSettings = true,
|
||||||
|
bool includeDefaults = true, bool isForBackup = false);
|
||||||
|
bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType);
|
||||||
|
|
||||||
void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
|
void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
|
||||||
const QJsonObject& settingDescription);
|
const QJsonObject& settingDescription);
|
||||||
|
@ -132,8 +141,17 @@ private:
|
||||||
void sortPermissions();
|
void sortPermissions();
|
||||||
void persistToFile();
|
void persistToFile();
|
||||||
|
|
||||||
|
void splitSettingsDescription();
|
||||||
|
|
||||||
|
bool restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType);
|
||||||
|
|
||||||
double _descriptionVersion;
|
double _descriptionVersion;
|
||||||
|
|
||||||
QJsonArray _descriptionArray;
|
QJsonArray _descriptionArray;
|
||||||
|
QJsonArray _domainSettingsDescription;
|
||||||
|
QJsonArray _contentSettingsDescription;
|
||||||
|
QJsonObject _settingsMenuGroups;
|
||||||
|
|
||||||
HifiConfigVariantMap _configMap;
|
HifiConfigVariantMap _configMap;
|
||||||
|
|
||||||
friend class DomainServer;
|
friend class DomainServer;
|
||||||
|
|
|
@ -88,8 +88,8 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{ "from": "Keyboard.W", "to": "Actions.LONGITUDINAL_FORWARD" },
|
{ "from": "Keyboard.W", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_FORWARD" },
|
||||||
{ "from": "Keyboard.S", "to": "Actions.LONGITUDINAL_BACKWARD" },
|
{ "from": "Keyboard.S", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_BACKWARD" },
|
||||||
{ "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" },
|
{ "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" },
|
||||||
{ "from": "Keyboard.E", "to": "Actions.VERTICAL_UP" },
|
{ "from": "Keyboard.E", "to": "Actions.VERTICAL_UP" },
|
||||||
{ "from": "Keyboard.Left", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_LEFT" },
|
{ "from": "Keyboard.Left", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_LEFT" },
|
||||||
|
|
|
@ -48,7 +48,7 @@ Rectangle {
|
||||||
// The letterbox used for popup messages
|
// The letterbox used for popup messages
|
||||||
LetterboxMessage {
|
LetterboxMessage {
|
||||||
id: letterboxMessage;
|
id: letterboxMessage;
|
||||||
z: 999; // Force the popup on top of everything else
|
z: 998; // Force the popup on top of everything else
|
||||||
}
|
}
|
||||||
Connections {
|
Connections {
|
||||||
target: GlobalServices
|
target: GlobalServices
|
||||||
|
@ -60,7 +60,7 @@ Rectangle {
|
||||||
// The ComboDialog used for setting availability
|
// The ComboDialog used for setting availability
|
||||||
ComboDialog {
|
ComboDialog {
|
||||||
id: comboDialog;
|
id: comboDialog;
|
||||||
z: 999; // Force the ComboDialog on top of everything else
|
z: 998; // Force the ComboDialog on top of everything else
|
||||||
dialogWidth: parent.width - 50;
|
dialogWidth: parent.width - 50;
|
||||||
dialogHeight: parent.height - 100;
|
dialogHeight: parent.height - 100;
|
||||||
}
|
}
|
||||||
|
@ -1013,7 +1013,7 @@ Rectangle {
|
||||||
}
|
}
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent;
|
anchors.fill: parent;
|
||||||
enabled: myData.userName !== "Unknown user";
|
enabled: myData.userName !== "Unknown user" && !userInfoViewer.visible;
|
||||||
hoverEnabled: true;
|
hoverEnabled: true;
|
||||||
onClicked: {
|
onClicked: {
|
||||||
popupComboDialog("Set your availability:",
|
popupComboDialog("Set your availability:",
|
||||||
|
@ -1044,6 +1044,7 @@ Rectangle {
|
||||||
|
|
||||||
HifiControls.TabletWebView {
|
HifiControls.TabletWebView {
|
||||||
id: userInfoViewer;
|
id: userInfoViewer;
|
||||||
|
z: 999;
|
||||||
anchors {
|
anchors {
|
||||||
top: parent.top;
|
top: parent.top;
|
||||||
bottom: parent.bottom;
|
bottom: parent.bottom;
|
||||||
|
|
Binary file not shown.
|
@ -1111,7 +1111,7 @@ void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::setEnableMeshVisible(bool isEnabled) {
|
void MyAvatar::setEnableMeshVisible(bool isEnabled) {
|
||||||
_skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE);
|
_skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::setEnableInverseKinematics(bool isEnabled) {
|
void MyAvatar::setEnableInverseKinematics(bool isEnabled) {
|
||||||
|
@ -1463,7 +1463,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||||
_skeletonModelChangeCount++;
|
_skeletonModelChangeCount++;
|
||||||
int skeletonModelChangeCount = _skeletonModelChangeCount;
|
int skeletonModelChangeCount = _skeletonModelChangeCount;
|
||||||
Avatar::setSkeletonModelURL(skeletonModelURL);
|
Avatar::setSkeletonModelURL(skeletonModelURL);
|
||||||
_skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE);
|
_skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true);
|
||||||
_headBoneSet.clear();
|
_headBoneSet.clear();
|
||||||
_cauterizationNeedsUpdate = true;
|
_cauterizationNeedsUpdate = true;
|
||||||
|
|
||||||
|
@ -1821,7 +1821,7 @@ void MyAvatar::attach(const QString& modelURL, const QString& jointName,
|
||||||
|
|
||||||
void MyAvatar::setVisibleInSceneIfReady(Model* model, const render::ScenePointer& scene, bool visible) {
|
void MyAvatar::setVisibleInSceneIfReady(Model* model, const render::ScenePointer& scene, bool visible) {
|
||||||
if (model->isActive() && model->isRenderable()) {
|
if (model->isActive() && model->isRenderable()) {
|
||||||
model->setVisibleInScene(visible, scene, render::ItemKey::TAG_BITS_NONE);
|
model->setVisibleInScene(visible, scene, render::ItemKey::TAG_BITS_NONE, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2015,7 +2015,7 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) {
|
||||||
_attachmentData[i].jointName.compare("HeadTop_End", Qt::CaseInsensitive) == 0 ||
|
_attachmentData[i].jointName.compare("HeadTop_End", Qt::CaseInsensitive) == 0 ||
|
||||||
_attachmentData[i].jointName.compare("Face", Qt::CaseInsensitive) == 0) {
|
_attachmentData[i].jointName.compare("Face", Qt::CaseInsensitive) == 0) {
|
||||||
_attachmentModels[i]->setVisibleInScene(shouldDrawHead, qApp->getMain3DScene(),
|
_attachmentModels[i]->setVisibleInScene(shouldDrawHead, qApp->getMain3DScene(),
|
||||||
render::ItemKey::TAG_BITS_NONE);
|
render::ItemKey::TAG_BITS_NONE, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -425,10 +425,10 @@ void Circle3DOverlay::setProperties(const QVariantMap& properties) {
|
||||||
* <em>Write-only.</em>
|
* <em>Write-only.</em>
|
||||||
* @property {Color} outerColor - Sets the values of <code>outerStartColor</code> and <code>outerEndColor</code>.
|
* @property {Color} outerColor - Sets the values of <code>outerStartColor</code> and <code>outerEndColor</code>.
|
||||||
* <em>Write-only.</em>
|
* <em>Write-only.</em>
|
||||||
* @property {Color} innerStartcolor - The color at the inner start point of the overlay. <em>Write-only.</em>
|
* @property {Color} innerStartcolor - The color at the inner start point of the overlay.
|
||||||
* @property {Color} innerEndColor - The color at the inner end point of the overlay. <em>Write-only.</em>
|
* @property {Color} innerEndColor - The color at the inner end point of the overlay.
|
||||||
* @property {Color} outerStartColor - The color at the outer start point of the overlay. <em>Write-only.</em>
|
* @property {Color} outerStartColor - The color at the outer start point of the overlay.
|
||||||
* @property {Color} outerEndColor - The color at the outer end point of the overlay. <em>Write-only.</em>
|
* @property {Color} outerEndColor - The color at the outer end point of the overlay.
|
||||||
* @property {number} alpha=0.5 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>. Setting this value also sets
|
* @property {number} alpha=0.5 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>. Setting this value also sets
|
||||||
* the values of <code>innerStartAlpha</code>, <code>innerEndAlpha</code>, <code>outerStartAlpha</code>, and
|
* the values of <code>innerStartAlpha</code>, <code>innerEndAlpha</code>, <code>outerStartAlpha</code>, and
|
||||||
* <code>outerEndAlpha</code>. Synonym: <code>Alpha</code>; <em>write-only</em>.
|
* <code>outerEndAlpha</code>. Synonym: <code>Alpha</code>; <em>write-only</em>.
|
||||||
|
@ -440,10 +440,10 @@ void Circle3DOverlay::setProperties(const QVariantMap& properties) {
|
||||||
* <em>Write-only.</em>
|
* <em>Write-only.</em>
|
||||||
* @property {number} outerAlpha - Sets the values of <code>outerStartAlpha</code> and <code>outerEndAlpha</code>.
|
* @property {number} outerAlpha - Sets the values of <code>outerStartAlpha</code> and <code>outerEndAlpha</code>.
|
||||||
* <em>Write-only.</em>
|
* <em>Write-only.</em>
|
||||||
* @property {number} innerStartAlpha=0 - The alpha at the inner start point of the overlay. <em>Write-only.</em>
|
* @property {number} innerStartAlpha=0 - The alpha at the inner start point of the overlay.
|
||||||
* @property {number} innerEndAlpha=0 - The alpha at the inner end point of the overlay. <em>Write-only.</em>
|
* @property {number} innerEndAlpha=0 - The alpha at the inner end point of the overlay.
|
||||||
* @property {number} outerStartAlpha=0 - The alpha at the outer start point of the overlay. <em>Write-only.</em>
|
* @property {number} outerStartAlpha=0 - The alpha at the outer start point of the overlay.
|
||||||
* @property {number} outerEndAlpha=0 - The alpha at the outer end point of the overlay. <em>Write-only.</em>
|
* @property {number} outerEndAlpha=0 - The alpha at the outer end point of the overlay.
|
||||||
|
|
||||||
* @property {boolean} hasTickMarks=false - If <code>true</code>, tick marks are drawn.
|
* @property {boolean} hasTickMarks=false - If <code>true</code>, tick marks are drawn.
|
||||||
* @property {number} majorTickMarksAngle=0 - The angle between major tick marks, in degrees.
|
* @property {number} majorTickMarksAngle=0 - The angle between major tick marks, in degrees.
|
||||||
|
|
|
@ -87,7 +87,7 @@ void ModelOverlay::update(float deltatime) {
|
||||||
if (_visibleDirty) {
|
if (_visibleDirty) {
|
||||||
_visibleDirty = false;
|
_visibleDirty = false;
|
||||||
// don't show overlays in mirrors
|
// don't show overlays in mirrors
|
||||||
_model->setVisibleInScene(getVisible(), scene, render::ItemKey::TAG_BITS_0);
|
_model->setVisibleInScene(getVisible(), scene, render::ItemKey::TAG_BITS_0, false);
|
||||||
}
|
}
|
||||||
if (_drawInFrontDirty) {
|
if (_drawInFrontDirty) {
|
||||||
_drawInFrontDirty = false;
|
_drawInFrontDirty = false;
|
||||||
|
|
|
@ -50,7 +50,7 @@ const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f);
|
||||||
|
|
||||||
namespace render {
|
namespace render {
|
||||||
template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) {
|
template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) {
|
||||||
return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(ItemKey::TAG_BITS_0 | ItemKey::TAG_BITS_1);
|
return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(ItemKey::TAG_BITS_0 | ItemKey::TAG_BITS_1).withMetaCullGroup();
|
||||||
}
|
}
|
||||||
template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar) {
|
template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar) {
|
||||||
return static_pointer_cast<Avatar>(avatar)->getBounds();
|
return static_pointer_cast<Avatar>(avatar)->getBounds();
|
||||||
|
|
|
@ -30,6 +30,20 @@ QVector<QUuid> AvatarHashMap::getAvatarIdentifiers() {
|
||||||
return _avatarHash.keys().toVector();
|
return _avatarHash.keys().toVector();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVector<QUuid> AvatarHashMap::getAvatarsInRange(const glm::vec3& position, float rangeMeters) const {
|
||||||
|
auto hashCopy = getHashCopy();
|
||||||
|
QVector<QUuid> avatarsInRange;
|
||||||
|
auto rangeMetersSquared = rangeMeters * rangeMeters;
|
||||||
|
for (const AvatarSharedPointer& sharedAvatar : hashCopy) {
|
||||||
|
glm::vec3 avatarPosition = sharedAvatar->getWorldPosition();
|
||||||
|
auto distanceSquared = glm::distance2(avatarPosition, position);
|
||||||
|
if (distanceSquared < rangeMetersSquared) {
|
||||||
|
avatarsInRange.push_back(sharedAvatar->getSessionUUID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return avatarsInRange;
|
||||||
|
}
|
||||||
|
|
||||||
bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range) {
|
bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range) {
|
||||||
auto hashCopy = getHashCopy();
|
auto hashCopy = getHashCopy();
|
||||||
foreach(const AvatarSharedPointer& sharedAvatar, hashCopy) {
|
foreach(const AvatarSharedPointer& sharedAvatar, hashCopy) {
|
||||||
|
|
|
@ -35,10 +35,12 @@ class AvatarHashMap : public QObject, public Dependency {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AvatarHash getHashCopy() { QReadLocker lock(&_hashLock); return _avatarHash; }
|
AvatarHash getHashCopy() { QReadLocker lock(&_hashLock); return _avatarHash; }
|
||||||
|
const AvatarHash getHashCopy() const { QReadLocker lock(&_hashLock); return _avatarHash; }
|
||||||
int size() { return _avatarHash.size(); }
|
int size() { return _avatarHash.size(); }
|
||||||
|
|
||||||
// Currently, your own avatar will be included as the null avatar id.
|
// Currently, your own avatar will be included as the null avatar id.
|
||||||
Q_INVOKABLE QVector<QUuid> getAvatarIdentifiers();
|
Q_INVOKABLE QVector<QUuid> getAvatarIdentifiers();
|
||||||
|
Q_INVOKABLE QVector<QUuid> getAvatarsInRange(const glm::vec3& position, float rangeMeters) const;
|
||||||
|
|
||||||
// Null/Default-constructed QUuids will return MyAvatar
|
// Null/Default-constructed QUuids will return MyAvatar
|
||||||
Q_INVOKABLE virtual ScriptAvatarData* getAvatar(QUuid avatarID) { return new ScriptAvatarData(getAvatarBySessionID(avatarID)); }
|
Q_INVOKABLE virtual ScriptAvatarData* getAvatar(QUuid avatarID) { return new ScriptAvatarData(getAvatarBySessionID(avatarID)); }
|
||||||
|
|
|
@ -79,7 +79,7 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url,
|
||||||
QHash<QByteArray, QByteArray> redirectHeader;
|
QHash<QByteArray, QByteArray> redirectHeader;
|
||||||
redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8());
|
redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8());
|
||||||
|
|
||||||
connection->respond(HTTPConnection::StatusCode301, "", HTTPConnection::DefaultContentType, redirectHeader);
|
connection->respond(HTTPConnection::StatusCode302, "", HTTPConnection::DefaultContentType, redirectHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the last thing is a trailing slash then we want to look for index file
|
// if the last thing is a trailing slash then we want to look for index file
|
||||||
|
|
|
@ -1352,7 +1352,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
|
||||||
// FIXME: this seems like it could be optimized if we tracked our last known visible state in
|
// FIXME: this seems like it could be optimized if we tracked our last known visible state in
|
||||||
// the renderable item. As it stands now the model checks it's visible/invisible state
|
// the renderable item. As it stands now the model checks it's visible/invisible state
|
||||||
// so most of the time we don't do anything in this function.
|
// so most of the time we don't do anything in this function.
|
||||||
model->setVisibleInScene(_visible, scene, viewTaskBits);
|
model->setVisibleInScene(_visible, scene, viewTaskBits, false);
|
||||||
}
|
}
|
||||||
// TODO? early exit here when not visible?
|
// TODO? early exit here when not visible?
|
||||||
|
|
||||||
|
|
|
@ -54,11 +54,9 @@ void KeyboardMouseDevice::InputDevice::focusOutEvent() {
|
||||||
|
|
||||||
void KeyboardMouseDevice::keyPressEvent(QKeyEvent* event) {
|
void KeyboardMouseDevice::keyPressEvent(QKeyEvent* event) {
|
||||||
auto input = _inputDevice->makeInput((Qt::Key) event->key());
|
auto input = _inputDevice->makeInput((Qt::Key) event->key());
|
||||||
if (!(event->modifiers() & Qt::KeyboardModifier::ControlModifier)) {
|
auto result = _inputDevice->_buttonPressedMap.insert(input.getChannel());
|
||||||
auto result = _inputDevice->_buttonPressedMap.insert(input.getChannel());
|
if (result.second) {
|
||||||
if (result.second) {
|
// key pressed again ? without catching the release event ?
|
||||||
// key pressed again ? without catching the release event ?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,6 +235,7 @@ controller::Input::NamedVector KeyboardMouseDevice::InputDevice::getAvailableInp
|
||||||
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageUp), QKeySequence(Qt::Key_PageUp).toString()));
|
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageUp), QKeySequence(Qt::Key_PageUp).toString()));
|
||||||
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageDown), QKeySequence(Qt::Key_PageDown).toString()));
|
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageDown), QKeySequence(Qt::Key_PageDown).toString()));
|
||||||
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Tab), QKeySequence(Qt::Key_Tab).toString()));
|
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Tab), QKeySequence(Qt::Key_Tab).toString()));
|
||||||
|
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Control), "Control"));
|
||||||
|
|
||||||
availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton), "LeftMouseButton"));
|
availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton), "LeftMouseButton"));
|
||||||
availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "MiddleMouseButton"));
|
availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "MiddleMouseButton"));
|
||||||
|
|
|
@ -71,7 +71,7 @@ void MeshPartPayload::updateMaterial(graphics::MaterialPointer drawMaterial) {
|
||||||
_drawMaterial = drawMaterial;
|
_drawMaterial = drawMaterial;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits) {
|
void MeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled) {
|
||||||
ItemKey::Builder builder;
|
ItemKey::Builder builder;
|
||||||
builder.withTypeShape();
|
builder.withTypeShape();
|
||||||
|
|
||||||
|
@ -85,6 +85,10 @@ void MeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits)
|
||||||
builder.withLayered();
|
builder.withLayered();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isGroupCulled) {
|
||||||
|
builder.withSubMetaCulled();
|
||||||
|
}
|
||||||
|
|
||||||
if (_drawMaterial) {
|
if (_drawMaterial) {
|
||||||
auto matKey = _drawMaterial->getKey();
|
auto matKey = _drawMaterial->getKey();
|
||||||
if (matKey.isTranslucent()) {
|
if (matKey.isTranslucent()) {
|
||||||
|
@ -403,7 +407,7 @@ void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& render
|
||||||
_worldBound.transform(boundTransform);
|
_worldBound.transform(boundTransform);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits) {
|
void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled) {
|
||||||
ItemKey::Builder builder;
|
ItemKey::Builder builder;
|
||||||
builder.withTypeShape();
|
builder.withTypeShape();
|
||||||
|
|
||||||
|
@ -417,6 +421,10 @@ void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tag
|
||||||
builder.withLayered();
|
builder.withLayered();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isGroupCulled) {
|
||||||
|
builder.withSubMetaCulled();
|
||||||
|
}
|
||||||
|
|
||||||
if (_isBlendShaped || _isSkinned) {
|
if (_isBlendShaped || _isSkinned) {
|
||||||
builder.withDeformed();
|
builder.withDeformed();
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ public:
|
||||||
typedef render::Payload<MeshPartPayload> Payload;
|
typedef render::Payload<MeshPartPayload> Payload;
|
||||||
typedef Payload::DataPointer Pointer;
|
typedef Payload::DataPointer Pointer;
|
||||||
|
|
||||||
virtual void updateKey(bool isVisible, bool isLayered, uint8_t tagBits);
|
virtual void updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled = false);
|
||||||
|
|
||||||
virtual void updateMeshPart(const std::shared_ptr<const graphics::Mesh>& drawMesh, int partIndex);
|
virtual void updateMeshPart(const std::shared_ptr<const graphics::Mesh>& drawMesh, int partIndex);
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ public:
|
||||||
using TransformType = glm::mat4;
|
using TransformType = glm::mat4;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void updateKey(bool isVisible, bool isLayered, uint8_t tagBits) override;
|
void updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled = false) override;
|
||||||
void updateClusterBuffer(const std::vector<TransformType>& clusterTransforms);
|
void updateClusterBuffer(const std::vector<TransformType>& clusterTransforms);
|
||||||
void updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform);
|
void updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform);
|
||||||
|
|
||||||
|
|
|
@ -271,6 +271,7 @@ void Model::updateRenderItems() {
|
||||||
uint8_t viewTagBits = self->getViewTagBits();
|
uint8_t viewTagBits = self->getViewTagBits();
|
||||||
bool isLayeredInFront = self->isLayeredInFront();
|
bool isLayeredInFront = self->isLayeredInFront();
|
||||||
bool isLayeredInHUD = self->isLayeredInHUD();
|
bool isLayeredInHUD = self->isLayeredInHUD();
|
||||||
|
bool isGroupCulled = self->isGroupCulled();
|
||||||
|
|
||||||
render::Transaction transaction;
|
render::Transaction transaction;
|
||||||
for (int i = 0; i < (int) self->_modelMeshRenderItemIDs.size(); i++) {
|
for (int i = 0; i < (int) self->_modelMeshRenderItemIDs.size(); i++) {
|
||||||
|
@ -284,7 +285,7 @@ void Model::updateRenderItems() {
|
||||||
transaction.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, clusterTransforms,
|
transaction.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, clusterTransforms,
|
||||||
invalidatePayloadShapeKey, isWireframe, isVisible,
|
invalidatePayloadShapeKey, isWireframe, isVisible,
|
||||||
viewTagBits, isLayeredInFront,
|
viewTagBits, isLayeredInFront,
|
||||||
isLayeredInHUD](ModelMeshPartPayload& data) {
|
isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) {
|
||||||
data.updateClusterBuffer(clusterTransforms);
|
data.updateClusterBuffer(clusterTransforms);
|
||||||
|
|
||||||
Transform renderTransform = modelTransform;
|
Transform renderTransform = modelTransform;
|
||||||
|
@ -300,7 +301,7 @@ void Model::updateRenderItems() {
|
||||||
}
|
}
|
||||||
data.updateTransformForSkinnedMesh(renderTransform, modelTransform);
|
data.updateTransformForSkinnedMesh(renderTransform, modelTransform);
|
||||||
|
|
||||||
data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits);
|
data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled);
|
||||||
data.setLayer(isLayeredInFront, isLayeredInHUD);
|
data.setLayer(isLayeredInFront, isLayeredInHUD);
|
||||||
data.setShapeKey(invalidatePayloadShapeKey, isWireframe);
|
data.setShapeKey(invalidatePayloadShapeKey, isWireframe);
|
||||||
});
|
});
|
||||||
|
@ -684,10 +685,11 @@ void Model::calculateTriangleSets() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::setVisibleInScene(bool isVisible, const render::ScenePointer& scene, uint8_t viewTagBits) {
|
void Model::setVisibleInScene(bool isVisible, const render::ScenePointer& scene, uint8_t viewTagBits, bool isGroupCulled) {
|
||||||
if (_isVisible != isVisible || _viewTagBits != viewTagBits) {
|
if (_isVisible != isVisible || _viewTagBits != viewTagBits || _isGroupCulled != isGroupCulled) {
|
||||||
_isVisible = isVisible;
|
_isVisible = isVisible;
|
||||||
_viewTagBits = viewTagBits;
|
_viewTagBits = viewTagBits;
|
||||||
|
_isGroupCulled = isGroupCulled;
|
||||||
|
|
||||||
bool isLayeredInFront = _isLayeredInFront;
|
bool isLayeredInFront = _isLayeredInFront;
|
||||||
bool isLayeredInHUD = _isLayeredInHUD;
|
bool isLayeredInHUD = _isLayeredInHUD;
|
||||||
|
@ -695,14 +697,14 @@ void Model::setVisibleInScene(bool isVisible, const render::ScenePointer& scene,
|
||||||
render::Transaction transaction;
|
render::Transaction transaction;
|
||||||
foreach (auto item, _modelMeshRenderItemsMap.keys()) {
|
foreach (auto item, _modelMeshRenderItemsMap.keys()) {
|
||||||
transaction.updateItem<ModelMeshPartPayload>(item, [isVisible, viewTagBits, isLayeredInFront,
|
transaction.updateItem<ModelMeshPartPayload>(item, [isVisible, viewTagBits, isLayeredInFront,
|
||||||
isLayeredInHUD](ModelMeshPartPayload& data) {
|
isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) {
|
||||||
data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits);
|
data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
foreach(auto item, _collisionRenderItemsMap.keys()) {
|
foreach(auto item, _collisionRenderItemsMap.keys()) {
|
||||||
transaction.updateItem<ModelMeshPartPayload>(item, [isVisible, viewTagBits, isLayeredInFront,
|
transaction.updateItem<ModelMeshPartPayload>(item, [isVisible, viewTagBits, isLayeredInFront,
|
||||||
isLayeredInHUD](ModelMeshPartPayload& data) {
|
isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) {
|
||||||
data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits);
|
data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
scene->enqueueTransaction(transaction);
|
scene->enqueueTransaction(transaction);
|
||||||
|
@ -717,19 +719,20 @@ void Model::setLayeredInFront(bool isLayeredInFront, const render::ScenePointer&
|
||||||
bool isVisible = _isVisible;
|
bool isVisible = _isVisible;
|
||||||
uint8_t viewTagBits = _viewTagBits;
|
uint8_t viewTagBits = _viewTagBits;
|
||||||
bool isLayeredInHUD = _isLayeredInHUD;
|
bool isLayeredInHUD = _isLayeredInHUD;
|
||||||
|
bool isGroupCulled = _isGroupCulled;
|
||||||
|
|
||||||
render::Transaction transaction;
|
render::Transaction transaction;
|
||||||
foreach(auto item, _modelMeshRenderItemsMap.keys()) {
|
foreach(auto item, _modelMeshRenderItemsMap.keys()) {
|
||||||
transaction.updateItem<ModelMeshPartPayload>(item, [isVisible, viewTagBits, isLayeredInFront,
|
transaction.updateItem<ModelMeshPartPayload>(item, [isVisible, viewTagBits, isLayeredInFront,
|
||||||
isLayeredInHUD](ModelMeshPartPayload& data) {
|
isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) {
|
||||||
data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits);
|
data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled);
|
||||||
data.setLayer(isLayeredInFront, isLayeredInHUD);
|
data.setLayer(isLayeredInFront, isLayeredInHUD);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
foreach(auto item, _collisionRenderItemsMap.keys()) {
|
foreach(auto item, _collisionRenderItemsMap.keys()) {
|
||||||
transaction.updateItem<ModelMeshPartPayload>(item, [isVisible, viewTagBits, isLayeredInFront,
|
transaction.updateItem<ModelMeshPartPayload>(item, [isVisible, viewTagBits, isLayeredInFront,
|
||||||
isLayeredInHUD](ModelMeshPartPayload& data) {
|
isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) {
|
||||||
data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits);
|
data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled);
|
||||||
data.setLayer(isLayeredInFront, isLayeredInHUD);
|
data.setLayer(isLayeredInFront, isLayeredInHUD);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -744,19 +747,20 @@ void Model::setLayeredInHUD(bool isLayeredInHUD, const render::ScenePointer& sce
|
||||||
bool isVisible = _isVisible;
|
bool isVisible = _isVisible;
|
||||||
uint8_t viewTagBits = _viewTagBits;
|
uint8_t viewTagBits = _viewTagBits;
|
||||||
bool isLayeredInFront = _isLayeredInFront;
|
bool isLayeredInFront = _isLayeredInFront;
|
||||||
|
bool isGroupCulled = _isGroupCulled;
|
||||||
|
|
||||||
render::Transaction transaction;
|
render::Transaction transaction;
|
||||||
foreach(auto item, _modelMeshRenderItemsMap.keys()) {
|
foreach(auto item, _modelMeshRenderItemsMap.keys()) {
|
||||||
transaction.updateItem<ModelMeshPartPayload>(item, [isVisible, viewTagBits, isLayeredInFront,
|
transaction.updateItem<ModelMeshPartPayload>(item, [isVisible, viewTagBits, isLayeredInFront,
|
||||||
isLayeredInHUD](ModelMeshPartPayload& data) {
|
isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) {
|
||||||
data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits);
|
data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled);
|
||||||
data.setLayer(isLayeredInFront, isLayeredInHUD);
|
data.setLayer(isLayeredInFront, isLayeredInHUD);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
foreach(auto item, _collisionRenderItemsMap.keys()) {
|
foreach(auto item, _collisionRenderItemsMap.keys()) {
|
||||||
transaction.updateItem<ModelMeshPartPayload>(item, [isVisible, viewTagBits, isLayeredInFront,
|
transaction.updateItem<ModelMeshPartPayload>(item, [isVisible, viewTagBits, isLayeredInFront,
|
||||||
isLayeredInHUD](ModelMeshPartPayload& data) {
|
isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) {
|
||||||
data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits);
|
data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled);
|
||||||
data.setLayer(isLayeredInFront, isLayeredInHUD);
|
data.setLayer(isLayeredInFront, isLayeredInHUD);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,7 +86,7 @@ public:
|
||||||
const QUrl& getURL() const { return _url; }
|
const QUrl& getURL() const { return _url; }
|
||||||
|
|
||||||
// new Scene/Engine rendering support
|
// new Scene/Engine rendering support
|
||||||
void setVisibleInScene(bool isVisible, const render::ScenePointer& scene, uint8_t viewTagBits);
|
void setVisibleInScene(bool isVisible, const render::ScenePointer& scene, uint8_t viewTagBits, bool isGroupCulled);
|
||||||
void setLayeredInFront(bool isLayeredInFront, const render::ScenePointer& scene);
|
void setLayeredInFront(bool isLayeredInFront, const render::ScenePointer& scene);
|
||||||
void setLayeredInHUD(bool isLayeredInHUD, const render::ScenePointer& scene);
|
void setLayeredInHUD(bool isLayeredInHUD, const render::ScenePointer& scene);
|
||||||
bool needsFixupInScene() const;
|
bool needsFixupInScene() const;
|
||||||
|
@ -109,6 +109,8 @@ public:
|
||||||
bool isLayeredInFront() const { return _isLayeredInFront; }
|
bool isLayeredInFront() const { return _isLayeredInFront; }
|
||||||
bool isLayeredInHUD() const { return _isLayeredInHUD; }
|
bool isLayeredInHUD() const { return _isLayeredInHUD; }
|
||||||
|
|
||||||
|
bool isGroupCulled() const { return _isGroupCulled; }
|
||||||
|
|
||||||
virtual void updateRenderItems();
|
virtual void updateRenderItems();
|
||||||
void setRenderItemsNeedUpdate();
|
void setRenderItemsNeedUpdate();
|
||||||
bool getRenderItemsNeedUpdate() { return _renderItemsNeedUpdate; }
|
bool getRenderItemsNeedUpdate() { return _renderItemsNeedUpdate; }
|
||||||
|
@ -462,6 +464,8 @@ protected:
|
||||||
bool _isLayeredInFront { false };
|
bool _isLayeredInFront { false };
|
||||||
bool _isLayeredInHUD { false };
|
bool _isLayeredInHUD { false };
|
||||||
|
|
||||||
|
bool _isGroupCulled{ false };
|
||||||
|
|
||||||
bool shouldInvalidatePayloadShapeKey(int meshIndex);
|
bool shouldInvalidatePayloadShapeKey(int meshIndex);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -211,13 +211,14 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
|
||||||
outItems.clear();
|
outItems.clear();
|
||||||
outItems.reserve(inSelection.numItems());
|
outItems.reserve(inSelection.numItems());
|
||||||
|
|
||||||
const auto filter = inputs.get1();
|
const auto srcFilter = inputs.get1();
|
||||||
if (!filter.selectsNothing()) {
|
if (!srcFilter.selectsNothing()) {
|
||||||
// Now get the bound, and
|
auto filter = render::ItemFilter::Builder(srcFilter).withoutSubMetaCulled().build();
|
||||||
|
|
||||||
|
// Now get the bound, and
|
||||||
// filter individually against the _filter
|
// filter individually against the _filter
|
||||||
// visibility cull if partially selected ( octree cell contianing it was partial)
|
// visibility cull if partially selected ( octree cell contianing it was partial)
|
||||||
// distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item)
|
// distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item)
|
||||||
|
|
||||||
if (_skipCulling) {
|
if (_skipCulling) {
|
||||||
// inside & fit items: filter only, culling is disabled
|
// inside & fit items: filter only, culling is disabled
|
||||||
{
|
{
|
||||||
|
@ -227,6 +228,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
|
||||||
if (filter.test(item.getKey())) {
|
if (filter.test(item.getKey())) {
|
||||||
ItemBound itemBound(id, item.getBound());
|
ItemBound itemBound(id, item.getBound());
|
||||||
outItems.emplace_back(itemBound);
|
outItems.emplace_back(itemBound);
|
||||||
|
if (item.getKey().isMetaCullGroup()) {
|
||||||
|
item.fetchMetaSubItemBounds(outItems, (*scene));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,6 +243,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
|
||||||
if (filter.test(item.getKey())) {
|
if (filter.test(item.getKey())) {
|
||||||
ItemBound itemBound(id, item.getBound());
|
ItemBound itemBound(id, item.getBound());
|
||||||
outItems.emplace_back(itemBound);
|
outItems.emplace_back(itemBound);
|
||||||
|
if (item.getKey().isMetaCullGroup()) {
|
||||||
|
item.fetchMetaSubItemBounds(outItems, (*scene));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -251,6 +258,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
|
||||||
if (filter.test(item.getKey())) {
|
if (filter.test(item.getKey())) {
|
||||||
ItemBound itemBound(id, item.getBound());
|
ItemBound itemBound(id, item.getBound());
|
||||||
outItems.emplace_back(itemBound);
|
outItems.emplace_back(itemBound);
|
||||||
|
if (item.getKey().isMetaCullGroup()) {
|
||||||
|
item.fetchMetaSubItemBounds(outItems, (*scene));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -263,6 +273,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
|
||||||
if (filter.test(item.getKey())) {
|
if (filter.test(item.getKey())) {
|
||||||
ItemBound itemBound(id, item.getBound());
|
ItemBound itemBound(id, item.getBound());
|
||||||
outItems.emplace_back(itemBound);
|
outItems.emplace_back(itemBound);
|
||||||
|
if (item.getKey().isMetaCullGroup()) {
|
||||||
|
item.fetchMetaSubItemBounds(outItems, (*scene));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -277,6 +290,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
|
||||||
if (filter.test(item.getKey())) {
|
if (filter.test(item.getKey())) {
|
||||||
ItemBound itemBound(id, item.getBound());
|
ItemBound itemBound(id, item.getBound());
|
||||||
outItems.emplace_back(itemBound);
|
outItems.emplace_back(itemBound);
|
||||||
|
if (item.getKey().isMetaCullGroup()) {
|
||||||
|
item.fetchMetaSubItemBounds(outItems, (*scene));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -290,6 +306,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
|
||||||
ItemBound itemBound(id, item.getBound());
|
ItemBound itemBound(id, item.getBound());
|
||||||
if (test.solidAngleTest(itemBound.bound)) {
|
if (test.solidAngleTest(itemBound.bound)) {
|
||||||
outItems.emplace_back(itemBound);
|
outItems.emplace_back(itemBound);
|
||||||
|
if (item.getKey().isMetaCullGroup()) {
|
||||||
|
item.fetchMetaSubItemBounds(outItems, (*scene));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -304,6 +323,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
|
||||||
ItemBound itemBound(id, item.getBound());
|
ItemBound itemBound(id, item.getBound());
|
||||||
if (test.frustumTest(itemBound.bound)) {
|
if (test.frustumTest(itemBound.bound)) {
|
||||||
outItems.emplace_back(itemBound);
|
outItems.emplace_back(itemBound);
|
||||||
|
if (item.getKey().isMetaCullGroup()) {
|
||||||
|
item.fetchMetaSubItemBounds(outItems, (*scene));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -319,6 +341,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
|
||||||
if (test.frustumTest(itemBound.bound)) {
|
if (test.frustumTest(itemBound.bound)) {
|
||||||
if (test.solidAngleTest(itemBound.bound)) {
|
if (test.solidAngleTest(itemBound.bound)) {
|
||||||
outItems.emplace_back(itemBound);
|
outItems.emplace_back(itemBound);
|
||||||
|
if (item.getKey().isMetaCullGroup()) {
|
||||||
|
item.fetchMetaSubItemBounds(outItems, (*scene));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,6 +113,21 @@ const ShapeKey Item::getShapeKey() const {
|
||||||
return shapeKey;
|
return shapeKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t Item::fetchMetaSubItemBounds(ItemBounds& subItemBounds, Scene& scene) const {
|
||||||
|
ItemIDs subItems;
|
||||||
|
auto numSubs = fetchMetaSubItems(subItems);
|
||||||
|
|
||||||
|
for (auto id : subItems) {
|
||||||
|
auto& item = scene.getItem(id);
|
||||||
|
if (item.exist()) {
|
||||||
|
subItemBounds.emplace_back(id, item.getBound());
|
||||||
|
} else {
|
||||||
|
numSubs--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return numSubs;
|
||||||
|
}
|
||||||
|
|
||||||
namespace render {
|
namespace render {
|
||||||
template <> const ItemKey payloadGetKey(const PayloadProxyInterface::Pointer& payload) {
|
template <> const ItemKey payloadGetKey(const PayloadProxyInterface::Pointer& payload) {
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
|
|
|
@ -78,6 +78,8 @@ public:
|
||||||
INVISIBLE, // Visible or not in the scene?
|
INVISIBLE, // Visible or not in the scene?
|
||||||
SHADOW_CASTER, // Item cast shadows
|
SHADOW_CASTER, // Item cast shadows
|
||||||
LAYERED, // Item belongs to one of the layers different from the default layer
|
LAYERED, // Item belongs to one of the layers different from the default layer
|
||||||
|
META_CULL_GROUP, // As a meta item, the culling of my sub items is based solely on my bounding box and my visibility in the view
|
||||||
|
SUB_META_CULLED, // As a sub item of a meta render item set as cull group, need to be set to my culling to the meta render it
|
||||||
|
|
||||||
FIRST_TAG_BIT, // 8 Tags available to organize the items and filter them against
|
FIRST_TAG_BIT, // 8 Tags available to organize the items and filter them against
|
||||||
LAST_TAG_BIT = FIRST_TAG_BIT + NUM_TAGS,
|
LAST_TAG_BIT = FIRST_TAG_BIT + NUM_TAGS,
|
||||||
|
@ -122,6 +124,8 @@ public:
|
||||||
Builder& withInvisible() { _flags.set(INVISIBLE); return (*this); }
|
Builder& withInvisible() { _flags.set(INVISIBLE); return (*this); }
|
||||||
Builder& withShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); }
|
Builder& withShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); }
|
||||||
Builder& withLayered() { _flags.set(LAYERED); return (*this); }
|
Builder& withLayered() { _flags.set(LAYERED); return (*this); }
|
||||||
|
Builder& withMetaCullGroup() { _flags.set(META_CULL_GROUP); return (*this); }
|
||||||
|
Builder& withSubMetaCulled() { _flags.set(SUB_META_CULLED); return (*this); }
|
||||||
|
|
||||||
Builder& withTag(Tag tag) { _flags.set(FIRST_TAG_BIT + tag); return (*this); }
|
Builder& withTag(Tag tag) { _flags.set(FIRST_TAG_BIT + tag); return (*this); }
|
||||||
// Set ALL the tags in one call using the Tag bits
|
// Set ALL the tags in one call using the Tag bits
|
||||||
|
@ -159,6 +163,12 @@ public:
|
||||||
bool isLayered() const { return _flags[LAYERED]; }
|
bool isLayered() const { return _flags[LAYERED]; }
|
||||||
bool isSpatial() const { return !isLayered(); }
|
bool isSpatial() const { return !isLayered(); }
|
||||||
|
|
||||||
|
bool isMetaCullGroup() const { return _flags[META_CULL_GROUP]; }
|
||||||
|
void setMetaCullGroup(bool cullGroup) { (cullGroup ? _flags.set(META_CULL_GROUP) : _flags.reset(META_CULL_GROUP)); }
|
||||||
|
|
||||||
|
bool isSubMetaCulled() const { return _flags[SUB_META_CULLED]; }
|
||||||
|
void setSubMetaCulled(bool metaCulled) { (metaCulled ? _flags.set(SUB_META_CULLED) : _flags.reset(SUB_META_CULLED)); }
|
||||||
|
|
||||||
bool isTag(Tag tag) const { return _flags[FIRST_TAG_BIT + tag]; }
|
bool isTag(Tag tag) const { return _flags[FIRST_TAG_BIT + tag]; }
|
||||||
uint8_t getTagBits() const { return ((_flags.to_ulong() & KEY_TAG_BITS_MASK) >> FIRST_TAG_BIT); }
|
uint8_t getTagBits() const { return ((_flags.to_ulong() & KEY_TAG_BITS_MASK) >> FIRST_TAG_BIT); }
|
||||||
|
|
||||||
|
@ -193,6 +203,7 @@ public:
|
||||||
ItemKey::Flags _mask{ 0 };
|
ItemKey::Flags _mask{ 0 };
|
||||||
public:
|
public:
|
||||||
Builder() {}
|
Builder() {}
|
||||||
|
Builder(const ItemFilter& srcFilter) : _value(srcFilter._value), _mask(srcFilter._mask) {}
|
||||||
|
|
||||||
ItemFilter build() const { return ItemFilter(_value, _mask); }
|
ItemFilter build() const { return ItemFilter(_value, _mask); }
|
||||||
|
|
||||||
|
@ -221,6 +232,12 @@ public:
|
||||||
Builder& withoutLayered() { _value.reset(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); }
|
Builder& withoutLayered() { _value.reset(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); }
|
||||||
Builder& withLayered() { _value.set(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); }
|
Builder& withLayered() { _value.set(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); }
|
||||||
|
|
||||||
|
Builder& withoutMetaCullGroup() { _value.reset(ItemKey::META_CULL_GROUP); _mask.set(ItemKey::META_CULL_GROUP); return (*this); }
|
||||||
|
Builder& withMetaCullGroup() { _value.set(ItemKey::META_CULL_GROUP); _mask.set(ItemKey::META_CULL_GROUP); return (*this); }
|
||||||
|
|
||||||
|
Builder& withoutSubMetaCulled() { _value.reset(ItemKey::SUB_META_CULLED); _mask.set(ItemKey::SUB_META_CULLED); return (*this); }
|
||||||
|
Builder& withSubMetaCulled() { _value.set(ItemKey::SUB_META_CULLED); _mask.set(ItemKey::SUB_META_CULLED); return (*this); }
|
||||||
|
|
||||||
Builder& withoutTag(ItemKey::Tag tagIndex) { _value.reset(ItemKey::FIRST_TAG_BIT + tagIndex); _mask.set(ItemKey::FIRST_TAG_BIT + tagIndex); return (*this); }
|
Builder& withoutTag(ItemKey::Tag tagIndex) { _value.reset(ItemKey::FIRST_TAG_BIT + tagIndex); _mask.set(ItemKey::FIRST_TAG_BIT + tagIndex); return (*this); }
|
||||||
Builder& withTag(ItemKey::Tag tagIndex) { _value.set(ItemKey::FIRST_TAG_BIT + tagIndex); _mask.set(ItemKey::FIRST_TAG_BIT + tagIndex); return (*this); }
|
Builder& withTag(ItemKey::Tag tagIndex) { _value.set(ItemKey::FIRST_TAG_BIT + tagIndex); _mask.set(ItemKey::FIRST_TAG_BIT + tagIndex); return (*this); }
|
||||||
// Set ALL the tags in one call using the Tag bits and the Tag bits touched
|
// Set ALL the tags in one call using the Tag bits and the Tag bits touched
|
||||||
|
@ -420,6 +437,7 @@ public:
|
||||||
|
|
||||||
// Meta Type Interface
|
// Meta Type Interface
|
||||||
uint32_t fetchMetaSubItems(ItemIDs& subItems) const { return _payload->fetchMetaSubItems(subItems); }
|
uint32_t fetchMetaSubItems(ItemIDs& subItems) const { return _payload->fetchMetaSubItems(subItems); }
|
||||||
|
uint32_t fetchMetaSubItemBounds(ItemBounds& subItemBounds, Scene& scene) const;
|
||||||
|
|
||||||
// Access the status
|
// Access the status
|
||||||
const StatusPointer& getStatus() const { return _payload->getStatus(); }
|
const StatusPointer& getStatus() const { return _payload->getStatus(); }
|
||||||
|
|
|
@ -30,7 +30,7 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin
|
||||||
const auto culledSpatialSelection = task.addJob<CullSpatialSelection>("CullSceneSelection", cullInputs, cullFunctor, RenderDetails::ITEM);
|
const auto culledSpatialSelection = task.addJob<CullSpatialSelection>("CullSceneSelection", cullInputs, cullFunctor, RenderDetails::ITEM);
|
||||||
|
|
||||||
// Overlays are not culled
|
// Overlays are not culled
|
||||||
const ItemFilter overlayfilter = ItemFilter::Builder().withVisible().withTagBits(tagBits, tagMask);
|
const ItemFilter overlayfilter = ItemFilter::Builder().withVisible().withoutSubMetaCulled().withTagBits(tagBits, tagMask);
|
||||||
const auto nonspatialFilter = render::Varying(overlayfilter);
|
const auto nonspatialFilter = render::Varying(overlayfilter);
|
||||||
const auto nonspatialSelection = task.addJob<FetchNonspatialItems>("FetchOverlaySelection", nonspatialFilter);
|
const auto nonspatialSelection = task.addJob<FetchNonspatialItems>("FetchOverlaySelection", nonspatialFilter);
|
||||||
|
|
||||||
|
|
|
@ -798,10 +798,13 @@ function loaded() {
|
||||||
|
|
||||||
// HTML workaround since image is not yet a separate entity type
|
// HTML workaround since image is not yet a separate entity type
|
||||||
var IMAGE_MODEL_NAME = 'default-image-model.fbx';
|
var IMAGE_MODEL_NAME = 'default-image-model.fbx';
|
||||||
var urlParts = properties.modelURL.split('/')
|
if (properties.type === "Model") {
|
||||||
var propsFilename = urlParts[urlParts.length - 1];
|
var urlParts = properties.modelURL.split('/');
|
||||||
if (properties.type === "Model" && propsFilename === IMAGE_MODEL_NAME) {
|
var propsFilename = urlParts[urlParts.length - 1];
|
||||||
properties.type = "Image";
|
|
||||||
|
if (propsFilename === IMAGE_MODEL_NAME) {
|
||||||
|
properties.type = "Image";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create class name for css ruleset filtering
|
// Create class name for css ruleset filtering
|
||||||
|
|
|
@ -47,7 +47,7 @@ function calcSpawnInfo(hand, landscape) {
|
||||||
var headPos = (HMD.active && Camera.mode === "first person") ? HMD.position : Camera.position;
|
var headPos = (HMD.active && Camera.mode === "first person") ? HMD.position : Camera.position;
|
||||||
var headRot = (HMD.active && Camera.mode === "first person") ? HMD.orientation : Camera.orientation;
|
var headRot = (HMD.active && Camera.mode === "first person") ? HMD.orientation : Camera.orientation;
|
||||||
|
|
||||||
var forward = Quat.getForward(headRot);
|
var forward = Quat.getForward(Quat.cancelOutRollAndPitch(headRot));
|
||||||
var FORWARD_OFFSET = 0.5 * MyAvatar.sensorToWorldScale;
|
var FORWARD_OFFSET = 0.5 * MyAvatar.sensorToWorldScale;
|
||||||
finalPosition = Vec3.sum(headPos, Vec3.multiply(FORWARD_OFFSET, forward));
|
finalPosition = Vec3.sum(headPos, Vec3.multiply(FORWARD_OFFSET, forward));
|
||||||
var orientation = Quat.lookAt({x: 0, y: 0, z: 0}, forward, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.UNIT_Y));
|
var orientation = Quat.lookAt({x: 0, y: 0, z: 0}, forward, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.UNIT_Y));
|
||||||
|
@ -269,8 +269,9 @@ WebTablet.prototype.setLandscape = function(newLandscapeValue) {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.landscape = newLandscapeValue;
|
this.landscape = newLandscapeValue;
|
||||||
|
var cameraOrientation = Quat.cancelOutRollAndPitch(Camera.orientation);
|
||||||
Overlays.editOverlay(this.tabletEntityID,
|
Overlays.editOverlay(this.tabletEntityID,
|
||||||
{ rotation: Quat.multiply(Camera.orientation, this.landscape ? ROT_LANDSCAPE : ROT_Y_180) });
|
{ rotation: Quat.multiply(cameraOrientation, this.landscape ? ROT_LANDSCAPE : ROT_Y_180) });
|
||||||
|
|
||||||
var tabletWidth = getTabletWidthFromSettings() * MyAvatar.sensorToWorldScale;
|
var tabletWidth = getTabletWidthFromSettings() * MyAvatar.sensorToWorldScale;
|
||||||
var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x;
|
var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x;
|
||||||
|
@ -278,7 +279,7 @@ WebTablet.prototype.setLandscape = function(newLandscapeValue) {
|
||||||
var screenWidth = 0.82 * tabletWidth;
|
var screenWidth = 0.82 * tabletWidth;
|
||||||
var screenHeight = 0.81 * tabletHeight;
|
var screenHeight = 0.81 * tabletHeight;
|
||||||
Overlays.editOverlay(this.webOverlayID, {
|
Overlays.editOverlay(this.webOverlayID, {
|
||||||
rotation: Quat.multiply(Camera.orientation, ROT_LANDSCAPE_WINDOW),
|
rotation: Quat.multiply(cameraOrientation, ROT_LANDSCAPE_WINDOW),
|
||||||
dimensions: {x: this.landscape ? screenHeight : screenWidth, y: this.landscape ? screenWidth : screenHeight, z: 0.1}
|
dimensions: {x: this.landscape ? screenHeight : screenWidth, y: this.landscape ? screenWidth : screenHeight, z: 0.1}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
1288
server-console/package-lock.json
generated
1288
server-console/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -8,7 +8,7 @@
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron-packager": "^6.0.2",
|
"electron-packager": "^11.0.0",
|
||||||
"electron-prebuilt": "0.37.5"
|
"electron-prebuilt": "0.37.5"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
"cheerio": "^0.19.0",
|
"cheerio": "^0.19.0",
|
||||||
"extend": "^3.0.0",
|
"extend": "^3.0.0",
|
||||||
"fs-extra": "^0.26.4",
|
"fs-extra": "^0.26.4",
|
||||||
"node-notifier": "^4.4.0",
|
"node-notifier": "^5.2.1",
|
||||||
"os-homedir": "^1.0.1",
|
"os-homedir": "^1.0.1",
|
||||||
"request": "^2.67.0",
|
"request": "^2.67.0",
|
||||||
"request-progress": "1.0.2",
|
"request-progress": "1.0.2",
|
||||||
|
|
Loading…
Reference in a new issue