Add better player card details and ringers

master
John Montagu, the 4th Earl of Sandvich 2024-10-25 09:51:36 -07:00
parent c08f2434e6
commit 849b628130
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
15 changed files with 530 additions and 38 deletions

View File

@ -9,7 +9,9 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"axios": "^1.7.7", "axios": "^1.7.7",
"bootstrap-icons": "^1.11.3",
"pinia": "^2.2.4", "pinia": "^2.2.4",
"v-tooltip": "^2.1.3",
"vue": "^3.5.12", "vue": "^3.5.12",
"vue-router": "^4.4.5" "vue-router": "^4.4.5"
}, },
@ -68,6 +70,18 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/@babel/runtime": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.9.tgz",
"integrity": "sha512-4zpTHZ9Cm6L9L+uIqghQX8ZXg8HKFcjYO3qHoO8zTmRm6HQUJ8SSJ+KRvbMBZn0EGVlT4DRYeQ/6hjlyXBh+Kg==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.25.9", "version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.9.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.9.tgz",
@ -2064,6 +2078,22 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/bootstrap-icons": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz",
"integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"license": "MIT"
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@ -4442,7 +4472,6 @@
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.merge": { "node_modules/lodash.merge": {
@ -5171,6 +5200,17 @@
} }
} }
}, },
"node_modules/popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.47", "version": "8.4.47",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
@ -5374,6 +5414,12 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0" "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
} }
}, },
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"license": "MIT"
},
"node_modules/request-progress": { "node_modules/request-progress": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz",
@ -5676,6 +5722,16 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"license": "BSD-3-Clause",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@ -6195,6 +6251,73 @@
"uuid": "dist/bin/uuid" "uuid": "dist/bin/uuid"
} }
}, },
"node_modules/v-tooltip": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/v-tooltip/-/v-tooltip-2.1.3.tgz",
"integrity": "sha512-xXngyxLQTOx/yUEy50thb8te7Qo4XU6h4LZB6cvEfVd9mnysUxLEoYwGWDdqR+l69liKsy3IPkdYff3J1gAJ5w==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.13.10",
"lodash": "^4.17.21",
"popper.js": "^1.16.1",
"vue-resize": "^1.0.1"
}
},
"node_modules/v-tooltip/node_modules/@vue/compiler-sfc": {
"version": "2.7.16",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz",
"integrity": "sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==",
"peer": true,
"dependencies": {
"@babel/parser": "^7.23.5",
"postcss": "^8.4.14",
"source-map": "^0.6.1"
},
"optionalDependencies": {
"prettier": "^1.18.2 || ^2.0.0"
}
},
"node_modules/v-tooltip/node_modules/prettier": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"license": "MIT",
"optional": true,
"peer": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/v-tooltip/node_modules/vue": {
"version": "2.7.16",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.16.tgz",
"integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==",
"deprecated": "Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-sfc": "2.7.16",
"csstype": "^3.1.0"
}
},
"node_modules/v-tooltip/node_modules/vue-resize": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-1.0.1.tgz",
"integrity": "sha512-z5M7lJs0QluJnaoMFTIeGx6dIkYxOwHThlZDeQnWZBizKblb99GSejPnK37ZbNE/rVwDcYcHY+Io+AxdpY952w==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"vue": "^2.6.0"
}
},
"node_modules/verror": { "node_modules/verror": {
"version": "1.10.0", "version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",

View File

@ -15,7 +15,9 @@
}, },
"dependencies": { "dependencies": {
"axios": "^1.7.7", "axios": "^1.7.7",
"bootstrap-icons": "^1.11.3",
"pinia": "^2.2.4", "pinia": "^2.2.4",
"v-tooltip": "^2.1.3",
"vue": "^3.5.12", "vue": "^3.5.12",
"vue-router": "^4.4.5" "vue-router": "^4.4.5"
}, },

View File

@ -1,3 +1,6 @@
@import url("tf2icons.css");
@import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css");
/* color palette from <https://github.com/vuejs/theme> */ /* color palette from <https://github.com/vuejs/theme> */
:root { :root {
--vt-c-white: #ffffff; --vt-c-white: #ffffff;

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 93 KiB

View File

@ -23,12 +23,26 @@ a,
} }
button { button {
color: var(--text); font-weight: 700;
border: none; color: var(--text);
background-color: var(--surface-0);
border: none;
padding: 8px;
border-radius: 4px;
} }
button.accent {
background-color: var(--accent);
color: var(--base);
text-transform: uppercase;
}
h1 { h1 {
font-weight: 800; font-weight: 800;
font-size: 300%; font-size: 200%;
line-height: 2em;
}
em.aside {
color: var(--overlay-0);
} }

View File

@ -0,0 +1,75 @@
@font-face {
font-family: 'tf2-classicons';
src: url('fonts/tf2-classicons.eot?bv99da');
src: url('fonts/tf2-classicons.eot?bv99da#iefix') format('embedded-opentype'),
url('fonts/tf2-classicons.ttf?bv99da') format('truetype'),
url('fonts/tf2-classicons.woff?bv99da') format('woff'),
url('fonts/tf2-classicons.svg?bv99da#tf2-classicons') format('svg');
font-weight: normal;
font-style: normal;
}
i, .icomoon-liga {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'tf2-classicons' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Enable Ligatures ================ */
letter-spacing: 0;
-webkit-font-feature-settings: "liga";
-moz-font-feature-settings: "liga=1";
-moz-font-feature-settings: "liga";
-ms-font-feature-settings: "liga" 1;
font-feature-settings: "liga";
-webkit-font-variant-ligatures: discretionary-ligatures;
font-variant-ligatures: discretionary-ligatures;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.tf2-Heavy:before {
content: "\1f4aa";
}
.tf2-Medic:before {
content: "\1f497";
}
.tf2-Pyro:before {
content: "\1f525";
}
.tf2-Scout:before {
content: "\1f407";
}
.tf2-Sniper:before {
content: "\1f3b7";
}
.tf2-Soldier:before {
content: "\1f4a5";
}
.tf2-Spy:before {
content: "\1f4e6";
}
.tf2-Demo:before {
content: "\1f4a3";
}
.tf2-Engineer:before {
content: "\1f527";
}
.tf2-FlankSoldier:before {
content: "\1f400";
}
.tf2-PocketSoldier:before {
content: "\1f418";
}
.tf2-FlankScout:before {
content: "\1f43f";
}
.tf2-PocketScout:before {
content: "\1f416";
}

View File

@ -9,21 +9,47 @@ const props = defineProps({
roleTitle: String, roleTitle: String,
player: Object as PropType<PlayerTeamRole>, player: Object as PropType<PlayerTeamRole>,
isRoster: Boolean, isRoster: Boolean,
isRinger: Boolean,
}); });
const isSelected = computed(() => { const isSelected = computed(() => {
if (props.isRoster) { if (props.isRoster) {
return rosterStore.selectedRole == props.roleTitle; return rosterStore.selectedRole == props.roleTitle;
} }
if (props.isRinger) {
return rosterStore.selectedPlayers[props.roleTitle]?.playtime == -1;
}
return Object.values(rosterStore.selectedPlayers).includes(props.player); return Object.values(rosterStore.selectedPlayers).includes(props.player);
}); });
function onClick() { function onClick() {
if (props.isRoster) { if (props.isRoster) {
rosterStore.selectedRole = props.roleTitle; if (rosterStore.selectedRole == props.roleTitle) {
rosterStore.selectedRole = undefined;
} else {
rosterStore.selectedRole = props.roleTitle;
}
} else { } else {
// we are selecting the player // we are selecting the player
rosterStore.selectPlayerForRole(props.player, props.roleTitle); if (isSelected.value) {
rosterStore.selectPlayerForRole(undefined, props.roleTitle);
} else {
if (props.isRinger) {
const ringerPlayer: PlayerTeamRole = {
steamId: -1,
name: "Ringer",
role: props.roleTitle,
main: false,
availability: 1,
playtime: -1,
};
rosterStore.selectPlayerForRole(ringerPlayer, props.roleTitle);
} else {
rosterStore.selectPlayerForRole(props.player, props.roleTitle);
}
}
} }
}; };
</script> </script>
@ -31,45 +57,81 @@ function onClick() {
<template> <template>
<button :class="{ <button :class="{
'player-card': true, 'player-card': true,
'no-player': !player, 'no-player': !player && !isRinger,
'selected': isSelected, 'selected': isSelected,
}" @click="onClick"> 'can-be-available': player?.availability == 2
<div v-if="player"> }" @click="onClick">
<h1>{{ player.name }}</h1> <div class="role-icon">
<span v-if="roleTitle != player.role"> <i :class="rosterStore.roleIcons[roleTitle]" />
Subbing in as </div>
</span> <div v-if="player" class="role-info">
{{ player.role }} <span>
<span v-if="!player.main"> <h4 class="player-name">{{ player.name }}</h4>
(alternate role) <span v-if="roleTitle != player.role">
Subbing in as
</span>
{{ player.role }}
<span v-if="!player.main && isRoster">
(alternate)
</span>
</span> </span>
</div> </div>
<div v-else> <div v-else-if="isRinger" class="role-info">
{{ roleTitle }} <span>
<h4 class="player-name">Ringer</h4>
{{ roleTitle }}
</span>
</div>
<div v-else class="role-info">
<span>
{{ roleTitle }}
</span>
</div> </div>
</button> </button>
</template> </template>
<style scoped> <style scoped>
.player-card { .player-card {
background-color: var(--crust); background-color: white;
padding: 1em; padding: 1em;
border-radius: 8px; border-radius: 8px;
user-select: none; user-select: none;
display: flex;
gap: 1em;
align-items: center;
border: 2px solid white;
box-shadow: 1px 1px 8px var(--surface-0);
}
.player-card.can-be-available {
color: var(--overlay-0);
}
.player-card .role-icon {
display: flex;
flex-direction: row;
align-items: center;
font-size: 2em;
}
.player-card .role-info {
text-align: left;
} }
.player-card:hover { .player-card:hover {
background-color: var(--surface-0); background-color: var(--surface-0);
transition-duration: 200ms; transition-duration: 200ms;
border-color: var(--surface-0);
} }
.player-card.no-player { .player-card.no-player {
border: 2px dashed var(--overlay-0); border: 2px solid var(--overlay-0);
box-shadow: none;
} }
.player-card.no-player.selected { .player-card.no-player.selected {
background-color: var(--accent-transparent); background-color: var(--accent-transparent);
border: 2px dashed var(--accent); border: 2px solid var(--accent);
color: var(--accent); color: var(--accent);
} }
@ -89,8 +151,8 @@ function onClick() {
color: var(--accent); color: var(--accent);
} }
h1 { .player-name {
font-size: 24px; font-size: 16px;
font-weight: 700; font-weight: 600;
} }
</style> </style>

View File

@ -9,4 +9,5 @@ export interface PlayerTeamRole {
role: string; role: string;
main: boolean; main: boolean;
availability: number; availability: number;
playtime: number;
} }

View File

@ -20,7 +20,7 @@ const router = createRouter({
path: "/schedule/roster", path: "/schedule/roster",
name: "roster-builder", name: "roster-builder",
component: RosterBuilderView component: RosterBuilderView
} },
] ]
}) })

View File

@ -14,7 +14,7 @@ export const useRosterStore = defineStore("roster", () => {
const selectedPlayers: Reactive<{ [key: string]: PlayerTeamRole }> = reactive({}); const selectedPlayers: Reactive<{ [key: string]: PlayerTeamRole }> = reactive({});
const selectedRole: Ref<String | undefined> = ref("Pocket Scout"); const selectedRole: Ref<String | undefined> = ref(undefined);
const availablePlayers: Reactive<Array<PlayerTeamRole>> = reactive([ const availablePlayers: Reactive<Array<PlayerTeamRole>> = reactive([
{ {
@ -23,6 +23,7 @@ export const useRosterStore = defineStore("roster", () => {
role: "Flank Scout", role: "Flank Scout",
main: true, main: true,
availability: 1, availability: 1,
playtime: 35031,
}, },
{ {
steamId: 2839, steamId: 2839,
@ -30,6 +31,7 @@ export const useRosterStore = defineStore("roster", () => {
role: "Flank Scout", role: "Flank Scout",
main: false, main: false,
availability: 1, availability: 1,
playtime: 28811,
}, },
{ {
steamId: 2839, steamId: 2839,
@ -37,6 +39,7 @@ export const useRosterStore = defineStore("roster", () => {
role: "Pocket Scout", role: "Pocket Scout",
main: true, main: true,
availability: 1, availability: 1,
playtime: 28811,
}, },
{ {
steamId: 2841, steamId: 2841,
@ -44,6 +47,7 @@ export const useRosterStore = defineStore("roster", () => {
role: "Pocket Soldier", role: "Pocket Soldier",
main: true, main: true,
availability: 2, availability: 2,
playtime: 98372,
}, },
{ {
steamId: 2841, steamId: 2841,
@ -51,6 +55,7 @@ export const useRosterStore = defineStore("roster", () => {
role: "Roamer", role: "Roamer",
main: false, main: false,
availability: 2, availability: 2,
playtime: 98372,
}, },
{ {
steamId: 2282, steamId: 2282,
@ -58,6 +63,7 @@ export const useRosterStore = defineStore("roster", () => {
role: "Demoman", role: "Demoman",
main: true, main: true,
availability: 2, availability: 2,
playtime: 47324,
}, },
{ {
steamId: 2842, steamId: 2842,
@ -65,6 +71,7 @@ export const useRosterStore = defineStore("roster", () => {
role: "Roamer", role: "Roamer",
main: false, main: false,
availability: 2, availability: 2,
playtime: 12028,
}, },
{ {
steamId: 2842, steamId: 2842,
@ -72,6 +79,7 @@ export const useRosterStore = defineStore("roster", () => {
role: "Demoman", role: "Demoman",
main: false, main: false,
availability: 2, availability: 2,
playtime: 12028,
}, },
{ {
steamId: 2842, steamId: 2842,
@ -79,6 +87,7 @@ export const useRosterStore = defineStore("roster", () => {
role: "Pocket Scout", role: "Pocket Scout",
main: false, main: false,
availability: 2, availability: 2,
playtime: 12028,
}, },
//{ //{
// steamId: 2843, // steamId: 2843,
@ -93,6 +102,7 @@ export const useRosterStore = defineStore("roster", () => {
role: "Pocket Soldier", role: "Pocket Soldier",
main: false, main: false,
availability: 2, availability: 2,
playtime: 50201,
}, },
{ {
steamId: 2843, steamId: 2843,
@ -100,6 +110,7 @@ export const useRosterStore = defineStore("roster", () => {
role: "Roamer", role: "Roamer",
main: false, main: false,
availability: 2, availability: 2,
playtime: 50201,
}, },
{ {
steamId: 2844, steamId: 2844,
@ -107,6 +118,7 @@ export const useRosterStore = defineStore("roster", () => {
role: "Roamer", role: "Roamer",
main: true, main: true,
availability: 1, availability: 1,
playtime: 4732,
}, },
{ {
steamId: 2844, steamId: 2844,
@ -114,6 +126,7 @@ export const useRosterStore = defineStore("roster", () => {
role: "Pocket Soldier", role: "Pocket Soldier",
main: false, main: false,
availability: 1, availability: 1,
playtime: 4732,
}, },
]); ]);
@ -129,9 +142,27 @@ export const useRosterStore = defineStore("roster", () => {
return availablePlayerRoles.value.filter((player) => player.availability == 1); return availablePlayerRoles.value.filter((player) => player.availability == 1);
}); });
const mainRoles = computed(() => {
return availablePlayerRoles.value.filter((player) => player.main)
.sort((a, b) => b.playtime - a.playtime);
});
const alternateRoles = computed(() => {
return availablePlayerRoles.value.filter((player) => !player.main)
.sort((a, b) => b.playtime - a.playtime);
});
const roleIcons = reactive({
"Pocket Scout": "tf2-PocketScout",
"Flank Scout": "tf2-FlankScout",
"Pocket Soldier": "tf2-PocketSoldier",
"Roamer": "tf2-FlankSoldier",
"Demoman": "tf2-Demo",
"Medic": "tf2-Medic",
});
function selectPlayerForRole(player: PlayerTeamRole, role: string) { function selectPlayerForRole(player: PlayerTeamRole, role: string) {
console.log("selecting."); if (player && player.steamId > 0) {
if (player) {
const existingRole = Object.keys(selectedPlayers).find((selectedRole) => { const existingRole = Object.keys(selectedPlayers).find((selectedRole) => {
return selectedPlayers[selectedRole]?.steamId == player.steamId && return selectedPlayers[selectedRole]?.steamId == player.steamId &&
role != selectedRole; role != selectedRole;
@ -154,5 +185,8 @@ export const useRosterStore = defineStore("roster", () => {
selectPlayerForRole, selectPlayerForRole,
definitelyAvailable, definitelyAvailable,
canBeAvailable, canBeAvailable,
roleIcons,
mainRoles,
alternateRoles,
} }
}); });

View File

@ -10,11 +10,27 @@ const rosterStore = useRosterStore();
const hasAvailablePlayers = computed(() => { const hasAvailablePlayers = computed(() => {
return rosterStore.availablePlayerRoles.length > 0; return rosterStore.availablePlayerRoles.length > 0;
}); });
const hasAlternates = computed(() => {
return rosterStore.alternateRoles.length > 0;
});
</script> </script>
<template> <template>
<main> <main>
<h1>Roster</h1> <div class="top">
<h1 class="roster-title">
Roster for Snus Brotherhood
<em class="aside date">Aug. 13, 2036 @ 11:30 PM EST</em>
</h1>
<div class="button-group">
<button>
<i class="bi bi-box-arrow-left"></i>
Back
</button>
<button class="accent">Submit</button>
</div>
</div>
<div class="columns"> <div class="columns">
<div class="column"> <div class="column">
<PlayerCard v-for="role in rosterStore.neededRoles" <PlayerCard v-for="role in rosterStore.neededRoles"
@ -23,25 +39,38 @@ const hasAvailablePlayers = computed(() => {
is-roster /> is-roster />
</div> </div>
<div class="column"> <div class="column">
<h3 v-if="hasAvailablePlayers">Available</h3> <PlayerCard v-for="player in rosterStore.mainRoles"
<PlayerCard v-for="player in rosterStore.definitelyAvailable"
:player="player" :player="player"
:role-title="player.role" /> :role-title="player.role" />
<span v-if="!hasAvailablePlayers"> <span v-if="!hasAvailablePlayers && rosterStore.selectedRole">
No players are currently available for this role. No players are currently available for this role.
</span> </span>
</div> <h3 v-if="hasAvailablePlayers">Alternates</h3>
<div class="column"> <PlayerCard v-for="player in rosterStore.alternateRoles"
<h3 v-if="hasAvailablePlayers">Available if needed</h3>
<PlayerCard v-for="player in rosterStore.canBeAvailable"
:player="player" :player="player"
:role-title="player.role" /> :role-title="player.role" />
<PlayerCard v-if="rosterStore.selectedRole"
is-ringer
:role-title="rosterStore.selectedRole" />
</div> </div>
</div> </div>
</main> </main>
</template> </template>
<style scoped> <style scoped>
.top {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.top .button-group {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
.columns { .columns {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -57,8 +86,20 @@ const hasAvailablePlayers = computed(() => {
width: 100%; width: 100%;
} }
h3 { .column h3 {
font-weight: 700; font-weight: 700;
color: var(--subtext-0); font-size: 14px;
text-transform: uppercase;
color: var(--overlay-0);
}
.roster-title {
display: flex;
gap: 0.5em;
}
em.aside.date {
font-size: 14px;
vertical-align: middle;
} }
</style> </style>

View File

@ -0,0 +1,79 @@
<script setup lang="ts">
import PlayerCard from "../components/PlayerCard.vue";
import RoleSlot from "../components/RoleSlot.vue";
import PlayerTeamRole from "../player.ts";
import { computed, reactive } from "vue";
import { useRosterStore } from "../stores/roster";
const rosterStore = useRosterStore();
const hasAvailablePlayers = computed(() => {
return rosterStore.availablePlayerRoles.length > 0;
});
</script>
<template>
<main>
<h1 class="roster-title">
Roster for Snus Brotherhood
<emph class="aside date">Aug. 13, 2036 @ 11:30 PM EST</emph>
</h1>
<div class="columns">
<div class="column">
<PlayerCard v-for="role in rosterStore.neededRoles"
:player="rosterStore.selectedPlayers[role]"
:role-title="role"
is-roster />
</div>
<div class="column">
<h3 v-if="hasAvailablePlayers">Available</h3>
<PlayerCard v-for="player in rosterStore.definitelyAvailableAll"
:player="player"
:role-title="player.role" />
<span v-if="!hasAvailablePlayers">
No players are currently available for this role.
</span>
</div>
<div class="column">
<h3 v-if="hasAvailablePlayers">Available if needed</h3>
<PlayerCard v-for="player in rosterStore.canBeAvailableAll"
:player="player"
:role-title="player.role" />
</div>
</div>
</main>
</template>
<style scoped>
.columns {
display: flex;
flex-direction: row;
}
.column {
display: flex;
flex-grow: 1;
margin-left: 4em;
margin-right: 4em;
flex-direction: column;
row-gap: 8px;
width: 100%;
}
.column h3 {
font-weight: 700;
font-size: 14px;
text-transform: uppercase;
color: var(--overlay-0);
}
.roster-title {
display: flex;
gap: 0.5em;
}
emph.aside.date {
font-size: 14px;
vertical-align: middle;
}
</style>