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"