mirror of
https://github.com/lubosz/overte.git
synced 2025-04-23 09:25:31 +02:00
merge from master
This commit is contained in:
commit
b6e73e06a2
73 changed files with 3053 additions and 937 deletions
|
@ -113,7 +113,7 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
|||
avatarAudioStream->setupCodec(_codec, _selectedCodecName, AudioConstants::MONO);
|
||||
qDebug() << "creating new AvatarAudioStream... codec:" << _selectedCodecName;
|
||||
|
||||
connect(avatarAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioMixerClientData::sendSelectAudioFormat);
|
||||
connect(avatarAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioMixerClientData::handleMismatchAudioFormat);
|
||||
|
||||
auto emplaced = _audioStreams.emplace(
|
||||
QUuid(),
|
||||
|
@ -345,6 +345,11 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() {
|
|||
return result;
|
||||
}
|
||||
|
||||
void AudioMixerClientData::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) {
|
||||
qDebug() << __FUNCTION__ << "sendingNode:" << *node << "currentCodec:" << currentCodec << "recievedCodec:" << recievedCodec;
|
||||
sendSelectAudioFormat(node, currentCodec);
|
||||
}
|
||||
|
||||
void AudioMixerClientData::sendSelectAudioFormat(SharedNodePointer node, const QString& selectedCodecName) {
|
||||
auto replyPacket = NLPacket::create(PacketType::SelectedAudioFormat);
|
||||
replyPacket->writeString(selectedCodecName);
|
||||
|
|
|
@ -84,6 +84,7 @@ signals:
|
|||
void injectorStreamFinished(const QUuid& streamIdentifier);
|
||||
|
||||
public slots:
|
||||
void handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec);
|
||||
void sendSelectAudioFormat(SharedNodePointer node, const QString& selectedCodecName);
|
||||
|
||||
private:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": 1.5,
|
||||
"version": 1.7,
|
||||
"settings": [
|
||||
{
|
||||
"name": "metaverse",
|
||||
|
@ -384,18 +384,18 @@
|
|||
"name": "standard_permissions",
|
||||
"type": "table",
|
||||
"label": "Domain-Wide User Permissions",
|
||||
"help": "Indicate which users or groups can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
|
||||
"help": "Indicate which types of users can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
|
||||
"caption": "Standard Permissions",
|
||||
"can_add_new_rows": false,
|
||||
|
||||
"groups": [
|
||||
{
|
||||
"label": "User / Group",
|
||||
"label": "Type of User",
|
||||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
||||
"span": 6
|
||||
"span": 7
|
||||
}
|
||||
],
|
||||
|
||||
|
@ -409,7 +409,7 @@
|
|||
"label": "Connect",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": true
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_adjust_locks",
|
||||
|
@ -445,6 +445,13 @@
|
|||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_kick",
|
||||
"label": "Kick Users",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
],
|
||||
|
||||
|
@ -452,33 +459,62 @@
|
|||
"non-deletable-row-values": ["localhost", "anonymous", "logged-in"]
|
||||
},
|
||||
{
|
||||
"name": "permissions",
|
||||
"name": "group_permissions",
|
||||
"type": "table",
|
||||
"caption": "Permissions for Specific Users",
|
||||
"can_add_new_rows": true,
|
||||
"caption": "Permissions for Users in Groups",
|
||||
"categorize_by_key": "permissions_id",
|
||||
"can_add_new_categories": true,
|
||||
"can_add_new_rows": false,
|
||||
"new_category_placeholder": "Add Group",
|
||||
"new_category_message": "Save and reload to see ranks",
|
||||
|
||||
"groups": [
|
||||
{
|
||||
"label": "User / Group",
|
||||
"label": "Rank",
|
||||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
||||
"span": 6
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in, as well as permissions from the previous section. Group permissions are only granted if the user doesn’t have their own row in the per-account section, below.</p>'>?</a>",
|
||||
"span": 7
|
||||
}
|
||||
],
|
||||
|
||||
"columns": [
|
||||
{
|
||||
"name": "permissions_id",
|
||||
"label": ""
|
||||
"label": "Group Name",
|
||||
"readonly": true,
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rank_id",
|
||||
"label": "Rank ID",
|
||||
"readonly": true,
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rank_order",
|
||||
"label": "Rank Order",
|
||||
"readonly": true,
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rank_name",
|
||||
"label": "",
|
||||
"readonly": true
|
||||
},
|
||||
{
|
||||
"name": "group_id",
|
||||
"label": "Group ID",
|
||||
"readonly": true,
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect",
|
||||
"label": "Connect",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": true
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_adjust_locks",
|
||||
|
@ -514,6 +550,257 @@
|
|||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_kick",
|
||||
"label": "Kick Users",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "group_forbiddens",
|
||||
"type": "table",
|
||||
"caption": "Permissions Denied to Users in Groups",
|
||||
"categorize_by_key": "permissions_id",
|
||||
"can_add_new_categories": true,
|
||||
"can_add_new_rows": false,
|
||||
"new_category_placeholder": "Add Blacklist Group",
|
||||
"new_category_message": "Save and reload to see ranks",
|
||||
|
||||
"groups": [
|
||||
{
|
||||
"label": "Rank",
|
||||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in. Group permissions are only granted if the user doesn’t have their own row in the per-account section, below.</p>'>?</a>",
|
||||
"span": 7
|
||||
}
|
||||
],
|
||||
|
||||
"columns": [
|
||||
{
|
||||
"name": "permissions_id",
|
||||
"label": "Group Name",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rank_id",
|
||||
"label": "Rank ID",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rank_order",
|
||||
"label": "Rank Order",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rank_name",
|
||||
"label": "",
|
||||
"readonly": true
|
||||
},
|
||||
{
|
||||
"name": "group_id",
|
||||
"label": "Group ID",
|
||||
"readonly": true,
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect",
|
||||
"label": "Connect",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_adjust_locks",
|
||||
"label": "Lock / Unlock",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez",
|
||||
"label": "Rez",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_tmp",
|
||||
"label": "Rez Temporary",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_write_to_asset_server",
|
||||
"label": "Write Assets",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect_past_max_capacity",
|
||||
"label": "Ignore Max Capacity",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_kick",
|
||||
"label": "Kick Users",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ip_permissions",
|
||||
"type": "table",
|
||||
"caption": "Permissions for Users from IP Addresses",
|
||||
"can_add_new_rows": true,
|
||||
"groups": [
|
||||
{
|
||||
"label": "IP Address",
|
||||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide IP Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users from specific IPs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific IPs can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users from specific IPs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users from specific IPs can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users from specific IPs can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users from specific IPs can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific IP will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). IP address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
|
||||
"span": 7
|
||||
}
|
||||
],
|
||||
|
||||
"columns": [
|
||||
{
|
||||
"name": "permissions_id",
|
||||
"label": ""
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect",
|
||||
"label": "Connect",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_adjust_locks",
|
||||
"label": "Lock / Unlock",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez",
|
||||
"label": "Rez",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_tmp",
|
||||
"label": "Rez Temporary",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_write_to_asset_server",
|
||||
"label": "Write Assets",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect_past_max_capacity",
|
||||
"label": "Ignore Max Capacity",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_kick",
|
||||
"label": "Kick Users",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "permissions",
|
||||
"type": "table",
|
||||
"caption": "Permissions for Specific Users",
|
||||
"can_add_new_rows": true,
|
||||
|
||||
"groups": [
|
||||
{
|
||||
"label": "User",
|
||||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>",
|
||||
"span": 7
|
||||
}
|
||||
],
|
||||
|
||||
"columns": [
|
||||
{
|
||||
"name": "permissions_id",
|
||||
"label": ""
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect",
|
||||
"label": "Connect",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_adjust_locks",
|
||||
"label": "Lock / Unlock",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez",
|
||||
"label": "Rez",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_tmp",
|
||||
"label": "Rez Temporary",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_write_to_asset_server",
|
||||
"label": "Write Assets",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect_past_max_capacity",
|
||||
"label": "Ignore Max Capacity",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_kick",
|
||||
"label": "Kick Users",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -3,6 +3,10 @@ body {
|
|||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.table-lead .lead-line {
|
||||
background-color: black;
|
||||
}
|
||||
|
@ -20,7 +24,9 @@ body {
|
|||
top: 40px;
|
||||
}
|
||||
|
||||
.table .value-row td, .table .inputs td {
|
||||
.table .value-row td,
|
||||
.table .value-category td,
|
||||
.table .inputs td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
@ -31,6 +37,31 @@ body {
|
|||
margin-right: auto;
|
||||
}
|
||||
|
||||
.value-category:not(.inputs) {
|
||||
font-weight: bold;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.table .value-category [message]::after {
|
||||
content: attr(message);
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.table .value-row.contracted,
|
||||
.table .inputs.contracted {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toggle-category {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toggle-category-icon {
|
||||
padding: 4px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.glyphicon-remove {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
@ -44,15 +75,6 @@ span.port {
|
|||
color: #666666;
|
||||
}
|
||||
|
||||
.locked {
|
||||
color: #428bca;
|
||||
}
|
||||
|
||||
.locked-table {
|
||||
cursor: not-allowed;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.advanced-setting {
|
||||
display: none;
|
||||
}
|
||||
|
@ -133,7 +155,7 @@ table .headers + .headers td {
|
|||
color: #222;
|
||||
}
|
||||
|
||||
table[name="security.standard_permissions"] .headers td + td, table[name="security.permissions"] .headers td + td {
|
||||
#security table .headers td + td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
|
|
@ -57,15 +57,13 @@
|
|||
<div class="panel-body">
|
||||
<% _.each(split_settings[0], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, false,
|
||||
(_.has(locked, group.name) && _.has(locked[group.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,
|
||||
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
|
||||
<%= getFormGroup(keypath, setting, values, true) %>
|
||||
<% }); %>
|
||||
<% }%>
|
||||
</div>
|
||||
|
|
|
@ -5,10 +5,20 @@ var Settings = {
|
|||
TRIGGER_CHANGE_CLASS: 'trigger-change',
|
||||
DATA_ROW_CLASS: 'value-row',
|
||||
DATA_COL_CLASS: 'value-col',
|
||||
DATA_CATEGORY_CLASS: 'value-category',
|
||||
ADD_ROW_BUTTON_CLASS: 'add-row',
|
||||
ADD_ROW_SPAN_CLASSES: 'glyphicon glyphicon-plus add-row',
|
||||
DEL_ROW_BUTTON_CLASS: 'del-row',
|
||||
DEL_ROW_SPAN_CLASSES: 'glyphicon glyphicon-remove del-row',
|
||||
ADD_CATEGORY_BUTTON_CLASS: 'add-category',
|
||||
ADD_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-plus add-category',
|
||||
TOGGLE_CATEGORY_COLUMN_CLASS: 'toggle-category',
|
||||
TOGGLE_CATEGORY_SPAN_CLASS: 'toggle-category-icon',
|
||||
TOGGLE_CATEGORY_SPAN_CLASSES: 'glyphicon toggle-category-icon',
|
||||
TOGGLE_CATEGORY_EXPANDED_CLASS: 'glyphicon-triangle-bottom',
|
||||
TOGGLE_CATEGORY_CONTRACTED_CLASS: 'glyphicon-triangle-right',
|
||||
DEL_CATEGORY_BUTTON_CLASS: 'del-category',
|
||||
DEL_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-remove del-category',
|
||||
MOVE_UP_BUTTON_CLASS: 'move-up',
|
||||
MOVE_UP_SPAN_CLASSES: 'glyphicon glyphicon-chevron-up move-up',
|
||||
MOVE_DOWN_BUTTON_CLASS: 'move-down',
|
||||
|
@ -31,11 +41,11 @@ var Settings = {
|
|||
};
|
||||
|
||||
var viewHelpers = {
|
||||
getFormGroup: function(keypath, setting, values, isAdvanced, isLocked) {
|
||||
getFormGroup: function(keypath, setting, values, isAdvanced) {
|
||||
form_group = "<div class='form-group " + (isAdvanced ? Settings.ADVANCED_CLASS : "") + "' data-keypath='" + keypath + "'>";
|
||||
setting_value = _(values).valueForKeyPath(keypath);
|
||||
|
||||
if (typeof setting_value == 'undefined' || setting_value === null) {
|
||||
if (_.isUndefined(setting_value) || _.isNull(setting_value)) {
|
||||
if (_.has(setting, 'default')) {
|
||||
setting_value = setting.default;
|
||||
} else {
|
||||
|
@ -44,16 +54,13 @@ var viewHelpers = {
|
|||
}
|
||||
|
||||
label_class = 'control-label';
|
||||
if (isLocked) {
|
||||
label_class += ' locked';
|
||||
}
|
||||
|
||||
function common_attrs(extra_classes) {
|
||||
extra_classes = (typeof extra_classes !== 'undefined' ? extra_classes : "");
|
||||
extra_classes = (!_.isUndefined(extra_classes) ? extra_classes : "");
|
||||
return " class='" + (setting.type !== 'checkbox' ? 'form-control' : '')
|
||||
+ " " + Settings.TRIGGER_CHANGE_CLASS + " " + extra_classes + "' data-short-name='"
|
||||
+ setting.name + "' name='" + keypath + "' "
|
||||
+ "id='" + (typeof setting.html_id !== 'undefined' ? setting.html_id : keypath) + "'";
|
||||
+ "id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "'";
|
||||
}
|
||||
|
||||
if (setting.type === 'checkbox') {
|
||||
|
@ -61,9 +68,8 @@ var viewHelpers = {
|
|||
form_group += "<label class='" + label_class + "'>" + setting.label + "</label>"
|
||||
}
|
||||
|
||||
form_group += "<div class='toggle-checkbox-container" + (isLocked ? " disabled" : "") + "'>"
|
||||
form_group += "<input type='checkbox'" + common_attrs('toggle-checkbox') + (setting_value ? "checked" : "")
|
||||
form_group += (isLocked ? " disabled" : "") + "/>"
|
||||
form_group += "<div class='toggle-checkbox-container'>"
|
||||
form_group += "<input type='checkbox'" + common_attrs('toggle-checkbox') + (setting_value ? "checked" : "") + "/>"
|
||||
|
||||
if (setting.help) {
|
||||
form_group += "<span class='help-block checkbox-help'>" + setting.help + "</span>";
|
||||
|
@ -78,7 +84,7 @@ var viewHelpers = {
|
|||
}
|
||||
|
||||
if (input_type === 'table') {
|
||||
form_group += makeTable(setting, keypath, setting_value, isLocked)
|
||||
form_group += makeTable(setting, keypath, setting_value)
|
||||
} else {
|
||||
if (input_type === 'select') {
|
||||
form_group += "<select class='form-control' data-hidden-input='" + keypath + "'>'"
|
||||
|
@ -97,12 +103,10 @@ var viewHelpers = {
|
|||
|
||||
if (setting.href) {
|
||||
form_group += "<a href='" + setting.href + "'style='display: block;' role='button'"
|
||||
+ (isLocked ? " disabled" : "")
|
||||
+ common_attrs("btn " + setting.classes) + " target='_blank'>"
|
||||
+ setting.button_label + "</a>";
|
||||
} else {
|
||||
form_group += "<button " + common_attrs("btn " + setting.classes)
|
||||
+ (isLocked ? " disabled" : "") + ">"
|
||||
form_group += "<button " + common_attrs("btn " + setting.classes) + ">"
|
||||
+ setting.button_label + "</button>";
|
||||
}
|
||||
|
||||
|
@ -114,7 +118,7 @@ var viewHelpers = {
|
|||
|
||||
form_group += "<input type='" + input_type + "'" + common_attrs() +
|
||||
"placeholder='" + (_.has(setting, 'placeholder') ? setting.placeholder : "") +
|
||||
"' value='" + setting_value + "'" + (isLocked ? " disabled" : "") + "/>"
|
||||
"' value='" + setting_value + "'/>"
|
||||
}
|
||||
|
||||
form_group += "<span class='help-block'>" + setting.help + "</span>"
|
||||
|
@ -162,19 +166,31 @@ $(document).ready(function(){
|
|||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_ROW_BUTTON_CLASS, function(){
|
||||
addTableRow(this);
|
||||
addTableRow($(this).closest('tr'));
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){
|
||||
deleteTableRow(this);
|
||||
deleteTableRow($(this).closest('tr'));
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_CATEGORY_BUTTON_CLASS, function(){
|
||||
addTableCategory($(this).closest('tr'));
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_CATEGORY_BUTTON_CLASS, function(){
|
||||
deleteTableCategory($(this).closest('tr'));
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.TOGGLE_CATEGORY_COLUMN_CLASS, function(){
|
||||
toggleTableCategory($(this).closest('tr'));
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_UP_BUTTON_CLASS, function(){
|
||||
moveTableRow(this, true);
|
||||
moveTableRow($(this).closest('tr'), true);
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_DOWN_BUTTON_CLASS, function(){
|
||||
moveTableRow(this, false);
|
||||
moveTableRow($(this).closest('tr'), false);
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('keyup', function(e){
|
||||
|
@ -196,10 +212,11 @@ $(document).ready(function(){
|
|||
}
|
||||
|
||||
if (sibling.hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) {
|
||||
sibling.find('.' + Settings.ADD_ROW_BUTTON_CLASS).click()
|
||||
sibling.find('.' + Settings.ADD_ROW_BUTTON_CLASS).click();
|
||||
sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).click();
|
||||
|
||||
// set focus to the first input in the new row
|
||||
$target.closest('table').find('tr.inputs input:first').focus()
|
||||
$target.closest('table').find('tr.inputs input:first').focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -436,10 +453,8 @@ function setupHFAccountButton() {
|
|||
$("[data-keypath='metaverse.automatic_networking']").hide();
|
||||
}
|
||||
|
||||
var tokenLocked = _(Settings.data).valueForKeyPath("locked.metaverse.access_token");
|
||||
|
||||
// use the existing getFormGroup helper to ask for a button
|
||||
var buttonGroup = viewHelpers.getFormGroup('', buttonSetting, Settings.data.values, false, tokenLocked);
|
||||
var buttonGroup = viewHelpers.getFormGroup('', buttonSetting, Settings.data.values, false);
|
||||
|
||||
// add the button group to the top of the metaverse panel
|
||||
$('#metaverse .panel-body').prepend(buttonGroup);
|
||||
|
@ -629,7 +644,7 @@ function setupPlacesTable() {
|
|||
label: 'Places',
|
||||
html_id: Settings.PLACES_TABLE_ID,
|
||||
help: "The following places currently point to this domain.</br>To point places to this domain, "
|
||||
+ " go to the <a href='https://metaverse.highfidelity.com/user/places'>My Places</a> "
|
||||
+ " go to the <a href='" + Settings.METAVERSE_URL + "/user/places'>My Places</a> "
|
||||
+ "page in your High Fidelity Metaverse account.",
|
||||
read_only: true,
|
||||
columns: [
|
||||
|
@ -650,7 +665,7 @@ function setupPlacesTable() {
|
|||
}
|
||||
|
||||
// get a table for the places
|
||||
var placesTableGroup = viewHelpers.getFormGroup('', placesTableSetting, Settings.data.values, false, false);
|
||||
var placesTableGroup = viewHelpers.getFormGroup('', placesTableSetting, Settings.data.values, false);
|
||||
|
||||
// append the places table in the right place
|
||||
$('#places_paths .panel-body').prepend(placesTableGroup);
|
||||
|
@ -771,7 +786,7 @@ function chooseFromHighFidelityDomains(clickedButton) {
|
|||
modal_buttons["success"] = {
|
||||
label: 'Create new domain',
|
||||
callback: function() {
|
||||
window.open("https://metaverse.highfidelity.com/user/domains", '_blank');
|
||||
window.open(Settings.METAVERSE_URL + "/user/domains", '_blank');
|
||||
}
|
||||
}
|
||||
modal_body = "<p>You do not have any domains in your High Fidelity account." +
|
||||
|
@ -850,10 +865,8 @@ function reloadSettings(callback) {
|
|||
Settings.data = data;
|
||||
Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true);
|
||||
|
||||
if (!_.has(data["locked"], "metaverse") && !_.has(data["locked"]["metaverse"], "id")) {
|
||||
// append the domain selection modal, as long as it's not locked
|
||||
appendDomainIDButtons();
|
||||
}
|
||||
// append the domain selection modal
|
||||
appendDomainIDButtons();
|
||||
|
||||
// call our method to setup the HF account button
|
||||
setupHFAccountButton();
|
||||
|
@ -866,12 +879,6 @@ function reloadSettings(callback) {
|
|||
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
|
||||
// add tooltip to locked settings
|
||||
$('label.locked').tooltip({
|
||||
placement: 'right',
|
||||
title: 'This setting is in the master config file and cannot be changed'
|
||||
});
|
||||
|
||||
// call the callback now that settings are loaded
|
||||
callback(true);
|
||||
}).fail(function() {
|
||||
|
@ -920,9 +927,10 @@ $('body').on('click', '.save-button', function(e){
|
|||
return false;
|
||||
});
|
||||
|
||||
function makeTable(setting, keypath, setting_value, isLocked) {
|
||||
function makeTable(setting, keypath, setting_value) {
|
||||
var isArray = !_.has(setting, 'key');
|
||||
var isHash = !isArray;
|
||||
var categoryKey = setting.categorize_by_key;
|
||||
var isCategorized = !!categoryKey && isArray;
|
||||
|
||||
if (!isArray && setting.can_order) {
|
||||
setting.can_order = false;
|
||||
|
@ -937,9 +945,10 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
var nonDeletableRowKey = setting["non-deletable-row-key"];
|
||||
var nonDeletableRowValues = setting["non-deletable-row-values"];
|
||||
|
||||
html += "<table class='table table-bordered " + (isLocked ? "locked-table" : "") + "' data-short-name='" + setting.name
|
||||
+ "' name='" + keypath + "' id='" + (typeof setting.html_id !== 'undefined' ? setting.html_id : keypath)
|
||||
+ "' data-setting-type='" + (isArray ? 'array' : 'hash') + "'>";
|
||||
html += "<table class='table table-bordered' " +
|
||||
"data-short-name='" + setting.name + "' name='" + keypath + "' " +
|
||||
"id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "' " +
|
||||
"data-setting-type='" + (isArray ? 'array' : 'hash') + "'>";
|
||||
|
||||
if (setting.caption) {
|
||||
html += "<caption>" + setting.caption + "</caption>"
|
||||
|
@ -951,7 +960,7 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
_.each(setting.groups, function (group) {
|
||||
html += "<td colspan='" + group.span + "'><strong>" + group.label + "</strong></td>"
|
||||
})
|
||||
if (!isLocked && !setting.read_only) {
|
||||
if (!setting.read_only) {
|
||||
if (setting.can_order) {
|
||||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES +
|
||||
"'><a href='javascript:void(0);' class='glyphicon glyphicon-sort'></a></td>";
|
||||
|
@ -972,24 +981,43 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
html += "<td class='key'><strong>" + setting.key.label + "</strong></td>" // Key
|
||||
}
|
||||
|
||||
var numVisibleColumns = 0;
|
||||
_.each(setting.columns, function(col) {
|
||||
html += "<td class='data " + (col.class ? col.class : '') + "'><strong>" + col.label + "</strong></td>" // Data
|
||||
if (!col.hidden) numVisibleColumns++;
|
||||
html += "<td " + (col.hidden ? "style='display: none;'" : "") + "class='data " +
|
||||
(col.class ? col.class : '') + "'><strong>" + col.label + "</strong></td>" // Data
|
||||
})
|
||||
|
||||
if (!isLocked && !setting.read_only) {
|
||||
if (!setting.read_only) {
|
||||
if (setting.can_order) {
|
||||
numVisibleColumns++;
|
||||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES +
|
||||
"'><a href='javascript:void(0);' class='glyphicon glyphicon-sort'></a></td>";
|
||||
}
|
||||
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'></td></tr>"
|
||||
numVisibleColumns++;
|
||||
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'></td></tr>";
|
||||
}
|
||||
|
||||
// populate rows in the table from existing values
|
||||
var row_num = 1;
|
||||
|
||||
if (keypath.length > 0 && _.size(setting_value) > 0) {
|
||||
var rowIsObject = setting.columns.length > 1;
|
||||
|
||||
_.each(setting_value, function(row, rowIndexOrName) {
|
||||
html += "<tr class='" + Settings.DATA_ROW_CLASS + "'" + (isArray ? "" : "name='" + keypath + "." + rowIndexOrName + "'") + ">"
|
||||
var categoryPair = {};
|
||||
var categoryValue = "";
|
||||
if (isCategorized) {
|
||||
categoryValue = rowIsObject ? row[categoryKey] : row;
|
||||
categoryPair[categoryKey] = categoryValue;
|
||||
if (_.findIndex(setting_value, categoryPair) === rowIndexOrName) {
|
||||
html += makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, setting.can_add_new_categories, "");
|
||||
}
|
||||
}
|
||||
|
||||
html += "<tr class='" + Settings.DATA_ROW_CLASS + "' " +
|
||||
(isCategorized ? ("data-category='" + categoryValue + "'") : "") + " " +
|
||||
(isArray ? "" : "name='" + keypath + "." + rowIndexOrName + "'") + ">";
|
||||
|
||||
if (setting.numbered === true) {
|
||||
html += "<td class='numbered'>" + row_num + "</td>"
|
||||
|
@ -1003,8 +1031,8 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
|
||||
_.each(setting.columns, function(col) {
|
||||
|
||||
var colValue, colName;
|
||||
if (isArray) {
|
||||
rowIsObject = setting.columns.length > 1;
|
||||
colValue = rowIsObject ? row[col.name] : row;
|
||||
colName = keypath + "[" + rowIndexOrName + "]" + (rowIsObject ? "." + col.name : "");
|
||||
} else {
|
||||
|
@ -1016,22 +1044,30 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
|| (nonDeletableRowKey === col.name && nonDeletableRowValues.indexOf(colValue) !== -1);
|
||||
|
||||
if (isArray && col.type === "checkbox" && col.editable) {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>"
|
||||
+ "<input type='checkbox' class='form-control table-checkbox' "
|
||||
+ "name='" + colName + "'" + (colValue ? " checked" : "") + " /></td>";
|
||||
html +=
|
||||
"<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" +
|
||||
"<input type='checkbox' class='form-control table-checkbox' " +
|
||||
"name='" + colName + "'" + (colValue ? " checked" : "") + "/>" +
|
||||
"</td>";
|
||||
} else if (isArray && col.type === "time" && col.editable) {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>"
|
||||
+ "<input type='time' class='form-control table-time' "
|
||||
+ "name='" + colName + "' value='" + (colValue || col.default || "00:00") + "' /></td>";
|
||||
html +=
|
||||
"<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" +
|
||||
"<input type='time' class='form-control table-time' name='" + colName + "' " +
|
||||
"value='" + (colValue || col.default || "00:00") + "'/>" +
|
||||
"</td>";
|
||||
} else {
|
||||
// Use a hidden input so that the values are posted.
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "' name='" + colName + "'>"
|
||||
+ colValue + "<input type='hidden' name='" + colName + "' value='" + colValue + "'/></td>";
|
||||
html +=
|
||||
"<td class='" + Settings.DATA_COL_CLASS + "' " + (col.hidden ? "style='display: none;'" : "") +
|
||||
"name='" + colName + "'>" +
|
||||
colValue +
|
||||
"<input type='hidden' name='" + colName + "' value='" + colValue + "'/>" +
|
||||
"</td>";
|
||||
}
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
if (!isLocked && !setting.read_only) {
|
||||
if (!setting.read_only) {
|
||||
if (setting.can_order) {
|
||||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES+
|
||||
"'><a href='javascript:void(0);' class='" + Settings.MOVE_UP_SPAN_CLASSES + "'></a>"
|
||||
|
@ -1047,24 +1083,53 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
|
||||
html += "</tr>"
|
||||
|
||||
if (isCategorized && setting.can_add_new_rows && _.findLastIndex(setting_value, categoryPair) === rowIndexOrName) {
|
||||
html += makeTableInputs(setting, categoryPair, categoryValue);
|
||||
}
|
||||
|
||||
row_num++
|
||||
});
|
||||
}
|
||||
|
||||
// populate inputs in the table for new values
|
||||
if (!isLocked && !setting.read_only && setting.can_add_new_rows) {
|
||||
html += makeTableInputs(setting)
|
||||
if (!setting.read_only) {
|
||||
if (setting.can_add_new_categories) {
|
||||
html += makeTableCategoryInput(setting, numVisibleColumns);
|
||||
}
|
||||
if (setting.can_add_new_rows || setting.can_add_new_categories) {
|
||||
html += makeTableInputs(setting, {}, "");
|
||||
}
|
||||
}
|
||||
html += "</table>"
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function makeTableInputs(setting) {
|
||||
var html = "<tr class='inputs'>"
|
||||
function makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, canRemove, message) {
|
||||
var html =
|
||||
"<tr class='" + Settings.DATA_CATEGORY_CLASS + "' data-key='" + categoryKey + "' data-category='" + categoryValue + "'>" +
|
||||
"<td colspan='" + (numVisibleColumns - 1) + "' class='" + Settings.TOGGLE_CATEGORY_COLUMN_CLASS + "'>" +
|
||||
"<span class='" + Settings.TOGGLE_CATEGORY_SPAN_CLASSES + " " + Settings.TOGGLE_CATEGORY_EXPANDED_CLASS + "'></span>" +
|
||||
"<span message='" + message + "'>" + categoryValue + "</span>" +
|
||||
"</td>" +
|
||||
((canRemove) ? (
|
||||
"<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'>" +
|
||||
"<a href='javascript:void(0);' class='" + Settings.DEL_CATEGORY_SPAN_CLASSES + "'></a>" +
|
||||
"</td>"
|
||||
) : (
|
||||
"<td></td>"
|
||||
)) +
|
||||
"</tr>";
|
||||
return html;
|
||||
}
|
||||
|
||||
function makeTableInputs(setting, initialValues, categoryValue) {
|
||||
var html = "<tr class='inputs'" + (setting.can_add_new_categories && !categoryValue ? " hidden" : "") + " " +
|
||||
(categoryValue ? ("data-category='" + categoryValue + "'") : "") + " " +
|
||||
(setting.categorize_by_key ? ("data-keep-field='" + setting.categorize_by_key + "'") : "") + ">";
|
||||
|
||||
if (setting.numbered === true) {
|
||||
html += "<td class='numbered'></td>"
|
||||
html += "<td class='numbered'></td>";
|
||||
}
|
||||
|
||||
if (setting.key) {
|
||||
|
@ -1074,15 +1139,21 @@ function makeTableInputs(setting) {
|
|||
}
|
||||
|
||||
_.each(setting.columns, function(col) {
|
||||
var defaultValue = _.has(initialValues, col.name) ? initialValues[col.name] : col.default;
|
||||
if (col.type === "checkbox") {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>"
|
||||
+ "<input type='checkbox' class='form-control table-checkbox' "
|
||||
+ "name='" + col.name + "'" + (col.default ? " checked" : "") + "/></td>";
|
||||
html +=
|
||||
"<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" +
|
||||
"<input type='checkbox' class='form-control table-checkbox' " +
|
||||
"name='" + col.name + "'" + (defaultValue ? " checked" : "") + "/>" +
|
||||
"</td>";
|
||||
} else {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>\
|
||||
<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "'\
|
||||
value='" + (col.default ? col.default : "") + "' data-default='" + (col.default ? col.default : "") + "'>\
|
||||
</td>"
|
||||
html +=
|
||||
"<td " + (col.hidden ? "style='display: none;'" : "") + " class='" + Settings.DATA_COL_CLASS + "' " +
|
||||
"name='" + col.name + "'>" +
|
||||
"<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "' " +
|
||||
"value='" + (defaultValue || "") + "' data-default='" + (defaultValue || "") + "'" +
|
||||
(col.readonly ? " readonly" : "") + ">" +
|
||||
"</td>";
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -1090,12 +1161,30 @@ function makeTableInputs(setting) {
|
|||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'></td>"
|
||||
}
|
||||
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES +
|
||||
"'><a href='javascript:void(0);' class='glyphicon glyphicon-plus " + Settings.ADD_ROW_BUTTON_CLASS + "'></a></td>"
|
||||
"'><a href='javascript:void(0);' class='" + Settings.ADD_ROW_SPAN_CLASSES + "'></a></td>"
|
||||
html += "</tr>"
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
function makeTableCategoryInput(setting, numVisibleColumns) {
|
||||
var canAddRows = setting.can_add_new_rows;
|
||||
var categoryKey = setting.categorize_by_key;
|
||||
var placeholder = setting.new_category_placeholder || "";
|
||||
var message = setting.new_category_message || "";
|
||||
var html =
|
||||
"<tr class='" + Settings.DATA_CATEGORY_CLASS + " inputs' data-can-add-rows='" + canAddRows + "' " +
|
||||
"data-key='" + categoryKey + "' data-message='" + message + "'>" +
|
||||
"<td colspan='" + (numVisibleColumns - 1) + "'>" +
|
||||
"<input type='text' class='form-control' placeholder='" + placeholder + "'/>" +
|
||||
"</td>" +
|
||||
"<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'>" +
|
||||
"<a href='javascript:void(0);' class='" + Settings.ADD_CATEGORY_SPAN_CLASSES + "'></a>" +
|
||||
"</td>" +
|
||||
"</tr>";
|
||||
return html;
|
||||
}
|
||||
|
||||
function badgeSidebarForDifferences(changedElement) {
|
||||
// figure out which group this input is in
|
||||
var panelParentID = changedElement.closest('.panel').attr('id');
|
||||
|
@ -1134,13 +1223,12 @@ function badgeSidebarForDifferences(changedElement) {
|
|||
$("a[href='#" + panelParentID + "'] .badge").html(badgeValue);
|
||||
}
|
||||
|
||||
function addTableRow(add_glyphicon) {
|
||||
var row = $(add_glyphicon).closest('tr')
|
||||
function addTableRow(row) {
|
||||
var table = row.parents('table');
|
||||
var isArray = table.data('setting-type') === 'array';
|
||||
var keepField = row.data("keep-field");
|
||||
|
||||
var table = row.parents('table')
|
||||
var isArray = table.data('setting-type') === 'array'
|
||||
|
||||
var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS)
|
||||
var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS);
|
||||
|
||||
if (!isArray) {
|
||||
// Check key spaces
|
||||
|
@ -1257,10 +1345,12 @@ function addTableRow(add_glyphicon) {
|
|||
} else {
|
||||
console.log("Unknown table element")
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
input_clone.find('input').each(function(){
|
||||
$(this).val($(this).attr('data-default'));
|
||||
input_clone.children('td').each(function () {
|
||||
if ($(this).attr("name") !== keepField) {
|
||||
$(this).find("input").val($(this).attr('data-default'));
|
||||
}
|
||||
});
|
||||
|
||||
if (isArray) {
|
||||
|
@ -1272,44 +1362,132 @@ function addTableRow(add_glyphicon) {
|
|||
|
||||
badgeSidebarForDifferences($(table))
|
||||
|
||||
row.parent().append(input_clone)
|
||||
row.after(input_clone)
|
||||
}
|
||||
|
||||
function deleteTableRow(delete_glyphicon) {
|
||||
var row = $(delete_glyphicon).closest('tr')
|
||||
function deleteTableRow($row) {
|
||||
var $table = $row.closest('table');
|
||||
var categoryName = $row.data("category");
|
||||
var isArray = $table.data('setting-type') === 'array';
|
||||
|
||||
var table = $(row).closest('table')
|
||||
var isArray = table.data('setting-type') === 'array'
|
||||
|
||||
row.empty();
|
||||
$row.empty();
|
||||
|
||||
if (!isArray) {
|
||||
row.html("<input type='hidden' class='form-control' name='"
|
||||
+ row.attr('name') + "' data-changed='true' value=''>");
|
||||
$row.html("<input type='hidden' class='form-control' name='" + $row.attr('name') + "' data-changed='true' value=''>");
|
||||
} else {
|
||||
if (table.find('.' + Settings.DATA_ROW_CLASS).length > 1) {
|
||||
updateDataChangedForSiblingRows(row)
|
||||
if ($table.find('.' + Settings.DATA_ROW_CLASS + "[data-category='" + categoryName + "']").length <= 1) {
|
||||
// This is the last row of the category, so delete the header
|
||||
$table.find('.' + Settings.DATA_CATEGORY_CLASS + "[data-category='" + categoryName + "']").remove();
|
||||
}
|
||||
|
||||
if ($table.find('.' + Settings.DATA_ROW_CLASS).length > 1) {
|
||||
updateDataChangedForSiblingRows($row);
|
||||
|
||||
// this isn't the last row - we can just remove it
|
||||
row.remove()
|
||||
$row.remove();
|
||||
} else {
|
||||
// this is the last row, we can't remove it completely since we need to post an empty array
|
||||
|
||||
row.removeClass(Settings.DATA_ROW_CLASS).removeClass(Settings.NEW_ROW_CLASS)
|
||||
row.addClass('empty-array-row')
|
||||
|
||||
row.html("<input type='hidden' class='form-control' name='" + table.attr("name").replace('[]', '')
|
||||
+ "' data-changed='true' value=''>");
|
||||
$row
|
||||
.removeClass(Settings.DATA_ROW_CLASS)
|
||||
.removeClass(Settings.NEW_ROW_CLASS)
|
||||
.removeAttr("data-category")
|
||||
.addClass('empty-array-row')
|
||||
.html("<input type='hidden' class='form-control' name='" + $table.attr("name").replace('[]', '') + "' " +
|
||||
"data-changed='true' value=''>");
|
||||
}
|
||||
}
|
||||
|
||||
// we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated
|
||||
badgeSidebarForDifferences($(table))
|
||||
badgeSidebarForDifferences($table);
|
||||
}
|
||||
|
||||
function moveTableRow(move_glyphicon, move_up) {
|
||||
var row = $(move_glyphicon).closest('tr')
|
||||
function addTableCategory($categoryInputRow) {
|
||||
var $input = $categoryInputRow.find("input").first();
|
||||
var categoryValue = $input.prop("value");
|
||||
if (!categoryValue || $categoryInputRow.closest("table").find("tr[data-category='" + categoryValue + "']").length !== 0) {
|
||||
$categoryInputRow.addClass("has-warning");
|
||||
|
||||
setTimeout(function () {
|
||||
$categoryInputRow.removeClass("has-warning");
|
||||
}, 400);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var $rowInput = $categoryInputRow.next(".inputs").clone();
|
||||
if (!$rowInput) {
|
||||
console.error("Error cloning inputs");
|
||||
}
|
||||
|
||||
var canAddRows = $categoryInputRow.data("can-add-rows");
|
||||
var message = $categoryInputRow.data("message");
|
||||
var categoryKey = $categoryInputRow.data("key");
|
||||
var width = 0;
|
||||
$categoryInputRow
|
||||
.children("td")
|
||||
.each(function () {
|
||||
width += $(this).prop("colSpan") || 1;
|
||||
});
|
||||
|
||||
$input
|
||||
.prop("value", "")
|
||||
.focus();
|
||||
|
||||
$rowInput.find("td[name='" + categoryKey + "'] > input").first()
|
||||
.prop("value", categoryValue);
|
||||
$rowInput
|
||||
.attr("data-category", categoryValue)
|
||||
.addClass(Settings.NEW_ROW_CLASS);
|
||||
|
||||
var $newCategoryRow = $(makeTableCategoryHeader(categoryKey, categoryValue, width, true, " - " + message));
|
||||
$newCategoryRow.addClass(Settings.NEW_ROW_CLASS);
|
||||
|
||||
$categoryInputRow
|
||||
.before($newCategoryRow)
|
||||
.before($rowInput);
|
||||
|
||||
if (canAddRows) {
|
||||
$rowInput.removeAttr("hidden");
|
||||
} else {
|
||||
addTableRow($rowInput);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteTableCategory($categoryHeaderRow) {
|
||||
var categoryName = $categoryHeaderRow.data("category");
|
||||
|
||||
$categoryHeaderRow
|
||||
.closest("table")
|
||||
.find("tr[data-category='" + categoryName + "']")
|
||||
.each(function () {
|
||||
if ($(this).hasClass(Settings.DATA_ROW_CLASS)) {
|
||||
deleteTableRow($(this));
|
||||
} else {
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggleTableCategory($categoryHeaderRow) {
|
||||
var $icon = $categoryHeaderRow.find("." + Settings.TOGGLE_CATEGORY_SPAN_CLASS).first();
|
||||
var categoryName = $categoryHeaderRow.data("category");
|
||||
var wasExpanded = $icon.hasClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS);
|
||||
if (wasExpanded) {
|
||||
$icon
|
||||
.addClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS)
|
||||
.removeClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS);
|
||||
} else {
|
||||
$icon
|
||||
.addClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS)
|
||||
.removeClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS);
|
||||
}
|
||||
$categoryHeaderRow
|
||||
.closest("table")
|
||||
.find("tr[data-category='" + categoryName + "']")
|
||||
.toggleClass("contracted", wasExpanded);
|
||||
}
|
||||
|
||||
function moveTableRow(row, move_up) {
|
||||
var table = $(row).closest('table')
|
||||
var isArray = table.data('setting-type') === 'array'
|
||||
if (!isArray) {
|
||||
|
|
|
@ -120,6 +120,102 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
|||
}
|
||||
}
|
||||
|
||||
NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress) {
|
||||
NodePermissions userPerms;
|
||||
|
||||
userPerms.setAll(false);
|
||||
|
||||
if (isLocalUser) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: is local user, so:" << userPerms;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (verifiedUsername.isEmpty()) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: unverified or no username for" << userPerms.getID() << ", so:" << userPerms;
|
||||
#endif
|
||||
|
||||
if (_server->_settingsManager.hasPermissionsForIP(senderAddress)) {
|
||||
// this user comes from an IP we have in our permissions table, apply those permissions
|
||||
userPerms = _server->_settingsManager.getPermissionsForIP(senderAddress);
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: specific IP matches, so:" << userPerms;
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
if (_server->_settingsManager.havePermissionsForName(verifiedUsername)) {
|
||||
userPerms = _server->_settingsManager.getPermissionsForName(verifiedUsername);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: specific user matches, so:" << userPerms;
|
||||
#endif
|
||||
} else if (_server->_settingsManager.hasPermissionsForIP(senderAddress)) {
|
||||
// this user comes from an IP we have in our permissions table, apply those permissions
|
||||
userPerms = _server->_settingsManager.getPermissionsForIP(senderAddress);
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: specific IP matches, so:" << userPerms;
|
||||
#endif
|
||||
} else {
|
||||
// they are logged into metaverse, but we don't have specific permissions for them.
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: user is logged-into metaverse, so:" << userPerms;
|
||||
#endif
|
||||
|
||||
// if this user is a friend of the domain-owner, give them friend's permissions
|
||||
if (_domainOwnerFriends.contains(verifiedUsername)) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameFriends);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: user is friends with domain-owner, so:" << userPerms;
|
||||
#endif
|
||||
}
|
||||
|
||||
// if this user is a known member of a group, give them the implied permissions
|
||||
foreach (QUuid groupID, _server->_settingsManager.getGroupIDs()) {
|
||||
QUuid rankID = _server->_settingsManager.isGroupMember(verifiedUsername, groupID);
|
||||
if (rankID != QUuid()) {
|
||||
userPerms |= _server->_settingsManager.getPermissionsForGroup(groupID, rankID);
|
||||
|
||||
GroupRank rank = _server->_settingsManager.getGroupRank(groupID, rankID);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: user is in group:" << groupID << " rank:"
|
||||
<< rank.name << "so:" << userPerms;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// if this user is a known member of a blacklist group, remove the implied permissions
|
||||
foreach (QUuid groupID, _server->_settingsManager.getBlacklistGroupIDs()) {
|
||||
QUuid rankID = _server->_settingsManager.isGroupMember(verifiedUsername, groupID);
|
||||
if (rankID != QUuid()) {
|
||||
QUuid rankID = _server->_settingsManager.isGroupMember(verifiedUsername, groupID);
|
||||
if (rankID != QUuid()) {
|
||||
userPerms &= ~_server->_settingsManager.getForbiddensForGroup(groupID, rankID);
|
||||
|
||||
GroupRank rank = _server->_settingsManager.getGroupRank(groupID, rankID);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: user is in blacklist group:" << groupID << " rank:" << rank.name
|
||||
<< "so:" << userPerms;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
userPerms.setID(verifiedUsername);
|
||||
userPerms.setVerifiedUserName(verifiedUsername);
|
||||
}
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: final:" << userPerms;
|
||||
#endif
|
||||
return userPerms;
|
||||
}
|
||||
|
||||
void DomainGatekeeper::updateNodePermissions() {
|
||||
// If the permissions were changed on the domain-server webpage (and nothing else was), a restart isn't required --
|
||||
// we reprocess the permissions map and update the nodes here. The node list is frequently sent out to all
|
||||
|
@ -129,40 +225,34 @@ void DomainGatekeeper::updateNodePermissions() {
|
|||
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
limitedNodeList->eachNode([this, limitedNodeList, &nodesToKill](const SharedNodePointer& node){
|
||||
QString username = node->getPermissions().getUserName();
|
||||
NodePermissions userPerms(username);
|
||||
// the id and the username in NodePermissions will often be the same, but id is set before
|
||||
// authentication and verifiedUsername is only set once they user's key has been confirmed.
|
||||
QString verifiedUsername = node->getPermissions().getVerifiedUserName();
|
||||
NodePermissions userPerms(NodePermissionsKey(verifiedUsername, 0));
|
||||
|
||||
if (node->getPermissions().isAssignment) {
|
||||
// this node is an assignment-client
|
||||
userPerms.isAssignment = true;
|
||||
userPerms.canAdjustLocks = true;
|
||||
userPerms.canRezPermanentEntities = true;
|
||||
userPerms.canRezTemporaryEntities = true;
|
||||
userPerms.permissions |= NodePermissions::Permission::canConnectToDomain;
|
||||
userPerms.permissions |= NodePermissions::Permission::canAdjustLocks;
|
||||
userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities;
|
||||
userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities;
|
||||
} else {
|
||||
// this node is an agent
|
||||
userPerms.setAll(false);
|
||||
|
||||
const QHostAddress& addr = node->getLocalSocket().getAddress();
|
||||
bool isLocalUser = (addr == limitedNodeList->getLocalSockAddr().getAddress() ||
|
||||
addr == QHostAddress::LocalHost);
|
||||
if (isLocalUser) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
|
||||
}
|
||||
|
||||
if (username.isEmpty()) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||
} else {
|
||||
if (_server->_settingsManager.havePermissionsForName(username)) {
|
||||
userPerms = _server->_settingsManager.getPermissionsForName(username);
|
||||
} else {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
|
||||
}
|
||||
}
|
||||
// at this point we don't have a sending socket for packets from this node - assume it is the active socket
|
||||
// or the public socket if we haven't activated a socket for the node yet
|
||||
HifiSockAddr connectingAddr = node->getActiveSocket() ? *node->getActiveSocket() : node->getPublicSocket();
|
||||
|
||||
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, connectingAddr.getAddress());
|
||||
}
|
||||
|
||||
node->setPermissions(userPerms);
|
||||
|
||||
if (!userPerms.canConnectToDomain) {
|
||||
if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) {
|
||||
qDebug() << "node" << node->getUUID() << "no longer has permission to connect.";
|
||||
// hang up on this node
|
||||
nodesToKill << node;
|
||||
|
@ -215,12 +305,13 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
|
|||
// cleanup the PendingAssignedNodeData for this assignment now that it's connecting
|
||||
_pendingAssignedNodes.erase(it);
|
||||
|
||||
// always allow assignment clients to create and destroy entities
|
||||
NodePermissions userPerms;
|
||||
userPerms.isAssignment = true;
|
||||
userPerms.canAdjustLocks = true;
|
||||
userPerms.canRezPermanentEntities = true;
|
||||
userPerms.canRezTemporaryEntities = true;
|
||||
userPerms.permissions |= NodePermissions::Permission::canConnectToDomain;
|
||||
// always allow assignment clients to create and destroy entities
|
||||
userPerms.permissions |= NodePermissions::Permission::canAdjustLocks;
|
||||
userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities;
|
||||
userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities;
|
||||
newNode->setPermissions(userPerms);
|
||||
return newNode;
|
||||
}
|
||||
|
@ -234,64 +325,58 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
// start with empty permissions
|
||||
NodePermissions userPerms(username);
|
||||
NodePermissions userPerms(NodePermissionsKey(username, 0));
|
||||
userPerms.setAll(false);
|
||||
|
||||
// check if this user is on our local machine - if this is true set permissions to those for a "localhost" connection
|
||||
QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress();
|
||||
bool isLocalUser =
|
||||
(senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost);
|
||||
if (isLocalUser) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
|
||||
qDebug() << "user-permissions: is local user, so:" << userPerms;
|
||||
}
|
||||
|
||||
if (!username.isEmpty() && usernameSignature.isEmpty()) {
|
||||
// user is attempting to prove their identity to us, but we don't have enough information
|
||||
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
|
||||
// ask for their public key right now to make sure we have it
|
||||
requestUserPublicKey(username);
|
||||
if (!userPerms.canConnectToDomain) {
|
||||
QString verifiedUsername; // if this remains empty, consider this an anonymous connection attempt
|
||||
if (!username.isEmpty()) {
|
||||
if (usernameSignature.isEmpty()) {
|
||||
// user is attempting to prove their identity to us, but we don't have enough information
|
||||
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
|
||||
// ask for their public key right now to make sure we have it
|
||||
requestUserPublicKey(username);
|
||||
getGroupMemberships(username); // optimistically get started on group memberships
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login because we have no username-signature:" << username;
|
||||
#endif
|
||||
return SharedNodePointer();
|
||||
}
|
||||
}
|
||||
|
||||
if (username.isEmpty()) {
|
||||
// they didn't tell us who they are
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||
qDebug() << "user-permissions: no username, so:" << userPerms;
|
||||
} else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) {
|
||||
// they are sent us a username and the signature verifies it
|
||||
if (_server->_settingsManager.havePermissionsForName(username)) {
|
||||
// we have specific permissions for this user.
|
||||
userPerms = _server->_settingsManager.getPermissionsForName(username);
|
||||
qDebug() << "user-permissions: specific user matches, so:" << userPerms;
|
||||
} else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) {
|
||||
// they sent us a username and the signature verifies it
|
||||
getGroupMemberships(username);
|
||||
verifiedUsername = username;
|
||||
} else {
|
||||
// they are logged into metaverse, but we don't have specific permissions for them.
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
|
||||
qDebug() << "user-permissions: user is logged in, so:" << userPerms;
|
||||
}
|
||||
userPerms.setUserName(username);
|
||||
} else {
|
||||
// they sent us a username, but it didn't check out
|
||||
requestUserPublicKey(username);
|
||||
if (!userPerms.canConnectToDomain) {
|
||||
// they sent us a username, but it didn't check out
|
||||
requestUserPublicKey(username);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login because signature verification failed:" << username;
|
||||
#endif
|
||||
return SharedNodePointer();
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "user-permissions: final:" << userPerms;
|
||||
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, nodeConnection.senderSockAddr.getAddress());
|
||||
|
||||
if (!userPerms.canConnectToDomain) {
|
||||
if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) {
|
||||
sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.",
|
||||
nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::TooManyUsers);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login due to permissions:" << username;
|
||||
#endif
|
||||
return SharedNodePointer();
|
||||
}
|
||||
|
||||
if (!userPerms.canConnectPastMaxCapacity && !isWithinMaxCapacity()) {
|
||||
if (!userPerms.can(NodePermissions::Permission::canConnectPastMaxCapacity) && !isWithinMaxCapacity()) {
|
||||
// we can't allow this user to connect because we are at max capacity
|
||||
sendConnectionDeniedPacket("Too many connected users.", nodeConnection.senderSockAddr,
|
||||
DomainHandler::ConnectionRefusedReason::TooManyUsers);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login due to max capacity:" << username;
|
||||
#endif
|
||||
return SharedNodePointer();
|
||||
}
|
||||
|
||||
|
@ -305,10 +390,8 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
// we have a node that already has these exact sockets - this occurs if a node
|
||||
// is unable to connect to the domain
|
||||
hintNodeID = node->getUUID();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
@ -328,6 +411,10 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
nodeData->addOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
|
||||
uuidStringWithoutCurlyBraces(newNode->getUUID()), username);
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "accepting login:" << username;
|
||||
#endif
|
||||
|
||||
return newNode;
|
||||
}
|
||||
|
||||
|
@ -365,11 +452,11 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node
|
|||
bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
||||
const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr) {
|
||||
|
||||
// it's possible this user can be allowed to connect, but we need to check their username signature
|
||||
QByteArray publicKeyArray = _userPublicKeys.value(username);
|
||||
auto lowerUsername = username.toLower();
|
||||
QByteArray publicKeyArray = _userPublicKeys.value(lowerUsername);
|
||||
|
||||
const QUuid& connectionToken = _connectionTokenHash.value(username.toLower());
|
||||
const QUuid& connectionToken = _connectionTokenHash.value(lowerUsername);
|
||||
|
||||
if (!publicKeyArray.isEmpty() && !connectionToken.isNull()) {
|
||||
// if we do have a public key for the user, check for a signature match
|
||||
|
@ -379,8 +466,8 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
|||
// first load up the public key into an RSA struct
|
||||
RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size());
|
||||
|
||||
QByteArray lowercaseUsername = username.toLower().toUtf8();
|
||||
QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()),
|
||||
QByteArray lowercaseUsernameUTF8 = lowerUsername.toUtf8();
|
||||
QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsernameUTF8.append(connectionToken.toRfc4122()),
|
||||
QCryptographicHash::Sha256);
|
||||
|
||||
if (rsaPublicKey) {
|
||||
|
@ -471,10 +558,20 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) {
|
|||
return;
|
||||
}
|
||||
|
||||
QString lowerUsername = username.toLower();
|
||||
if (_inFlightPublicKeyRequests.contains(lowerUsername)) {
|
||||
// public-key request for this username is already flight, not rerequesting
|
||||
return;
|
||||
}
|
||||
_inFlightPublicKeyRequests += lowerUsername;
|
||||
|
||||
// even if we have a public key for them right now, request a new one in case it has just changed
|
||||
JSONCallbackParameters callbackParams;
|
||||
callbackParams.jsonCallbackReceiver = this;
|
||||
callbackParams.jsonCallbackMethod = "publicKeyJSONCallback";
|
||||
callbackParams.errorCallbackReceiver = this;
|
||||
callbackParams.errorCallbackMethod = "publicKeyJSONErrorCallback";
|
||||
|
||||
|
||||
const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key";
|
||||
|
||||
|
@ -485,28 +582,37 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) {
|
|||
QNetworkAccessManager::GetOperation, callbackParams);
|
||||
}
|
||||
|
||||
QString extractUsernameFromPublicKeyRequest(QNetworkReply& requestReply) {
|
||||
// extract the username from the request url
|
||||
QString username;
|
||||
const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key";
|
||||
QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING);
|
||||
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
|
||||
username = usernameRegex.cap(1);
|
||||
}
|
||||
return username.toLower();
|
||||
}
|
||||
|
||||
void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) {
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
QString username = extractUsernameFromPublicKeyRequest(requestReply);
|
||||
|
||||
if (jsonObject["status"].toString() == "success") {
|
||||
// figure out which user this is for
|
||||
if (jsonObject["status"].toString() == "success" && !username.isEmpty()) {
|
||||
// pull the public key as a QByteArray from this response
|
||||
const QString JSON_DATA_KEY = "data";
|
||||
const QString JSON_PUBLIC_KEY_KEY = "public_key";
|
||||
|
||||
const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key";
|
||||
QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING);
|
||||
|
||||
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
|
||||
QString username = usernameRegex.cap(1);
|
||||
|
||||
qDebug() << "Storing a public key for user" << username;
|
||||
|
||||
// pull the public key as a QByteArray from this response
|
||||
const QString JSON_DATA_KEY = "data";
|
||||
const QString JSON_PUBLIC_KEY_KEY = "public_key";
|
||||
|
||||
_userPublicKeys[username] =
|
||||
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8());
|
||||
}
|
||||
_userPublicKeys[username.toLower()] =
|
||||
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8());
|
||||
}
|
||||
|
||||
_inFlightPublicKeyRequests.remove(username);
|
||||
}
|
||||
|
||||
void DomainGatekeeper::publicKeyJSONErrorCallback(QNetworkReply& requestReply) {
|
||||
qDebug() << "publicKey api call failed:" << requestReply.error();
|
||||
QString username = extractUsernameFromPublicKeyRequest(requestReply);
|
||||
_inFlightPublicKeyRequests.remove(username);
|
||||
}
|
||||
|
||||
void DomainGatekeeper::sendProtocolMismatchConnectionDenial(const HifiSockAddr& senderSockAddr) {
|
||||
|
@ -645,3 +751,159 @@ void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer<ReceivedMessage>
|
|||
sendingPeer->activateMatchingOrNewSymmetricSocket(message->getSenderSockAddr());
|
||||
}
|
||||
}
|
||||
|
||||
void DomainGatekeeper::getGroupMemberships(const QString& username) {
|
||||
// loop through the groups mentioned on the settings page and ask if this user is in each. The replies
|
||||
// will be received asynchronously and permissions will be updated as the answers come in.
|
||||
|
||||
// if we've already asked, wait for the answer before asking again
|
||||
QString lowerUsername = username.toLower();
|
||||
if (_inFlightGroupMembershipsRequests.contains(lowerUsername)) {
|
||||
// public-key request for this username is already flight, not rerequesting
|
||||
return;
|
||||
}
|
||||
_inFlightGroupMembershipsRequests += lowerUsername;
|
||||
|
||||
QJsonObject json;
|
||||
QSet<QString> groupIDSet;
|
||||
foreach (QUuid groupID, _server->_settingsManager.getGroupIDs() + _server->_settingsManager.getBlacklistGroupIDs()) {
|
||||
groupIDSet += groupID.toString().mid(1,36);
|
||||
}
|
||||
QJsonArray groupIDs = QJsonArray::fromStringList(groupIDSet.toList());
|
||||
json["groups"] = groupIDs;
|
||||
|
||||
JSONCallbackParameters callbackParams;
|
||||
callbackParams.jsonCallbackReceiver = this;
|
||||
callbackParams.jsonCallbackMethod = "getIsGroupMemberJSONCallback";
|
||||
callbackParams.errorCallbackReceiver = this;
|
||||
callbackParams.errorCallbackMethod = "getIsGroupMemberErrorCallback";
|
||||
|
||||
const QString GET_IS_GROUP_MEMBER_PATH = "api/v1/groups/members/%2";
|
||||
DependencyManager::get<AccountManager>()->sendRequest(GET_IS_GROUP_MEMBER_PATH.arg(username),
|
||||
AccountManagerAuth::Required,
|
||||
QNetworkAccessManager::PostOperation, callbackParams,
|
||||
QJsonDocument(json).toJson());
|
||||
|
||||
}
|
||||
|
||||
QString extractUsernameFromGroupMembershipsReply(QNetworkReply& requestReply) {
|
||||
// extract the username from the request url
|
||||
QString username;
|
||||
const QString GROUP_MEMBERSHIPS_URL_REGEX_STRING = "api\\/v1\\/groups\\/members\\/([A-Za-z0-9_\\.]+)";
|
||||
QRegExp usernameRegex(GROUP_MEMBERSHIPS_URL_REGEX_STRING);
|
||||
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
|
||||
username = usernameRegex.cap(1);
|
||||
}
|
||||
return username.toLower();
|
||||
}
|
||||
|
||||
void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply& requestReply) {
|
||||
// {
|
||||
// "data":{
|
||||
// "username":"sethalves",
|
||||
// "groups":{
|
||||
// "fd55479a-265d-4990-854e-3d04214ad1b0":{
|
||||
// "name":"Blerg Blah",
|
||||
// "rank":{
|
||||
// "name":"admin",
|
||||
// "order":1
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// "status":"success"
|
||||
// }
|
||||
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
if (jsonObject["status"].toString() == "success") {
|
||||
QJsonObject data = jsonObject["data"].toObject();
|
||||
QJsonObject groups = data["groups"].toObject();
|
||||
QString username = data["username"].toString();
|
||||
_server->_settingsManager.clearGroupMemberships(username);
|
||||
foreach (auto groupID, groups.keys()) {
|
||||
QJsonObject group = groups[groupID].toObject();
|
||||
QJsonObject rank = group["rank"].toObject();
|
||||
QUuid rankID = QUuid(rank["id"].toString());
|
||||
_server->_settingsManager.recordGroupMembership(username, groupID, rankID);
|
||||
}
|
||||
} else {
|
||||
qDebug() << "getIsGroupMember api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
_inFlightGroupMembershipsRequests.remove(extractUsernameFromGroupMembershipsReply(requestReply));
|
||||
}
|
||||
|
||||
void DomainGatekeeper::getIsGroupMemberErrorCallback(QNetworkReply& requestReply) {
|
||||
qDebug() << "getIsGroupMember api call failed:" << requestReply.error();
|
||||
_inFlightGroupMembershipsRequests.remove(extractUsernameFromGroupMembershipsReply(requestReply));
|
||||
}
|
||||
|
||||
void DomainGatekeeper::getDomainOwnerFriendsList() {
|
||||
JSONCallbackParameters callbackParams;
|
||||
callbackParams.jsonCallbackReceiver = this;
|
||||
callbackParams.jsonCallbackMethod = "getDomainOwnerFriendsListJSONCallback";
|
||||
callbackParams.errorCallbackReceiver = this;
|
||||
callbackParams.errorCallbackMethod = "getDomainOwnerFriendsListErrorCallback";
|
||||
|
||||
const QString GET_FRIENDS_LIST_PATH = "api/v1/user/friends";
|
||||
DependencyManager::get<AccountManager>()->sendRequest(GET_FRIENDS_LIST_PATH, AccountManagerAuth::Required,
|
||||
QNetworkAccessManager::GetOperation, callbackParams, QByteArray(),
|
||||
NULL, QVariantMap());
|
||||
}
|
||||
|
||||
void DomainGatekeeper::getDomainOwnerFriendsListJSONCallback(QNetworkReply& requestReply) {
|
||||
// {
|
||||
// status: "success",
|
||||
// data: {
|
||||
// friends: [
|
||||
// "chris",
|
||||
// "freidrica",
|
||||
// "G",
|
||||
// "huffman",
|
||||
// "leo",
|
||||
// "philip",
|
||||
// "ryan",
|
||||
// "sam",
|
||||
// "ZappoMan"
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
if (jsonObject["status"].toString() == "success") {
|
||||
_domainOwnerFriends.clear();
|
||||
QJsonArray friends = jsonObject["data"].toObject()["friends"].toArray();
|
||||
for (int i = 0; i < friends.size(); i++) {
|
||||
_domainOwnerFriends += friends.at(i).toString();
|
||||
}
|
||||
} else {
|
||||
qDebug() << "getDomainOwnerFriendsList api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
}
|
||||
|
||||
void DomainGatekeeper::getDomainOwnerFriendsListErrorCallback(QNetworkReply& requestReply) {
|
||||
qDebug() << "getDomainOwnerFriendsList api call failed:" << requestReply.error();
|
||||
}
|
||||
|
||||
void DomainGatekeeper::refreshGroupsCache() {
|
||||
// if agents are connected to this domain, refresh our cached information about groups and memberships in such.
|
||||
getDomainOwnerFriendsList();
|
||||
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
nodeList->eachNode([&](const SharedNodePointer& node) {
|
||||
if (!node->getPermissions().isAssignment) {
|
||||
// this node is an agent
|
||||
const QString& verifiedUserName = node->getPermissions().getVerifiedUserName();
|
||||
if (!verifiedUserName.isEmpty()) {
|
||||
getGroupMemberships(verifiedUserName);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_server->_settingsManager.apiRefreshGroupInformation();
|
||||
|
||||
updateNodePermissions();
|
||||
|
||||
#if WANT_DEBUG
|
||||
_server->_settingsManager.debugDumpGroupsState();
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -51,7 +51,16 @@ public slots:
|
|||
void processICEPeerInformationPacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
void publicKeyJSONCallback(QNetworkReply& requestReply);
|
||||
|
||||
void publicKeyJSONErrorCallback(QNetworkReply& requestReply);
|
||||
|
||||
void getIsGroupMemberJSONCallback(QNetworkReply& requestReply);
|
||||
void getIsGroupMemberErrorCallback(QNetworkReply& requestReply);
|
||||
|
||||
void getDomainOwnerFriendsListJSONCallback(QNetworkReply& requestReply);
|
||||
void getDomainOwnerFriendsListErrorCallback(QNetworkReply& requestReply);
|
||||
|
||||
void refreshGroupsCache();
|
||||
|
||||
signals:
|
||||
void killNode(SharedNodePointer node);
|
||||
void connectedNode(SharedNodePointer node);
|
||||
|
@ -93,6 +102,15 @@ private:
|
|||
|
||||
QHash<QString, QUuid> _connectionTokenHash;
|
||||
QHash<QString, QByteArray> _userPublicKeys;
|
||||
QSet<QString> _inFlightPublicKeyRequests; // keep track of which we've already asked for
|
||||
QSet<QString> _domainOwnerFriends; // keep track of friends of the domain owner
|
||||
QSet<QString> _inFlightGroupMembershipsRequests; // keep track of which we've already asked for
|
||||
|
||||
NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress);
|
||||
|
||||
void getGroupMemberships(const QString& username);
|
||||
// void getIsGroupMember(const QString& username, const QUuid groupID);
|
||||
void getDomainOwnerFriendsList();
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -184,10 +184,10 @@ void DomainMetadata::securityChanged(bool send) {
|
|||
QString restriction;
|
||||
|
||||
const auto& settingsManager = static_cast<DomainServer*>(parent())->_settingsManager;
|
||||
bool hasAnonymousAccess =
|
||||
settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous).canConnectToDomain;
|
||||
bool hasHifiAccess =
|
||||
settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn).canConnectToDomain;
|
||||
bool hasAnonymousAccess = settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous).can(
|
||||
NodePermissions::Permission::canConnectToDomain);
|
||||
bool hasHifiAccess = settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn).can(
|
||||
NodePermissions::Permission::canConnectToDomain);
|
||||
if (hasAnonymousAccess) {
|
||||
restriction = hasHifiAccess ? RESTRICTION_OPEN : RESTRICTION_ANON;
|
||||
} else if (hasHifiAccess) {
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include <UUID.h>
|
||||
#include <LogHandler.h>
|
||||
#include <ServerPathUtils.h>
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
#include "DomainServerNodeData.h"
|
||||
#include "NodeConnectionData.h"
|
||||
|
@ -106,11 +107,15 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions,
|
||||
&_gatekeeper, &DomainGatekeeper::updateNodePermissions);
|
||||
|
||||
setupGroupCacheRefresh();
|
||||
|
||||
// if we were given a certificate/private key or oauth credentials they must succeed
|
||||
if (!(optionallyReadX509KeyAndCertificate() && optionallySetupOAuth())) {
|
||||
return;
|
||||
}
|
||||
|
||||
_settingsManager.apiRefreshGroupInformation();
|
||||
|
||||
setupNodeListAndAssignments();
|
||||
setupAutomaticNetworking();
|
||||
if (!getID().isNull()) {
|
||||
|
@ -406,6 +411,7 @@ void DomainServer::setupNodeListAndAssignments() {
|
|||
|
||||
// NodeList won't be available to the settings manager when it is created, so call registerListener here
|
||||
packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::NodeKickRequest, &_settingsManager, "processNodeKickRequestPacket");
|
||||
|
||||
// register the gatekeeper for the packets it needs to receive
|
||||
packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket");
|
||||
|
@ -1098,12 +1104,11 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
|||
static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
|
||||
domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting;
|
||||
|
||||
|
||||
// add access level for anonymous connections
|
||||
// consider the domain to be "restricted" if anonymous connections are disallowed
|
||||
static const QString RESTRICTED_ACCESS_FLAG = "restricted";
|
||||
NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||
domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.canConnectToDomain;
|
||||
domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.can(NodePermissions::Permission::canConnectToDomain);
|
||||
|
||||
const auto& temporaryDomainKey = DependencyManager::get<AccountManager>()->getTemporaryDomainKey(getID());
|
||||
if (!temporaryDomainKey.isEmpty()) {
|
||||
|
@ -2327,3 +2332,14 @@ void DomainServer::randomizeICEServerAddress(bool shouldTriggerHostLookup) {
|
|||
// immediately send an update to the metaverse API when our ice-server changes
|
||||
sendICEServerAddressToMetaverseAPI();
|
||||
}
|
||||
|
||||
void DomainServer::setupGroupCacheRefresh() {
|
||||
const int REFRESH_GROUPS_INTERVAL_MSECS = 15 * MSECS_PER_SECOND;
|
||||
|
||||
if (!_metaverseGroupCacheTimer) {
|
||||
// setup a timer to refresh this server's cached group details
|
||||
_metaverseGroupCacheTimer = new QTimer { this };
|
||||
connect(_metaverseGroupCacheTimer, &QTimer::timeout, &_gatekeeper, &DomainGatekeeper::refreshGroupsCache);
|
||||
_metaverseGroupCacheTimer->start(REFRESH_GROUPS_INTERVAL_MSECS);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
|
|||
public:
|
||||
DomainServer(int argc, char* argv[]);
|
||||
~DomainServer();
|
||||
|
||||
|
||||
static int const EXIT_CODE_REBOOT;
|
||||
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
|
||||
|
@ -64,7 +64,7 @@ public slots:
|
|||
void processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void processICEServerHeartbeatACK(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
|
||||
private slots:
|
||||
void aboutToQuit();
|
||||
|
||||
|
@ -74,7 +74,7 @@ private slots:
|
|||
void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr);
|
||||
void sendHeartbeatToMetaverse() { sendHeartbeatToMetaverse(QString()); }
|
||||
void sendHeartbeatToIceServer();
|
||||
|
||||
|
||||
void handleConnectedNode(SharedNodePointer newNode);
|
||||
|
||||
void handleTempDomainSuccess(QNetworkReply& requestReply);
|
||||
|
@ -96,7 +96,7 @@ signals:
|
|||
void iceServerChanged();
|
||||
void userConnected();
|
||||
void userDisconnected();
|
||||
|
||||
|
||||
private:
|
||||
const QUuid& getID();
|
||||
|
||||
|
@ -136,7 +136,7 @@ private:
|
|||
SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment);
|
||||
void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
|
||||
void addStaticAssignmentsToQueue();
|
||||
|
||||
|
||||
QUrl oauthRedirectURL();
|
||||
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
|
||||
|
||||
|
@ -151,7 +151,9 @@ private:
|
|||
|
||||
QJsonObject jsonForSocket(const HifiSockAddr& socket);
|
||||
QJsonObject jsonObjectForNode(const SharedNodePointer& node);
|
||||
|
||||
|
||||
void setupGroupCacheRefresh();
|
||||
|
||||
DomainGatekeeper _gatekeeper;
|
||||
|
||||
HTTPManager _httpManager;
|
||||
|
@ -184,6 +186,7 @@ private:
|
|||
DomainMetadata* _metadata { nullptr };
|
||||
QTimer* _iceHeartbeatTimer { nullptr };
|
||||
QTimer* _metaverseHeartbeatTimer { nullptr };
|
||||
QTimer* _metaverseGroupCacheTimer { nullptr };
|
||||
|
||||
QList<QHostAddress> _iceServerAddresses;
|
||||
QSet<QHostAddress> _failedIceServerAddresses;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -27,6 +27,12 @@ const QString SETTINGS_PATH = "/settings";
|
|||
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
|
||||
const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions";
|
||||
const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions";
|
||||
const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions";
|
||||
const QString GROUP_PERMISSIONS_KEYPATH = "security.group_permissions";
|
||||
const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens";
|
||||
|
||||
using GroupByUUIDKey = QPair<QUuid, QUuid>; // groupID, rankID
|
||||
|
||||
|
||||
class DomainServerSettingsManager : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -38,23 +44,67 @@ public:
|
|||
void setupConfigMap(const QStringList& argumentList);
|
||||
QVariant valueOrDefaultValueForKeyPath(const QString& keyPath);
|
||||
|
||||
QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); }
|
||||
QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); }
|
||||
QVariantMap& getSettingsMap() { return _configMap.getConfig(); }
|
||||
|
||||
QVariantMap& getDescriptorsMap();
|
||||
|
||||
bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name); }
|
||||
bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name); }
|
||||
NodePermissions getStandardPermissionsForName(const QString& name) const;
|
||||
// these give access to anonymous/localhost/logged-in settings from the domain-server settings page
|
||||
bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name, 0); }
|
||||
NodePermissions getStandardPermissionsForName(const NodePermissionsKey& name) const;
|
||||
|
||||
// these give access to permissions for specific user-names from the domain-server settings page
|
||||
bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name, 0); }
|
||||
NodePermissions getPermissionsForName(const QString& name) const;
|
||||
QStringList getAllNames() { return _agentPermissions.keys(); }
|
||||
NodePermissions getPermissionsForName(const NodePermissionsKey& key) const { return getPermissionsForName(key.first); }
|
||||
QStringList getAllNames() const;
|
||||
|
||||
// these give access to permissions for specific IPs from the domain-server settings page
|
||||
bool hasPermissionsForIP(const QHostAddress& address) const { return _ipPermissions.contains(address.toString(), 0); }
|
||||
NodePermissions getPermissionsForIP(const QHostAddress& address) const;
|
||||
|
||||
// these give access to permissions for specific groups from the domain-server settings page
|
||||
bool havePermissionsForGroup(const QString& groupName, QUuid rankID) const {
|
||||
return _groupPermissions.contains(groupName, rankID);
|
||||
}
|
||||
NodePermissions getPermissionsForGroup(const QString& groupName, QUuid rankID) const;
|
||||
NodePermissions getPermissionsForGroup(const QUuid& groupID, QUuid rankID) const;
|
||||
|
||||
// these remove permissions from users in certain groups
|
||||
bool haveForbiddensForGroup(const QString& groupName, QUuid rankID) const {
|
||||
return _groupForbiddens.contains(groupName, rankID);
|
||||
}
|
||||
NodePermissions getForbiddensForGroup(const QString& groupName, QUuid rankID) const;
|
||||
NodePermissions getForbiddensForGroup(const QUuid& groupID, QUuid rankID) const;
|
||||
|
||||
QStringList getAllKnownGroupNames();
|
||||
bool setGroupID(const QString& groupName, const QUuid& groupID);
|
||||
GroupRank getGroupRank(QUuid groupID, QUuid rankID) { return _groupRanks[groupID][rankID]; }
|
||||
|
||||
QList<QUuid> getGroupIDs();
|
||||
QList<QUuid> getBlacklistGroupIDs();
|
||||
|
||||
// these are used to locally cache the result of calling "api/v1/groups/.../is_member/..." on metaverse's api
|
||||
void clearGroupMemberships(const QString& name) { _groupMembership[name].clear(); }
|
||||
void recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID);
|
||||
QUuid isGroupMember(const QString& name, const QUuid& groupID); // returns rank or -1 if not a member
|
||||
|
||||
// calls http api to refresh group information
|
||||
void apiRefreshGroupInformation();
|
||||
|
||||
void debugDumpGroupsState();
|
||||
|
||||
signals:
|
||||
void updateNodePermissions();
|
||||
|
||||
public slots:
|
||||
void apiGetGroupIDJSONCallback(QNetworkReply& requestReply);
|
||||
void apiGetGroupIDErrorCallback(QNetworkReply& requestReply);
|
||||
void apiGetGroupRanksJSONCallback(QNetworkReply& requestReply);
|
||||
void apiGetGroupRanksErrorCallback(QNetworkReply& requestReply);
|
||||
|
||||
private slots:
|
||||
void processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void processNodeKickRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
|
||||
private:
|
||||
QStringList _argumentList;
|
||||
|
@ -76,11 +126,37 @@ private:
|
|||
|
||||
void validateDescriptorsMap();
|
||||
|
||||
void packPermissionsForMap(QString mapName, NodePermissionsMap& agentPermissions, QString keyPath);
|
||||
// these cause calls to metaverse's group api
|
||||
void apiGetGroupID(const QString& groupName);
|
||||
void apiGetGroupRanks(const QUuid& groupID);
|
||||
|
||||
void initializeGroupPermissions(NodePermissionsMap& permissionsRows, QString groupName, NodePermissionsPointer perms);
|
||||
void packPermissionsForMap(QString mapName, NodePermissionsMap& permissionsRows, QString keyPath);
|
||||
void packPermissions();
|
||||
void unpackPermissions();
|
||||
NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost
|
||||
bool unpackPermissionsForKeypath(const QString& keyPath, NodePermissionsMap* destinationMapPointer,
|
||||
std::function<void(NodePermissionsPointer)> customUnpacker = {});
|
||||
bool ensurePermissionsForGroupRanks();
|
||||
|
||||
NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost, friend-of-domain-owner
|
||||
NodePermissionsMap _agentPermissions; // specific account-names
|
||||
|
||||
NodePermissionsMap _ipPermissions; // permissions granted by node IP address
|
||||
|
||||
NodePermissionsMap _groupPermissions; // permissions granted by membership to specific groups
|
||||
NodePermissionsMap _groupForbiddens; // permissions denied due to membership in a specific group
|
||||
// these are like _groupPermissions and _groupForbiddens but with uuids rather than group-names in the keys
|
||||
QHash<GroupByUUIDKey, NodePermissionsPointer> _groupPermissionsByUUID;
|
||||
QHash<GroupByUUIDKey, NodePermissionsPointer> _groupForbiddensByUUID;
|
||||
|
||||
QHash<QString, QUuid> _groupIDs; // keep track of group-name to group-id mappings
|
||||
QHash<QUuid, QString> _groupNames; // keep track of group-id to group-name mappings
|
||||
|
||||
// remember the responses to api/v1/groups/%1/ranks
|
||||
QHash<QUuid, QHash<QUuid, GroupRank>> _groupRanks; // QHash<group-id, QHash<rankID, rank>>
|
||||
|
||||
// keep track of answers to api queries about which users are in which groups
|
||||
QHash<QString, QHash<QUuid, QUuid>> _groupMembership; // QHash<user-name, QHash<group-id, rank-id>>
|
||||
};
|
||||
|
||||
#endif // hifi_DomainServerSettingsManager_h
|
||||
|
|
|
@ -1,15 +1,26 @@
|
|||
{
|
||||
"name": "Hydra to Standard",
|
||||
"channels": [
|
||||
{ "from": "Hydra.LY", "filters": "invert", "to": "Standard.LY" },
|
||||
{ "from": "Hydra.LX", "to": "Standard.LX" },
|
||||
{ "from": "Hydra.LY", "to": "Standard.LY",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.05 },
|
||||
"invert"
|
||||
]
|
||||
},
|
||||
{ "from": "Hydra.LX", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Standard.LX" },
|
||||
{ "from": "Hydra.LT", "to": "Standard.LTClick",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
|
||||
},
|
||||
{ "from": "Hydra.LT", "to": "Standard.LT" },
|
||||
{ "from": "Hydra.RY", "filters": "invert", "to": "Standard.RY" },
|
||||
{ "from": "Hydra.RX", "to": "Standard.RX" },
|
||||
{ "from": "Hydra.LT", "to": "Standard.LT" },
|
||||
|
||||
{ "from": "Hydra.RY", "to": "Standard.RY",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.05 },
|
||||
"invert"
|
||||
]
|
||||
},
|
||||
{ "from": "Hydra.RX", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Standard.RX" },
|
||||
{ "from": "Hydra.RT", "to": "Standard.RTClick",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
|
||||
|
@ -28,7 +39,6 @@
|
|||
{ "from": [ "Hydra.R1", "Hydra.R3" ], "to": "Standard.RightPrimaryThumb" },
|
||||
{ "from": [ "Hydra.R2", "Hydra.R4" ], "to": "Standard.RightSecondaryThumb" },
|
||||
{ "from": [ "Hydra.L2", "Hydra.L4" ], "to": "Standard.LeftSecondaryThumb" },
|
||||
|
||||
{ "from": "Hydra.LeftHand", "to": "Standard.LeftHand" },
|
||||
{ "from": "Hydra.RightHand", "to": "Standard.RightHand" }
|
||||
]
|
||||
|
|
|
@ -2,12 +2,15 @@
|
|||
"name": "Oculus Touch to Standard",
|
||||
"channels": [
|
||||
{ "from": "OculusTouch.A", "to": "Standard.RightPrimaryThumb" },
|
||||
{ "from": "OculusTouch.B", "to": "Standard.RightSecondaryThumb" },
|
||||
{ "from": "OculusTouch.X", "to": "Standard.LeftPrimaryThumb" },
|
||||
{ "from": "OculusTouch.Y", "to": "Standard.LeftSecondaryThumb" },
|
||||
|
||||
{ "from": "OculusTouch.LY", "filters": "invert", "to": "Standard.LY" },
|
||||
{ "from": "OculusTouch.LX", "to": "Standard.LX" },
|
||||
{ "from": "OculusTouch.LY", "to": "Standard.LY",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.05 },
|
||||
"invert"
|
||||
]
|
||||
},
|
||||
{ "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Standard.LX" },
|
||||
{ "from": "OculusTouch.LT", "to": "Standard.LTClick",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
|
||||
|
@ -17,8 +20,13 @@
|
|||
{ "from": "OculusTouch.LeftGrip", "to": "Standard.LeftGrip" },
|
||||
{ "from": "OculusTouch.LeftHand", "to": "Standard.LeftHand" },
|
||||
|
||||
{ "from": "OculusTouch.RY", "filters": "invert", "to": "Standard.RY" },
|
||||
{ "from": "OculusTouch.RX", "to": "Standard.RX" },
|
||||
{ "from": "OculusTouch.RY", "to": "Standard.RY",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.05 },
|
||||
"invert"
|
||||
]
|
||||
},
|
||||
{ "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Standard.RX" },
|
||||
{ "from": "OculusTouch.RT", "to": "Standard.RTClick",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
|
||||
|
|
|
@ -39,6 +39,23 @@ Windows.ScrollingWindow {
|
|||
// missing signal
|
||||
signal sendToScript(var message);
|
||||
|
||||
signal moved(vector2d position);
|
||||
signal resized(size size);
|
||||
|
||||
function notifyMoved() {
|
||||
moved(Qt.vector2d(x, y));
|
||||
}
|
||||
|
||||
function notifyResized() {
|
||||
resized(Qt.size(width, height));
|
||||
}
|
||||
|
||||
onXChanged: notifyMoved();
|
||||
onYChanged: notifyMoved();
|
||||
|
||||
onWidthChanged: notifyResized();
|
||||
onHeightChanged: notifyResized();
|
||||
|
||||
Item {
|
||||
width: pane.contentWidth
|
||||
implicitHeight: pane.scrollHeight
|
||||
|
|
|
@ -35,7 +35,6 @@ WebEngineView {
|
|||
}
|
||||
|
||||
onUrlChanged: {
|
||||
console.log("Url changed to " + url);
|
||||
var originalUrl = url.toString();
|
||||
newUrl = urlHandler.fixupUrl(originalUrl).toString();
|
||||
if (newUrl !== originalUrl) {
|
||||
|
|
|
@ -26,7 +26,6 @@ WebEngineView {
|
|||
}
|
||||
|
||||
onUrlChanged: {
|
||||
console.log("Url changed to " + url);
|
||||
var originalUrl = url.toString();
|
||||
newUrl = urlHandler.fixupUrl(originalUrl).toString();
|
||||
if (newUrl !== originalUrl) {
|
||||
|
|
|
@ -87,6 +87,15 @@ ModalWindow {
|
|||
currentSelection.text = d.capitalizeDrive(helper.urlToPath(initialFolder));
|
||||
}
|
||||
|
||||
helper.contentsChanged.connect(function() {
|
||||
if (folderListModel) {
|
||||
// Make folderListModel refresh.
|
||||
var save = folderListModel.folder;
|
||||
folderListModel.folder = "";
|
||||
folderListModel.folder = save;
|
||||
}
|
||||
});
|
||||
|
||||
fileTableView.forceActiveFocus();
|
||||
}
|
||||
|
||||
|
@ -343,12 +352,14 @@ ModalWindow {
|
|||
onFolderChanged: {
|
||||
if (folder === rootFolder) {
|
||||
model = driveListModel;
|
||||
helper.monitorDirectory("");
|
||||
update();
|
||||
} else {
|
||||
var needsUpdate = model === driveListModel && folder === folderListModel.folder;
|
||||
|
||||
model = folderListModel;
|
||||
folderListModel.folder = folder;
|
||||
helper.monitorDirectory(helper.urlToPath(folder));
|
||||
|
||||
if (needsUpdate) {
|
||||
update();
|
||||
|
|
|
@ -78,7 +78,10 @@ Decoration {
|
|||
id: closeClickArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: window.shown = false;
|
||||
onClicked: {
|
||||
window.shown = false;
|
||||
window.windowClosed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ Fadable {
|
|||
//
|
||||
// Signals
|
||||
//
|
||||
signal windowClosed();
|
||||
signal windowDestroyed();
|
||||
signal mouseEntered();
|
||||
signal mouseExited();
|
||||
|
|
|
@ -62,8 +62,10 @@ void AssetMappingsScriptingInterface::getMapping(QString path, QJSValue callback
|
|||
auto request = assetClient->createGetMappingRequest(path);
|
||||
|
||||
connect(request, &GetMappingRequest::finished, this, [this, callback](GetMappingRequest* request) mutable {
|
||||
auto hash = request->getHash();
|
||||
|
||||
if (callback.isCallable()) {
|
||||
QJSValueList args { request->getErrorString() };
|
||||
QJSValueList args { request->getErrorString(), hash };
|
||||
callback.call(args);
|
||||
}
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ void Web3DOverlay::render(RenderArgs* args) {
|
|||
|
||||
batch.setModelTransform(transform);
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
geometryCache->bindSimpleProgram(batch, true, false, false, true, false);
|
||||
geometryCache->bindSimpleSRGBTexturedUnlitNoTexAlphaProgram(batch);
|
||||
geometryCache->renderQuad(batch, halfSize * -1.0f, halfSize, vec2(0), vec2(1), color);
|
||||
batch.setResourceTexture(0, args->_whiteTexture); // restore default white color after me
|
||||
}
|
||||
|
|
|
@ -119,6 +119,9 @@ AudioClient::AudioClient() :
|
|||
this, &AudioClient::processReceivedSamples, Qt::DirectConnection);
|
||||
connect(this, &AudioClient::changeDevice, this, [=](const QAudioDeviceInfo& outputDeviceInfo) { switchOutputToAudioDevice(outputDeviceInfo); });
|
||||
|
||||
connect(&_receivedAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioClient::handleMismatchAudioFormat);
|
||||
|
||||
|
||||
_inputDevices = getDeviceNames(QAudio::AudioInput);
|
||||
_outputDevices = getDeviceNames(QAudio::AudioOutput);
|
||||
|
||||
|
@ -147,6 +150,12 @@ AudioClient::~AudioClient() {
|
|||
}
|
||||
}
|
||||
|
||||
void AudioClient::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) {
|
||||
qDebug() << __FUNCTION__ << "sendingNode:" << *node << "currentCodec:" << currentCodec << "recievedCodec:" << recievedCodec;
|
||||
selectAudioFormat(recievedCodec);
|
||||
}
|
||||
|
||||
|
||||
void AudioClient::reset() {
|
||||
_receivedAudioStream.reset();
|
||||
_stats.reset();
|
||||
|
@ -532,7 +541,13 @@ void AudioClient::negotiateAudioFormat() {
|
|||
}
|
||||
|
||||
void AudioClient::handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message) {
|
||||
_selectedCodecName = message->readString();
|
||||
QString selectedCodecName = message->readString();
|
||||
selectAudioFormat(selectedCodecName);
|
||||
}
|
||||
|
||||
void AudioClient::selectAudioFormat(const QString& selectedCodecName) {
|
||||
|
||||
_selectedCodecName = selectedCodecName;
|
||||
|
||||
qDebug() << "Selected Codec:" << _selectedCodecName;
|
||||
|
||||
|
|
|
@ -104,6 +104,7 @@ public:
|
|||
};
|
||||
|
||||
void negotiateAudioFormat();
|
||||
void selectAudioFormat(const QString& selectedCodecName);
|
||||
|
||||
const MixedProcessedAudioStream& getReceivedAudioStream() const { return _receivedAudioStream; }
|
||||
MixedProcessedAudioStream& getReceivedAudioStream() { return _receivedAudioStream; }
|
||||
|
@ -153,6 +154,7 @@ public slots:
|
|||
void handleNoisyMutePacket(QSharedPointer<ReceivedMessage> message);
|
||||
void handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message);
|
||||
void handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec);
|
||||
|
||||
void sendDownstreamAudioStatsPacket() { _stats.sendDownstreamAudioStatsPacket(); }
|
||||
void handleAudioInput();
|
||||
|
|
|
@ -147,7 +147,7 @@ int InboundAudioStream::parseData(ReceivedMessage& message) {
|
|||
writeDroppableSilentSamples(networkSamples);
|
||||
// inform others of the mismatch
|
||||
auto sendingNode = DependencyManager::get<NodeList>()->nodeWithUUID(message.getSourceID());
|
||||
emit mismatchedAudioCodec(sendingNode, _selectedCodecName);
|
||||
emit mismatchedAudioCodec(sendingNode, _selectedCodecName, codecInPacket);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -182,7 +182,7 @@ public:
|
|||
void cleanupCodec();
|
||||
|
||||
signals:
|
||||
void mismatchedAudioCodec(SharedNodePointer sendingNode, const QString& desiredCodec);
|
||||
void mismatchedAudioCodec(SharedNodePointer sendingNode, const QString& currentCodec, const QString& recievedCodec);
|
||||
|
||||
public slots:
|
||||
/// This function should be called every second for all the stats to function properly. If dynamic jitter buffers
|
||||
|
|
|
@ -916,6 +916,16 @@ bool RenderableModelEntityItem::contains(const glm::vec3& point) const {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool RenderableModelEntityItem::shouldBePhysical() const {
|
||||
// If we have a model, make sure it hasn't failed to download.
|
||||
// If it has, we'll report back that we shouldn't be physical so that physics aren't held waiting for us to be ready.
|
||||
if (_model && _model->didGeometryRequestFail()) {
|
||||
return false;
|
||||
} else {
|
||||
return ModelEntityItem::shouldBePhysical();
|
||||
}
|
||||
}
|
||||
|
||||
glm::quat RenderableModelEntityItem::getAbsoluteJointRotationInObjectFrame(int index) const {
|
||||
if (_model) {
|
||||
glm::quat result;
|
||||
|
|
|
@ -65,6 +65,8 @@ public:
|
|||
|
||||
virtual bool contains(const glm::vec3& point) const override;
|
||||
|
||||
virtual bool shouldBePhysical() const override;
|
||||
|
||||
// these are in the frame of this object (model space)
|
||||
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
|
||||
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
|
||||
|
|
|
@ -206,17 +206,15 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
|
|||
if (!success) {
|
||||
return;
|
||||
}
|
||||
bool textured = false, culled = false, emissive = false;
|
||||
if (_texture) {
|
||||
batch._glActiveBindTexture(GL_TEXTURE0, GL_TEXTURE_2D, _texture);
|
||||
textured = emissive = true;
|
||||
}
|
||||
|
||||
float fadeRatio = Interpolate::calculateFadeRatio(_fadeStartTime);
|
||||
bool transparent = fadeRatio < 1.0f;
|
||||
batch._glColor4f(1.0f, 1.0f, 1.0f, fadeRatio);
|
||||
|
||||
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch, textured, transparent, culled, emissive);
|
||||
DependencyManager::get<GeometryCache>()->bindSimpleSRGBTexturedUnlitNoTexAlphaProgram(batch);
|
||||
DependencyManager::get<GeometryCache>()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
}
|
||||
|
||||
|
|
|
@ -403,6 +403,8 @@ void GeometryResourceWatcher::setResource(GeometryResource::Pointer resource) {
|
|||
void GeometryResourceWatcher::resourceFinished(bool success) {
|
||||
if (success) {
|
||||
_geometryRef = std::make_shared<Geometry>(*_resource);
|
||||
} else {
|
||||
emit resourceFailed();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -111,6 +111,9 @@ public:
|
|||
|
||||
QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); }
|
||||
|
||||
signals:
|
||||
void resourceFailed();
|
||||
|
||||
private:
|
||||
void startWatching();
|
||||
void stopWatching();
|
||||
|
|
|
@ -200,8 +200,9 @@ void AccountManager::sendRequest(const QString& path,
|
|||
const JSONCallbackParameters& callbackParams,
|
||||
const QByteArray& dataByteArray,
|
||||
QHttpMultiPart* dataMultiPart,
|
||||
const QVariantMap& propertyMap) {
|
||||
|
||||
const QVariantMap& propertyMap,
|
||||
QUrlQuery query) {
|
||||
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "sendRequest",
|
||||
Q_ARG(const QString&, path),
|
||||
|
@ -213,9 +214,9 @@ void AccountManager::sendRequest(const QString& path,
|
|||
Q_ARG(QVariantMap, propertyMap));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
||||
|
||||
QNetworkRequest networkRequest;
|
||||
|
||||
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter());
|
||||
|
@ -224,13 +225,17 @@ void AccountManager::sendRequest(const QString& path,
|
|||
uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit());
|
||||
|
||||
QUrl requestURL = _authURL;
|
||||
|
||||
|
||||
if (path.startsWith("/")) {
|
||||
requestURL.setPath(path);
|
||||
} else {
|
||||
requestURL.setPath("/" + path);
|
||||
}
|
||||
|
||||
|
||||
if (!query.isEmpty()) {
|
||||
requestURL.setQuery(query);
|
||||
}
|
||||
|
||||
if (authType != AccountManagerAuth::None ) {
|
||||
if (hasValidAccessToken()) {
|
||||
networkRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER,
|
||||
|
@ -241,22 +246,21 @@ void AccountManager::sendRequest(const QString& path,
|
|||
<< path << "that requires authentication";
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
networkRequest.setUrl(requestURL);
|
||||
|
||||
|
||||
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
|
||||
qCDebug(networking) << "Making a request to" << qPrintable(requestURL.toString());
|
||||
|
||||
|
||||
if (!dataByteArray.isEmpty()) {
|
||||
qCDebug(networking) << "The POST/PUT body -" << QString(dataByteArray);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QNetworkReply* networkReply = NULL;
|
||||
|
||||
|
||||
switch (operation) {
|
||||
case QNetworkAccessManager::GetOperation:
|
||||
networkReply = networkAccessManager.get(networkRequest);
|
||||
|
@ -269,7 +273,7 @@ void AccountManager::sendRequest(const QString& path,
|
|||
} else {
|
||||
networkReply = networkAccessManager.put(networkRequest, dataMultiPart);
|
||||
}
|
||||
|
||||
|
||||
// make sure dataMultiPart is destroyed when the reply is
|
||||
connect(networkReply, &QNetworkReply::destroyed, dataMultiPart, &QHttpMultiPart::deleteLater);
|
||||
} else {
|
||||
|
@ -280,7 +284,7 @@ void AccountManager::sendRequest(const QString& path,
|
|||
networkReply = networkAccessManager.put(networkRequest, dataByteArray);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
case QNetworkAccessManager::DeleteOperation:
|
||||
networkReply = networkAccessManager.sendCustomRequest(networkRequest, "DELETE");
|
||||
|
@ -289,7 +293,7 @@ void AccountManager::sendRequest(const QString& path,
|
|||
// other methods not yet handled
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (networkReply) {
|
||||
if (!propertyMap.isEmpty()) {
|
||||
// we have properties to set on the reply so the user can check them after
|
||||
|
@ -297,18 +301,18 @@ void AccountManager::sendRequest(const QString& path,
|
|||
networkReply->setProperty(qPrintable(propertyKey), propertyMap.value(propertyKey));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (!callbackParams.isEmpty()) {
|
||||
// if we have information for a callback, insert the callbackParams into our local map
|
||||
_pendingCallbackMap.insert(networkReply, callbackParams);
|
||||
|
||||
|
||||
if (callbackParams.updateReciever && !callbackParams.updateSlot.isEmpty()) {
|
||||
callbackParams.updateReciever->connect(networkReply, SIGNAL(uploadProgress(qint64, qint64)),
|
||||
callbackParams.updateSlot.toStdString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if we ended up firing of a request, hook up to it now
|
||||
connect(networkReply, SIGNAL(finished()), SLOT(processReply()));
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <QtCore/QObject>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include "NetworkAccessManager.h"
|
||||
|
||||
|
@ -67,7 +68,8 @@ public:
|
|||
const JSONCallbackParameters& callbackParams = JSONCallbackParameters(),
|
||||
const QByteArray& dataByteArray = QByteArray(),
|
||||
QHttpMultiPart* dataMultiPart = NULL,
|
||||
const QVariantMap& propertyMap = QVariantMap());
|
||||
const QVariantMap& propertyMap = QVariantMap(),
|
||||
QUrlQuery query = QUrlQuery());
|
||||
|
||||
void setIsAgent(bool isAgent) { _isAgent = isAgent; }
|
||||
|
||||
|
|
36
libraries/networking/src/GroupRank.h
Normal file
36
libraries/networking/src/GroupRank.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// GroupRank.h
|
||||
// libraries/networking/src/
|
||||
//
|
||||
// Created by Seth Alves on 2016-7-21.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_GroupRank_h
|
||||
#define hifi_GroupRank_h
|
||||
|
||||
class GroupRank {
|
||||
public:
|
||||
GroupRank() {}
|
||||
GroupRank(QUuid id, unsigned int order, QString name, unsigned int membersCount) :
|
||||
id(id), order(order), name(name), membersCount(membersCount) {}
|
||||
|
||||
QUuid id;
|
||||
int order { -1 };
|
||||
QString name;
|
||||
int membersCount { -1 };
|
||||
};
|
||||
|
||||
inline bool operator==(const GroupRank& lhs, const GroupRank& rhs) {
|
||||
return
|
||||
lhs.id == rhs.id &&
|
||||
lhs.order == rhs.order &&
|
||||
lhs.name == rhs.name &&
|
||||
lhs.membersCount == rhs.membersCount;
|
||||
}
|
||||
inline bool operator!=(const GroupRank& lhs, const GroupRank& rhs) { return !(lhs == rhs); }
|
||||
|
||||
#endif // hifi_GroupRank_h
|
|
@ -135,17 +135,25 @@ void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) {
|
|||
|
||||
_permissions = newPermissions;
|
||||
|
||||
if (originalPermissions.canAdjustLocks != newPermissions.canAdjustLocks) {
|
||||
emit isAllowedEditorChanged(_permissions.canAdjustLocks);
|
||||
if (originalPermissions.can(NodePermissions::Permission::canAdjustLocks) !=
|
||||
newPermissions.can(NodePermissions::Permission::canAdjustLocks)) {
|
||||
emit isAllowedEditorChanged(_permissions.can(NodePermissions::Permission::canAdjustLocks));
|
||||
}
|
||||
if (originalPermissions.canRezPermanentEntities != newPermissions.canRezPermanentEntities) {
|
||||
emit canRezChanged(_permissions.canRezPermanentEntities);
|
||||
if (originalPermissions.can(NodePermissions::Permission::canRezPermanentEntities) !=
|
||||
newPermissions.can(NodePermissions::Permission::canRezPermanentEntities)) {
|
||||
emit canRezChanged(_permissions.can(NodePermissions::Permission::canRezPermanentEntities));
|
||||
}
|
||||
if (originalPermissions.canRezTemporaryEntities != newPermissions.canRezTemporaryEntities) {
|
||||
emit canRezTmpChanged(_permissions.canRezTemporaryEntities);
|
||||
if (originalPermissions.can(NodePermissions::Permission::canRezTemporaryEntities) !=
|
||||
newPermissions.can(NodePermissions::Permission::canRezTemporaryEntities)) {
|
||||
emit canRezTmpChanged(_permissions.can(NodePermissions::Permission::canRezTemporaryEntities));
|
||||
}
|
||||
if (originalPermissions.canWriteToAssetServer != newPermissions.canWriteToAssetServer) {
|
||||
emit canWriteAssetsChanged(_permissions.canWriteToAssetServer);
|
||||
if (originalPermissions.can(NodePermissions::Permission::canWriteToAssetServer) !=
|
||||
newPermissions.can(NodePermissions::Permission::canWriteToAssetServer)) {
|
||||
emit canWriteAssetsChanged(_permissions.can(NodePermissions::Permission::canWriteToAssetServer));
|
||||
}
|
||||
if (originalPermissions.can(NodePermissions::Permission::canKick) !=
|
||||
newPermissions.can(NodePermissions::Permission::canKick)) {
|
||||
emit canKickChanged(_permissions.can(NodePermissions::Permission::canKick));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -106,10 +106,11 @@ public:
|
|||
void setSessionUUID(const QUuid& sessionUUID);
|
||||
|
||||
void setPermissions(const NodePermissions& newPermissions);
|
||||
bool isAllowedEditor() const { return _permissions.canAdjustLocks; }
|
||||
bool getThisNodeCanRez() const { return _permissions.canRezPermanentEntities; }
|
||||
bool getThisNodeCanRezTmp() const { return _permissions.canRezTemporaryEntities; }
|
||||
bool getThisNodeCanWriteAssets() const { return _permissions.canWriteToAssetServer; }
|
||||
bool isAllowedEditor() const { return _permissions.can(NodePermissions::Permission::canAdjustLocks); }
|
||||
bool getThisNodeCanRez() const { return _permissions.can(NodePermissions::Permission::canRezPermanentEntities); }
|
||||
bool getThisNodeCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); }
|
||||
bool getThisNodeCanWriteAssets() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); }
|
||||
bool getThisNodeCanKick() const { return _permissions.can(NodePermissions::Permission::canKick); }
|
||||
|
||||
quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); }
|
||||
QUdpSocket& getDTLSSocket();
|
||||
|
@ -258,6 +259,7 @@ signals:
|
|||
void canRezChanged(bool canRez);
|
||||
void canRezTmpChanged(bool canRezTmp);
|
||||
void canWriteAssetsChanged(bool canWriteAssets);
|
||||
void canKickChanged(bool canKick);
|
||||
|
||||
protected slots:
|
||||
void connectedForLocalSocketTest();
|
||||
|
|
|
@ -65,10 +65,11 @@ public:
|
|||
|
||||
void setPermissions(const NodePermissions& newPermissions) { _permissions = newPermissions; }
|
||||
NodePermissions getPermissions() const { return _permissions; }
|
||||
bool isAllowedEditor() const { return _permissions.canAdjustLocks; }
|
||||
bool getCanRez() const { return _permissions.canRezPermanentEntities; }
|
||||
bool getCanRezTmp() const { return _permissions.canRezTemporaryEntities; }
|
||||
bool getCanWriteToAssetServer() const { return _permissions.canWriteToAssetServer; }
|
||||
bool isAllowedEditor() const { return _permissions.can(NodePermissions::Permission::canAdjustLocks); }
|
||||
bool getCanRez() const { return _permissions.can(NodePermissions::Permission::canRezPermanentEntities); }
|
||||
bool getCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); }
|
||||
bool getCanWriteToAssetServer() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); }
|
||||
bool getCanKick() const { return _permissions.can(NodePermissions::Permission::canKick); }
|
||||
|
||||
void parseIgnoreRequestMessage(QSharedPointer<ReceivedMessage> message);
|
||||
void addIgnoredNode(const QUuid& otherNodeID);
|
||||
|
|
|
@ -283,12 +283,7 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
const QUuid& connectionToken = _domainHandler.getConnectionToken();
|
||||
|
||||
// we assume that we're on the same box as the DS if it has the same local address and
|
||||
// it didn't present us with a connection token to use for username signature
|
||||
bool localhostDomain = _domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost
|
||||
|| (_domainHandler.getSockAddr().getAddress() == _localSockAddr.getAddress() && connectionToken.isNull());
|
||||
|
||||
bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull() && !localhostDomain;
|
||||
bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull();
|
||||
|
||||
if (requiresUsernameSignature && !accountManager->getAccountInfo().hasPrivateKey()) {
|
||||
qWarning() << "A keypair is required to present a username signature to the domain-server"
|
||||
|
@ -732,7 +727,7 @@ void NodeList::ignoreNodeBySessionID(const QUuid& nodeID) {
|
|||
emit ignoredNode(nodeID);
|
||||
|
||||
} else {
|
||||
qWarning() << "UsersScriptingInterface::ignore called with an invalid ID or an ID which matches the current session ID.";
|
||||
qWarning() << "NodeList::ignoreNodeBySessionID called with an invalid ID or an ID which matches the current session ID.";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -764,3 +759,28 @@ void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeList::kickNodeBySessionID(const QUuid& nodeID) {
|
||||
// send a request to domain-server to kick the node with the given session ID
|
||||
// the domain-server will handle the persistence of the kick (via username or IP)
|
||||
|
||||
if (!nodeID.isNull() && _sessionUUID != nodeID ) {
|
||||
if (getThisNodeCanKick()) {
|
||||
// setup the packet
|
||||
auto kickPacket = NLPacket::create(PacketType::NodeKickRequest, NUM_BYTES_RFC4122_UUID, true);
|
||||
|
||||
// write the node ID to the packet
|
||||
kickPacket->write(nodeID.toRfc4122());
|
||||
|
||||
qDebug() << "Sending packet to kick node" << uuidStringWithoutCurlyBraces(nodeID);
|
||||
|
||||
sendPacket(std::move(kickPacket), _domainHandler.getSockAddr());
|
||||
} else {
|
||||
qWarning() << "You do not have permissions to kick in this domain."
|
||||
<< "Request to kick node" << uuidStringWithoutCurlyBraces(nodeID) << "will not be sent";
|
||||
}
|
||||
} else {
|
||||
qWarning() << "NodeList::kickNodeBySessionID called with an invalid ID or an ID which matches the current session ID.";
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,6 +73,8 @@ public:
|
|||
void ignoreNodeBySessionID(const QUuid& nodeID);
|
||||
bool isIgnoringNode(const QUuid& nodeID) const;
|
||||
|
||||
void kickNodeBySessionID(const QUuid& nodeID);
|
||||
|
||||
public slots:
|
||||
void reset();
|
||||
void sendDomainServerCheckIn();
|
||||
|
|
|
@ -13,78 +13,121 @@
|
|||
#include <QtCore/QDebug>
|
||||
#include "NodePermissions.h"
|
||||
|
||||
QString NodePermissions::standardNameLocalhost = QString("localhost");
|
||||
QString NodePermissions::standardNameLoggedIn = QString("logged-in");
|
||||
QString NodePermissions::standardNameAnonymous = QString("anonymous");
|
||||
NodePermissionsKey NodePermissions::standardNameLocalhost = NodePermissionsKey("localhost", 0);
|
||||
NodePermissionsKey NodePermissions::standardNameLoggedIn = NodePermissionsKey("logged-in", 0);
|
||||
NodePermissionsKey NodePermissions::standardNameAnonymous = NodePermissionsKey("anonymous", 0);
|
||||
NodePermissionsKey NodePermissions::standardNameFriends = NodePermissionsKey("friends", 0);
|
||||
|
||||
QStringList NodePermissions::standardNames = QList<QString>()
|
||||
<< NodePermissions::standardNameLocalhost
|
||||
<< NodePermissions::standardNameLoggedIn
|
||||
<< NodePermissions::standardNameAnonymous;
|
||||
<< NodePermissions::standardNameLocalhost.first
|
||||
<< NodePermissions::standardNameLoggedIn.first
|
||||
<< NodePermissions::standardNameAnonymous.first
|
||||
<< NodePermissions::standardNameFriends.first;
|
||||
|
||||
NodePermissions::NodePermissions(QMap<QString, QVariant> perms) {
|
||||
_id = perms["permissions_id"].toString().toLower();
|
||||
if (perms.contains("group_id")) {
|
||||
_groupID = perms["group_id"].toUuid();
|
||||
if (!_groupID.isNull()) {
|
||||
_groupIDSet = true;
|
||||
}
|
||||
}
|
||||
if (perms.contains("rank_id")) {
|
||||
_rankID = QUuid(perms["rank_id"].toString());
|
||||
}
|
||||
|
||||
permissions = NodePermissions::Permissions();
|
||||
permissions |= perms["id_can_connect"].toBool() ? Permission::canConnectToDomain : Permission::none;
|
||||
permissions |= perms["id_can_adjust_locks"].toBool() ? Permission::canAdjustLocks : Permission::none;
|
||||
permissions |= perms["id_can_rez"].toBool() ? Permission::canRezPermanentEntities : Permission::none;
|
||||
permissions |= perms["id_can_rez_tmp"].toBool() ? Permission::canRezTemporaryEntities : Permission::none;
|
||||
permissions |= perms["id_can_write_to_asset_server"].toBool() ? Permission::canWriteToAssetServer : Permission::none;
|
||||
permissions |= perms["id_can_connect_past_max_capacity"].toBool() ?
|
||||
Permission::canConnectPastMaxCapacity : Permission::none;
|
||||
permissions |= perms["id_can_kick"].toBool() ? Permission::canKick : Permission::none;
|
||||
}
|
||||
|
||||
QVariant NodePermissions::toVariant(QHash<QUuid, GroupRank> groupRanks) {
|
||||
QMap<QString, QVariant> values;
|
||||
values["permissions_id"] = _id;
|
||||
if (_groupIDSet) {
|
||||
values["group_id"] = _groupID;
|
||||
if (groupRanks.contains(_rankID)) {
|
||||
values["rank_id"] = _rankID;
|
||||
values["rank_name"] = groupRanks[_rankID].name;
|
||||
values["rank_order"] = groupRanks[_rankID].order;
|
||||
}
|
||||
}
|
||||
values["id_can_connect"] = can(Permission::canConnectToDomain);
|
||||
values["id_can_adjust_locks"] = can(Permission::canAdjustLocks);
|
||||
values["id_can_rez"] = can(Permission::canRezPermanentEntities);
|
||||
values["id_can_rez_tmp"] = can(Permission::canRezTemporaryEntities);
|
||||
values["id_can_write_to_asset_server"] = can(Permission::canWriteToAssetServer);
|
||||
values["id_can_connect_past_max_capacity"] = can(Permission::canConnectPastMaxCapacity);
|
||||
values["id_can_kick"] = can(Permission::canKick);
|
||||
return QVariant(values);
|
||||
}
|
||||
|
||||
void NodePermissions::setAll(bool value) {
|
||||
permissions = NodePermissions::Permissions();
|
||||
if (value) {
|
||||
permissions = ~permissions;
|
||||
}
|
||||
}
|
||||
|
||||
NodePermissions& NodePermissions::operator|=(const NodePermissions& rhs) {
|
||||
this->canConnectToDomain |= rhs.canConnectToDomain;
|
||||
this->canAdjustLocks |= rhs.canAdjustLocks;
|
||||
this->canRezPermanentEntities |= rhs.canRezPermanentEntities;
|
||||
this->canRezTemporaryEntities |= rhs.canRezTemporaryEntities;
|
||||
this->canWriteToAssetServer |= rhs.canWriteToAssetServer;
|
||||
this->canConnectPastMaxCapacity |= rhs.canConnectPastMaxCapacity;
|
||||
permissions |= rhs.permissions;
|
||||
return *this;
|
||||
}
|
||||
NodePermissions& NodePermissions::operator|=(const NodePermissionsPointer& rhs) {
|
||||
if (rhs) {
|
||||
*this |= *rhs.get();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
NodePermissionsPointer& operator|=(NodePermissionsPointer& lhs, const NodePermissionsPointer& rhs) {
|
||||
if (lhs && rhs) {
|
||||
*lhs.get() |= rhs;
|
||||
}
|
||||
return lhs;
|
||||
}
|
||||
|
||||
NodePermissions& NodePermissions::operator&=(const NodePermissions& rhs) {
|
||||
permissions &= rhs.permissions;
|
||||
return *this;
|
||||
}
|
||||
|
||||
NodePermissions NodePermissions::operator~() {
|
||||
NodePermissions result = *this;
|
||||
result.permissions = ~permissions;
|
||||
return result;
|
||||
}
|
||||
|
||||
QDataStream& operator<<(QDataStream& out, const NodePermissions& perms) {
|
||||
out << perms.canConnectToDomain;
|
||||
out << perms.canAdjustLocks;
|
||||
out << perms.canRezPermanentEntities;
|
||||
out << perms.canRezTemporaryEntities;
|
||||
out << perms.canWriteToAssetServer;
|
||||
out << perms.canConnectPastMaxCapacity;
|
||||
out << (uint)perms.permissions;
|
||||
return out;
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream& in, NodePermissions& perms) {
|
||||
in >> perms.canConnectToDomain;
|
||||
in >> perms.canAdjustLocks;
|
||||
in >> perms.canRezPermanentEntities;
|
||||
in >> perms.canRezTemporaryEntities;
|
||||
in >> perms.canWriteToAssetServer;
|
||||
in >> perms.canConnectPastMaxCapacity;
|
||||
uint permissionsInt;
|
||||
in >> permissionsInt;
|
||||
perms.permissions = (NodePermissions::Permissions)permissionsInt;
|
||||
return in;
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const NodePermissions& perms) {
|
||||
debug.nospace() << "[permissions: " << perms.getID() << " --";
|
||||
if (perms.canConnectToDomain) {
|
||||
debug.nospace() << "[permissions: " << perms.getID() << "/" << perms.getVerifiedUserName() << " -- ";
|
||||
debug.nospace() << "rank=" << perms.getRankID()
|
||||
<< ", groupID=" << perms.getGroupID() << "/" << (perms.isGroup() ? "y" : "n");
|
||||
if (perms.can(NodePermissions::Permission::canConnectToDomain)) {
|
||||
debug << " connect";
|
||||
}
|
||||
if (perms.canAdjustLocks) {
|
||||
if (perms.can(NodePermissions::Permission::canAdjustLocks)) {
|
||||
debug << " locks";
|
||||
}
|
||||
if (perms.canRezPermanentEntities) {
|
||||
if (perms.can(NodePermissions::Permission::canRezPermanentEntities)) {
|
||||
debug << " rez";
|
||||
}
|
||||
if (perms.canRezTemporaryEntities) {
|
||||
if (perms.can(NodePermissions::Permission::canRezTemporaryEntities)) {
|
||||
debug << " rez-tmp";
|
||||
}
|
||||
if (perms.canWriteToAssetServer) {
|
||||
if (perms.can(NodePermissions::Permission::canWriteToAssetServer)) {
|
||||
debug << " asset-server";
|
||||
}
|
||||
if (perms.canConnectPastMaxCapacity) {
|
||||
if (perms.can(NodePermissions::Permission::canConnectPastMaxCapacity)) {
|
||||
debug << " ignore-max-cap";
|
||||
}
|
||||
if (perms.can(NodePermissions::Permission::canKick)) {
|
||||
debug << " kick";
|
||||
}
|
||||
debug.nospace() << "]";
|
||||
return debug.nospace();
|
||||
}
|
||||
|
|
|
@ -18,90 +18,109 @@
|
|||
#include <QVariant>
|
||||
#include <QUuid>
|
||||
|
||||
#include "GroupRank.h"
|
||||
|
||||
class NodePermissions;
|
||||
using NodePermissionsPointer = std::shared_ptr<NodePermissions>;
|
||||
using NodePermissionsKey = QPair<QString, QUuid>; // name, rankID
|
||||
using NodePermissionsKeyList = QList<QPair<QString, QUuid>>;
|
||||
|
||||
|
||||
class NodePermissions {
|
||||
public:
|
||||
NodePermissions() { _id = QUuid::createUuid().toString(); }
|
||||
NodePermissions(const QString& name) { _id = name.toLower(); }
|
||||
NodePermissions(QMap<QString, QVariant> perms) {
|
||||
_id = perms["permissions_id"].toString().toLower();
|
||||
canConnectToDomain = perms["id_can_connect"].toBool();
|
||||
canAdjustLocks = perms["id_can_adjust_locks"].toBool();
|
||||
canRezPermanentEntities = perms["id_can_rez"].toBool();
|
||||
canRezTemporaryEntities = perms["id_can_rez_tmp"].toBool();
|
||||
canWriteToAssetServer = perms["id_can_write_to_asset_server"].toBool();
|
||||
canConnectPastMaxCapacity = perms["id_can_connect_past_max_capacity"].toBool();
|
||||
}
|
||||
NodePermissions() { _id = QUuid::createUuid().toString(); _rankID = QUuid(); }
|
||||
NodePermissions(const QString& name) { _id = name.toLower(); _rankID = QUuid(); }
|
||||
NodePermissions(const NodePermissionsKey& key) { _id = key.first.toLower(); _rankID = key.second; }
|
||||
NodePermissions(QMap<QString, QVariant> perms);
|
||||
|
||||
QString getID() const { return _id; }
|
||||
const QString& getID() const { return _id; } // a user-name or a group-name, not verified
|
||||
void setID(const QString& id) { _id = id; }
|
||||
void setRankID(QUuid& rankID) { _rankID = rankID; }
|
||||
const QUuid& getRankID() const { return _rankID; }
|
||||
NodePermissionsKey getKey() const { return NodePermissionsKey(_id, _rankID); }
|
||||
|
||||
// the _id member isn't authenticated and _username is.
|
||||
void setUserName(QString userName) { _userName = userName.toLower(); }
|
||||
QString getUserName() { return _userName; }
|
||||
// the _id member isn't authenticated/verified and _username is.
|
||||
void setVerifiedUserName(QString userName) { _verifiedUserName = userName.toLower(); }
|
||||
const QString& getVerifiedUserName() const { return _verifiedUserName; }
|
||||
|
||||
void setGroupID(QUuid groupID) { _groupID = groupID; if (!groupID.isNull()) { _groupIDSet = true; }}
|
||||
const QUuid& getGroupID() const { return _groupID; }
|
||||
bool isGroup() const { return _groupIDSet; }
|
||||
|
||||
bool isAssignment { false };
|
||||
|
||||
// these 3 names have special meaning.
|
||||
static QString standardNameLocalhost;
|
||||
static QString standardNameLoggedIn;
|
||||
static QString standardNameAnonymous;
|
||||
static NodePermissionsKey standardNameLocalhost;
|
||||
static NodePermissionsKey standardNameLoggedIn;
|
||||
static NodePermissionsKey standardNameAnonymous;
|
||||
static NodePermissionsKey standardNameFriends;
|
||||
static QStringList standardNames;
|
||||
|
||||
// the initializations here should match the defaults in describe-settings.json
|
||||
bool canConnectToDomain { true };
|
||||
bool canAdjustLocks { false };
|
||||
bool canRezPermanentEntities { false };
|
||||
bool canRezTemporaryEntities { false };
|
||||
bool canWriteToAssetServer { false };
|
||||
bool canConnectPastMaxCapacity { false };
|
||||
enum class Permission {
|
||||
none = 0,
|
||||
canConnectToDomain = 1,
|
||||
canAdjustLocks = 2,
|
||||
canRezPermanentEntities = 4,
|
||||
canRezTemporaryEntities = 8,
|
||||
canWriteToAssetServer = 16,
|
||||
canConnectPastMaxCapacity = 32,
|
||||
canKick = 64
|
||||
};
|
||||
Q_DECLARE_FLAGS(Permissions, Permission)
|
||||
Permissions permissions;
|
||||
|
||||
void setAll(bool value) {
|
||||
canConnectToDomain = value;
|
||||
canAdjustLocks = value;
|
||||
canRezPermanentEntities = value;
|
||||
canRezTemporaryEntities = value;
|
||||
canWriteToAssetServer = value;
|
||||
canConnectPastMaxCapacity = value;
|
||||
}
|
||||
QVariant toVariant(QHash<QUuid, GroupRank> groupRanks = QHash<QUuid, GroupRank>());
|
||||
|
||||
QVariant toVariant() {
|
||||
QMap<QString, QVariant> values;
|
||||
values["permissions_id"] = _id;
|
||||
values["id_can_connect"] = canConnectToDomain;
|
||||
values["id_can_adjust_locks"] = canAdjustLocks;
|
||||
values["id_can_rez"] = canRezPermanentEntities;
|
||||
values["id_can_rez_tmp"] = canRezTemporaryEntities;
|
||||
values["id_can_write_to_asset_server"] = canWriteToAssetServer;
|
||||
values["id_can_connect_past_max_capacity"] = canConnectPastMaxCapacity;
|
||||
return QVariant(values);
|
||||
}
|
||||
void setAll(bool value);
|
||||
|
||||
NodePermissions& operator|=(const NodePermissions& rhs);
|
||||
NodePermissions& operator|=(const NodePermissionsPointer& rhs);
|
||||
NodePermissions& operator&=(const NodePermissions& rhs);
|
||||
NodePermissions operator~();
|
||||
friend QDataStream& operator<<(QDataStream& out, const NodePermissions& perms);
|
||||
friend QDataStream& operator>>(QDataStream& in, NodePermissions& perms);
|
||||
|
||||
void clear(Permission p) { permissions &= (Permission) (~(uint)p); }
|
||||
void set(Permission p) { permissions |= p; }
|
||||
bool can(Permission p) const { return permissions.testFlag(p); }
|
||||
|
||||
protected:
|
||||
QString _id;
|
||||
QString _userName;
|
||||
QUuid _rankID { QUuid() }; // 0 unless this is for a group
|
||||
QString _verifiedUserName;
|
||||
|
||||
bool _groupIDSet { false };
|
||||
QUuid _groupID;
|
||||
};
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(NodePermissions::Permissions)
|
||||
|
||||
|
||||
// wrap QHash in a class that forces all keys to be lowercase
|
||||
class NodePermissionsMap {
|
||||
public:
|
||||
NodePermissionsMap() { }
|
||||
NodePermissionsPointer& operator[](const QString& key) { return _data[key.toLower()]; }
|
||||
NodePermissionsPointer operator[](const QString& key) const { return _data.value(key.toLower()); }
|
||||
bool contains(const QString& key) const { return _data.contains(key.toLower()); }
|
||||
QList<QString> keys() const { return _data.keys(); }
|
||||
QHash<QString, NodePermissionsPointer> get() { return _data; }
|
||||
NodePermissionsPointer& operator[](const NodePermissionsKey& key) {
|
||||
NodePermissionsKey dataKey(key.first.toLower(), key.second);
|
||||
if (!_data.contains(dataKey)) {
|
||||
_data[dataKey] = NodePermissionsPointer(new NodePermissions(key));
|
||||
}
|
||||
return _data[dataKey];
|
||||
}
|
||||
NodePermissionsPointer operator[](const NodePermissionsKey& key) const {
|
||||
return _data.value(NodePermissionsKey(key.first.toLower(), key.second));
|
||||
}
|
||||
bool contains(const NodePermissionsKey& key) const {
|
||||
return _data.contains(NodePermissionsKey(key.first.toLower(), key.second));
|
||||
}
|
||||
bool contains(const QString& keyFirst, QUuid keySecond) const {
|
||||
return _data.contains(NodePermissionsKey(keyFirst.toLower(), keySecond));
|
||||
}
|
||||
QList<NodePermissionsKey> keys() const { return _data.keys(); }
|
||||
QHash<NodePermissionsKey, NodePermissionsPointer> get() { return _data; }
|
||||
void clear() { _data.clear(); }
|
||||
void remove(const NodePermissionsKey& key) { _data.remove(key); }
|
||||
|
||||
private:
|
||||
QHash<QString, NodePermissionsPointer> _data;
|
||||
QHash<NodePermissionsKey, NodePermissionsPointer> _data;
|
||||
};
|
||||
|
||||
|
||||
|
@ -109,6 +128,8 @@ const NodePermissions DEFAULT_AGENT_PERMISSIONS;
|
|||
|
||||
QDebug operator<<(QDebug debug, const NodePermissions& perms);
|
||||
QDebug operator<<(QDebug debug, const NodePermissionsPointer& perms);
|
||||
NodePermissionsPointer& operator|=(NodePermissionsPointer& lhs, const NodePermissionsPointer& rhs);
|
||||
NodePermissionsPointer& operator&=(NodePermissionsPointer& lhs, const NodePermissionsPointer& rhs);
|
||||
NodePermissionsPointer& operator&=(NodePermissionsPointer& lhs, NodePermissions::Permission rhs);
|
||||
NodePermissionsPointer operator~(NodePermissionsPointer& lhs);
|
||||
|
||||
#endif // hifi_NodePermissions_h
|
||||
|
|
|
@ -26,7 +26,7 @@ const QSet<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>()
|
|||
<< PacketType::NodeJsonStats << PacketType::EntityQuery
|
||||
<< PacketType::OctreeDataNack << PacketType::EntityEditNack
|
||||
<< PacketType::DomainListRequest << PacketType::StopNode
|
||||
<< PacketType::DomainDisconnectRequest;
|
||||
<< PacketType::DomainDisconnectRequest << PacketType::NodeKickRequest;
|
||||
|
||||
const QSet<PacketType> NON_SOURCED_PACKETS = QSet<PacketType>()
|
||||
<< PacketType::StunResponse << PacketType::CreateAssignment << PacketType::RequestAssignment
|
||||
|
|
|
@ -98,7 +98,8 @@ public:
|
|||
NegotiateAudioFormat,
|
||||
SelectedAudioFormat,
|
||||
MoreEntityShapes,
|
||||
LAST_PACKET_TYPE = MoreEntityShapes
|
||||
NodeKickRequest,
|
||||
LAST_PACKET_TYPE = NodeKickRequest
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -374,8 +374,6 @@ void AnimDebugDraw::update() {
|
|||
}
|
||||
}
|
||||
}
|
||||
data._vertexBuffer->resize(sizeof(Vertex) * numVerts);
|
||||
data._vertexBuffer->setSubData<Vertex>(0, vertices);
|
||||
|
||||
// draw markers from shared DebugDraw singleton
|
||||
for (auto& iter : markerMap) {
|
||||
|
@ -403,6 +401,9 @@ void AnimDebugDraw::update() {
|
|||
}
|
||||
DebugDraw::getInstance().clearRays();
|
||||
|
||||
data._vertexBuffer->resize(sizeof(Vertex) * numVerts);
|
||||
data._vertexBuffer->setSubData<Vertex>(0, vertices);
|
||||
|
||||
assert((!numVerts && !v) || (numVerts == (v - &vertices[0])));
|
||||
|
||||
render::Item::Bound theBound;
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "simple_vert.h"
|
||||
#include "simple_textured_frag.h"
|
||||
#include "simple_textured_unlit_frag.h"
|
||||
#include "simple_srgb_textured_unlit_no_tex_alpha_frag.h"
|
||||
#include "glowLine_vert.h"
|
||||
#include "glowLine_geom.h"
|
||||
#include "glowLine_frag.h"
|
||||
|
@ -1762,6 +1763,38 @@ inline bool operator==(const SimpleProgramKey& a, const SimpleProgramKey& b) {
|
|||
return a.getRaw() == b.getRaw();
|
||||
}
|
||||
|
||||
void GeometryCache::bindSimpleSRGBTexturedUnlitNoTexAlphaProgram(gpu::Batch& batch) {
|
||||
batch.setPipeline(getSimpleSRGBTexturedUnlitNoTexAlphaPipeline());
|
||||
// Set a default normal map
|
||||
batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING,
|
||||
DependencyManager::get<TextureCache>()->getNormalFittingTexture());
|
||||
}
|
||||
|
||||
gpu::PipelinePointer GeometryCache::getSimpleSRGBTexturedUnlitNoTexAlphaPipeline() {
|
||||
// Compile the shaders, once
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&]() {
|
||||
auto VS = gpu::Shader::createVertex(std::string(simple_vert));
|
||||
auto PS = gpu::Shader::createPixel(std::string(simple_srgb_textured_unlit_no_tex_alpha_frag));
|
||||
|
||||
_simpleSRGBTexturedUnlitNoTexAlphaShader = gpu::Shader::createProgram(VS, PS);
|
||||
|
||||
gpu::Shader::BindingSet slotBindings;
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), render::ShapePipeline::Slot::MAP::NORMAL_FITTING));
|
||||
gpu::Shader::makeProgram(*_simpleSRGBTexturedUnlitNoTexAlphaShader, slotBindings);
|
||||
auto state = std::make_shared<gpu::State>();
|
||||
state->setCullMode(gpu::State::CULL_NONE);
|
||||
state->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
state->setBlendFunction(false,
|
||||
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
|
||||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
|
||||
_simpleSRGBTexturedUnlitNoTexAlphaPipeline = gpu::Pipeline::create(_simpleSRGBTexturedUnlitNoTexAlphaShader, state);
|
||||
});
|
||||
|
||||
return _simpleSRGBTexturedUnlitNoTexAlphaPipeline;
|
||||
}
|
||||
|
||||
void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool transparent, bool culled, bool unlit, bool depthBiased) {
|
||||
batch.setPipeline(getSimplePipeline(textured, transparent, culled, unlit, depthBiased));
|
||||
|
||||
|
|
|
@ -157,6 +157,10 @@ public:
|
|||
// Get the pipeline to render static geometry
|
||||
gpu::PipelinePointer getSimplePipeline(bool textured = false, bool transparent = false, bool culled = true,
|
||||
bool unlit = false, bool depthBias = false);
|
||||
|
||||
void bindSimpleSRGBTexturedUnlitNoTexAlphaProgram(gpu::Batch& batch);
|
||||
gpu::PipelinePointer getSimpleSRGBTexturedUnlitNoTexAlphaPipeline();
|
||||
|
||||
render::ShapePipelinePointer getOpaqueShapePipeline() { return GeometryCache::_simpleOpaquePipeline; }
|
||||
render::ShapePipelinePointer getTransparentShapePipeline() { return GeometryCache::_simpleTransparentPipeline; }
|
||||
render::ShapePipelinePointer getWireShapePipeline() { return GeometryCache::_simpleWirePipeline; }
|
||||
|
@ -418,6 +422,10 @@ private:
|
|||
static render::ShapePipelinePointer _simpleWirePipeline;
|
||||
gpu::PipelinePointer _glowLinePipeline;
|
||||
QHash<SimpleProgramKey, gpu::PipelinePointer> _simplePrograms;
|
||||
|
||||
gpu::ShaderPointer _simpleSRGBTexturedUnlitNoTexAlphaShader;
|
||||
gpu::PipelinePointer _simpleSRGBTexturedUnlitNoTexAlphaPipeline;
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_GeometryCache_h
|
||||
|
|
|
@ -102,13 +102,17 @@ Model::Model(RigPointer rig, QObject* parent) :
|
|||
_calculatedMeshTrianglesValid(false),
|
||||
_meshGroupsKnown(false),
|
||||
_isWireframe(false),
|
||||
_rig(rig) {
|
||||
_rig(rig)
|
||||
{
|
||||
// we may have been created in the network thread, but we live in the main thread
|
||||
if (_viewState) {
|
||||
moveToThread(_viewState->getMainThread());
|
||||
}
|
||||
|
||||
setSnapModelToRegistrationPoint(true, glm::vec3(0.5f));
|
||||
|
||||
// handle download failure reported by the GeometryResourceWatcher
|
||||
connect(&_renderWatcher, &GeometryResourceWatcher::resourceFailed, this, &Model::handleGeometryResourceFailure);
|
||||
}
|
||||
|
||||
Model::~Model() {
|
||||
|
@ -822,6 +826,7 @@ void Model::setURL(const QUrl& url) {
|
|||
_needsReload = true;
|
||||
_needsUpdateTextures = true;
|
||||
_meshGroupsKnown = false;
|
||||
_geometryRequestFailed = false;
|
||||
invalidCalculatedMeshBoxes();
|
||||
deleteGeometry();
|
||||
|
||||
|
|
|
@ -147,8 +147,9 @@ public:
|
|||
Q_INVOKABLE void setCollisionModelURL(const QUrl& url);
|
||||
const QUrl& getCollisionURL() const { return _collisionUrl; }
|
||||
|
||||
|
||||
bool isActive() const { return isLoaded(); }
|
||||
|
||||
bool didGeometryRequestFail() const { return _geometryRequestFailed; }
|
||||
|
||||
bool convexHullContains(glm::vec3 point);
|
||||
|
||||
|
@ -392,6 +393,11 @@ protected:
|
|||
RigPointer _rig;
|
||||
|
||||
uint32_t _deleteGeometryCounter { 0 };
|
||||
|
||||
bool _geometryRequestFailed { false };
|
||||
|
||||
private slots:
|
||||
void handleGeometryResourceFailure() { _geometryRequestFailed = true; }
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(ModelPointer)
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// simple_srgb_texture_unlit_no_tex_alpha.frag
|
||||
// fragment shader
|
||||
//
|
||||
// Created by Anthony Thibault on 7/25/16.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
<@include gpu/Color.slh@>
|
||||
<@include DeferredBufferWrite.slh@>
|
||||
|
||||
// the albedo texture
|
||||
uniform sampler2D originalTexture;
|
||||
|
||||
// the interpolated normal
|
||||
in vec3 _normal;
|
||||
in vec4 _color;
|
||||
in vec2 _texCoord0;
|
||||
|
||||
void main(void) {
|
||||
vec4 texel = texture(originalTexture, _texCoord0.st);
|
||||
texel = colorToLinearRGBA(texel);
|
||||
|
||||
const float ALPHA_THRESHOLD = 0.999;
|
||||
if (_color.a * texel.a < ALPHA_THRESHOLD) {
|
||||
packDeferredFragmentTranslucent(
|
||||
normalize(_normal),
|
||||
_color.a * texel.a,
|
||||
_color.rgb * texel.rgb,
|
||||
DEFAULT_FRESNEL,
|
||||
DEFAULT_ROUGHNESS);
|
||||
} else {
|
||||
packDeferredFragmentUnlit(
|
||||
normalize(_normal),
|
||||
1.0,
|
||||
_color.rgb * texel.rgb);
|
||||
}
|
||||
}
|
|
@ -13,7 +13,23 @@
|
|||
|
||||
#include <NodeList.h>
|
||||
|
||||
UsersScriptingInterface::UsersScriptingInterface() {
|
||||
// emit a signal when kick permissions have changed
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
connect(nodeList.data(), &LimitedNodeList::canKickChanged, this, &UsersScriptingInterface::canKickChanged);
|
||||
}
|
||||
|
||||
void UsersScriptingInterface::ignore(const QUuid& nodeID) {
|
||||
// ask the NodeList to ignore this user (based on the session ID of their node)
|
||||
DependencyManager::get<NodeList>()->ignoreNodeBySessionID(nodeID);
|
||||
}
|
||||
|
||||
void UsersScriptingInterface::kick(const QUuid& nodeID) {
|
||||
// ask the NodeList to kick the user with the given session ID
|
||||
DependencyManager::get<NodeList>()->kickNodeBySessionID(nodeID);
|
||||
}
|
||||
|
||||
bool UsersScriptingInterface::getCanKick() {
|
||||
// ask the NodeList to return our ability to kick
|
||||
return DependencyManager::get<NodeList>()->getThisNodeCanKick();
|
||||
}
|
||||
|
|
|
@ -20,8 +20,19 @@ class UsersScriptingInterface : public QObject, public Dependency {
|
|||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
Q_PROPERTY(bool canKick READ getCanKick)
|
||||
|
||||
public:
|
||||
UsersScriptingInterface();
|
||||
|
||||
public slots:
|
||||
void ignore(const QUuid& nodeID);
|
||||
void kick(const QUuid& nodeID);
|
||||
|
||||
bool getCanKick();
|
||||
|
||||
signals:
|
||||
void canKickChanged(bool canKick);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -111,6 +111,13 @@ void HifiConfigVariantMap::loadMasterAndUserConfig(const QStringList& argumentLi
|
|||
loadMapFromJSONFile(_masterConfig, masterConfigFilepath);
|
||||
}
|
||||
|
||||
// load the user config - that method replace loadMasterAndUserConfig after the 1.7 migration
|
||||
loadConfig(argumentList);
|
||||
|
||||
mergeMasterAndUserConfigs();
|
||||
}
|
||||
|
||||
void HifiConfigVariantMap::loadConfig(const QStringList& argumentList) {
|
||||
// load the user config
|
||||
const QString USER_CONFIG_FILE_OPTION = "--user-config";
|
||||
static const QString USER_CONFIG_FILE_NAME = "config.json";
|
||||
|
@ -159,12 +166,10 @@ void HifiConfigVariantMap::loadMasterAndUserConfig(const QStringList& argumentLi
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
loadMapFromJSONFile(_userConfig, _userConfigFilename);
|
||||
|
||||
mergeMasterAndUserConfigs();
|
||||
}
|
||||
|
||||
void HifiConfigVariantMap::mergeMasterAndUserConfigs() {
|
||||
|
|
|
@ -15,16 +15,22 @@
|
|||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QVariantMap>
|
||||
|
||||
QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool shouldCreateIfMissing = false);
|
||||
|
||||
class HifiConfigVariantMap {
|
||||
public:
|
||||
static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList);
|
||||
|
||||
HifiConfigVariantMap();
|
||||
void loadMasterAndUserConfig(const QStringList& argumentList);
|
||||
void loadConfig(const QStringList& argumentList);
|
||||
|
||||
const QVariant value(const QString& key) const { return _userConfig.value(key); }
|
||||
QVariant* valueForKeyPath(const QString& keyPath, bool shouldCreateIfMissing = false)
|
||||
{ return ::valueForKeyPath(_userConfig, keyPath, shouldCreateIfMissing); }
|
||||
|
||||
const QVariantMap& getMasterConfig() const { return _masterConfig; }
|
||||
QVariantMap& getUserConfig() { return _userConfig; }
|
||||
QVariantMap& getMergedConfig() { return _mergedConfig; }
|
||||
QVariantMap& getConfig() { return _userConfig; }
|
||||
|
||||
void mergeMasterAndUserConfigs();
|
||||
|
||||
|
@ -40,6 +46,4 @@ private:
|
|||
void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap);
|
||||
};
|
||||
|
||||
QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool shouldCreateIfMissing = false);
|
||||
|
||||
#endif // hifi_HifiConfigVariantMap_h
|
||||
|
|
|
@ -115,3 +115,15 @@ QList<QUrl> FileDialogHelper::urlToList(const QUrl& url) {
|
|||
return results;
|
||||
}
|
||||
|
||||
void FileDialogHelper::monitorDirectory(const QString& path) {
|
||||
if (!_fsWatcherPath.isEmpty()) {
|
||||
_fsWatcher.removePath(_fsWatcherPath);
|
||||
_fsWatcherPath = "";
|
||||
}
|
||||
|
||||
if (!path.isEmpty()) {
|
||||
_fsWatcher.addPath(path);
|
||||
_fsWatcherPath = path;
|
||||
connect(&_fsWatcher, &QFileSystemWatcher::directoryChanged, this, &FileDialogHelper::contentsChanged);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,12 @@
|
|||
#ifndef hifi_ui_FileDialogHelper_h
|
||||
#define hifi_ui_FileDialogHelper_h
|
||||
|
||||
#include <QtCore/QFileSystemWatcher>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QStandardPaths>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
|
||||
class FileDialogHelper : public QObject {
|
||||
|
@ -61,6 +62,15 @@ public:
|
|||
Q_INVOKABLE QList<QUrl> urlToList(const QUrl& url);
|
||||
|
||||
Q_INVOKABLE void openDirectory(const QString& path);
|
||||
|
||||
Q_INVOKABLE void monitorDirectory(const QString& path);
|
||||
|
||||
signals:
|
||||
void contentsChanged();
|
||||
|
||||
private:
|
||||
QFileSystemWatcher _fsWatcher;
|
||||
QString _fsWatcherPath;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ public:
|
|||
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
|
||||
static QMessageBox::StandardButton question(const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
|
||||
static QMessageBox::StandardButton warning(const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
|
||||
|
|
|
@ -124,6 +124,10 @@ void QmlWindowClass::initQml(QVariantMap properties) {
|
|||
// Forward messages received from QML on to the script
|
||||
connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);
|
||||
connect(_qmlWindow, SIGNAL(visibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection);
|
||||
|
||||
connect(_qmlWindow, SIGNAL(resized(QSizeF)), this, SIGNAL(resized(QSizeF)), Qt::QueuedConnection);
|
||||
connect(_qmlWindow, SIGNAL(moved(QVector2D)), this, SLOT(hasMoved(QVector2D)), Qt::QueuedConnection);
|
||||
connect(_qmlWindow, SIGNAL(windowClosed()), this, SLOT(hasClosed()), Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
Q_ASSERT(_qmlWindow);
|
||||
|
@ -259,7 +263,12 @@ void QmlWindowClass::close() {
|
|||
}
|
||||
}
|
||||
|
||||
void QmlWindowClass::hasMoved(QVector2D position) {
|
||||
emit moved(glm::vec2(position.x(), position.y()));
|
||||
}
|
||||
|
||||
void QmlWindowClass::hasClosed() {
|
||||
emit closed();
|
||||
}
|
||||
|
||||
void QmlWindowClass::raise() {
|
||||
|
|
|
@ -62,6 +62,7 @@ signals:
|
|||
void fromQml(const QVariant& message);
|
||||
|
||||
protected slots:
|
||||
void hasMoved(QVector2D);
|
||||
void hasClosed();
|
||||
void qmlToScript(const QVariant& message);
|
||||
|
||||
|
|
|
@ -182,6 +182,12 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, const control
|
|||
_poseStateMap.clear();
|
||||
_buttonPressedMap.clear();
|
||||
|
||||
ovrSessionStatus status;
|
||||
if (!OVR_SUCCESS(ovr_GetSessionStatus(_parent._session, &status)) || (ovrFalse == status.HmdMounted)) {
|
||||
// if the HMD isn't on someone's head, don't take input from the controllers
|
||||
return;
|
||||
}
|
||||
|
||||
int numTrackedControllers = 0;
|
||||
static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked & ovrStatus_PositionTracked;
|
||||
auto tracking = ovr_GetTrackingState(_parent._session, 0, false);
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
//
|
||||
HIFI_PUBLIC_BUCKET = 'http://s3.amazonaws.com/hifi-public/';
|
||||
|
||||
Script.include('../../libraries/toolBars.js');
|
||||
Script.include('/~/system/libraries/toolBars.js');
|
||||
|
||||
const DEFAULT_NUM_LAYERS = 16;
|
||||
const DEFAULT_BASE_DIMENSION = { x: 7, y: 2, z: 7 };
|
||||
|
|
|
@ -17,7 +17,7 @@ Script.load("system/goto.js");
|
|||
Script.load("system/hmd.js");
|
||||
Script.load("system/marketplace.js");
|
||||
Script.load("system/edit.js");
|
||||
Script.load("system/ignore.js");
|
||||
Script.load("system/mod.js");
|
||||
Script.load("system/selectAudioDevice.js");
|
||||
Script.load("system/notifications.js");
|
||||
Script.load("system/controllers/handControllerGrab.js");
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, 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 23.7 34.7" style="enable-background:new 0 0 23.7 34.7;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#414042;}
|
||||
.st1{fill:#58595B;}
|
||||
.st2{fill:#EF3B4E;}
|
||||
</style>
|
||||
<path class="st0" d="M23,12.5c0-6.2-5-11.2-11.2-11.2c-6.2,0-11.2,5-11.2,11.2c0,5.2,3.6,9.6,8.4,10.8l3.2,10.1l2.6-10.2
|
||||
C19.6,21.8,23,17.5,23,12.5z M11.9,22.2c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
C21.6,17.9,17.3,22.2,11.9,22.2z"/>
|
||||
<path class="st0" d="M12.3,33.9L9,23.4c-5-1.3-8.5-5.8-8.5-10.9c0-6.2,5.1-11.3,11.3-11.3c6.2,0,11.3,5.1,11.3,11.3
|
||||
c0,5-3.3,9.4-8.1,10.8L12.3,33.9z M11.9,1.4c-6.1,0-11.1,5-11.1,11.1c0,5.1,3.4,9.5,8.3,10.7l0.1,0l0,0.1l3.1,9.7l2.5-9.9l0.1,0
|
||||
c4.7-1.4,8-5.7,8-10.6C22.9,6.4,18,1.4,11.9,1.4z M11.9,22.4c-5.5,0-9.9-4.4-9.9-9.9s4.4-9.9,9.9-9.9s9.9,4.4,9.9,9.9
|
||||
S17.3,22.4,11.9,22.4z M11.9,2.8c-5.3,0-9.7,4.3-9.7,9.7c0,5.3,4.3,9.7,9.7,9.7s9.7-4.3,9.7-9.7C21.5,7.1,17.2,2.8,11.9,2.8z"/>
|
||||
<g>
|
||||
<path class="st0" d="M16,8.3c-0.4-0.4-1.1-0.4-1.5,0L11.8,11L9,8.2c-0.4-0.4-1.1-0.4-1.5,0C7.1,8.7,7,9.4,7.5,9.8l2.7,2.7l-2.7,2.7
|
||||
c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0c0.4-0.4,0.4-1.1,0-1.5l-2.7-2.7l2.7-2.7
|
||||
C16.5,9.4,16.5,8.7,16,8.3z"/>
|
||||
<path class="st1" d="M8.3,17.3c-0.3,0-0.6-0.1-0.9-0.4C7.2,16.7,7,16.4,7,16.1c0-0.3,0.1-0.6,0.4-0.9l2.6-2.7L7.4,9.9
|
||||
c-0.5-0.5-0.5-1.2,0-1.7c0.5-0.5,1.2-0.5,1.7,0l2.7,2.7l2.6-2.7c0.2-0.2,0.5-0.4,0.9-0.4l0,0c0.3,0,0.6,0.1,0.9,0.4l0,0
|
||||
c0,0,0,0,0,0l0,0c0.2,0.2,0.4,0.5,0.4,0.9c0,0.3-0.1,0.6-0.4,0.9l-2.7,2.7l2.7,2.6c0.2,0.2,0.4,0.5,0.4,0.9c0,0.3-0.1,0.6-0.4,0.9
|
||||
c-0.5,0.5-1.3,0.5-1.7,0l-2.7-2.6l-2.7,2.7C8.9,17.2,8.6,17.3,8.3,17.3z M8.3,8.4C8.3,8.4,8.3,8.4,8.3,8.4C8,8.4,7.7,8.3,7.6,8.5
|
||||
C7.4,8.7,7.3,8.9,7.3,9.1c0,0.3,0.1,0.5,0.3,0.6l2.8,2.8l-2.8,2.8c-0.4,0.4-0.4,1,0,1.4c0.4,0.4,1,0.4,1.4,0l2.8-2.8l2.8,2.8
|
||||
c0.4,0.4,1,0.4,1.4,0c0.2-0.2,0.3-0.4,0.3-0.7c0-0.3-0.1-0.5-0.3-0.7l-2.8-2.8L16,9.7c0.2-0.2,0.3-0.4,0.3-0.7
|
||||
c0-0.3-0.1-0.5-0.3-0.7l0,0c-0.2-0.2-0.4-0.3-0.7-0.3l0,0c-0.3,0-0.5,0.1-0.7,0.3l-2.8,2.8L8.9,8.5C8.8,8.3,8.5,8.4,8.3,8.4z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st2" d="M23,12c0-6.2-5-11.2-11.2-11.2C5.7,0.8,0.7,5.9,0.7,12c0,5.2,3.6,9.6,8.4,10.8L12.3,33L15,22.8
|
||||
C19.6,21.4,23,17.1,23,12z M11.9,21.8c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
C21.7,17.4,17.3,21.8,11.9,21.8z"/>
|
||||
<path class="st2" d="M16.1,7.8c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7L9,7.8c-0.4-0.4-1.1-0.4-1.5,0C7.1,8.3,7.1,9,7.5,9.4l2.7,2.7
|
||||
l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0c0.4-0.4,0.4-1.1,0-1.5l-2.7-2.7
|
||||
l2.7-2.7C16.5,9,16.5,8.3,16.1,7.8z"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.8 KiB |
53
scripts/system/assets/images/ignore-target.svg
Normal file
53
scripts/system/assets/images/ignore-target.svg
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, 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 293 225.4" style="enable-background:new 0 0 293 225.4;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#EF3B4E;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
</style>
|
||||
<polygon points="121.5,121.9 152,219 177.7,121.9 "/>
|
||||
<polygon class="st0" points="119.2,119 149.7,216.1 175.4,119 "/>
|
||||
<path d="M16.2,23.2H283c3.9,0,7.1,3.2,7.1,7.1V88c0,3.9-3.2,7.1-7.1,7.1H16.2c-3.9,0-7.1-3.2-7.1-7.1V30.3
|
||||
C9.1,26.4,12.3,23.2,16.2,23.2z"/>
|
||||
<path class="st0" d="M13.9,20.3h266.8c3.9,0,7.1,3.2,7.1,7.1v57.7c0,3.9-3.2,7.1-7.1,7.1H13.9c-3.9,0-7.1-3.2-7.1-7.1V27.4
|
||||
C6.8,23.5,10,20.3,13.9,20.3z"/>
|
||||
<path d="M40,79.8v-41h8v41H40z"/>
|
||||
<path d="M85.6,75.2c-2.8,3.1-6.7,4.9-10.9,4.9c-2.6-0.1-5.2-0.8-7.5-2c-2.1-1.1-4-2.6-5.6-4.3c-1.7-2-3.1-4.3-4-6.7
|
||||
c-1.2-2.6-1.8-5.4-1.9-8.2c0-2.6,0.6-5.3,1.5-7.7c1-2.5,2.5-4.7,4.4-6.6c1.6-1.8,3.6-3.3,5.8-4.4c5.6-2.5,12-2.2,17.4,0.8
|
||||
c2.5,1.6,4.5,3.9,5.8,6.6l-6,4.4c-0.8-1.9-2-3.5-3.7-4.7c-1.7-1.1-3.7-1.6-5.8-1.6c-1.6,0-3.2,0.3-4.6,1.1c-1.4,0.7-2.5,1.8-3.5,3
|
||||
c-1,1.3-1.7,2.8-2.2,4.4c-0.5,1.7-0.8,3.5-0.8,5.3c0,1.8,0.3,3.7,0.9,5.4c0.5,1.6,1.3,3.1,2.4,4.3c1,1.2,2.3,2.2,3.7,2.9
|
||||
c1.4,0.7,3,1.1,4.6,1.1c4-0.2,7.7-2.2,10-5.5v-2.9h-8.3v-5.8h14.8v21h-6.6V75.2z"/>
|
||||
<path d="M108.4,53.5v26.3h-8.3V38.7h6.2l21.4,27V38.8h8v41h-6.2L108.4,53.5z"/>
|
||||
<path d="M163.1,80.1c-2.8-0.1-5.5-0.7-7.9-2c-2.4-1.1-4.5-2.7-6.2-4.7c-1.7-2-3.1-4.2-4-6.7c-0.9-2.5-1.4-5.1-1.3-7.7
|
||||
c0-5.4,2-10.6,5.6-14.6c1.7-1.9,3.8-3.5,6.2-4.6c2.4-1.2,5.1-1.8,7.8-1.7c2.7,0,5.5,0.6,7.9,1.8c2.4,1.2,4.5,2.8,6.2,4.8
|
||||
c3.4,4,5.3,9.1,5.3,14.4c0,2.7-0.5,5.3-1.4,7.8c-0.9,2.4-2.3,4.6-4,6.6c-1.7,1.9-3.8,3.4-6.2,4.5C168.5,79.3,165.8,80,163.1,80.1z
|
||||
M151.7,59.3c0,1.7,0.3,3.5,0.8,5.1c0.5,1.6,1.2,3.1,2.2,4.4c1,1.3,2.2,2.3,3.7,3.1c1.5,0.8,3.1,1.2,4.8,1.2
|
||||
c1.7,0.1,3.4-0.3,4.9-1.2c1.4-0.9,2.7-2.1,3.6-3.5c1-1.3,1.7-2.8,2.2-4.4c0.5-1.6,0.8-3.3,0.8-5c0-1.7-0.2-3.5-0.8-5.1
|
||||
c-0.5-1.6-1.3-3.1-2.3-4.4c-1.1-1.2-2.5-2.1-4-2.7c-3-1.5-6.6-1.5-9.6,0c-1.4,0.8-2.6,1.8-3.6,3.1c-1,1.3-1.7,2.8-2.2,4.4
|
||||
C151.8,55.9,151.6,57.6,151.7,59.3z"/>
|
||||
<path d="M190.3,79.8V38.7h18.1c1.8,0,3.6,0.4,5.2,1.2c1.6,0.8,3,1.8,4.1,3.1c1.2,1.3,2.1,2.8,2.7,4.4c0.6,1.6,1,3.2,1,4.9
|
||||
c0,2.6-0.7,5.1-2,7.2c-1.2,2.1-3.1,3.8-5.4,4.7l9.6,15.4h-8.9L206.2,66h-7.9v13.8H190.3z M198.3,58.8h9.6c0.8,0.2,1.6,0.2,2.4,0
|
||||
c0.6-0.4,1.2-0.9,1.6-1.4c0.5-0.6,0.8-1.4,1.1-2.1c0.1-0.9,0.1-1.7,0-2.6c0.2-0.9,0.2-1.8,0-2.7c-0.3-0.8-0.7-1.5-1.3-2.1
|
||||
c-0.5-0.6-1.1-1-1.8-1.3c-0.6-0.3-1.3-0.5-2-0.5h-9.6L198.3,58.8z"/>
|
||||
<path d="M259.2,72.8v6.9h-28.9V38.7h28v7h-19.6v9.6h17.3v6.7h-17.3v10.7H259.2z"/>
|
||||
<path class="st1" d="M38.1,78.1V37.2h8v40.9H38.1z"/>
|
||||
<path class="st1" d="M83.7,73.5c-2.9,3-6.9,4.6-11,4.5c-2.5,0.1-5-0.3-7.3-1.3c-2.3-1.1-4.4-2.7-6.1-4.6c-1.7-2-3.1-4.3-4-6.7
|
||||
c-1-2.5-1.5-5.2-1.4-7.9c-0.1-2.8,0.4-5.6,1.4-8.3c1-2.4,2.4-4.7,4.1-6.6c1.8-1.9,3.8-3.4,6.2-4.4c5.6-2.3,12-1.8,17.3,1.3
|
||||
c2.6,1.6,4.7,3.8,6,6.6l-6,4.4c-0.9-1.9-2.3-3.6-4.1-4.7c-1.7-1.1-3.7-1.6-5.8-1.6c-1.6,0-3.2,0.3-4.6,1.1c-1.4,0.7-2.5,1.8-3.5,3
|
||||
c-1,1.3-1.7,2.8-2.2,4.4c-0.6,1.6-1,3.3-1.1,5c0,1.8,0.3,3.7,0.9,5.4c0.5,1.6,1.3,3.1,2.4,4.3c1,1.2,2.3,2.2,3.7,2.9
|
||||
c1.4,0.7,3,1.1,4.6,1.1c4.2,0,8.2-2.1,10.6-5.5v-2.9h-8.3V57h15v21h-6.7V73.5z"/>
|
||||
<path class="st1" d="M106.6,51.8v26.2h-8v-41h6.2l21.4,27V37.2h8v40.9h-6.5L106.6,51.8z"/>
|
||||
<path class="st1" d="M161.2,78.1c-2.7,0-5.4-0.6-7.9-1.7c-2.4-1.1-4.5-2.7-6.2-4.7c-1.7-2-3.1-4.2-4-6.7c-0.9-2.5-1.4-5.1-1.3-7.7
|
||||
c0-5.4,2-10.6,5.6-14.6c1.7-1.9,3.8-3.5,6.2-4.6c2.4-1.2,5.1-1.8,7.8-1.7c2.7,0,5.5,0.6,7.9,1.8c2.4,1.2,4.5,2.8,6.2,4.8
|
||||
c3.4,4,5.3,9.1,5.3,14.4c0,2.7-0.5,5.3-1.4,7.8c-0.9,2.4-2.3,4.6-4,6.6c-1.7,1.9-3.8,3.4-6.2,4.5C166.7,77.4,164,78.1,161.2,78.1z
|
||||
M149.9,57.6c0,1.7,0.3,3.5,0.8,5.1c0.5,1.6,1.2,3.1,2.2,4.4c1,1.3,2.2,2.3,3.7,3.1c1.5,0.8,3.1,1.2,4.8,1.2
|
||||
c1.7,0.1,3.4-0.3,4.9-1.2c1.4-0.8,2.6-1.9,3.6-3.2c1-1.3,1.7-2.8,2.2-4.4c0.5-1.6,0.8-3.3,0.8-5c0-1.7-0.2-3.5-0.8-5.1
|
||||
c-0.5-1.6-1.3-3.1-2.3-4.4c-1-1.2-2.2-2.3-3.6-3c-3-1.5-6.6-1.5-9.6,0c-1.4,0.8-2.6,1.8-3.6,3.1c-1,1.3-1.7,2.8-2.2,4.4
|
||||
C150.2,54.2,149.9,55.9,149.9,57.6z"/>
|
||||
<path class="st1" d="M188.4,78.1v-41h17.7c1.8,0,3.6,0.4,5.2,1.2c1.7,0.7,3.2,1.8,4.4,3.2c1.2,1.3,2.1,2.8,2.7,4.4
|
||||
c0.6,1.6,1,3.2,1,4.9c0,2.6-0.7,5.1-2,7.2c-1.2,2.1-3.1,3.8-5.4,4.7l9.6,15.4h-8.8l-8.6-13.8h-7.8v13.7H188.4z M196.5,57.4h9.6
|
||||
c0.7,0,1.4-0.2,2-0.5c0.6-0.4,1.2-0.9,1.6-1.4c0.5-0.6,0.8-1.4,1.1-2.1c0.1-0.9,0.1-1.7,0-2.6c0.2-0.9,0.2-1.8,0-2.7
|
||||
c-0.3-0.8-0.7-1.5-1.3-2.1c-0.5-0.6-1.1-1-1.8-1.3c-0.5-0.3-1.1-0.4-1.6-0.5h-9.6V57.4z"/>
|
||||
<path class="st1" d="M257.4,71.1v6.9h-28.9v-41h28v7h-19.6v9.6h17.5v6.7h-17.5v10.7H257.4z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 4.6 KiB |
33
scripts/system/assets/images/kick-target.svg
Normal file
33
scripts/system/assets/images/kick-target.svg
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, 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 293 225.4" style="enable-background:new 0 0 293 225.4;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#EF3B4E;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
</style>
|
||||
<path d="M15.1,22.7h266.8c3.9,0,7.1,3.2,7.1,7.1v57.7c0,3.9-3.2,7.1-7.1,7.1H15.1c-3.9,0-7.1-3.2-7.1-7.1V29.9
|
||||
C7.9,25.9,11.1,22.7,15.1,22.7z"/>
|
||||
<polygon points="120.4,121.9 150.8,219 176.6,121.9 "/>
|
||||
<path class="st0" d="M12.8,19.9h266.8c3.9,0,7.1,3.2,7.1,7.1v57.7c0,3.9-3.2,7.1-7.1,7.1H12.8c-3.9,0-7.1-3.2-7.1-7.1V27
|
||||
C5.6,23,8.8,19.9,12.8,19.9z"/>
|
||||
<path d="M81.8,74.3V34.8h7.5v19.3l17.2-19.3h8L99,52.3l16.1,22h-7.7L94.8,56.7l-5.5,5.7v12H81.8z"/>
|
||||
<path d="M120.4,74.3V34.8h7.5v39.5h-7.7H120.4z"/>
|
||||
<path d="M135.4,54.3c0-2.4,0.4-4.8,1.3-7.1c0.8-2.3,2.1-4.5,3.7-6.4c1.7-1.9,3.7-3.4,6-4.5c2.5-1.2,5.2-1.8,8-1.7
|
||||
c3.4-0.2,6.7,0.7,9.6,2.4c2.5,1.5,4.4,3.7,5.7,6.3l-5.9,4c-0.4-1-1-2-1.7-2.8c-0.7-0.7-1.4-1.4-2.3-1.8c-0.8-0.4-1.7-0.8-2.6-1
|
||||
c-0.9-0.1-1.7-0.1-2.6,0c-1.7,0-3.3,0.4-4.8,1.2c-1.4,0.8-2.6,1.8-3.5,3.1c-0.9,1.3-1.6,2.7-2,4.2c-0.5,1.5-0.7,3.1-0.7,4.7
|
||||
c0,1.7,0.2,3.4,0.8,5c0.5,1.5,1.3,3,2.3,4.2c1,1.2,2.2,2.2,3.6,3c1.4,0.8,2.9,1.1,4.5,1.2c0.7,0,1.4-0.1,2.1-0.3
|
||||
c1.9-0.4,3.6-1.5,4.9-2.9c0.7-0.8,1.3-1.8,1.7-2.8l6.3,3.7c-0.6,1.5-1.5,2.8-2.6,4c-1.1,1.1-2.4,2.1-3.9,2.9c-1.4,0.8-3,1.4-4.5,1.8
|
||||
c-1.5,0.4-3.1,0.6-4.7,0.6c-2.6,0-5.2-0.6-7.5-1.8c-2.3-1.4-4.3-3.2-5.9-5.4C137.3,64.1,135.5,59.3,135.4,54.3z"/>
|
||||
<path d="M176.1,74.3V34.8h7.7v19.3L201,34.8h8l-15.1,17.5l16.1,22H202l-12.6-17.6l-5.3,5.8v12L176.1,74.3L176.1,74.3z"/>
|
||||
<path class="st1" d="M79.7,72.7V33.2h7.7v19.3l17.2-19.3h8L97.6,50.6l16.1,22h-8.1L93,54.9l-5.3,5.8v12H79.7z"/>
|
||||
<path class="st1" d="M118.2,72.7V33.2h7.7v39.5H118.2z"/>
|
||||
<path class="st1" d="M133.6,52.5c0-2.4,0.4-4.8,1.3-7.1c0.8-2.3,2.1-4.5,3.8-6.4c1.7-1.9,3.7-3.4,6-4.5c2.5-1.2,5.2-1.8,8-1.7
|
||||
c3.4-0.2,6.7,0.7,9.6,2.4c2.5,1.5,4.4,3.7,5.7,6.3l-5.9,4c-0.4-1-1-2-1.7-2.8c-0.7-0.7-1.4-1.4-2.3-1.8c-0.8-0.4-1.7-0.8-2.6-1
|
||||
c-0.9-0.1-1.7-0.1-2.6,0c-1.7,0-3.3,0.4-4.8,1.2c-1.4,0.8-2.6,1.8-3.5,3.1c-0.9,1.3-1.6,2.7-2,4.2c-0.5,1.5-0.7,3.1-0.7,4.7
|
||||
c0,1.7,0.2,3.4,0.8,5c0.5,1.5,1.3,3,2.3,4.2c1,1.2,2.2,2.2,3.6,3c1.4,0.8,2.9,1.1,4.5,1.2c0.9,0.1,1.7,0.1,2.6,0
|
||||
c1.9-0.4,3.6-1.5,4.9-2.9c0.7-0.8,1.3-1.8,1.7-2.8l6.3,3.7c-0.6,1.5-1.5,2.8-2.6,4c-1.1,1.1-2.4,2.1-3.9,2.9c-1.4,0.8-3,1.4-4.5,1.8
|
||||
c-1.5,0.4-3.1,0.6-4.7,0.6c-2.6,0-5.2-0.6-7.5-1.8c-2.2-1.2-4.2-2.7-5.9-4.6C135.6,63.3,133.6,58,133.6,52.5z"/>
|
||||
<path class="st1" d="M174.6,72.7V33.2h7.7v19.3l17.2-19.3h8l-15.2,17.5l16.1,22h-8.1l-12.6-17.6l-5.3,5.8v12H174.6z"/>
|
||||
<polygon class="st0" points="118,119 148.5,216.1 174.3,119 "/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="540" height="90" viewBox="0 0 540.00 90.00" enable-background="new 0 0 540.00 90.00" xml:space="preserve">
|
||||
<path fill="#343434" fill-opacity="0.803922" stroke-width="0.2" stroke-linejoin="round" d="M 8.00003,2.28882e-005L 532,2.28882e-005C 536.418,2.28882e-005 540,3.58175 540,8.00002L 540,82C 540,86.4183 536.418,90 532,90L 8.00003,90C 3.58174,90 2.48421e-005,86.4183 2.48421e-005,82L 2.48421e-005,8.00002C 2.48421e-005,3.58175 3.58174,2.28882e-005 8.00003,2.28882e-005 Z "/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="520" height="50" viewBox="0 0 520.00 50.00" enable-background="new 0 0 540.00 50.00" xml:space="preserve">
|
||||
<rect x="0" y="0" rx="8" ry="8" width="520" height="50" style="fille:black;opacity:0.6" />
|
||||
</svg>
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.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="-159 536 960 30" enable-background="new -159 536 960 30" xml:space="preserve">
|
||||
<rect x="-159" y="536" fill="#4CC1B8" fill-opacity="0.8039" width="480" height="30"/>
|
||||
<rect x="321" y="536" fill="#FFFFFF" fill-opacity="0.8039" width="480" height="30"/>
|
||||
viewBox="0 0 960 10" enable-background="new -159 536 960 30" xml:space="preserve">
|
||||
<defs>
|
||||
<linearGradient id="loadingGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:rgb(16,128,184);stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:rgb(0,180,239);stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="0" y="0" fill="url(#loadingGrad)" width="480" height="10"/>
|
||||
<rect x="480" y="0" fill="#000000" fill-opacity="0.8039" width="480" height="10"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 535 B After Width: | Height: | Size: 671 B |
140
scripts/system/assets/images/tools/kick.svg
Normal file
140
scripts/system/assets/images/tools/kick.svg
Normal file
|
@ -0,0 +1,140 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, 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 50 200.1" style="enable-background:new 0 0 50 200.1;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#414042;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
.st2{fill:#1E1E1E;}
|
||||
.st3{fill:#333333;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M50.1,146.1c0,2.2-1.8,4-4,4h-42c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V146.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M50,196.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V196.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st1" d="M50,46c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4V4c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V46z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st2" d="M50,96.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V96.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<path class="st1" d="M24.7,81.2c-6.2,0-11.2-5-11.2-11.2c0-6.2,5-11.2,11.2-11.2c6.2,0,11.2,5,11.2,11.2
|
||||
C35.9,76.2,30.9,81.2,24.7,81.2z M24.7,60.3c-5.4,0-9.8,4.4-9.8,9.8c0,5.4,4.4,9.8,9.8,9.8c5.4,0,9.8-4.4,9.8-9.8
|
||||
C34.5,64.6,30.1,60.3,24.7,60.3z"/>
|
||||
<path class="st1" d="M27.1,79.5c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9c-0.3-5.9-5.2-10.6-11.2-10.6c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
|
||||
c1,0,2-0.2,3-0.4C27.5,80.4,27.3,80,27.1,79.5z"/>
|
||||
<path class="st1" d="M31.2,66.5L31.2,66.5c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
|
||||
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
|
||||
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
|
||||
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
|
||||
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
|
||||
c0.3-0.1,0.4-0.3,0.5-0.6C31.4,66.9,31.4,66.7,31.2,66.5z"/>
|
||||
<circle class="st1" cx="24.7" cy="64" r="1.7"/>
|
||||
<path class="st3" d="M27.1,29.7c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9C35.6,13.7,30.7,9,24.7,9c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
|
||||
c1,0,2-0.2,3-0.4C27.5,30.5,27.3,30.1,27.1,29.7z"/>
|
||||
<path class="st3" d="M31.2,16.6L31.2,16.6c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
|
||||
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
|
||||
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
|
||||
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
|
||||
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
|
||||
c0.3-0.1,0.4-0.3,0.5-0.6C31.4,17.1,31.4,16.8,31.2,16.6z"/>
|
||||
<circle class="st3" cx="24.7" cy="14.1" r="1.7"/>
|
||||
<g>
|
||||
<polygon class="st3" points="36.4,30.1 36.4,30.1 36.4,30.1 "/>
|
||||
<path class="st3" d="M35.2,25.8l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7c-0.4-0.4-1.1-0.4-1.5,0
|
||||
c-0.4,0.4-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0
|
||||
c0.4-0.4,0.4-1.1,0-1.5L35.2,25.8z"/>
|
||||
</g>
|
||||
<path class="st1" d="M27.3,129.6c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9c-0.3-5.9-5.2-10.6-11.2-10.6c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
|
||||
c1,0,2-0.2,3-0.4C27.6,130.5,27.4,130.1,27.3,129.6z"/>
|
||||
<path class="st1" d="M31.3,116.6L31.3,116.6c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
|
||||
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
|
||||
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
|
||||
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
|
||||
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
|
||||
c0.3-0.1,0.4-0.3,0.5-0.6C31.6,117,31.5,116.8,31.3,116.6z"/>
|
||||
<circle class="st1" cx="24.9" cy="114.1" r="1.7"/>
|
||||
<g>
|
||||
<polygon class="st1" points="36.5,130.1 36.5,130.1 36.5,130.1 "/>
|
||||
<path class="st1" d="M35.3,125.8l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7c-0.4-0.4-1.1-0.4-1.5,0
|
||||
c-0.4,0.4-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0
|
||||
c0.4-0.4,0.4-1.1,0-1.5L35.3,125.8z"/>
|
||||
</g>
|
||||
<path class="st1" d="M27.1,179.9c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9c-0.3-5.9-5.2-10.6-11.2-10.6c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
|
||||
c1,0,2-0.2,3-0.4C27.5,180.7,27.3,180.3,27.1,179.9z"/>
|
||||
<path class="st1" d="M31.2,166.8L31.2,166.8c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
|
||||
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
|
||||
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
|
||||
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
|
||||
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
|
||||
c0.3-0.1,0.4-0.3,0.5-0.6C31.4,167.3,31.4,167,31.2,166.8z"/>
|
||||
<circle class="st1" cx="24.7" cy="164.3" r="1.7"/>
|
||||
<g>
|
||||
<polygon class="st1" points="36.4,180.3 36.4,180.3 36.4,180.3 "/>
|
||||
<path class="st1" d="M35.2,176l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7c-0.4-0.4-1.1-0.4-1.5,0
|
||||
c-0.4,0.4-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0
|
||||
c0.4-0.4,0.4-1.1,0-1.5L35.2,176z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M14.7,92.3v-6.4h1.2v3.1l2.8-3.1H20l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H14.7z"/>
|
||||
<path class="st1" d="M20.9,92.3v-6.4h1.2v6.4H20.9z"/>
|
||||
<path class="st1" d="M23.3,89.1c0-0.4,0.1-0.8,0.2-1.2c0.1-0.4,0.3-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7s0.8-0.3,1.3-0.3
|
||||
c0.6,0,1.1,0.1,1.5,0.4c0.4,0.3,0.7,0.6,0.9,1l-1,0.7c-0.1-0.2-0.2-0.3-0.3-0.5c-0.1-0.1-0.2-0.2-0.4-0.3c-0.1-0.1-0.3-0.1-0.4-0.2
|
||||
c-0.1,0-0.3,0-0.4,0c-0.3,0-0.6,0.1-0.8,0.2s-0.4,0.3-0.6,0.5c-0.1,0.2-0.3,0.4-0.3,0.7s-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8
|
||||
c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.1,0,0.3,0,0.4-0.1c0.1,0,0.3-0.1,0.4-0.2
|
||||
c0.1-0.1,0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.3,0.3-0.4l1,0.6c-0.1,0.2-0.2,0.5-0.4,0.6c-0.2,0.2-0.4,0.3-0.6,0.5
|
||||
c-0.2,0.1-0.5,0.2-0.7,0.3c-0.3,0.1-0.5,0.1-0.8,0.1c-0.4,0-0.9-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-1-0.8c-0.3-0.3-0.5-0.7-0.6-1.1
|
||||
C23.4,89.9,23.3,89.5,23.3,89.1z"/>
|
||||
<path class="st1" d="M30,92.3v-6.4h1.2v3.1l2.8-3.1h1.3l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H30z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st3" d="M14.7,42.6v-6.4h1.2v3.1l2.8-3.1H20l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H14.7z"/>
|
||||
<path class="st3" d="M20.9,42.6v-6.4h1.2v6.4H20.9z"/>
|
||||
<path class="st3" d="M23.3,39.4c0-0.4,0.1-0.8,0.2-1.2c0.1-0.4,0.3-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7s0.8-0.3,1.3-0.3
|
||||
c0.6,0,1.1,0.1,1.5,0.4c0.4,0.3,0.7,0.6,0.9,1l-1,0.7c-0.1-0.2-0.2-0.3-0.3-0.5c-0.1-0.1-0.2-0.2-0.4-0.3c-0.1-0.1-0.3-0.1-0.4-0.2
|
||||
c-0.1,0-0.3,0-0.4,0c-0.3,0-0.6,0.1-0.8,0.2S25.2,37.8,25,38c-0.1,0.2-0.3,0.4-0.3,0.7s-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8
|
||||
c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.1,0,0.3,0,0.4-0.1c0.1,0,0.3-0.1,0.4-0.2
|
||||
c0.1-0.1,0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.3,0.3-0.4l1,0.6c-0.1,0.2-0.2,0.5-0.4,0.6c-0.2,0.2-0.4,0.3-0.6,0.5
|
||||
c-0.2,0.1-0.5,0.2-0.7,0.3c-0.3,0.1-0.5,0.1-0.8,0.1c-0.4,0-0.9-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-1-0.8c-0.3-0.3-0.5-0.7-0.6-1.1
|
||||
C23.4,40.2,23.3,39.8,23.3,39.4z"/>
|
||||
<path class="st3" d="M30,42.6v-6.4h1.2v3.1l2.8-3.1h1.3l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H30z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M14.8,142.4v-6.4H16v3.1l2.8-3.1h1.3l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H14.8z"/>
|
||||
<path class="st1" d="M21,142.4v-6.4h1.2v6.4H21z"/>
|
||||
<path class="st1" d="M23.4,139.2c0-0.4,0.1-0.8,0.2-1.2c0.1-0.4,0.3-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7c0.4-0.2,0.8-0.3,1.3-0.3
|
||||
c0.6,0,1.1,0.1,1.5,0.4c0.4,0.3,0.7,0.6,0.9,1l-1,0.7c-0.1-0.2-0.2-0.3-0.3-0.5c-0.1-0.1-0.2-0.2-0.4-0.3c-0.1-0.1-0.3-0.1-0.4-0.2
|
||||
c-0.1,0-0.3,0-0.4,0c-0.3,0-0.6,0.1-0.8,0.2s-0.4,0.3-0.6,0.5c-0.1,0.2-0.3,0.4-0.3,0.7s-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8
|
||||
c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.1,0,0.3,0,0.4-0.1c0.1,0,0.3-0.1,0.4-0.2
|
||||
s0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.3,0.3-0.4l1,0.6c-0.1,0.2-0.2,0.5-0.4,0.6c-0.2,0.2-0.4,0.3-0.6,0.5c-0.2,0.1-0.5,0.2-0.7,0.3
|
||||
c-0.3,0.1-0.5,0.1-0.8,0.1c-0.4,0-0.9-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-1-0.8c-0.3-0.3-0.5-0.7-0.6-1.1
|
||||
C23.5,140,23.4,139.6,23.4,139.2z"/>
|
||||
<path class="st1" d="M30.1,142.4v-6.4h1.2v3.1l2.8-3.1h1.3l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H30.1z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M14.7,192.7v-6.4h1.2v3.1l2.8-3.1H20l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H14.7z"/>
|
||||
<path class="st1" d="M20.9,192.7v-6.4h1.2v6.4H20.9z"/>
|
||||
<path class="st1" d="M23.3,189.4c0-0.4,0.1-0.8,0.2-1.2c0.1-0.4,0.3-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7c0.4-0.2,0.8-0.3,1.3-0.3
|
||||
c0.6,0,1.1,0.1,1.5,0.4s0.7,0.6,0.9,1l-1,0.7c-0.1-0.2-0.2-0.3-0.3-0.5c-0.1-0.1-0.2-0.2-0.4-0.3s-0.3-0.1-0.4-0.2
|
||||
c-0.1,0-0.3,0-0.4,0c-0.3,0-0.6,0.1-0.8,0.2s-0.4,0.3-0.6,0.5c-0.1,0.2-0.3,0.4-0.3,0.7s-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8
|
||||
c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.1,0,0.3,0,0.4-0.1c0.1,0,0.3-0.1,0.4-0.2
|
||||
c0.1-0.1,0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.3,0.3-0.4l1,0.6c-0.1,0.2-0.2,0.5-0.4,0.6c-0.2,0.2-0.4,0.3-0.6,0.5
|
||||
c-0.2,0.1-0.5,0.2-0.7,0.3c-0.3,0.1-0.5,0.1-0.8,0.1c-0.4,0-0.9-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-1-0.8c-0.3-0.3-0.5-0.7-0.6-1.1
|
||||
C23.4,190.2,23.3,189.8,23.3,189.4z"/>
|
||||
<path class="st1" d="M29.9,192.7v-6.4h1.2v3.1l2.8-3.1h1.3l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H29.9z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 10 KiB |
|
@ -70,15 +70,15 @@ var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance
|
|||
var MOVE_WITH_HEAD = true; // experimental head-control of distantly held objects
|
||||
|
||||
var COLORS_GRAB_SEARCHING_HALF_SQUEEZE = {
|
||||
red: 255,
|
||||
green: 97,
|
||||
blue: 129
|
||||
red: 10,
|
||||
green: 10,
|
||||
blue: 255
|
||||
};
|
||||
|
||||
var COLORS_GRAB_SEARCHING_FULL_SQUEEZE = {
|
||||
red: 255,
|
||||
green: 97,
|
||||
blue: 129
|
||||
red: 250,
|
||||
green: 10,
|
||||
blue: 10
|
||||
};
|
||||
|
||||
var COLORS_GRAB_DISTANCE_HOLD = {
|
||||
|
@ -1267,7 +1267,7 @@ function MyController(hand) {
|
|||
if (this.triggerSmoothedGrab()) {
|
||||
this.grabbedHotspot = potentialEquipHotspot;
|
||||
this.grabbedEntity = potentialEquipHotspot.entityID;
|
||||
this.setState(STATE_HOLD, "eqipping '" + entityPropertiesCache.getProps(this.grabbedEntity).name + "'");
|
||||
this.setState(STATE_HOLD, "equipping '" + entityPropertiesCache.getProps(this.grabbedEntity).name + "'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1378,7 +1378,7 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.distanceHoldingEnter = function() {
|
||||
|
||||
Messages.sendLocalMessage('Hifi-Teleport-Disabler','both');
|
||||
this.clearEquipHaptics();
|
||||
|
||||
// controller pose is in avatar frame
|
||||
|
@ -1534,7 +1534,9 @@ function MyController(hand) {
|
|||
|
||||
// visualizations
|
||||
|
||||
this.overlayLineOn(handPosition, grabbedProperties.position, COLORS_GRAB_DISTANCE_HOLD);
|
||||
var rayPickInfo = this.calcRayPickInfo(this.hand);
|
||||
|
||||
this.overlayLineOn(rayPickInfo.searchRay.origin, grabbedProperties.position, COLORS_GRAB_DISTANCE_HOLD);
|
||||
|
||||
var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition));
|
||||
var success = Entities.updateAction(this.grabbedEntity, this.actionID, {
|
||||
|
@ -1634,7 +1636,14 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.nearGrabbingEnter = function() {
|
||||
if (this.hand === 0) {
|
||||
Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'left');
|
||||
|
||||
}
|
||||
if (this.hand === 1) {
|
||||
Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'right');
|
||||
|
||||
}
|
||||
this.lineOff();
|
||||
this.overlayLineOff();
|
||||
|
||||
|
@ -1961,6 +1970,7 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.release = function() {
|
||||
Messages.sendLocalMessage('Hifi-Teleport-Disabler','none');
|
||||
this.turnOffVisualizations();
|
||||
|
||||
var noVelocity = false;
|
||||
|
@ -2354,9 +2364,20 @@ var handleHandMessages = function(channel, message, sender) {
|
|||
try {
|
||||
data = JSON.parse(message);
|
||||
var selectedController = (data.hand === 'left') ? leftController : rightController;
|
||||
var hotspotIndex = data.hotspotIndex !== undefined ? parseInt(data.hotspotIndex) : 0;
|
||||
selectedController.release();
|
||||
var wearableEntity = data.entityID;
|
||||
entityPropertiesCache.addEntity(wearableEntity);
|
||||
selectedController.grabbedEntity = wearableEntity;
|
||||
var hotspots = selectedController.collectEquipHotspots(selectedController.grabbedEntity);
|
||||
if (hotspots.length > 0) {
|
||||
if (hotspotIndex >= hotspots.length) {
|
||||
hotspotIndex = 0;
|
||||
}
|
||||
selectedController.grabbedHotspot = hotspots[hotspotIndex];
|
||||
}
|
||||
selectedController.setState(STATE_HOLD, "Hifi-Hand-Grab msg received");
|
||||
selectedController.grabbedEntity = data.entityID;
|
||||
selectedController.nearGrabbingEnter();
|
||||
|
||||
} catch (e) {
|
||||
print("WARNING: error parsing Hifi-Hand-Grab message");
|
||||
|
|
|
@ -508,4 +508,3 @@ Script.scriptEnding.connect(function () {
|
|||
Script.clearInterval(settingsChecker);
|
||||
OffscreenFlags.navigationFocusDisabled = false;
|
||||
});
|
||||
|
||||
|
|
|
@ -1,21 +1,13 @@
|
|||
// Created by james b. pollack @imgntn on 7/2/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Creates a beam and target and then teleports you there when you let go of either activation button.
|
||||
// Creates a beam and target and then teleports you there.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
var inTeleportMode = false;
|
||||
|
||||
var currentFadeSphereOpacity = 1;
|
||||
var fadeSphereInterval = null;
|
||||
var fadeSphereUpdateInterval = null;
|
||||
//milliseconds between fading one-tenth -- so this is a half second fade total
|
||||
var USE_FADE_MODE = false;
|
||||
var USE_FADE_OUT = true;
|
||||
var FADE_OUT_INTERVAL = 25;
|
||||
|
||||
// instant
|
||||
// var NUMBER_OF_STEPS = 0;
|
||||
// var SMOOTH_ARRIVAL_SPACING = 0;
|
||||
|
@ -36,16 +28,13 @@ var NUMBER_OF_STEPS = 6;
|
|||
// var SMOOTH_ARRIVAL_SPACING = 10;
|
||||
// var NUMBER_OF_STEPS = 20;
|
||||
|
||||
|
||||
var TARGET_MODEL_URL = Script.resolvePath("../assets/models/teleport.fbx");
|
||||
var TARGET_MODEL_DIMENSIONS = {
|
||||
x: 1.15,
|
||||
y: 0.5,
|
||||
z: 1.15
|
||||
|
||||
};
|
||||
|
||||
|
||||
var COLORS_TELEPORT_CAN_TELEPORT = {
|
||||
red: 97,
|
||||
green: 247,
|
||||
|
@ -58,14 +47,22 @@ var COLORS_TELEPORT_CANNOT_TELEPORT = {
|
|||
blue: 141
|
||||
};
|
||||
|
||||
var MAX_AVATAR_SPEED = 0.25;
|
||||
|
||||
function ThumbPad(hand) {
|
||||
this.hand = hand;
|
||||
var _thisPad = this;
|
||||
|
||||
this.buttonPress = function(value) {
|
||||
_thisPad.buttonValue = value;
|
||||
};
|
||||
if (value === 0) {
|
||||
if (activationTimeout !== null) {
|
||||
Script.clearTimeout(activationTimeout);
|
||||
activationTimeout = null;
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function Trigger(hand) {
|
||||
|
@ -91,7 +88,6 @@ function Teleporter() {
|
|||
this.targetOverlay = null;
|
||||
this.updateConnected = null;
|
||||
this.smoothArrivalInterval = null;
|
||||
this.fadeSphere = null;
|
||||
this.teleportHand = null;
|
||||
|
||||
this.initialize = function() {
|
||||
|
@ -128,83 +124,25 @@ function Teleporter() {
|
|||
if (inTeleportMode === true) {
|
||||
return;
|
||||
}
|
||||
if (isDisabled === 'both') {
|
||||
return;
|
||||
}
|
||||
|
||||
inTeleportMode = true;
|
||||
|
||||
if (this.smoothArrivalInterval !== null) {
|
||||
Script.clearInterval(this.smoothArrivalInterval);
|
||||
}
|
||||
if (fadeSphereInterval !== null) {
|
||||
Script.clearInterval(fadeSphereInterval);
|
||||
if (activationTimeout !== null) {
|
||||
Script.clearInterval(activationTimeout);
|
||||
}
|
||||
|
||||
this.teleportHand = hand;
|
||||
this.initialize();
|
||||
Script.update.connect(this.update);
|
||||
this.updateConnected = true;
|
||||
};
|
||||
|
||||
this.createFadeSphere = function(avatarHead) {
|
||||
var sphereProps = {
|
||||
position: avatarHead,
|
||||
size: -1,
|
||||
color: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0,
|
||||
},
|
||||
alpha: 1,
|
||||
solid: true,
|
||||
visible: true,
|
||||
ignoreRayIntersection: true,
|
||||
drawInFront: false
|
||||
};
|
||||
|
||||
currentFadeSphereOpacity = 10;
|
||||
|
||||
_this.fadeSphere = Overlays.addOverlay("sphere", sphereProps);
|
||||
Script.clearInterval(fadeSphereInterval)
|
||||
Script.update.connect(_this.updateFadeSphere);
|
||||
if (USE_FADE_OUT === true) {
|
||||
this.fadeSphereOut();
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
this.fadeSphereOut = function() {
|
||||
|
||||
fadeSphereInterval = Script.setInterval(function() {
|
||||
if (currentFadeSphereOpacity <= 0) {
|
||||
Script.clearInterval(fadeSphereInterval);
|
||||
_this.deleteFadeSphere();
|
||||
fadeSphereInterval = null;
|
||||
return;
|
||||
}
|
||||
if (currentFadeSphereOpacity > 0) {
|
||||
currentFadeSphereOpacity = currentFadeSphereOpacity - 1;
|
||||
}
|
||||
|
||||
Overlays.editOverlay(_this.fadeSphere, {
|
||||
alpha: currentFadeSphereOpacity / 10
|
||||
})
|
||||
|
||||
}, FADE_OUT_INTERVAL);
|
||||
};
|
||||
|
||||
|
||||
this.updateFadeSphere = function() {
|
||||
var headPosition = MyAvatar.getHeadPosition();
|
||||
Overlays.editOverlay(_this.fadeSphere, {
|
||||
position: headPosition
|
||||
})
|
||||
};
|
||||
|
||||
this.deleteFadeSphere = function() {
|
||||
if (_this.fadeSphere !== null) {
|
||||
Script.update.disconnect(_this.updateFadeSphere);
|
||||
Overlays.deleteOverlay(_this.fadeSphere);
|
||||
_this.fadeSphere = null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.deleteTargetOverlay = function() {
|
||||
if (this.targetOverlay === null) {
|
||||
|
@ -221,6 +159,10 @@ function Teleporter() {
|
|||
}
|
||||
|
||||
this.exitTeleportMode = function(value) {
|
||||
if (activationTimeout !== null) {
|
||||
Script.clearTimeout(activationTimeout);
|
||||
activationTimeout = null;
|
||||
}
|
||||
if (this.updateConnected === true) {
|
||||
Script.update.disconnect(this.update);
|
||||
}
|
||||
|
@ -239,19 +181,26 @@ function Teleporter() {
|
|||
|
||||
|
||||
this.update = function() {
|
||||
if (isDisabled === 'both') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (teleporter.teleportHand === 'left') {
|
||||
if (isDisabled === 'left') {
|
||||
return;
|
||||
}
|
||||
teleporter.leftRay();
|
||||
|
||||
if ((leftPad.buttonValue === 0 || leftTrigger.buttonValue === 0) && inTeleportMode === true) {
|
||||
if ((leftPad.buttonValue === 0) && inTeleportMode === true) {
|
||||
_this.teleport();
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (isDisabled === 'right') {
|
||||
return;
|
||||
}
|
||||
teleporter.rightRay();
|
||||
|
||||
if ((rightPad.buttonValue === 0 || rightTrigger.buttonValue === 0) && inTeleportMode === true) {
|
||||
if ((rightPad.buttonValue === 0) && inTeleportMode === true) {
|
||||
_this.teleport();
|
||||
return;
|
||||
}
|
||||
|
@ -261,14 +210,12 @@ function Teleporter() {
|
|||
|
||||
this.rightRay = function() {
|
||||
|
||||
|
||||
var rightPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, Controller.getPoseValue(Controller.Standard.RightHand).translation), MyAvatar.position);
|
||||
|
||||
var rightControllerRotation = Controller.getPoseValue(Controller.Standard.RightHand).rotation;
|
||||
|
||||
var rightRotation = Quat.multiply(MyAvatar.orientation, rightControllerRotation)
|
||||
|
||||
|
||||
var rightFinal = Quat.multiply(rightRotation, Quat.angleAxis(90, {
|
||||
x: 1,
|
||||
y: 0,
|
||||
|
@ -308,14 +255,12 @@ function Teleporter() {
|
|||
|
||||
var leftRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(Controller.Standard.LeftHand).rotation)
|
||||
|
||||
|
||||
var leftFinal = Quat.multiply(leftRotation, Quat.angleAxis(90, {
|
||||
x: 1,
|
||||
y: 0,
|
||||
z: 0
|
||||
}));
|
||||
|
||||
|
||||
var leftPickRay = {
|
||||
origin: leftPosition,
|
||||
direction: Quat.getUp(leftRotation),
|
||||
|
@ -337,7 +282,6 @@ function Teleporter() {
|
|||
this.createTargetOverlay();
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
this.deleteTargetOverlay();
|
||||
|
@ -351,7 +295,7 @@ function Teleporter() {
|
|||
start: closePoint,
|
||||
end: farPoint,
|
||||
color: color,
|
||||
ignoreRayIntersection: true, // always ignore this
|
||||
ignoreRayIntersection: true,
|
||||
visible: true,
|
||||
alpha: 1,
|
||||
solid: true,
|
||||
|
@ -363,14 +307,9 @@ function Teleporter() {
|
|||
|
||||
} else {
|
||||
var success = Overlays.editOverlay(this.rightOverlayLine, {
|
||||
lineWidth: 50,
|
||||
start: closePoint,
|
||||
end: farPoint,
|
||||
color: color,
|
||||
visible: true,
|
||||
ignoreRayIntersection: true, // always ignore this
|
||||
alpha: 1,
|
||||
glow: 1.0
|
||||
color: color
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -378,7 +317,7 @@ function Teleporter() {
|
|||
this.leftLineOn = function(closePoint, farPoint, color) {
|
||||
if (this.leftOverlayLine === null) {
|
||||
var lineProperties = {
|
||||
ignoreRayIntersection: true, // always ignore this
|
||||
ignoreRayIntersection: true,
|
||||
start: closePoint,
|
||||
end: farPoint,
|
||||
color: color,
|
||||
|
@ -395,11 +334,7 @@ function Teleporter() {
|
|||
var success = Overlays.editOverlay(this.leftOverlayLine, {
|
||||
start: closePoint,
|
||||
end: farPoint,
|
||||
color: color,
|
||||
visible: true,
|
||||
alpha: 1,
|
||||
solid: true,
|
||||
glow: 1.0
|
||||
color: color
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -452,16 +387,11 @@ function Teleporter() {
|
|||
this.exitTeleportMode();
|
||||
}
|
||||
if (this.intersection !== null) {
|
||||
if (USE_FADE_MODE === true) {
|
||||
this.createFadeSphere();
|
||||
}
|
||||
var offset = getAvatarFootOffset();
|
||||
this.intersection.intersection.y += offset;
|
||||
this.exitTeleportMode();
|
||||
this.smoothArrival();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -471,12 +401,8 @@ function Teleporter() {
|
|||
return midpoint
|
||||
};
|
||||
|
||||
|
||||
|
||||
this.getArrivalPoints = function(startPoint, endPoint) {
|
||||
var arrivalPoints = [];
|
||||
|
||||
|
||||
var i;
|
||||
var lastPoint;
|
||||
|
||||
|
@ -489,9 +415,9 @@ function Teleporter() {
|
|||
arrivalPoints.push(newPoint);
|
||||
}
|
||||
|
||||
arrivalPoints.push(endPoint)
|
||||
arrivalPoints.push(endPoint);
|
||||
|
||||
return arrivalPoints
|
||||
return arrivalPoints;
|
||||
};
|
||||
|
||||
this.smoothArrival = function() {
|
||||
|
@ -502,7 +428,6 @@ function Teleporter() {
|
|||
Script.clearInterval(_this.smoothArrivalInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
var landingPoint = _this.arrivalPoints.shift();
|
||||
MyAvatar.position = landingPoint;
|
||||
|
||||
|
@ -510,8 +435,7 @@ function Teleporter() {
|
|||
_this.deleteTargetOverlay();
|
||||
}
|
||||
|
||||
|
||||
}, SMOOTH_ARRIVAL_SPACING)
|
||||
}, SMOOTH_ARRIVAL_SPACING);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -536,14 +460,14 @@ function getAvatarFootOffset() {
|
|||
toe = d.translation.y;
|
||||
}
|
||||
if (jointName === "RightToe_End") {
|
||||
toeTop = d.translation.y
|
||||
toeTop = d.translation.y;
|
||||
}
|
||||
})
|
||||
|
||||
var myPosition = MyAvatar.position;
|
||||
var offset = upperLeg + lowerLeg + foot + toe + toeTop;
|
||||
offset = offset / 100;
|
||||
return offset
|
||||
return offset;
|
||||
};
|
||||
|
||||
function getJointData() {
|
||||
|
@ -570,8 +494,18 @@ var rightTrigger = new Trigger('right');
|
|||
|
||||
var mappingName, teleportMapping;
|
||||
|
||||
var TELEPORT_DELAY = 100;
|
||||
var activationTimeout = null;
|
||||
var TELEPORT_DELAY = 0;
|
||||
|
||||
function isMoving() {
|
||||
var LY = Controller.getValue(Controller.Standard.LY);
|
||||
var LX = Controller.getValue(Controller.Standard.LX);
|
||||
if (LY !== 0 || LX !== 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function registerMappings() {
|
||||
mappingName = 'Hifi-Teleporter-Dev-' + Math.random();
|
||||
|
@ -582,23 +516,49 @@ function registerMappings() {
|
|||
teleportMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(rightPad.buttonPress);
|
||||
teleportMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(leftPad.buttonPress);
|
||||
|
||||
teleportMapping.from(Controller.Standard.LeftPrimaryThumb).when(leftTrigger.down).to(function(value) {
|
||||
teleporter.enterTeleportMode('left')
|
||||
return;
|
||||
});
|
||||
teleportMapping.from(Controller.Standard.RightPrimaryThumb).when(rightTrigger.down).to(function(value) {
|
||||
teleporter.enterTeleportMode('right')
|
||||
return;
|
||||
});
|
||||
teleportMapping.from(Controller.Standard.RT).when(Controller.Standard.RightPrimaryThumb).to(function(value) {
|
||||
teleporter.enterTeleportMode('right')
|
||||
return;
|
||||
});
|
||||
teleportMapping.from(Controller.Standard.LT).when(Controller.Standard.LeftPrimaryThumb).to(function(value) {
|
||||
teleporter.enterTeleportMode('left')
|
||||
return;
|
||||
});
|
||||
teleportMapping.from(Controller.Standard.LeftPrimaryThumb)
|
||||
.to(function(value) {
|
||||
if (isDisabled === 'left' || isDisabled === 'both') {
|
||||
return;
|
||||
}
|
||||
if (activationTimeout !== null) {
|
||||
return
|
||||
}
|
||||
if (leftTrigger.down()) {
|
||||
return;
|
||||
}
|
||||
if (isMoving() === true) {
|
||||
return;
|
||||
}
|
||||
activationTimeout = Script.setTimeout(function() {
|
||||
Script.clearTimeout(activationTimeout);
|
||||
activationTimeout = null;
|
||||
teleporter.enterTeleportMode('left')
|
||||
}, TELEPORT_DELAY)
|
||||
return;
|
||||
});
|
||||
teleportMapping.from(Controller.Standard.RightPrimaryThumb)
|
||||
.to(function(value) {
|
||||
if (isDisabled === 'right' || isDisabled === 'both') {
|
||||
return;
|
||||
}
|
||||
if (activationTimeout !== null) {
|
||||
return
|
||||
}
|
||||
if (rightTrigger.down()) {
|
||||
return;
|
||||
}
|
||||
if (isMoving() === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
activationTimeout = Script.setTimeout(function() {
|
||||
teleporter.enterTeleportMode('right')
|
||||
Script.clearTimeout(activationTimeout);
|
||||
activationTimeout = null;
|
||||
}, TELEPORT_DELAY)
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
registerMappings();
|
||||
|
@ -614,8 +574,32 @@ function cleanup() {
|
|||
teleporter.disableMappings();
|
||||
teleporter.deleteTargetOverlay();
|
||||
teleporter.turnOffOverlayBeams();
|
||||
teleporter.deleteFadeSphere();
|
||||
if (teleporter.updateConnected !== null) {
|
||||
Script.update.disconnect(teleporter.update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isDisabled = false;
|
||||
var handleHandMessages = function(channel, message, sender) {
|
||||
var data;
|
||||
if (sender === MyAvatar.sessionUUID) {
|
||||
if (channel === 'Hifi-Teleport-Disabler') {
|
||||
if (message === 'both') {
|
||||
isDisabled = 'both';
|
||||
}
|
||||
if (message === 'left') {
|
||||
isDisabled = 'left';
|
||||
}
|
||||
if (message === 'right') {
|
||||
isDisabled = 'right'
|
||||
}
|
||||
if (message === 'none') {
|
||||
isDisabled = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Messages.subscribe('Hifi-Teleport-Disabler');
|
||||
Messages.messageReceived.connect(handleHandMessages);
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// ignore.js
|
||||
// mod.js
|
||||
// scripts/system/
|
||||
//
|
||||
// Created by Stephen Birarda on 07/11/2016
|
||||
|
@ -12,10 +12,14 @@
|
|||
// grab the toolbar
|
||||
var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
|
||||
|
||||
// setup the ignore button and add it to the toolbar
|
||||
function buttonImageURL() {
|
||||
return Script.resolvePath("assets/images/tools/" + (Users.canKick ? 'kick.svg' : 'ignore.svg'));
|
||||
}
|
||||
|
||||
// setup the mod button and add it to the toolbar
|
||||
var button = toolbar.addButton({
|
||||
objectName: 'ignore',
|
||||
imageURL: Script.resolvePath("assets/images/tools/ignore.svg"),
|
||||
objectName: 'mod',
|
||||
imageURL: buttonImageURL(),
|
||||
visible: true,
|
||||
buttonState: 1,
|
||||
defaultState: 2,
|
||||
|
@ -23,19 +27,24 @@ var button = toolbar.addButton({
|
|||
alpha: 0.9
|
||||
});
|
||||
|
||||
// if this user's kick permissions change, change the state of the button in the HUD
|
||||
Users.canKickChanged.connect(function(canKick){
|
||||
button.writeProperty('imageURL', buttonImageURL());
|
||||
});
|
||||
|
||||
var isShowingOverlays = false;
|
||||
var ignoreOverlays = {};
|
||||
var modOverlays = {};
|
||||
|
||||
function removeOverlays() {
|
||||
// enumerate the overlays and remove them
|
||||
var ignoreOverlayKeys = Object.keys(ignoreOverlays);
|
||||
var modOverlayKeys = Object.keys(modOverlays);
|
||||
|
||||
for (i = 0; i < ignoreOverlayKeys.length; ++i) {
|
||||
var avatarID = ignoreOverlayKeys[i];
|
||||
Overlays.deleteOverlay(ignoreOverlays[avatarID]);
|
||||
for (i = 0; i < modOverlayKeys.length; ++i) {
|
||||
var avatarID = modOverlayKeys[i];
|
||||
Overlays.deleteOverlay(modOverlays[avatarID]);
|
||||
}
|
||||
|
||||
ignoreOverlays = {};
|
||||
modOverlays = {};
|
||||
}
|
||||
|
||||
// handle clicks on the toolbar button
|
||||
|
@ -54,6 +63,10 @@ function buttonClicked(){
|
|||
|
||||
button.clicked.connect(buttonClicked);
|
||||
|
||||
function overlayURL() {
|
||||
return Script.resolvePath("assets") + "/images/" + (Users.canKick ? "kick-target.svg" : "ignore-target.svg");
|
||||
}
|
||||
|
||||
function updateOverlays() {
|
||||
if (isShowingOverlays) {
|
||||
|
||||
|
@ -80,17 +93,18 @@ function updateOverlays() {
|
|||
var overlayPosition = avatar.getJointPosition("Head");
|
||||
overlayPosition.y += 0.45;
|
||||
|
||||
if (avatarID in ignoreOverlays) {
|
||||
if (avatarID in modOverlays) {
|
||||
// keep the overlay above the current position of this avatar
|
||||
Overlays.editOverlay(ignoreOverlays[avatarID], {
|
||||
position: overlayPosition
|
||||
Overlays.editOverlay(modOverlays[avatarID], {
|
||||
position: overlayPosition,
|
||||
url: overlayURL()
|
||||
});
|
||||
} else {
|
||||
// add the overlay above this avatar
|
||||
var newOverlay = Overlays.addOverlay("image3d", {
|
||||
url: Script.resolvePath("assets/images/ignore-target-01.svg"),
|
||||
url: overlayURL(),
|
||||
position: overlayPosition,
|
||||
size: 0.4,
|
||||
size: 1,
|
||||
scale: 0.4,
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
alpha: 1,
|
||||
|
@ -100,7 +114,7 @@ function updateOverlays() {
|
|||
});
|
||||
|
||||
// push this overlay to our array of overlays
|
||||
ignoreOverlays[avatarID] = newOverlay;
|
||||
modOverlays[avatarID] = newOverlay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -113,24 +127,28 @@ AvatarList.avatarRemovedEvent.connect(function(avatarID){
|
|||
// we are currently showing overlays and an avatar just went away
|
||||
|
||||
// first remove the rendered overlay
|
||||
Overlays.deleteOverlay(ignoreOverlays[avatarID]);
|
||||
Overlays.deleteOverlay(modOverlays[avatarID]);
|
||||
|
||||
// delete the saved ID of the overlay from our ignored overlays object
|
||||
delete ignoreOverlays[avatarID];
|
||||
// delete the saved ID of the overlay from our mod overlays object
|
||||
delete modOverlays[avatarID];
|
||||
}
|
||||
});
|
||||
|
||||
function handleSelectedOverlay(clickedOverlay) {
|
||||
// see this is one of our ignore overlays
|
||||
// see this is one of our mod overlays
|
||||
|
||||
var ignoreOverlayKeys = Object.keys(ignoreOverlays)
|
||||
for (i = 0; i < ignoreOverlayKeys.length; ++i) {
|
||||
var avatarID = ignoreOverlayKeys[i];
|
||||
var ignoreOverlay = ignoreOverlays[avatarID];
|
||||
var modOverlayKeys = Object.keys(modOverlays)
|
||||
for (i = 0; i < modOverlayKeys.length; ++i) {
|
||||
var avatarID = modOverlayKeys[i];
|
||||
var modOverlay = modOverlays[avatarID];
|
||||
|
||||
if (clickedOverlay.overlayID == ignoreOverlay) {
|
||||
// matched to an overlay, ask for the matching avatar to be ignored
|
||||
Users.ignore(avatarID);
|
||||
if (clickedOverlay.overlayID == modOverlay) {
|
||||
// matched to an overlay, ask for the matching avatar to be kicked or ignored
|
||||
if (Users.canKick) {
|
||||
Users.kick(avatarID);
|
||||
} else {
|
||||
Users.ignore(avatarID);
|
||||
}
|
||||
|
||||
// cleanup of the overlay is handled by the connection to avatarRemovedEvent
|
||||
}
|
||||
|
@ -161,15 +179,9 @@ Controller.mousePressEvent.connect(function(event){
|
|||
// But we dont' get mousePressEvents.
|
||||
var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
|
||||
|
||||
var TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor.
|
||||
var TRIGGER_ON_VALUE = 0.4;
|
||||
var TRIGGER_OFF_VALUE = 0.15;
|
||||
var triggered = false;
|
||||
var activeHand = Controller.Standard.RightHand;
|
||||
|
||||
function controllerComputePickRay() {
|
||||
var controllerPose = Controller.getPoseValue(activeHand);
|
||||
if (controllerPose.valid && triggered) {
|
||||
function controllerComputePickRay(hand) {
|
||||
var controllerPose = Controller.getPoseValue(hand);
|
||||
if (controllerPose.valid) {
|
||||
var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation),
|
||||
MyAvatar.position);
|
||||
// This gets point direction right, but if you want general quaternion it would be more complicated:
|
||||
|
@ -178,37 +190,27 @@ function controllerComputePickRay() {
|
|||
}
|
||||
}
|
||||
|
||||
function makeTriggerHandler(hand) {
|
||||
return function (value) {
|
||||
if (isShowingOverlays) {
|
||||
if (!triggered && (value > TRIGGER_GRAB_VALUE)) { // should we smooth?
|
||||
triggered = true;
|
||||
if (activeHand !== hand) {
|
||||
// No switching while the other is already triggered, so no need to release.
|
||||
activeHand = (activeHand === Controller.Standard.RightHand) ? Controller.Standard.LeftHand : Controller.Standard.RightHand;
|
||||
function makeClickHandler(hand) {
|
||||
return function(clicked) {
|
||||
if (clicked == 1.0 && isShowingOverlays) {
|
||||
var pickRay = controllerComputePickRay(hand);
|
||||
if (pickRay) {
|
||||
var overlayIntersection = Overlays.findRayIntersection(pickRay);
|
||||
if (overlayIntersection.intersects) {
|
||||
handleSelectedOverlay(overlayIntersection);
|
||||
}
|
||||
|
||||
var pickRay = controllerComputePickRay();
|
||||
if (pickRay) {
|
||||
var overlayIntersection = Overlays.findRayIntersection(pickRay);
|
||||
if (overlayIntersection.intersects) {
|
||||
handleSelectedOverlay(overlayIntersection);
|
||||
}
|
||||
}
|
||||
} else if (triggered && (value < TRIGGER_OFF_VALUE)) {
|
||||
triggered = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
triggerMapping.from(Controller.Standard.RT).peek().to(makeTriggerHandler(Controller.Standard.RightHand));
|
||||
triggerMapping.from(Controller.Standard.LT).peek().to(makeTriggerHandler(Controller.Standard.LeftHand));
|
||||
triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
|
||||
triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
|
||||
|
||||
triggerMapping.enable();
|
||||
|
||||
// cleanup the toolbar button and overlays when script is stopped
|
||||
Script.scriptEnding.connect(function() {
|
||||
toolbar.removeButton('ignore');
|
||||
toolbar.removeButton('mod');
|
||||
removeOverlays();
|
||||
triggerMapping.disable();
|
||||
});
|
|
@ -13,6 +13,11 @@
|
|||
|
||||
(function() {
|
||||
|
||||
function debug() {
|
||||
return;
|
||||
print.apply(null, arguments);
|
||||
}
|
||||
|
||||
var rawProgress = 100, // % raw value.
|
||||
displayProgress = 100, // % smoothed value to display.
|
||||
DISPLAY_PROGRESS_MINOR_MAXIMUM = 8, // % displayed progress bar goes up to while 0% raw progress.
|
||||
|
@ -28,35 +33,18 @@
|
|||
FADE_OUT_WAIT = 1000, // Wait before starting to fade out after progress 100%.
|
||||
visible = false,
|
||||
BAR_WIDTH = 480, // Dimension of SVG in pixels of visible portion (half) of the bar.
|
||||
BAR_HEIGHT = 30,
|
||||
BAR_HEIGHT = 10,
|
||||
BAR_Y_OFFSET_2D = -10, // Offset of progress bar while in desktop mode
|
||||
BAR_Y_OFFSET_HMD = -300, // Offset of progress bar while in HMD
|
||||
BAR_URL = Script.resolvePath("assets/images/progress-bar.svg"),
|
||||
BACKGROUND_WIDTH = 540,
|
||||
BACKGROUND_HEIGHT = 90,
|
||||
BACKGROUND_WIDTH = 520,
|
||||
BACKGROUND_HEIGHT = 50,
|
||||
BACKGROUND_URL = Script.resolvePath("assets/images/progress-bar-background.svg"),
|
||||
isOnHMD = false,
|
||||
windowWidth = 0,
|
||||
windowHeight = 0,
|
||||
background2D = {},
|
||||
bar2D = {},
|
||||
SCALE_2D = 0.35, // Scale the SVGs for 2D display.
|
||||
background3D = {},
|
||||
bar3D = {},
|
||||
PROGRESS_3D_DIRECTION = 0.0, // Degrees from avatar orientation.
|
||||
PROGRESS_3D_DISTANCE = 0.602, // Horizontal distance from avatar position.
|
||||
PROGRESS_3D_ELEVATION = -0.8, // Height of top middle of top notification relative to avatar eyes.
|
||||
PROGRESS_3D_YAW = 0.0, // Degrees relative to notifications direction.
|
||||
PROGRESS_3D_PITCH = -60.0, // Degrees from vertical.
|
||||
SCALE_3D = 0.0011, // Scale the bar SVG for 3D display.
|
||||
BACKGROUND_3D_SIZE = {
|
||||
x: 0.76,
|
||||
y: 0.08
|
||||
}, // Match up with the 3D background with those of notifications.js notices.
|
||||
BACKGROUND_3D_COLOR = {
|
||||
red: 2,
|
||||
green: 2,
|
||||
blue: 2
|
||||
},
|
||||
BACKGROUND_3D_ALPHA = 0.7;
|
||||
SCALE_2D = 0.35; // Scale the SVGs for 2D display.
|
||||
|
||||
function fade() {
|
||||
|
||||
|
@ -64,9 +52,7 @@
|
|||
|
||||
if (alpha < 0) {
|
||||
alpha = 0;
|
||||
}
|
||||
|
||||
if (alpha > 1) {
|
||||
} else if (alpha > 1) {
|
||||
alpha = 1;
|
||||
}
|
||||
|
||||
|
@ -79,115 +65,123 @@
|
|||
visible = false;
|
||||
}
|
||||
|
||||
if (isOnHMD) {
|
||||
Overlays.editOverlay(background3D.overlay, {
|
||||
backgroundAlpha: alpha * BACKGROUND_3D_ALPHA,
|
||||
visible: visible
|
||||
});
|
||||
} else {
|
||||
Overlays.editOverlay(background2D.overlay, {
|
||||
alpha: alpha,
|
||||
visible: visible
|
||||
});
|
||||
}
|
||||
Overlays.editOverlay(isOnHMD ? bar3D.overlay : bar2D.overlay, {
|
||||
Overlays.editOverlay(background2D.overlay, {
|
||||
alpha: alpha,
|
||||
visible: visible
|
||||
});
|
||||
Overlays.editOverlay(bar2D.overlay, {
|
||||
alpha: alpha,
|
||||
visible: visible
|
||||
});
|
||||
}
|
||||
|
||||
Window.domainChanged.connect(function() {
|
||||
isDownloading = false;
|
||||
bestRawProgress = 100;
|
||||
rawProgress = 100;
|
||||
displayProgress = 100;
|
||||
});
|
||||
|
||||
// Max seen since downloads started. This is reset when all downloads have completed.
|
||||
var maxSeen = 0;
|
||||
|
||||
// Progress is defined as: (pending_downloads + active_downloads) / max_seen
|
||||
// We keep track of both the current progress (rawProgress) and the
|
||||
// best progress we've seen (bestRawProgress). As you are downloading, you may
|
||||
// encounter new assets that require downloads, increasing the number of
|
||||
// pending downloads and thus decreasing your overall progress.
|
||||
var bestRawProgress = 0;
|
||||
|
||||
// True if we have known active downloads
|
||||
var isDownloading = false;
|
||||
|
||||
// Entities are streamed to users, so you don't receive them all at once; instead, you
|
||||
// receive them over a period of time. In many cases we end up in a situation where
|
||||
//
|
||||
// The initial delay cooldown keeps us from tracking progress before the allotted time
|
||||
// has passed.
|
||||
var INITIAL_DELAY_COOLDOWN_TIME = 1000;
|
||||
var initialDelayCooldown = 0;
|
||||
function onDownloadInfoChanged(info) {
|
||||
var i;
|
||||
|
||||
debug("PROGRESS: Download info changed ", info.downloading.length, info.pending, maxSeen);
|
||||
|
||||
// Update raw progress value
|
||||
if (info.downloading.length + info.pending === 0) {
|
||||
isDownloading = false;
|
||||
rawProgress = 100;
|
||||
bestRawProgress = 100;
|
||||
initialDelayCooldown = INITIAL_DELAY_COOLDOWN_TIME;
|
||||
} else {
|
||||
rawProgress = 0;
|
||||
for (i = 0; i < info.downloading.length; i += 1) {
|
||||
rawProgress += info.downloading[i];
|
||||
var count = info.downloading.length + info.pending;
|
||||
if (!isDownloading) {
|
||||
isDownloading = true;
|
||||
bestRawProgress = 0;
|
||||
rawProgress = 0;
|
||||
initialDelayCooldown = INITIAL_DELAY_COOLDOWN_TIME;
|
||||
displayProgress = 0;
|
||||
maxSeen = count;
|
||||
}
|
||||
if (count > maxSeen) {
|
||||
maxSeen = count;
|
||||
}
|
||||
if (initialDelayCooldown <= 0) {
|
||||
rawProgress = ((maxSeen - count) / maxSeen) * 100;
|
||||
|
||||
if (rawProgress > bestRawProgress) {
|
||||
bestRawProgress = rawProgress;
|
||||
}
|
||||
}
|
||||
rawProgress = rawProgress / (info.downloading.length + info.pending);
|
||||
}
|
||||
debug("PROGRESS:", rawProgress, bestRawProgress, maxSeen);
|
||||
}
|
||||
|
||||
function createOverlays() {
|
||||
if (isOnHMD) {
|
||||
|
||||
background3D.overlay = Overlays.addOverlay("rectangle3d", {
|
||||
size: BACKGROUND_3D_SIZE,
|
||||
color: BACKGROUND_3D_COLOR,
|
||||
alpha: BACKGROUND_3D_ALPHA,
|
||||
solid: true,
|
||||
isFacingAvatar: false,
|
||||
visible: false,
|
||||
ignoreRayIntersection: true
|
||||
});
|
||||
bar3D.overlay = Overlays.addOverlay("image3d", {
|
||||
url: BAR_URL,
|
||||
subImage: {
|
||||
x: BAR_WIDTH,
|
||||
y: 0,
|
||||
width: BAR_WIDTH,
|
||||
height: BAR_HEIGHT
|
||||
},
|
||||
scale: SCALE_3D * BAR_WIDTH,
|
||||
isFacingAvatar: false,
|
||||
visible: false,
|
||||
alpha: 0.0,
|
||||
ignoreRayIntersection: true
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
background2D.overlay = Overlays.addOverlay("image", {
|
||||
imageURL: BACKGROUND_URL,
|
||||
width: background2D.width,
|
||||
height: background2D.height,
|
||||
visible: false,
|
||||
alpha: 0.0
|
||||
});
|
||||
bar2D.overlay = Overlays.addOverlay("image", {
|
||||
imageURL: BAR_URL,
|
||||
subImage: {
|
||||
x: BAR_WIDTH,
|
||||
y: 0,
|
||||
width: BAR_WIDTH,
|
||||
height: BAR_HEIGHT
|
||||
},
|
||||
width: bar2D.width,
|
||||
height: bar2D.height,
|
||||
visible: false,
|
||||
alpha: 0.0
|
||||
});
|
||||
}
|
||||
background2D.overlay = Overlays.addOverlay("image", {
|
||||
imageURL: BACKGROUND_URL,
|
||||
width: background2D.width,
|
||||
height: background2D.height,
|
||||
visible: false,
|
||||
alpha: 0.0
|
||||
});
|
||||
bar2D.overlay = Overlays.addOverlay("image", {
|
||||
imageURL: BAR_URL,
|
||||
subImage: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: BAR_WIDTH,
|
||||
height: BAR_HEIGHT
|
||||
},
|
||||
width: bar2D.width,
|
||||
height: bar2D.height,
|
||||
visible: false,
|
||||
alpha: 0.0
|
||||
});
|
||||
}
|
||||
|
||||
function deleteOverlays() {
|
||||
Overlays.deleteOverlay(isOnHMD ? background3D.overlay : background2D.overlay);
|
||||
Overlays.deleteOverlay(isOnHMD ? bar3D.overlay : bar2D.overlay);
|
||||
Overlays.deleteOverlay(background2D.overlay);
|
||||
Overlays.deleteOverlay(bar2D.overlay);
|
||||
}
|
||||
|
||||
var b = 0;
|
||||
var currentOrientation = null;
|
||||
function update() {
|
||||
initialDelayCooldown -= 30;
|
||||
var viewport,
|
||||
eyePosition,
|
||||
avatarOrientation;
|
||||
|
||||
if (isOnHMD !== HMD.active) {
|
||||
deleteOverlays();
|
||||
isOnHMD = !isOnHMD;
|
||||
createOverlays();
|
||||
if (displayProgress < rawProgress) {
|
||||
var diff = rawProgress - displayProgress;
|
||||
if (diff < 0.5) {
|
||||
displayProgress = rawProgress;
|
||||
} else {
|
||||
displayProgress += diff * 0.05;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate progress value to display
|
||||
if (rawProgress === 0 && displayProgress <= DISPLAY_PROGRESS_MINOR_MAXIMUM) {
|
||||
displayProgress = Math.min(displayProgress + DISPLAY_PROGRESS_MINOR_INCREMENT, DISPLAY_PROGRESS_MINOR_MAXIMUM);
|
||||
} else if (rawProgress < displayProgress) {
|
||||
displayProgress = rawProgress;
|
||||
} else if (rawProgress > displayProgress) {
|
||||
displayProgress = Math.min(rawProgress, displayProgress + DISPLAY_PROGRESS_MAJOR_INCREMENT);
|
||||
} // else (rawProgress === displayProgress); do nothing.
|
||||
|
||||
// Update state
|
||||
if (!visible) { // Not visible because no recent downloads
|
||||
if (displayProgress < 100) { // Have started downloading so fade in
|
||||
|
@ -197,7 +191,7 @@
|
|||
}
|
||||
} else if (alphaDelta !== 0.0) { // Fading in or out
|
||||
if (alphaDelta > 0) {
|
||||
if (displayProgress === 100) { // Was downloading but now have finished so fade out
|
||||
if (rawProgress === 100) { // Was downloading but now have finished so fade out
|
||||
alphaDelta = ALPHA_DELTA_OUT;
|
||||
}
|
||||
} else {
|
||||
|
@ -207,7 +201,7 @@
|
|||
}
|
||||
} else { // Fully visible because downloading or recently so
|
||||
if (fadeWaitTimer === null) {
|
||||
if (displayProgress === 100) { // Was downloading but have finished so fade out soon
|
||||
if (rawProgress === 100) { // Was downloading but have finished so fade out soon
|
||||
fadeWaitTimer = Script.setTimeout(function() {
|
||||
alphaDelta = ALPHA_DELTA_OUT;
|
||||
fadeTimer = Script.setInterval(fade, FADE_INTERVAL);
|
||||
|
@ -225,73 +219,58 @@
|
|||
if (visible) {
|
||||
|
||||
// Update progress bar
|
||||
Overlays.editOverlay(isOnHMD ? bar3D.overlay : bar2D.overlay, {
|
||||
visible: visible,
|
||||
Overlays.editOverlay(bar2D.overlay, {
|
||||
visible: true,
|
||||
subImage: {
|
||||
x: BAR_WIDTH * (1 - displayProgress / 100),
|
||||
y: 0,
|
||||
width: BAR_WIDTH,
|
||||
height: BAR_HEIGHT
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Update position
|
||||
if (isOnHMD) {
|
||||
// Update 3D overlays to maintain positions relative to avatar
|
||||
eyePosition = MyAvatar.getDefaultEyePosition();
|
||||
avatarOrientation = MyAvatar.orientation;
|
||||
Overlays.editOverlay(background2D.overlay, {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
Overlays.editOverlay(background3D.overlay, {
|
||||
position: Vec3.sum(eyePosition, Vec3.multiplyQbyV(avatarOrientation, background3D.offset)),
|
||||
rotation: Quat.multiply(avatarOrientation, background3D.orientation)
|
||||
});
|
||||
Overlays.editOverlay(bar3D.overlay, {
|
||||
position: Vec3.sum(eyePosition, Vec3.multiplyQbyV(avatarOrientation, bar3D.offset)),
|
||||
rotation: Quat.multiply(avatarOrientation, bar3D.orientation)
|
||||
});
|
||||
// Update 2D overlays to maintain positions at bottom middle of window
|
||||
viewport = Controller.getViewportDimensions();
|
||||
|
||||
} else {
|
||||
// Update 2D overlays to maintain positions at bottom middle of window
|
||||
viewport = Controller.getViewportDimensions();
|
||||
|
||||
if (viewport.x !== windowWidth || viewport.y !== windowHeight) {
|
||||
windowWidth = viewport.x;
|
||||
windowHeight = viewport.y;
|
||||
|
||||
Overlays.editOverlay(background2D.overlay, {
|
||||
x: windowWidth / 2 - background2D.width / 2,
|
||||
y: windowHeight - background2D.height - bar2D.height
|
||||
});
|
||||
|
||||
Overlays.editOverlay(bar2D.overlay, {
|
||||
x: windowWidth / 2 - bar2D.width / 2,
|
||||
y: windowHeight - background2D.height - bar2D.height + (background2D.height - bar2D.height) / 2
|
||||
});
|
||||
}
|
||||
if (viewport.x !== windowWidth || viewport.y !== windowHeight) {
|
||||
updateProgressBarLocation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateProgressBarLocation() {
|
||||
viewport = Controller.getViewportDimensions();
|
||||
windowWidth = viewport.x;
|
||||
windowHeight = viewport.y;
|
||||
|
||||
var yOffset = HMD.active ? BAR_Y_OFFSET_HMD : BAR_Y_OFFSET_2D;
|
||||
|
||||
background2D.width = SCALE_2D * BACKGROUND_WIDTH;
|
||||
background2D.height = SCALE_2D * BACKGROUND_HEIGHT;
|
||||
bar2D.width = SCALE_2D * BAR_WIDTH;
|
||||
bar2D.height = SCALE_2D * BAR_HEIGHT;
|
||||
|
||||
Overlays.editOverlay(background2D.overlay, {
|
||||
x: windowWidth / 2 - background2D.width / 2,
|
||||
y: windowHeight - background2D.height - bar2D.height + yOffset
|
||||
});
|
||||
|
||||
Overlays.editOverlay(bar2D.overlay, {
|
||||
x: windowWidth / 2 - bar2D.width / 2,
|
||||
y: windowHeight - background2D.height - bar2D.height + (background2D.height - bar2D.height) / 2 + yOffset
|
||||
});
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
background2D.width = SCALE_2D * BACKGROUND_WIDTH;
|
||||
background2D.height = SCALE_2D * BACKGROUND_HEIGHT;
|
||||
bar2D.width = SCALE_2D * BAR_WIDTH;
|
||||
bar2D.height = SCALE_2D * BAR_HEIGHT;
|
||||
|
||||
background3D.offset = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, PROGRESS_3D_DIRECTION, 0), {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: -PROGRESS_3D_DISTANCE
|
||||
});
|
||||
background3D.offset.y += PROGRESS_3D_ELEVATION;
|
||||
background3D.orientation = Quat.fromPitchYawRollDegrees(PROGRESS_3D_PITCH, PROGRESS_3D_DIRECTION + PROGRESS_3D_YAW, 0);
|
||||
bar3D.offset = Vec3.sum(background3D.offset, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0.001
|
||||
}); // Just in front of background
|
||||
bar3D.orientation = background3D.orientation;
|
||||
|
||||
createOverlays();
|
||||
}
|
||||
|
||||
|
@ -302,6 +281,6 @@
|
|||
setUp();
|
||||
GlobalServices.downloadInfoChanged.connect(onDownloadInfoChanged);
|
||||
GlobalServices.updateDownloadInfo();
|
||||
Script.update.connect(update);
|
||||
Script.setInterval(update, 1000/60);
|
||||
Script.scriptEnding.connect(tearDown);
|
||||
}());
|
||||
}());
|
||||
|
|
Loading…
Reference in a new issue