merge from master

This commit is contained in:
SamGondelman 2016-08-04 13:34:38 -07:00
commit b6e73e06a2
73 changed files with 3053 additions and 937 deletions

View file

@ -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);

View file

@ -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:

View file

@ -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 &ldquo;locked&rdquo; 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&rsquo;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&rsquo;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 &ldquo;locked&rdquo; 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&rsquo;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&rsquo;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 &ldquo;locked&rdquo; 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&rsquo;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&rsquo;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 &ldquo;locked&rdquo; 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&rsquo;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&rsquo;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 &ldquo;locked&rdquo; 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&rsquo;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&rsquo;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 &ldquo;locked&rdquo; 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&rsquo;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&rsquo;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 &ldquo;locked&rdquo; 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&rsquo;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 &ldquo;locked&rdquo; 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&rsquo;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
}
]
}

View file

@ -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;
}

View file

@ -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>

View file

@ -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) {

View file

@ -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
}

View file

@ -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();
};

View file

@ -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) {

View file

@ -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);
}
}

View file

@ -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

View file

@ -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

View file

@ -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" }
]

View file

@ -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 } ]

View file

@ -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

View file

@ -35,7 +35,6 @@ WebEngineView {
}
onUrlChanged: {
console.log("Url changed to " + url);
var originalUrl = url.toString();
newUrl = urlHandler.fixupUrl(originalUrl).toString();
if (newUrl !== originalUrl) {

View file

@ -26,7 +26,6 @@ WebEngineView {
}
onUrlChanged: {
console.log("Url changed to " + url);
var originalUrl = url.toString();
newUrl = urlHandler.fixupUrl(originalUrl).toString();
if (newUrl !== originalUrl) {

View file

@ -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();

View file

@ -78,7 +78,10 @@ Decoration {
id: closeClickArea
anchors.fill: parent
hoverEnabled: true
onClicked: window.shown = false;
onClicked: {
window.shown = false;
window.windowClosed();
}
}
}
}

View file

@ -30,6 +30,7 @@ Fadable {
//
// Signals
//
signal windowClosed();
signal windowDestroyed();
signal mouseEntered();
signal mouseExited();

View file

@ -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);
}

View file

@ -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
}

View file

@ -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;

View file

@ -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();

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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));
}

View file

@ -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();
}
}

View file

@ -111,6 +111,9 @@ public:
QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); }
signals:
void resourceFailed();
private:
void startWatching();
void stopWatching();

View file

@ -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()));
}

View file

@ -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; }

View 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

View file

@ -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));
}
}

View file

@ -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();

View file

@ -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);

View file

@ -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.";
}
}

View file

@ -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();

View file

@ -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();
}

View file

@ -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

View file

@ -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

View file

@ -98,7 +98,8 @@ public:
NegotiateAudioFormat,
SelectedAudioFormat,
MoreEntityShapes,
LAST_PACKET_TYPE = MoreEntityShapes
NodeKickRequest,
LAST_PACKET_TYPE = NodeKickRequest
};
};

View file

@ -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;

View file

@ -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));

View file

@ -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

View file

@ -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();

View file

@ -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)

View file

@ -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);
}
}

View file

@ -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();
}

View file

@ -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);
};

View file

@ -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() {

View file

@ -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

View file

@ -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);
}
}

View file

@ -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;
};

View file

@ -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,

View file

@ -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() {

View file

@ -62,6 +62,7 @@ signals:
void fromQml(const QVariant& message);
protected slots:
void hasMoved(QVector2D);
void hasClosed();
void qmlToScript(const QVariant& message);

View file

@ -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);

View file

@ -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 };

View file

@ -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");

View file

@ -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

View 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

View 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

View file

@ -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>

View file

@ -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

View 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

View file

@ -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");

View file

@ -508,4 +508,3 @@ Script.scriptEnding.connect(function () {
Script.clearInterval(settingsChecker);
OffscreenFlags.navigationFocusDisabled = false;
});

View file

@ -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);

View file

@ -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();
});

View file

@ -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);
}());
}());