diff --git a/App.config b/App.config
index 88fa4027..8d234373 100644
--- a/App.config
+++ b/App.config
@@ -1,6 +1,6 @@
-
+
-
+
-
\ No newline at end of file
+
diff --git a/AppApi.cs b/AppApi.cs
index a63aabbe..70b63a08 100644
--- a/AppApi.cs
+++ b/AppApi.cs
@@ -11,6 +11,10 @@ using System.Linq;
using System.Management;
using System.Text.RegularExpressions;
using System.Windows.Forms;
+using System.IO;
+using System.Net;
+using Windows.UI.Notifications;
+using Windows.Data.Xml.Dom;
namespace VRCX
{
@@ -179,6 +183,30 @@ namespace VRCX
return CpuMonitor.Instance.CpuUsage;
}
+ public void DesktopNotification(string BoldText, string Text, string ImageURL = "")
+ {
+ XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastImageAndText02);
+ XmlNodeList stringElements = toastXml.GetElementsByTagName("text");
+ String imagePath = Path.Combine(Program.BaseDirectory, "cache\\toast");
+ if (ImageURL == String.Empty)
+ {
+ imagePath = Path.Combine(Program.BaseDirectory, "VRCX.ico");
+ }
+ else
+ {
+ using (var client = new WebClient())
+ {
+ client.DownloadFile(ImageURL, imagePath);
+ }
+ }
+ stringElements[0].AppendChild(toastXml.CreateTextNode(BoldText));
+ stringElements[1].AppendChild(toastXml.CreateTextNode(Text));
+ XmlNodeList imageElements = toastXml.GetElementsByTagName("image");
+ imageElements[0].Attributes.GetNamedItem("src").NodeValue = imagePath;
+ ToastNotification toast = new ToastNotification(toastXml);
+ ToastNotificationManager.CreateToastNotifier("VRCX").Show(toast);
+ }
+
public void SetStartup(bool enabled)
{
try
diff --git a/VRCX.csproj b/VRCX.csproj
index 3dba55ac..575e2e3d 100644
--- a/VRCX.csproj
+++ b/VRCX.csproj
@@ -8,7 +8,7 @@
WinExe
VRCX
VRCX
- v4.5.2
+ v4.6.2
512
true
true
@@ -29,6 +29,7 @@
1.0.0.%2a
false
true
+
AnyCPU
@@ -135,6 +136,7 @@
True
Resources.resx
+ True
SettingsSingleFileGenerator
@@ -172,7 +174,10 @@
86.0.241
- 1.0.166
+ 1.0.169
+
+
+ 6.1.1
12.0.3
@@ -193,7 +198,7 @@
4.2.0
- 1.0.113.6
+ 1.0.113.7
diff --git a/html/src/app.js b/html/src/app.js
index d1b8e50d..edd7f880 100644
--- a/html/src/app.js
+++ b/html/src/app.js
@@ -3436,7 +3436,7 @@ speechSynthesis.getVoices();
var { data } = this.gameLogTable;
var i = data.length;
var j = 0;
- while (j < 25) {
+ while (j < 30) {
if (i <= 0) {
break;
}
@@ -3466,7 +3466,7 @@ speechSynthesis.getVoices();
var { data } = this.feedTable;
var i = data.length;
var j = 0;
- while (j < 25) {
+ while (j < 30) {
if (i <= 0) {
break;
}
@@ -3515,8 +3515,8 @@ speechSynthesis.getVoices();
}
return 0;
});
- if (arr.length > 25) {
- arr.length = 25;
+ if (arr.length > 30) {
+ arr.length = 30;
}
sharedRepository.setArray('feeds', arr);
};
@@ -3835,23 +3835,16 @@ speechSynthesis.getVoices();
$app.data.orderFriendsGroup1 = configRepository.getBool('orderFriendGroup1');
$app.data.orderFriendsGroup2 = configRepository.getBool('orderFriendGroup2');
$app.data.orderFriendsGroup3 = configRepository.getBool('orderFriendGroup3');
- $app.data.displayVRCPlusIconsAsAvatar = configRepository.getBool('displayVRCPlusIconsAsAvatar');
var saveOrderFriendGroup = function () {
configRepository.setBool('orderFriendGroup0', this.orderFriendsGroup0);
configRepository.setBool('orderFriendGroup1', this.orderFriendsGroup1);
configRepository.setBool('orderFriendGroup2', this.orderFriendsGroup2);
configRepository.setBool('orderFriendGroup3', this.orderFriendsGroup3);
- configRepository.setBool('displayVRCPlusIconsAsAvatar', this.displayVRCPlusIconsAsAvatar);
};
$app.watch.orderFriendsGroup0 = saveOrderFriendGroup;
$app.watch.orderFriendsGroup1 = saveOrderFriendGroup;
$app.watch.orderFriendsGroup2 = saveOrderFriendGroup;
$app.watch.orderFriendsGroup3 = saveOrderFriendGroup;
- $app.watch.displayVRCPlusIconsAsAvatar = saveOrderFriendGroup;
- if (configRepository.getBool('displayVRCPlusIconsAsAvatar') === null) {
- $app.data.displayVRCPlusIconsAsAvatar = true;
- configRepository.setBool('displayVRCPlusIconsAsAvatar', $app.data.displayVRCPlusIconsAsAvatar);
- }
$app.methods.fetchActiveFriend = function (userId) {
this.pendingActiveFriends.add(userId);
@@ -5706,7 +5699,9 @@ speechSynthesis.getVoices();
$app.data.hidePrivateFromFeed = configRepository.getBool('VRCX_hidePrivateFromFeed');
$app.data.hideDevicesFromFeed = configRepository.getBool('VRCX_hideDevicesFromFeed');
$app.data.overlayNotifications = configRepository.getBool('VRCX_overlayNotifications');
+ $app.data.desktopToast = configRepository.getBool('VRCX_desktopToast');
$app.data.minimalFeed = configRepository.getBool('VRCX_minimalFeed');
+ $app.data.displayVRCPlusIconsAsAvatar = configRepository.getBool('displayVRCPlusIconsAsAvatar');
$app.data.notificationTTS = configRepository.getBool('VRCX_notificationTTS');
$app.data.notificationTTSVoice = configRepository.getString('VRCX_notificationTTSVoice');
$app.data.notificationTimeout = configRepository.getString('VRCX_notificationTimeout');
@@ -5717,7 +5712,9 @@ speechSynthesis.getVoices();
configRepository.setBool('VRCX_hidePrivateFromFeed', this.hidePrivateFromFeed);
configRepository.setBool('VRCX_hideDevicesFromFeed', this.hideDevicesFromFeed);
configRepository.setBool('VRCX_overlayNotifications', this.overlayNotifications);
+ configRepository.setBool('VRCX_desktopToast', this.desktopToast);
configRepository.setBool('VRCX_minimalFeed', this.minimalFeed);
+ configRepository.setBool('displayVRCPlusIconsAsAvatar', this.displayVRCPlusIconsAsAvatar);
AppApi.RefreshVR();
};
$app.data.TTSvoices = speechSynthesis.getVoices();
@@ -5735,7 +5732,9 @@ speechSynthesis.getVoices();
$app.watch.hidePrivateFromFeed = saveOpenVROption;
$app.watch.hideDevicesFromFeed = saveOpenVROption;
$app.watch.overlayNotifications = saveOpenVROption;
+ $app.watch.desktopToast = saveOpenVROption;
$app.watch.minimalFeed = saveOpenVROption;
+ $app.watch.displayVRCPlusIconsAsAvatar = saveOpenVROption;
$app.watch.notificationTTS = saveNotificationTTS;
$app.data.isDarkMode = configRepository.getBool('isDarkMode');
$appDarkStyle.disabled = $app.data.isDarkMode === false;
@@ -5761,6 +5760,10 @@ speechSynthesis.getVoices();
$app.watch.isAutoLogin = saveVRCXWindowOption;
//setting defaults
+ if (configRepository.getBool('displayVRCPlusIconsAsAvatar') === null) {
+ $app.data.displayVRCPlusIconsAsAvatar = true;
+ configRepository.setBool('displayVRCPlusIconsAsAvatar', $app.data.displayVRCPlusIconsAsAvatar);
+ }
if (!configRepository.getString('VRCX_notificationPosition')) {
$app.data.notificationPosition = 'topCenter';
configRepository.setString('VRCX_notificationPosition', $app.data.notificationPosition);
@@ -5907,6 +5910,17 @@ speechSynthesis.getVoices();
}
$app.watch.isGameRunning = isGameRunningStateChange;
+ sharedRepository.setBool('is_Game_No_VR', false);
+ var isGameNoVRStateChange = function () {
+ sharedRepository.setBool('is_Game_No_VR', this.isGameNoVR);
+ }
+ $app.watch.isGameNoVR = isGameNoVRStateChange;
+
+ var lastLocationStateChange = function () {
+ sharedRepository.setString('last_location', $app.lastLocation);
+ }
+ $app.watch.lastLocation = lastLocationStateChange;
+
API.$on('LOGIN', function () {
$app.currentUserTreeData = [];
$app.pastDisplayNameTable.data = [];
diff --git a/html/src/app.scss b/html/src/app.scss
index 8cf13ef4..a356a916 100644
--- a/html/src/app.scss
+++ b/html/src/app.scss
@@ -555,7 +555,12 @@ i.x-user-status.busy {
.options-container .header {
font-weight: bold;
- font-size: 18px;
+ font-size: 20px;
+}
+
+.options-container .sub-header {
+ font-weight: bold;
+ font-size: 15px;
}
.options-container-item {
diff --git a/html/src/index.pug b/html/src/index.pug
index 37f6d765..8418e32a 100644
--- a/html/src/index.pug
+++ b/html/src/index.pug
@@ -557,18 +557,34 @@ html
div.options-container-item
span.name(style="min-width:137px") Overlay Button
el-switch(v-model="overlaybutton" inactive-text="Grip" active-text="Menu" :disabled="!openVR")
- div.options-container-item
- span.name Hide VR Devices
- el-switch(v-model="hideDevicesFromFeed" :disabled="!openVR")
- div.options-container-item
- span.name Hide Private Worlds
- el-switch(v-model="hidePrivateFromFeed" :disabled="!openVR")
+ br
+ span.sub-header Display Options
div.options-container-item
span.name Minimal Feed Icons
el-switch(v-model="minimalFeed" :disabled="!openVR")
+ div.options-container-item
+ span.name Hide Private Worlds
+ el-switch(v-model="hidePrivateFromFeed" :disabled="!openVR")
+ div.options-container-item
+ span.name Hide VR Devices
+ el-switch(v-model="hideDevicesFromFeed" :disabled="!openVR")
+ div.options-container-item
+ el-button(size="small" icon="el-icon-notebook-2" @click="showWristFeedFiltersDialog()" :disabled="!openVR") Wrist Feed Filters
+ br
+ span.sub-header Notification Options
div.options-container-item
span.name Overlay Notifications
el-switch(v-model="overlayNotifications" :disabled="!openVR")
+ div.options-container-item
+ span.name Desktop Notifications
+ el-switch(v-model="desktopToast" :disabled="!openVR")
+
+ div.options-container-item
+ el-button(size="small" icon="el-icon-chat-square" @click="showNotyFeedFiltersDialog()" :disabled="!openVR") Notification Filters
+ el-button(size="small" icon="el-icon-time" @click="promptNotificationTimeout()" :disabled="!overlayNotifications || !openVR") Notification Timeout
+ el-button(size="small" icon="el-icon-rank" @click="showNotificationPositionDialog()" :disabled="!overlayNotifications || !openVR") Notification Position
+ br
+ span.sub-header TTS Options
div.options-container-item
span.name Notification TTS
el-switch(v-model="notificationTTS" :disabled="!openVR")
@@ -577,13 +593,7 @@ html
el-dropdown(@command="(voice) => changeTTSVoice(voice)" trigger="click" size="small")
el-button(v-text="TTSvoices[notificationTTSVoice].name" size="mini" :disabled="!openVR || !notificationTTS")
el-dropdown-menu(#default="dropdown")
- el-dropdown-item(v-if="voice" v-for="(voice, index) in TTSvoices" :key="index" v-text="voice.name" :command="index")
- div.options-container-item
- el-button(size="small" icon="el-icon-notebook-2" @click="showWristFeedFiltersDialog()" :disabled="!openVR") Wrist Feed Filters
- el-button(size="small" icon="el-icon-chat-square" @click="showNotyFeedFiltersDialog()" :disabled="!overlayNotifications || !openVR") Notification Filters
- div.options-container-item
- el-button(size="small" icon="el-icon-time" @click="promptNotificationTimeout()" :disabled="!overlayNotifications || !openVR") Notification Timeout
- el-button(size="small" icon="el-icon-rank" @click="showNotificationPositionDialog()" :disabled="!overlayNotifications || !openVR") Notification Position
+ el-dropdown-item(v-if="voice" v-for="(voice, index) in TTSvoices" :key="index" v-text="voice.name" :command="index")
div.options-container
span.header Application
div.options-container-item
diff --git a/html/src/vr.js b/html/src/vr.js
index 16b8d962..6e07077f 100644
--- a/html/src/vr.js
+++ b/html/src/vr.js
@@ -564,6 +564,113 @@ speechSynthesis.getVoices();
});
};
+ // API: User
+
+ API.cachedUsers = new Map();
+
+ API.$on('USER', function (args) {
+ args.ref = this.applyUser(args.json);
+ });
+
+ API.applyUser = function (json) {
+ var ref = this.cachedUsers.get(json.id);
+ if (ref === undefined) {
+ ref = {
+ id: '',
+ username: '',
+ displayName: '',
+ userIcon: '',
+ bio: '',
+ bioLinks: [],
+ currentAvatarImageUrl: '',
+ currentAvatarThumbnailImageUrl: '',
+ status: '',
+ statusDescription: '',
+ state: '',
+ tags: [],
+ developerType: '',
+ last_login: '',
+ last_platform: '',
+ allowAvatarCopying: false,
+ isFriend: false,
+ location: '',
+ worldId: '',
+ instanceId: '',
+ // VRCX
+ ...json
+ };
+ this.cachedUsers.set(ref.id, ref);
+ } else {
+ var props = {};
+ for (var prop in ref) {
+ if (ref[prop] !== Object(ref[prop])) {
+ props[prop] = true;
+ }
+ }
+ var $ref = { ...ref };
+ Object.assign(ref, json);
+ for (var prop in ref) {
+ if (ref[prop] !== Object(ref[prop])) {
+ props[prop] = true;
+ }
+ }
+ var has = false;
+ for (var prop in props) {
+ var asis = $ref[prop];
+ var tobe = ref[prop];
+ if (asis === tobe) {
+ delete props[prop];
+ } else {
+ has = true;
+ props[prop] = [
+ tobe,
+ asis
+ ];
+ }
+ }
+ }
+ return ref;
+ };
+
+ /*
+ params: {
+ userId: string
+ }
+ */
+ API.getUser = function (params) {
+ return this.call(`users/${params.userId}`, {
+ method: 'GET'
+ }).then((json) => {
+ var args = {
+ json,
+ params
+ };
+ this.$emit('USER', args);
+ return args;
+ });
+ };
+
+ /*
+ params: {
+ userId: string
+ }
+ */
+ API.getCachedUser = function (params) {
+ return new Promise((resolve, reject) => {
+ var ref = this.cachedUsers.get(params.userId);
+ if (ref === undefined) {
+ this.getUser(params).catch(reject).then(resolve);
+ } else {
+ resolve({
+ cache: true,
+ json: ref,
+ params,
+ ref
+ });
+ }
+ });
+ };
+
var $app = {
data: {
API,
@@ -574,17 +681,20 @@ speechSynthesis.getVoices();
currentUserStatus: null,
cpuUsage: 0,
isGameRunning: false,
+ isGameNoVR: false,
lastLocation: '',
lastFeedEntry: [],
feedFilters: [],
wristFeed: [],
notyMap: [],
devices: [],
+ desktopToastToggle: false,
overlayNotificationsToggle: false,
notificationTTSToggle: false,
notificationTTSVoice: '0',
hideDevicesToggle: false,
isMinimalFeed: false,
+ displayVRCPlusIconsAsAvatar: false,
notificationPosition: 'topCenter',
notificationTimeout: '3000',
notificationTheme: 'relax'
@@ -613,14 +723,13 @@ speechSynthesis.getVoices();
throw err;
}).then((args) => {
this.initConfigVars();
- this.initNotyMap();
+ if (this.appType === '1') {
+ this.updateCpuUsageLoop();
+ }
+ if (this.appType === '2') {
+ this.initNotyMap();
+ }
this.updateLoop();
- this.updateCpuUsageLoop();
- this.$nextTick(function () {
- if (this.appType === '1') {
- this.$el.style.display = '';
- }
- });
return args;
});
}
@@ -630,9 +739,11 @@ speechSynthesis.getVoices();
this.notificationTTSToggle = configRepository.getBool('VRCX_notificationTTS');
this.notificationTTSVoice = configRepository.getString('VRCX_notificationTTSVoice');
this.overlayNotificationsToggle = configRepository.getBool('VRCX_overlayNotifications');
+ this.desktopToastToggle = configRepository.getBool('VRCX_desktopToast');
this.hidePrivateFromFeed = configRepository.getBool('VRCX_hidePrivateFromFeed');
this.hideDevicesToggle = configRepository.getBool('VRCX_hideDevicesFromFeed');
this.isMinimalFeed = configRepository.getBool('VRCX_minimalFeed');
+ this.displayVRCPlusIconsAsAvatar = configRepository.getBool('displayVRCPlusIconsAsAvatar');
this.feedFilters = JSON.parse(configRepository.getString('sharedFeedFilters'));
this.notificationPosition = configRepository.getString('VRCX_notificationPosition');
this.notificationTimeout = configRepository.getString('VRCX_notificationTimeout');
@@ -678,8 +789,9 @@ speechSynthesis.getVoices();
this.currentTime = new Date().toJSON();
this.currentUserStatus = sharedRepository.getString('current_user_status');
this.isGameRunning = sharedRepository.getBool('is_game_running');
+ this.isGameNoVR = sharedRepository.getBool('is_Game_No_VR');
this.lastLocation = sharedRepository.getString('last_location');
- if (!this.hideDevicesToggle) {
+ if ((!this.hideDevicesToggle) && (this.appType === '1')) {
AppApi.GetVRDevices().then((devices) => {
devices.forEach((device) => {
device[2] = parseInt(device[2], 10);
@@ -824,8 +936,14 @@ speechSynthesis.getVoices();
if ((this.currentUserStatus === 'busy') || (!this.isGameRunning)) {
return;
}
- notyToPlay.forEach(async (noty) => {
- if (this.overlayNotificationsToggle) {
+ var bias = new Date(Date.now() - 60000).toJSON();
+ var noty = {};
+ for (var i = 0; i < notyToPlay.length; i++) {
+ noty = notyToPlay[i];
+ if (noty.created_at < bias) {
+ continue;
+ }
+ if ((this.overlayNotificationsToggle) && (!this.isGameNoVR)) {
var text = '';
switch (noty.type) {
case 'OnPlayerJoined':
@@ -927,7 +1045,66 @@ speechSynthesis.getVoices();
break;
}
}
- });
+ if ((this.desktopToastToggle) && (this.isGameNoVR)) {
+ var imageURL = '';
+ if (noty.userId) {
+ await API.getCachedUser({
+ userId: noty.userId
+ }).catch((err) => {
+ throw err;
+ }).then((args) => {
+ imageURL = args.json.currentAvatarThumbnailImageUrl;
+ if ((this.displayVRCPlusIconsAsAvatar) && (args.json.userIcon)) {
+ imageURL = args.json.userIcon;
+ }
+ });
+ }
+ switch (noty.type) {
+ case 'OnPlayerJoined':
+ AppApi.DesktopNotification(noty.data, 'has joined', imageURL);
+ break;
+ case 'OnPlayerLeft':
+ AppApi.DesktopNotification(noty.data, 'has left', imageURL);
+ break;
+ case 'OnPlayerJoining':
+ AppApi.DesktopNotification(noty.data, 'is joining', imageURL);
+ break;
+ case 'GPS':
+ AppApi.DesktopNotification(noty.displayName, 'is in ' + await this.displayLocation(noty.location[0]), imageURL);
+ break;
+ case 'Online':
+ AppApi.DesktopNotification(noty.displayName, 'has logged in', imageURL);
+ break;
+ case 'Offline':
+ AppApi.DesktopNotification(noty.displayName, 'has logged out', imageURL);
+ break;
+ case 'Status':
+ AppApi.DesktopNotification(noty.displayName, `status is now ${noty.status[0].status} ${noty.status[0].statusDescription}`, imageURL);
+ break;
+ case 'invite':
+ AppApi.DesktopNotification(noty.senderUsername, `has invited you to ${noty.details.worldName}`, imageURL);
+ break;
+ case 'requestInvite':
+ AppApi.DesktopNotification(noty.senderUsername, 'has requested an invite', imageURL);
+ break;
+ case 'friendRequest':
+ AppApi.DesktopNotification(noty.senderUsername, 'has sent you a friend request', imageURL);
+ break;
+ case 'Friend':
+ AppApi.DesktopNotification(noty.displayName, 'has sent you a friend request', imageURL);
+ break;
+ case 'Unfriend':
+ AppApi.DesktopNotification(noty.displayName, 'has unfriended you', imageURL);
+ break;
+ case 'TrustLevel':
+ AppApi.DesktopNotification(noty.displayName, `trust level is now ${noty.trustLevel}`, imageURL);
+ break;
+ case 'DisplayName':
+ AppApi.DesktopNotification(noty.previousDisplayName, `changed their name to ${noty.displayName}`, imageURL);
+ break;
+ }
+ }
+ }
};
$app.methods.userStatusClass = function (user) {