diff --git a/package.json b/package.json index 4f7717c..863346c 100644 --- a/package.json +++ b/package.json @@ -33,5 +33,6 @@ "run-script-os": "^1.1.6", "ts-jest": "^29.1.2", "typescript": "^5.3.3" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/src/css/style.css b/src/css/style.css index 85377e2..c8ba76f 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -87,40 +87,40 @@ display: none; } -tr.user-admin > td, .chat-username-admin, .username-admin { +tr.user-admin .userlist-username, .chat-username-admin, .username-admin { color: #FF0000 !important; } -tr.user-moderator > td, .chat-username-moderator, .username-moderator { +tr.user-moderator .userlist-username, .chat-username-moderator, .username-moderator { color: #00FF00 !important; } html[data-bs-theme="dark"] { - tr.user-unregistered > td, .chat-username-unregistered, .username-unregistered { + tr.user-unregistered .userlist-username, .chat-username-unregistered, .username-unregistered { color: #b1b1b1 !important; } - tr.user-registered > td, .chat-username-registered, .username-registered { + tr.user-registered .userlist-username, .chat-username-registered, .username-registered { color: #FFFFFF !important; } - tr.user-registered.user-turn > td, tr.user-registered.user-waiting > td { + tr.user-registered.user-turn .userlist-username, tr.user-registered.user-waiting .userlist-username { color: #000000 !important; --bs-table-color: #000000 !important; } - tr.user-unregistered.user-turn > td, tr.user-unregistered.user-waiting > td { + tr.user-unregistered.user-turn .userlist-username, tr.user-unregistered.user-waiting .userlist-username { color: #585858 !important; --bs-table-color: #585858 !important; } } html[data-bs-theme="light"] { - tr.user-unregistered > td, .chat-username-unregistered, .username-unregistered { + tr.user-unregistered .userlist-username, .chat-username-unregistered, .username-unregistered { color: #6b6b6b !important; } - tr.user-registered > td, .chat-username-registered, .username-registered { + tr.user-registered .userlist-username, .chat-username-registered, .username-registered { color: #000 !important; } } @@ -149,7 +149,7 @@ tr.user-waiting > td { --bs-table-bg-state: #ece1be !important; } -.user-current { +.user-current .userlist-username { font-style: italic; } @@ -339,4 +339,12 @@ Theme: cvmDisabled @font-face { font-family: 'Noto Color Emoji'; src: url('../assets/NotoColorEmoji.ttf'); +} + +.userlist-flag { + padding-right: 0.5rem; +} + +.userlist-flag:empty { + display: none; } \ No newline at end of file diff --git a/src/html/index.html b/src/html/index.html index 88a73f2..a7783bd 100644 --- a/src/html/index.html +++ b/src/html/index.html @@ -141,6 +141,7 @@

+
diff --git a/src/ts/fallbackLanguage.ts b/src/ts/fallbackLanguage.ts index f1586f8..c1f7d31 100644 --- a/src/ts/fallbackLanguage.ts +++ b/src/ts/fallbackLanguage.ts @@ -89,6 +89,7 @@ const fallbackLanguage : Language = { "kAccountModal_ConfirmNewPassword": "Confirm New Password", "kAccountModal_CurrentPassword": "Current Password", "kAccountModal_ConfirmPassword": "Confirm Password", + "kAccountModal_HideFlag": "Hide my Country Flag", "kMissingCaptcha": "Please fill out the captcha.", "kPasswordsMustMatch": "Passwords must match.", diff --git a/src/ts/i18n.ts b/src/ts/i18n.ts index 0a2c2ab..91e3960 100644 --- a/src/ts/i18n.ts +++ b/src/ts/i18n.ts @@ -94,6 +94,7 @@ export enum I18nStringKey { kAccountModal_ConfirmNewPassword = 'kAccountModal_ConfirmNewPassword', kAccountModal_CurrentPassword = 'kAccountModal_CurrentPassword', kAccountModal_ConfirmPassword = 'kAccountModal_ConfirmPassword', + kAccountModal_HideFlag = 'kAccountModal_HideFlag', kAccountModal_VerifyText = 'kAccountModal_VerifyText', kAccountModal_VerifyPasswordResetText = 'kAccountModal_VerifyPasswordResetText', @@ -295,6 +296,7 @@ export class I18n { accountSettingsNewPasswordLabel: I18nStringKey.kAccountModal_NewPassword, accountSettingsConfirmNewPasswordLabel: I18nStringKey.kAccountModal_ConfirmNewPassword, accountSettingsCurrentPasswordLabel: I18nStringKey.kAccountModal_CurrentPassword, + hideFlagCheckboxLabel: I18nStringKey.kAccountModal_HideFlag, updateAccountSettingsBtn: I18nStringKey.kGeneric_Update, accountResetPasswordEmailLabel: I18nStringKey.kGeneric_EMail, accountResetPasswordUsernameLabel: I18nStringKey.kGeneric_Username, diff --git a/src/ts/main.ts b/src/ts/main.ts index d216f0e..7c29c40 100644 --- a/src/ts/main.ts +++ b/src/ts/main.ts @@ -117,6 +117,7 @@ const elements = { accountSettingsNewPassword: document.getElementById("accountSettingsNewPassword") as HTMLInputElement, accountSettingsConfirmNewPassword: document.getElementById("accountSettingsConfirmNewPassword") as HTMLInputElement, accountSettingsCurrentPassword: document.getElementById("accountSettingsCurrentPassword") as HTMLInputElement, + hideFlagCheckbox: document.getElementById("hideFlagCheckbox") as HTMLInputElement, accountResetPasswordSection: document.getElementById("accountResetPasswordSection") as HTMLDivElement, accountResetPasswordForm: document.getElementById("accountResetPasswordForm") as HTMLFormElement, @@ -316,6 +317,8 @@ const vms: VM[] = []; const cards: HTMLDivElement[] = []; const users: { user: User; + usernameElement: HTMLSpanElement; + flagElement: HTMLSpanElement; element: HTMLTableRowElement; }[] = []; let turnInterval: number | undefined = undefined; @@ -392,6 +395,7 @@ async function openVM(vm: VM): Promise { VM!.on('chat', (username, message) => chatMessage(username, message)); VM!.on('adduser', (user) => addUser(user)); + VM!.on('flag', () => flag()); VM!.on('remuser', (user) => remUser(user)); VM!.on('rename', (oldname, newname, selfrename) => userRenamed(oldname, newname, selfrename)); @@ -598,7 +602,14 @@ function addUser(user: User) { let tr = document.createElement('tr'); tr.setAttribute('data-cvm-turn', '-1'); let td = document.createElement('td'); - td.innerHTML = user.username; + let flagSpan = document.createElement('span'); + let usernameSpan = document.createElement('span'); + flagSpan.classList.add("userlist-flag"); + usernameSpan.classList.add("userlist-username"); + td.appendChild(flagSpan); + if (user.countryCode !== null) flagSpan.innerHTML = getFlagEmoji(user.countryCode); + td.appendChild(usernameSpan); + usernameSpan.innerText = user.username; switch (user.rank) { case Rank.Admin: tr.classList.add('user-admin'); @@ -615,7 +626,7 @@ function addUser(user: User) { } if (user.username === w.username) tr.classList.add('user-current'); tr.appendChild(td); - let u = { user: user, element: tr }; + let u = { user: user, element: tr, usernameElement: usernameSpan, flagElement: flagSpan }; if (rank === Rank.Admin || rank === Rank.Moderator) userModOptions(u); elements.userlist.appendChild(tr); if (olduser !== undefined) olduser.element = tr; @@ -630,10 +641,21 @@ function remUser(user: User) { users.splice(olduser, 1); } +function getFlagEmoji(countryCode: string) { + if (countryCode.length !== 2) throw new Error('Invalid country code'); + return String.fromCodePoint(...countryCode.toUpperCase().split('').map(char => 127397 + char.charCodeAt(0))); +} + +function flag() { + for (let user of users.filter(u => u.user.countryCode !== null)) { + user.flagElement.innerHTML = getFlagEmoji(user.user.countryCode!); + } +} + function userRenamed(oldname: string, newname: string, selfrename: boolean) { let user = users.find((u) => u.user.username === newname); if (user) { - user.element.children[0].innerHTML = newname; + user.usernameElement.innerHTML = newname; } if (selfrename) { w.username = newname; @@ -1110,6 +1132,11 @@ elements.accountSettingsForm.addEventListener('submit', async e => { elements.accountModalError.style.display = "block"; return false; } + localStorage.setItem("collabvm-hide-flag", JSON.stringify(elements.hideFlagCheckbox.checked)); + if (!password && !email && !username) { + accountModal.hide(); + return false + } var result = await auth!.updateAccount(currentPassword, email, username, password); if (result.success) { elements.accountSettingsNewPassword.value = ""; @@ -1311,6 +1338,10 @@ document.addEventListener('DOMContentLoaded', async () => { renderAuth(); } + var hideFlag = JSON.parse(localStorage.getItem("collabvm-hide-flag")!); + if (hideFlag === null) hideFlag = false; + elements.hideFlagCheckbox.checked = hideFlag; + document.title = TheI18n.GetString(I18nStringKey.kGeneric_CollabVM); // Load all VMs diff --git a/src/ts/protocol/CollabVMClient.ts b/src/ts/protocol/CollabVMClient.ts index bffed35..feb0ddd 100644 --- a/src/ts/protocol/CollabVMClient.ts +++ b/src/ts/protocol/CollabVMClient.ts @@ -38,6 +38,8 @@ export interface CollabVMClientEvents { // Auth stuff auth: (server: string) => void; accountlogin: (success: boolean) => void; + + flag: () => void; } // types for private emitter @@ -413,6 +415,14 @@ export default class CollabVMClient { } } } + case 'flag': { + for (let i = 1; i < msgArr.length; i += 2) { + let user = this.users.find((u) => u.username === msgArr[i]); + if (user) user.countryCode = msgArr[i + 1]; + } + this.publicEmitter.emit('flag'); + break; + } } } @@ -493,6 +503,7 @@ export default class CollabVMClient { u(); res(success); }); + if (localStorage.getItem('collabvm-hide-flag') === 'true') this.send('noflag'); if (username === null) this.send('rename'); else this.send('rename', username); this.send('connect', id); diff --git a/src/ts/protocol/User.ts b/src/ts/protocol/User.ts index a191276..1a287cd 100644 --- a/src/ts/protocol/User.ts +++ b/src/ts/protocol/User.ts @@ -5,6 +5,7 @@ export class User { rank: Rank; // -1 means not in the turn queue, 0 means the current turn, anything else is the position in the queue turn: number; + countryCode: string | null = null; constructor(username: string, rank: Rank = Rank.Unregistered) { this.username = username; diff --git a/static/lang/en-us.json b/static/lang/en-us.json index 860e50e..80bf0b9 100644 --- a/static/lang/en-us.json +++ b/static/lang/en-us.json @@ -88,6 +88,7 @@ "kAccountModal_ConfirmNewPassword": "Confirm New Password", "kAccountModal_CurrentPassword": "Current Password", "kAccountModal_ConfirmPassword": "Confirm Password", + "kAccountModal_HideFlag": "Hide my Country Flag", "kMissingCaptcha": "Please fill out the captcha.", "kPasswordsMustMatch": "Passwords must match.", diff --git a/yarn.lock b/yarn.lock index abdea45..965c57c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1441,6 +1441,13 @@ dependencies: "@popperjs/core" "^2.9.2" +"@types/dompurify@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-3.0.5.tgz#02069a2fcb89a163bacf1a788f73cb415dd75cb7" + integrity sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg== + dependencies: + "@types/trusted-types" "*" + "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" @@ -1487,6 +1494,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== +"@types/trusted-types@*": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" + integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -1864,6 +1876,11 @@ csso@^4.2.0: dependencies: css-tree "^1.1.2" +dayjs@^1.11.10: + version "1.11.11" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e" + integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg== + debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -1922,6 +1939,11 @@ domhandler@^4.2.0, domhandler@^4.2.2, domhandler@^4.3.1: dependencies: domelementtype "^2.2.0" +dompurify@^3.1.0: + version "3.1.5" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.1.5.tgz#2c6a113fc728682a0f55684b1388c58ddb79dc38" + integrity sha512-lwG+n5h8QNpxtyrJW/gJWckL+1/DQiYMX8f7t8Z2AZTPw1esVrqjI63i7Zc2Gz0aKzLVMYC1V1PL/ky+aY/NgA== + domutils@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"