Add better player card details and ringers
							parent
							
								
									c08f2434e6
								
							
						
					
					
						commit
						849b628130
					
				| 
						 | 
				
			
			@ -9,7 +9,9 @@
 | 
			
		|||
      "version": "0.0.0",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "axios": "^1.7.7",
 | 
			
		||||
        "bootstrap-icons": "^1.11.3",
 | 
			
		||||
        "pinia": "^2.2.4",
 | 
			
		||||
        "v-tooltip": "^2.1.3",
 | 
			
		||||
        "vue": "^3.5.12",
 | 
			
		||||
        "vue-router": "^4.4.5"
 | 
			
		||||
      },
 | 
			
		||||
| 
						 | 
				
			
			@ -68,6 +70,18 @@
 | 
			
		|||
        "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": {
 | 
			
		||||
      "version": "7.25.9",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.9.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -2064,6 +2078,22 @@
 | 
			
		|||
      "dev": true,
 | 
			
		||||
      "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": {
 | 
			
		||||
      "version": "2.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -4442,7 +4472,6 @@
 | 
			
		|||
      "version": "4.17.21",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
 | 
			
		||||
      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "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": {
 | 
			
		||||
      "version": "8.4.47",
 | 
			
		||||
      "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_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": {
 | 
			
		||||
      "version": "3.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -5676,6 +5722,16 @@
 | 
			
		|||
        "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": {
 | 
			
		||||
      "version": "1.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -6195,6 +6251,73 @@
 | 
			
		|||
        "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": {
 | 
			
		||||
      "version": "1.10.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,7 +15,9 @@
 | 
			
		|||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "axios": "^1.7.7",
 | 
			
		||||
    "bootstrap-icons": "^1.11.3",
 | 
			
		||||
    "pinia": "^2.2.4",
 | 
			
		||||
    "v-tooltip": "^2.1.3",
 | 
			
		||||
    "vue": "^3.5.12",
 | 
			
		||||
    "vue-router": "^4.4.5"
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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> */
 | 
			
		||||
:root {
 | 
			
		||||
  --vt-c-white: #ffffff;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
											
												Binary file not shown.
											
										
									
								
											
												
													File diff suppressed because one or more lines are too long
												
											
										
									
								| 
		 After Width: | Height: | Size: 93 KiB  | 
											
												Binary file not shown.
											
										
									
								
											
												Binary file not shown.
											
										
									
								| 
						 | 
				
			
			@ -23,12 +23,26 @@ a,
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
button {
 | 
			
		||||
    color: var(--text);
 | 
			
		||||
    border: none;
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
  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 {
 | 
			
		||||
  font-weight: 800;
 | 
			
		||||
  font-size: 300%;
 | 
			
		||||
  font-size: 200%;
 | 
			
		||||
  line-height: 2em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
em.aside {
 | 
			
		||||
  color: var(--overlay-0);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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";
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -9,21 +9,47 @@ const props = defineProps({
 | 
			
		|||
  roleTitle: String,
 | 
			
		||||
  player: Object as PropType<PlayerTeamRole>,
 | 
			
		||||
  isRoster: Boolean,
 | 
			
		||||
  isRinger: Boolean,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const isSelected = computed(() => {
 | 
			
		||||
  if (props.isRoster) {
 | 
			
		||||
    return rosterStore.selectedRole == props.roleTitle;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (props.isRinger) {
 | 
			
		||||
    return rosterStore.selectedPlayers[props.roleTitle]?.playtime == -1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return Object.values(rosterStore.selectedPlayers).includes(props.player);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function onClick() {
 | 
			
		||||
  if (props.isRoster) {
 | 
			
		||||
    rosterStore.selectedRole = props.roleTitle;
 | 
			
		||||
    if (rosterStore.selectedRole == props.roleTitle) {
 | 
			
		||||
      rosterStore.selectedRole = undefined;
 | 
			
		||||
    } else {
 | 
			
		||||
      rosterStore.selectedRole = props.roleTitle;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    // 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>
 | 
			
		||||
| 
						 | 
				
			
			@ -31,45 +57,81 @@ function onClick() {
 | 
			
		|||
<template>
 | 
			
		||||
  <button :class="{
 | 
			
		||||
    'player-card': true,
 | 
			
		||||
    'no-player': !player,
 | 
			
		||||
    'no-player': !player && !isRinger,
 | 
			
		||||
    'selected': isSelected,
 | 
			
		||||
    }" @click="onClick">
 | 
			
		||||
    <div v-if="player">
 | 
			
		||||
      <h1>{{ player.name }}</h1>
 | 
			
		||||
      <span v-if="roleTitle != player.role">
 | 
			
		||||
        Subbing in as
 | 
			
		||||
      </span>
 | 
			
		||||
      {{ player.role }}
 | 
			
		||||
      <span v-if="!player.main">
 | 
			
		||||
        (alternate role)
 | 
			
		||||
    'can-be-available': player?.availability == 2
 | 
			
		||||
  }" @click="onClick">
 | 
			
		||||
    <div class="role-icon">
 | 
			
		||||
      <i :class="rosterStore.roleIcons[roleTitle]" />
 | 
			
		||||
    </div>
 | 
			
		||||
    <div v-if="player" class="role-info">
 | 
			
		||||
      <span>
 | 
			
		||||
        <h4 class="player-name">{{ player.name }}</h4>
 | 
			
		||||
        <span v-if="roleTitle != player.role">
 | 
			
		||||
          Subbing in as
 | 
			
		||||
        </span>
 | 
			
		||||
        {{ player.role }}
 | 
			
		||||
        <span v-if="!player.main && isRoster">
 | 
			
		||||
          (alternate)
 | 
			
		||||
        </span>
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div v-else>
 | 
			
		||||
      {{ roleTitle }}
 | 
			
		||||
    <div v-else-if="isRinger" class="role-info">
 | 
			
		||||
      <span>
 | 
			
		||||
        <h4 class="player-name">Ringer</h4>
 | 
			
		||||
        {{ roleTitle }}
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div v-else class="role-info">
 | 
			
		||||
      <span>
 | 
			
		||||
        {{ roleTitle }}
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
  </button>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.player-card {
 | 
			
		||||
  background-color: var(--crust);
 | 
			
		||||
  background-color: white;
 | 
			
		||||
  padding: 1em;
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
  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 {
 | 
			
		||||
  background-color: var(--surface-0);
 | 
			
		||||
  transition-duration: 200ms;
 | 
			
		||||
  border-color: var(--surface-0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.player-card.no-player {
 | 
			
		||||
  border: 2px dashed var(--overlay-0);
 | 
			
		||||
  border: 2px solid var(--overlay-0);
 | 
			
		||||
  box-shadow: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.player-card.no-player.selected {
 | 
			
		||||
  background-color: var(--accent-transparent);
 | 
			
		||||
  border: 2px dashed var(--accent);
 | 
			
		||||
  border: 2px solid var(--accent);
 | 
			
		||||
  color: var(--accent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -89,8 +151,8 @@ function onClick() {
 | 
			
		|||
  color: var(--accent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1 {
 | 
			
		||||
  font-size: 24px;
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
.player-name {
 | 
			
		||||
  font-size: 16px;
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,4 +9,5 @@ export interface PlayerTeamRole {
 | 
			
		|||
  role: string;
 | 
			
		||||
  main: boolean;
 | 
			
		||||
  availability: number;
 | 
			
		||||
  playtime: number;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ const router = createRouter({
 | 
			
		|||
      path: "/schedule/roster",
 | 
			
		||||
      name: "roster-builder",
 | 
			
		||||
      component: RosterBuilderView
 | 
			
		||||
    }
 | 
			
		||||
    },
 | 
			
		||||
  ]
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
 | 
			
		||||
  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([
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -23,6 +23,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
      role: "Flank Scout",
 | 
			
		||||
      main: true,
 | 
			
		||||
      availability: 1,
 | 
			
		||||
      playtime: 35031,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      steamId: 2839,
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +31,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
      role: "Flank Scout",
 | 
			
		||||
      main: false,
 | 
			
		||||
      availability: 1,
 | 
			
		||||
      playtime: 28811,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      steamId: 2839,
 | 
			
		||||
| 
						 | 
				
			
			@ -37,6 +39,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
      role: "Pocket Scout",
 | 
			
		||||
      main: true,
 | 
			
		||||
      availability: 1,
 | 
			
		||||
      playtime: 28811,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      steamId: 2841,
 | 
			
		||||
| 
						 | 
				
			
			@ -44,6 +47,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
      role: "Pocket Soldier",
 | 
			
		||||
      main: true,
 | 
			
		||||
      availability: 2,
 | 
			
		||||
      playtime: 98372,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      steamId: 2841,
 | 
			
		||||
| 
						 | 
				
			
			@ -51,6 +55,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
      role: "Roamer",
 | 
			
		||||
      main: false,
 | 
			
		||||
      availability: 2,
 | 
			
		||||
      playtime: 98372,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      steamId: 2282,
 | 
			
		||||
| 
						 | 
				
			
			@ -58,6 +63,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
      role: "Demoman",
 | 
			
		||||
      main: true,
 | 
			
		||||
      availability: 2,
 | 
			
		||||
      playtime: 47324,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      steamId: 2842,
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +71,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
      role: "Roamer",
 | 
			
		||||
      main: false,
 | 
			
		||||
      availability: 2,
 | 
			
		||||
      playtime: 12028,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      steamId: 2842,
 | 
			
		||||
| 
						 | 
				
			
			@ -72,6 +79,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
      role: "Demoman",
 | 
			
		||||
      main: false,
 | 
			
		||||
      availability: 2,
 | 
			
		||||
      playtime: 12028,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      steamId: 2842,
 | 
			
		||||
| 
						 | 
				
			
			@ -79,6 +87,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
      role: "Pocket Scout",
 | 
			
		||||
      main: false,
 | 
			
		||||
      availability: 2,
 | 
			
		||||
      playtime: 12028,
 | 
			
		||||
    },
 | 
			
		||||
    //{
 | 
			
		||||
    //  steamId: 2843,
 | 
			
		||||
| 
						 | 
				
			
			@ -93,6 +102,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
      role: "Pocket Soldier",
 | 
			
		||||
      main: false,
 | 
			
		||||
      availability: 2,
 | 
			
		||||
      playtime: 50201,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      steamId: 2843,
 | 
			
		||||
| 
						 | 
				
			
			@ -100,6 +110,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
      role: "Roamer",
 | 
			
		||||
      main: false,
 | 
			
		||||
      availability: 2,
 | 
			
		||||
      playtime: 50201,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      steamId: 2844,
 | 
			
		||||
| 
						 | 
				
			
			@ -107,6 +118,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
      role: "Roamer",
 | 
			
		||||
      main: true,
 | 
			
		||||
      availability: 1,
 | 
			
		||||
      playtime: 4732,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      steamId: 2844,
 | 
			
		||||
| 
						 | 
				
			
			@ -114,6 +126,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
      role: "Pocket Soldier",
 | 
			
		||||
      main: false,
 | 
			
		||||
      availability: 1,
 | 
			
		||||
      playtime: 4732,
 | 
			
		||||
    },
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -129,9 +142,27 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
    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) {
 | 
			
		||||
    console.log("selecting.");
 | 
			
		||||
    if (player) {
 | 
			
		||||
    if (player && player.steamId > 0) {
 | 
			
		||||
      const existingRole = Object.keys(selectedPlayers).find((selectedRole) => {
 | 
			
		||||
        return selectedPlayers[selectedRole]?.steamId == player.steamId &&
 | 
			
		||||
          role != selectedRole;
 | 
			
		||||
| 
						 | 
				
			
			@ -154,5 +185,8 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
    selectPlayerForRole,
 | 
			
		||||
    definitelyAvailable,
 | 
			
		||||
    canBeAvailable,
 | 
			
		||||
    roleIcons,
 | 
			
		||||
    mainRoles,
 | 
			
		||||
    alternateRoles,
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,11 +10,27 @@ const rosterStore = useRosterStore();
 | 
			
		|||
const hasAvailablePlayers = computed(() => {
 | 
			
		||||
  return rosterStore.availablePlayerRoles.length > 0;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const hasAlternates = computed(() => {
 | 
			
		||||
  return rosterStore.alternateRoles.length > 0;
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <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="column">
 | 
			
		||||
        <PlayerCard v-for="role in rosterStore.neededRoles"
 | 
			
		||||
| 
						 | 
				
			
			@ -23,25 +39,38 @@ const hasAvailablePlayers = computed(() => {
 | 
			
		|||
                    is-roster />
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="column">
 | 
			
		||||
        <h3 v-if="hasAvailablePlayers">Available</h3>
 | 
			
		||||
        <PlayerCard v-for="player in rosterStore.definitelyAvailable"
 | 
			
		||||
        <PlayerCard v-for="player in rosterStore.mainRoles"
 | 
			
		||||
                    :player="player"
 | 
			
		||||
                    :role-title="player.role" />
 | 
			
		||||
        <span v-if="!hasAvailablePlayers">
 | 
			
		||||
        <span v-if="!hasAvailablePlayers && rosterStore.selectedRole">
 | 
			
		||||
          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.canBeAvailable"
 | 
			
		||||
        <h3 v-if="hasAvailablePlayers">Alternates</h3>
 | 
			
		||||
        <PlayerCard v-for="player in rosterStore.alternateRoles"
 | 
			
		||||
                    :player="player"
 | 
			
		||||
                    :role-title="player.role" />
 | 
			
		||||
        <PlayerCard v-if="rosterStore.selectedRole"
 | 
			
		||||
                    is-ringer
 | 
			
		||||
                    :role-title="rosterStore.selectedRole" />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </main>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<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 {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: row;
 | 
			
		||||
| 
						 | 
				
			
			@ -57,8 +86,20 @@ const hasAvailablePlayers = computed(() => {
 | 
			
		|||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h3 {
 | 
			
		||||
.column h3 {
 | 
			
		||||
  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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
		Loading…
	
		Reference in New Issue