diff --git a/src/App.vue b/src/App.vue index 7b9069ca..8860d29e 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,6 +1,8 @@ diff --git a/src/bootstrap.js b/src/bootstrap.js index ee30ece6..4b4ccce3 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -18,12 +18,16 @@ import { systemIsDarkMode } from './shared/utils'; import { i18n } from './plugin'; +import { initNoty } from './plugin/noty'; + +initNoty(false); configRepository.init(); AppApi.SetUserAgent(); try { + // @ts-ignore i18n.locale = await configRepository.getString('VRCX_appLanguage', 'en'); const initThemeMode = await configRepository.getString( diff --git a/src/components/VrLocation.vue b/src/components/VrLocation.vue new file mode 100644 index 00000000..b00c8297 --- /dev/null +++ b/src/components/VrLocation.vue @@ -0,0 +1,84 @@ + + + \ No newline at end of file diff --git a/src/plugin/index.js b/src/plugin/index.js index b3e4402c..b9f4a3e1 100644 --- a/src/plugin/index.js +++ b/src/plugin/index.js @@ -1,6 +1,5 @@ import './ipc'; import './dayjs'; import './components'; -import './noty'; export { t, i18n } from './i18n'; diff --git a/src/plugin/noty.js b/src/plugin/noty.js index 1aab7084..efec0f34 100644 --- a/src/plugin/noty.js +++ b/src/plugin/noty.js @@ -1,11 +1,27 @@ import Noty from 'noty'; -Noty.overrideDefaults({ - animation: { - open: 'animate__animated animate__bounceInLeft', - close: 'animate__animated animate__bounceOutLeft' - }, - layout: 'bottomLeft', - theme: 'mint', - timeout: 6000 -}); +function initNoty(isVrOverlay) { + if (isVrOverlay) { + Noty.overrideDefaults({ + animation: { + open: 'animate__animated animate__fadeIn', + close: 'animate__animated animate__zoomOut' + }, + layout: 'topCenter', + theme: 'relax', + timeout: 3000 + }); + } else { + Noty.overrideDefaults({ + animation: { + open: 'animate__animated animate__bounceInLeft', + close: 'animate__animated animate__bounceOutLeft' + }, + layout: 'bottomLeft', + theme: 'mint', + timeout: 6000 + }); + } +} + +export { initNoty }; diff --git a/src/vr.js b/src/vr.js index 4f7de5d1..0875dfc3 100644 --- a/src/vr.js +++ b/src/vr.js @@ -4,777 +4,23 @@ // This work is licensed under the terms of the MIT license. // For a copy, see . -import '@fontsource/noto-sans-kr'; -import '@fontsource/noto-sans-jp'; -import '@fontsource/noto-sans-sc'; -import '@fontsource/noto-sans-tc'; -import '@infolektuell/noto-color-emoji'; -import ElementUI from 'element-ui'; -import Noty from 'noty'; import Vue from 'vue'; -import VueI18n from 'vue-i18n'; -import MarqueeText from 'vue-marquee-text-component'; -import * as workerTimers from 'worker-timers'; -import * as localizedStrings from './localization/localizedStrings.js'; - -import { displayLocation, parseLocation } from './shared/utils/location'; -import { escapeTag, escapeTagRecursive } from './shared/utils/base/string'; -import { removeFromArray } from './shared/utils/base/array'; -import { timeToText } from './shared/utils/base/format'; - -import pugTemplate from './vr.pug'; +import { initNoty } from './plugin/noty'; +import './plugin/i18n.js'; import InteropApi from './ipc-electron/interopApi.js'; +import Vr from './Vr.vue'; -Vue.component('marquee-text', MarqueeText); +initNoty(true); -(async function () { - let $app = {}; +if (WINDOWS) { + await CefSharp.BindObjectAsync('AppApiVr'); +} else { + // @ts-ignore + window.AppApiVr = InteropApi.AppApiVrElectron; +} - if (WINDOWS) { - await CefSharp.BindObjectAsync('AppApiVr'); - } else { - // @ts-ignore - window.AppApiVr = InteropApi.AppApiVrElectron; - } +const $app = new Vue({ + render: (h) => h(Vr) +}).$mount('#root'); - Noty.overrideDefaults({ - animation: { - open: 'animate__animated animate__fadeIn', - close: 'animate__animated animate__zoomOut' - }, - layout: 'topCenter', - theme: 'relax', - timeout: 3000 - }); - - // localization - Vue.use(VueI18n); - const i18n = new VueI18n({ - locale: 'en', - fallbackLocale: 'en', - messages: localizedStrings - }); - // eslint-disable-next-line no-unused-vars - const $t = i18n.t.bind(i18n); - Vue.use(ElementUI, { - i18n: (key, value) => i18n.t(key, value) - }); - - Vue.component('location', { - template: - '{{ text }}' + - '({{ groupName }})' + - '' + - '', - props: { - location: String, - hint: { - type: String, - default: '' - }, - grouphint: { - type: String, - default: '' - } - }, - data() { - return { - text: this.location, - region: this.region, - strict: this.strict, - groupName: this.groupName - }; - }, - methods: { - parse() { - this.text = this.location; - const L = parseLocation(this.location); - if (L.isOffline) { - this.text = 'Offline'; - } else if (L.isPrivate) { - this.text = 'Private'; - } else if (L.isTraveling) { - this.text = 'Traveling'; - } else if (typeof this.hint === 'string' && this.hint !== '') { - if (L.instanceId) { - this.text = `${this.hint} #${L.instanceName} ${L.accessTypeName}`; - } else { - this.text = this.hint; - } - } else if (L.worldId) { - if (L.instanceId) { - this.text = ` #${L.instanceName} ${L.accessTypeName}`; - } else { - this.text = this.location; - } - } - this.region = ''; - if ( - this.location !== '' && - L.instanceId && - !L.isOffline && - !L.isPrivate - ) { - this.region = L.region; - if (!L.region) { - this.region = 'us'; - } - } - this.strict = L.strict; - this.groupName = this.grouphint; - } - }, - watch: { - location() { - this.parse(); - } - }, - created() { - this.parse(); - } - }); - - const app = { - template: pugTemplate, - i18n, - data: { - // 1 = 대시보드랑 손목에 보이는거 - // 2 = 항상 화면에 보이는 거 - appType: location.href.substr(-1), - appLanguage: 'en', - currentCulture: 'en-nz', - isRunningUnderWine: false, - currentTime: new Date().toJSON(), - cpuUsageEnabled: false, - cpuUsage: 0, - pcUptimeEnabled: false, - pcUptime: '', - customInfo: '', - config: {}, - onlineFriendCount: 0, - nowPlaying: { - url: '', - name: '', - length: 0, - startTime: 0, - elapsed: 0, - percentage: 0, - remainingText: '' - }, - lastLocation: { - date: 0, - location: '', - name: '', - playerList: [], - friendList: [], - progressPie: false, - onlineFor: 0 - }, - lastLocationTimer: '', - onlineForTimer: '', - wristFeed: [], - devices: [], - deviceCount: 0, - notificationOpacity: 100 - }, - computed: {}, - methods: {}, - watch: {}, - el: '#root', - async mounted() { - if (WINDOWS) { - this.isRunningUnderWine = await AppApiVr.IsRunningUnderWine(); - await this.applyWineEmojis(); - } else { - this.updateVrElectronLoop(); - } - if (this.appType === '1') { - this.refreshCustomScript(); - this.updateStatsLoop(); - } - this.setDatetimeFormat(); - } - }; - Object.assign($app, app); - - $app.methods.configUpdate = function (json) { - $app.config = JSON.parse(json); - $app.hudFeed = []; - $app.hudTimeout = []; - $app.setDatetimeFormat(); - $app.setAppLanguage($app.config.appLanguage); - $app.updateFeedLength(); - if ( - $app.config.vrOverlayCpuUsage !== $app.cpuUsageEnabled || - $app.config.pcUptimeOnFeed !== $app.pcUptimeEnabled - ) { - $app.cpuUsageEnabled = $app.config.vrOverlayCpuUsage; - $app.pcUptimeEnabled = $app.config.pcUptimeOnFeed; - AppApiVr.ToggleSystemMonitor( - $app.cpuUsageEnabled || $app.pcUptimeEnabled - ); - } - if ($app.config.notificationOpacity !== $app.notificationOpacity) { - $app.notificationOpacity = $app.config.notificationOpacity; - $app.setNotyOpacity($app.notificationOpacity); - } - }; - - $app.methods.updateOnlineFriendCount = function (count) { - $app.onlineFriendCount = parseInt(count, 10); - }; - - $app.methods.nowPlayingUpdate = function (json) { - $app.nowPlaying = JSON.parse(json); - if ($app.appType === '2') { - const circle = document.querySelector('.np-progress-circle-stroke'); - if ( - $app.lastLocation.progressPie && - $app.nowPlaying.percentage !== 0 - ) { - circle.style.opacity = 0.5; - const circumference = circle.getTotalLength(); - circle.style.strokeDashoffset = - circumference - - ($app.nowPlaying.percentage / 100) * circumference; - } else { - circle.style.opacity = 0; - } - } - $app.updateFeedLength(); - }; - - $app.methods.lastLocationUpdate = function (json) { - $app.lastLocation = JSON.parse(json); - }; - - $app.methods.wristFeedUpdate = function (json) { - $app.wristFeed = JSON.parse(json); - $app.updateFeedLength(); - }; - - $app.methods.updateFeedLength = function () { - if ($app.appType === '2' || $app.wristFeed.length === 0) { - return; - } - let length = 16; - if (!$app.config.hideDevicesFromFeed) { - length -= 2; - if ($app.deviceCount > 8) { - length -= 1; - } - } - if ($app.nowPlaying.playing) { - length -= 1; - } - if (length < $app.wristFeed.length) { - $app.wristFeed.length = length; - } - }; - - $app.methods.refreshCustomScript = async function () { - if (document.contains(document.getElementById('vr-custom-script'))) { - document.getElementById('vr-custom-script').remove(); - } - const customScript = await AppApiVr.CustomVrScript(); - if (customScript) { - const head = document.head; - const $vrCustomScript = document.createElement('script'); - $vrCustomScript.setAttribute('id', 'vr-custom-script'); - $vrCustomScript.type = 'text/javascript'; - $vrCustomScript.textContent = customScript; - head.appendChild($vrCustomScript); - } - }; - - $app.methods.setNotyOpacity = function (value) { - const opacity = parseFloat(value / 100).toFixed(2); - let element = document.getElementById('noty-opacity'); - if (!element) { - document.body.insertAdjacentHTML( - 'beforeend', - `` - ); - element = document.getElementById('noty-opacity'); - } - element.innerHTML = `.noty_layout { opacity: ${opacity}; }`; - }; - - $app.methods.updateStatsLoop = async function () { - try { - $app.currentTime = new Date() - .toLocaleDateString($app.currentCulture, { - month: '2-digit', - day: '2-digit', - year: 'numeric', - hour: 'numeric', - minute: 'numeric', - second: 'numeric', - hourCycle: $app.config.dtHour12 ? 'h12' : 'h23' - }) - .replace(' AM', ' am') - .replace(' PM', ' pm') - .replace(',', ''); - - if ($app.cpuUsageEnabled) { - const cpuUsage = await AppApiVr.CpuUsage(); - $app.cpuUsage = cpuUsage.toFixed(0); - } - if ($app.lastLocation.date !== 0) { - $app.lastLocationTimer = timeToText( - Date.now() - $app.lastLocation.date - ); - } else { - $app.lastLocationTimer = ''; - } - if ($app.lastLocation.onlineFor) { - $app.onlineForTimer = timeToText( - Date.now() - $app.lastLocation.onlineFor - ); - } else { - $app.onlineForTimer = ''; - } - - if (!$app.config.hideDevicesFromFeed) { - AppApiVr.GetVRDevices().then((devices) => { - let deviceList = []; - let baseStations = 0; - devices.forEach((device) => { - device[3] = parseInt(device[3], 10); - if (device[0] === 'base' && device[1] === 'connected') { - baseStations++; - } else { - deviceList.push(device); - } - }); - $app.deviceCount = deviceList.length; - const deviceValue = (dev) => { - if (dev[0] === 'headset') return 0; - if (dev[0] === 'leftController') return 1; - if (dev[0] === 'rightController') return 2; - if (dev[0].toLowerCase().includes('controller')) - return 3; - if (dev[0] === 'tracker' || dev[0] === 'base') return 4; - return 5; - }; - deviceList.sort((a, b) => deviceValue(a) - deviceValue(b)); - deviceList.sort((a, b) => { - if (a[1] === b[1]) { - return 0; - } - if (a[1] === 'connected') { - return -1; - } - if (a[1] === 'disconnected') { - return 1; - } - return 0; - }); - if (baseStations > 0) { - deviceList.push([ - 'base', - 'connected', - '', - baseStations - ]); - $app.deviceCount += 1; - } - $app.devices = deviceList; - }); - } else { - $app.devices = []; - } - if ($app.config.pcUptimeOnFeed) { - AppApiVr.GetUptime().then((uptime) => { - if (uptime) { - $app.pcUptime = timeToText(uptime); - } - }); - } else { - $app.pcUptime = ''; - } - } catch (err) { - console.error(err); - } - workerTimers.setTimeout(() => $app.updateStatsLoop(), 500); - }; - - $app.methods.updateVrElectronLoop = async function () { - try { - if ($app.appType === '1') { - const wristOverlayQueue = - await AppApiVr.GetExecuteVrFeedFunctionQueue(); - if (wristOverlayQueue) { - wristOverlayQueue.forEach((item) => { - // item[0] is the function name, item[1] is already an object - const fullFunctionName = item[0]; - const jsonArg = item[1]; - - if ( - typeof window.$app === 'object' && - typeof window.$app[fullFunctionName] === 'function' - ) { - window.$app[fullFunctionName](jsonArg); - } else { - console.error( - `$app.${fullFunctionName} is not defined or is not a function` - ); - } - }); - } - } else { - const hmdOverlayQueue = - await AppApiVr.GetExecuteVrOverlayFunctionQueue(); - if (hmdOverlayQueue) { - hmdOverlayQueue.forEach((item) => { - // item[0] is the function name, item[1] is already an object - const fullFunctionName = item[0]; - const jsonArg = item[1]; - - if ( - typeof window.$app === 'object' && - typeof window.$app[fullFunctionName] === 'function' - ) { - window.$app[fullFunctionName](jsonArg); - } else { - console.error( - `$app.${fullFunctionName} is not defined or is not a function` - ); - } - }); - } - } - } catch (err) { - console.error(err); - } - workerTimers.setTimeout(() => $app.updateVrElectronLoop(), 500); - }; - - $app.methods.playNoty = function (json) { - let { noty, message, image } = JSON.parse(json); - if (typeof noty === 'undefined') { - console.error('noty is undefined'); - return; - } - noty = escapeTagRecursive(noty); - message = escapeTag(message) || ''; - let text = ''; - let img = ''; - if (image) { - img = ``; - } - switch (noty.type) { - case 'OnPlayerJoined': - text = `${noty.displayName} has joined`; - break; - case 'OnPlayerLeft': - text = `${noty.displayName} has left`; - break; - case 'OnPlayerJoining': - text = `${noty.displayName} is joining`; - break; - case 'GPS': - text = `${ - noty.displayName - } is in ${displayLocation( - noty.location, - noty.worldName, - noty.groupName - )}`; - break; - case 'Online': - let locationName = ''; - if (noty.worldName) { - locationName = ` to ${displayLocation( - noty.location, - noty.worldName, - noty.groupName - )}`; - } - text = `${noty.displayName} has logged in${locationName}`; - break; - case 'Offline': - text = `${noty.displayName} has logged out`; - break; - case 'Status': - text = `${noty.displayName} status is now ${noty.status} ${noty.statusDescription}`; - break; - case 'invite': - text = `${ - noty.senderUsername - } has invited you to ${displayLocation( - noty.details.worldId, - noty.details.worldName, - '' - )}${message}`; - break; - case 'requestInvite': - text = `${noty.senderUsername} has requested an invite ${message}`; - break; - case 'inviteResponse': - text = `${noty.senderUsername} has responded to your invite ${message}`; - break; - case 'requestInviteResponse': - text = `${noty.senderUsername} has responded to your invite request ${message}`; - break; - case 'friendRequest': - text = `${noty.senderUsername} has sent you a friend request`; - break; - case 'Friend': - text = `${noty.displayName} is now your friend`; - break; - case 'Unfriend': - text = `${noty.displayName} is no longer your friend`; - break; - case 'TrustLevel': - text = `${noty.displayName} trust level is now ${noty.trustLevel}`; - break; - case 'DisplayName': - text = `${noty.previousDisplayName} changed their name to ${noty.displayName}`; - break; - case 'boop': - text = noty.message; - break; - case 'groupChange': - text = `${noty.senderUsername} ${noty.message}`; - break; - case 'group.announcement': - text = noty.message; - break; - case 'group.informative': - text = noty.message; - break; - case 'group.invite': - text = noty.message; - break; - case 'group.joinRequest': - text = noty.message; - break; - case 'group.transfer': - text = noty.message; - break; - case 'group.queueReady': - text = noty.message; - break; - case 'instance.closed': - text = noty.message; - break; - case 'PortalSpawn': - if (noty.displayName) { - text = `${ - noty.displayName - } has spawned a portal to ${displayLocation( - noty.instanceId, - noty.worldName, - noty.groupName - )}`; - } else { - text = 'User has spawned a portal'; - } - break; - case 'AvatarChange': - text = `${noty.displayName} changed into avatar ${noty.name}`; - break; - case 'ChatBoxMessage': - text = `${noty.displayName} said ${noty.text}`; - break; - case 'Event': - text = noty.data; - break; - case 'External': - text = noty.message; - break; - case 'VideoPlay': - text = `Now playing: ${noty.notyName}`; - break; - case 'BlockedOnPlayerJoined': - text = `Blocked user ${noty.displayName} has joined`; - break; - case 'BlockedOnPlayerLeft': - text = `Blocked user ${noty.displayName} has left`; - break; - case 'MutedOnPlayerJoined': - text = `Muted user ${noty.displayName} has joined`; - break; - case 'MutedOnPlayerLeft': - text = `Muted user ${noty.displayName} has left`; - break; - case 'Blocked': - text = `${noty.displayName} has blocked you`; - break; - case 'Unblocked': - text = `${noty.displayName} has unblocked you`; - break; - case 'Muted': - text = `${noty.displayName} has muted you`; - break; - case 'Unmuted': - text = `${noty.displayName} has unmuted you`; - break; - default: - break; - } - if (text) { - new Noty({ - type: 'alert', - theme: $app.config.notificationTheme, - timeout: $app.config.notificationTimeout, - layout: $app.config.notificationPosition, - text: `${img}
${text}
` - }).show(); - } - }; - - $app.methods.statusClass = function (status) { - let style = {}; - if (typeof status === 'undefined') { - return style; - } - if (status === 'active') { - // Online - style.online = true; - } else if (status === 'join me') { - // Join Me - style.joinme = true; - } else if (status === 'ask me') { - // Ask Me - style.askme = true; - } else if (status === 'busy') { - // Do Not Disturb - style.busy = true; - } - return style; - }; - - $app.methods.notyClear = function () { - Noty.closeAll(); - }; - - $app.data.hudFeed = []; - $app.data.cleanHudFeedLoopStatus = false; - - $app.methods.cleanHudFeedLoop = function () { - if (!$app.cleanHudFeedLoopStatus) { - return; - } - $app.cleanHudFeed(); - if ($app.hudFeed.length === 0) { - $app.cleanHudFeedLoopStatus = false; - return; - } - workerTimers.setTimeout(() => $app.cleanHudFeedLoop(), 500); - }; - - $app.methods.cleanHudFeed = function () { - const dt = Date.now(); - $app.hudFeed.forEach((item) => { - if (item.time + $app.config.photonOverlayMessageTimeout < dt) { - removeFromArray($app.hudFeed, item); - } - }); - if ($app.hudFeed.length > 10) { - $app.hudFeed.length = 10; - } - if (!$app.cleanHudFeedLoopStatus) { - $app.cleanHudFeedLoopStatus = true; - $app.cleanHudFeedLoop(); - } - }; - - $app.methods.addEntryHudFeed = function (json) { - const data = JSON.parse(json); - let combo = 1; - $app.hudFeed.forEach((item) => { - if ( - item.displayName === data.displayName && - item.text === data.text - ) { - combo = item.combo + 1; - removeFromArray($app.hudFeed, item); - } - }); - $app.hudFeed.unshift({ - time: Date.now(), - combo, - ...data - }); - $app.cleanHudFeed(); - }; - - $app.methods.updateHudFeedTag = function (json) { - const ref = JSON.parse(json); - $app.hudFeed.forEach((item) => { - if (item.userId === ref.userId) { - item.colour = ref.colour; - } - }); - }; - - $app.data.hudTimeout = []; - - $app.methods.updateHudTimeout = function (json) { - $app.hudTimeout = JSON.parse(json); - }; - - $app.methods.setDatetimeFormat = async function () { - $app.currentCulture = await AppApiVr.CurrentCulture(); - const formatDate = function (date) { - if (!date) { - return ''; - } - const dt = new Date(date); - return dt - .toLocaleTimeString($app.currentCulture, { - hour: '2-digit', - minute: 'numeric', - hourCycle: $app.config.dtHour12 ? 'h12' : 'h23' - }) - .replace(' am', '') - .replace(' pm', ''); - }; - Vue.filter('formatDate', formatDate); - }; - - $app.methods.setAppLanguage = function (appLanguage) { - if (!appLanguage) { - return; - } - if (appLanguage !== $app.appLanguage) { - $app.appLanguage = appLanguage; - i18n.locale = $app.appLanguage; - } - }; - - $app.methods.applyWineEmojis = async function () { - if (document.contains(document.getElementById('app-emoji-font'))) { - document.getElementById('app-emoji-font').remove(); - } - if ($app.isRunningUnderWine) { - const $appEmojiFont = document.createElement('link'); - $appEmojiFont.setAttribute('id', 'app-emoji-font'); - $appEmojiFont.rel = 'stylesheet'; - $appEmojiFont.href = 'emoji.font.css'; - document.head.appendChild($appEmojiFont); - } - }; - - $app.methods.trackingResultToClass = function (deviceStatus) { - switch (deviceStatus) { - case 'Uninitialized': - case 'Calibrating_OutOfRange': - case 'Fallback_RotationOnly': - return 'tracker-error'; - - case 'Calibrating_InProgress': - case 'Running_OutOfRange': - return 'tracker-warning'; - - case 'Running_OK': - default: - return ''; - } - }; - - $app = new Vue($app); - window.$app = $app; -})(); +window.$app = $app; diff --git a/src/vr.pug b/src/vr.pug index c3726161..f1fcc38a 100644 --- a/src/vr.pug +++ b/src/vr.pug @@ -1,4 +1,3 @@ -doctype html #x-app.x-app.x-app-type(:class='{ background: appType === "1" && config && config.backgroundEnabled }') template(v-if='appType === "1"') .x-container(style='flex: 1') @@ -784,5 +783,3 @@ doctype html path( fill='#ED1B24' d='M102.9,75l11.3-11.3c10.3-10.3,11.5-26.1,3.8-37.8l17.4-17.4L126.9,0l-17.4,17.4C97.9,9.7,82,11,71.8,21.2L60.5,32.5C102,74,60.8,32.9,102.9,75z') -script(src='vendor.js') -script(src='vr.js')