@dcrackel/meyersquaredui 1.0.259 → 1.0.261

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,9 @@
1
1
  import Tableau from './Tableau.vue';
2
2
  //import getDEWithBouts from '../../../mocks/getDEWithBouts.js';
3
3
  //import getDEWithBouts from '../../../mocks/getDEWithBouts8.js';
4
- import getDEWithBouts from '../../../mocks/getDEWithBouts16.js';
5
- //import getDEWithBouts from '../../../mocks/getDEWithBouts2.js';
4
+ //import getDEWithBouts from '../../../mocks/getDEWithBouts16.js';
5
+ //import getDEWithBouts from '../../../mocks/getDEWithBouts32.js';
6
+ import getDEWithBouts from '../../../mocks/getDEWithBouts64.js';
6
7
  //import getDEWithBouts from '../../../mocks/getDEWithBouts68.js';
7
8
 
8
9
  export default {
@@ -85,7 +85,8 @@ export default {
85
85
  },
86
86
  computed: {
87
87
  isSplitMode() {
88
- return ["Table of 32", "Table of 64", "Table of 128", "Table of 256"].includes(this.bracketSize);
88
+ return ["Table of 64", "Table of 128", "Table of 256"].includes(this.bracketSize);
89
+ //return false;
89
90
  },
90
91
  splitData() {
91
92
  if (!this.isSplitMode) return null;
@@ -118,10 +119,8 @@ export default {
118
119
  document.fonts.ready.then(() => this.recompute());
119
120
  }
120
121
 
121
- // 3) after images/layout fully settle
122
122
  window.addEventListener("load", this.recompute, { once: true });
123
123
 
124
- // 4) two RAFs catches late layout shifts reliably
125
124
  requestAnimationFrame(() => requestAnimationFrame(() => this.recompute()));
126
125
  },
127
126
  beforeUnmount() {
@@ -133,7 +132,6 @@ export default {
133
132
  bouts: {
134
133
  deep: true,
135
134
  handler() {
136
- // realtime updates: recompute after DOM updates settle
137
135
  this.$nextTick(() => this.recompute());
138
136
  },
139
137
  },
@@ -213,44 +211,30 @@ export default {
213
211
  const t = TableauLayoutTuning;
214
212
  const depth = this.normalizeRoundNumber(roundName);
215
213
 
216
- // GAP
217
- const gapByDepth = t.gapByDepthPx || [];
218
- const gapPx = gapByDepth[depth] ?? (t.baseGapPx * Math.pow(2, depth));
214
+ const isCompact = t.compact?.enabledFor?.includes(this.bracketSize);
215
+
216
+ const gapArr = isCompact ? (t.gapByDepthPxCompact || []) : (t.gapByDepthPx || []);
217
+ const padArr = isCompact ? (t.padByDepthPxCompact || []) : (t.padByDepthPx || []);
218
+
219
+ const gapPx = gapArr[depth] ?? (t.baseGapPx * Math.pow(2, depth));
219
220
 
220
- // PAD BASE (fallback formula if no array entry)
221
221
  const prevGap = t.baseGapPx * Math.pow(2, Math.max(0, depth - 1));
222
222
  const padTopBase = depth === 0 ? 0 : prevGap / 2;
223
223
 
224
- const padByDepth = t.padByDepthPx || [];
225
- const padFromArray = padByDepth[depth];
224
+ const padFromArray = padArr[depth];
226
225
 
227
- // Named biases (special cases)
228
226
  const namedBias =
229
227
  roundName === "Final" ? (t.finalPadBiasPx || 0)
230
228
  : roundName === "Third Place" ? (t.thirdPlacePadBiasPx || 0)
231
229
  : 0;
232
230
 
233
- const isSplitFinal = this.isSplitMode && roundName === "Final";
234
-
235
- // 1) start from normal pad (array or fallback) + named bias
236
- let padTopPx = (padFromArray !== undefined ? padFromArray : padTopBase) + namedBias;
237
-
238
- // 2) split-final override (absolute)
239
- if (isSplitFinal && t.splitFinalPadTopPx !== undefined) {
240
- padTopPx = t.splitFinalPadTopPx + namedBias;
241
- }
242
-
243
- // 3) optional split-final additive tweak (small nudges)
244
- if (isSplitFinal) {
245
- padTopPx += (t.splitFinalPadBiasPx || 0);
246
- }
231
+ const splitFinalBias =
232
+ this.isSplitMode && roundName === "Final"
233
+ ? (t.splitFinalPadTopByBracket?.[this.bracketSize] ?? 0)
234
+ : 0;
247
235
 
248
- console.log(
249
- "layout", roundName,
250
- "depth", depth,
251
- "padTopPx", padTopPx,
252
- "isSplitFinal", isSplitFinal
253
- );
236
+ const padTopPx =
237
+ (padFromArray !== undefined ? padFromArray : padTopBase) + namedBias + splitFinalBias;
254
238
 
255
239
  return { gapPx, padTopPx };
256
240
  },
@@ -20,7 +20,7 @@
20
20
  <!-- Name -->
21
21
  <div class="flex-1 min-w-0 flex items-center px-2">
22
22
  <BaseText size="sm" weight="normal" color="primary" class="truncate">
23
- {{ person1Display.DisplayName }}
23
+ {{ person1Name }}
24
24
  </BaseText>
25
25
  </div>
26
26
 
@@ -49,7 +49,7 @@
49
49
  <!-- Name -->
50
50
  <div class="flex-1 min-w-0 flex items-center px-2">
51
51
  <BaseText size="sm" weight="normal" color="primary" class="truncate">
52
- {{ person2Display.DisplayName }}
52
+ {{person2Name }}
53
53
  </BaseText>
54
54
  </div>
55
55
 
@@ -130,10 +130,8 @@ export default {
130
130
  score1() { return `${this.bout.Score1}`; },
131
131
  dePosition2() { return this.bout.Person2?.EventPersons?.[0]?.DEPosition ?? ""; },
132
132
  score2() { return `${this.bout.Score2}`; },
133
-
134
133
  row1ScoreColor() { return this.getScoreColor(true); },
135
134
  row2ScoreColor() { return this.getScoreColor(false); },
136
-
137
135
  person1Display() {
138
136
  if (this.bout.Status === "Scheduled" && this.bout.Person1.DisplayName === "BYE") {
139
137
  return { ...this.bout.Person1, DisplayName: "", Club: { Name: "" } };
@@ -146,8 +144,24 @@ export default {
146
144
  }
147
145
  return this.bout.Person2;
148
146
  },
149
- showScore1() { return this.bout.Person1.DisplayName !== "BYE"; },
150
- showScore2() { return this.bout.Person2.DisplayName !== "BYE"; },
147
+ person1() {
148
+ return this.bout?.Person1 || { DisplayName: "", Club: { Name: "" } };
149
+ },
150
+ person2() {
151
+ return this.bout?.Person2 || { DisplayName: "", Club: { Name: "" } };
152
+ },
153
+ person1Name() {
154
+ const n = (this.person1?.DisplayName || "").trim();
155
+ if (!n || n === "BYE") return "";
156
+ return n.length > 19 ? n.substring(0, 19) : n;
157
+ },
158
+ person2Name() {
159
+ const n = (this.person2?.DisplayName || "").trim();
160
+ if (!n || n === "BYE") return "";
161
+ return n.length > 19 ? n.substring(0, 19) : n;
162
+ },
163
+ showScore1() { return (this.person1?.DisplayName || "") !== "BYE"; },
164
+ showScore2() { return (this.person2?.DisplayName || "") !== "BYE"; },
151
165
  },
152
166
  methods: {
153
167
  syncTimerFromBout(b) {
@@ -1,8 +1,8 @@
1
1
  <template>
2
2
  <section class="w-[260px] flex flex-col items-center">
3
3
  <!-- header stays aligned with others -->
4
- <div class="flex justify-center mb-4 border-accent border-b w-40">
5
- <BaseText size="sm" color="primary" weight="bold">Final</BaseText>
4
+ <div class="flex justify-center mb-4 border-white border-b w-40">
5
+ <BaseText size="sm" color="white" weight="bold">Final</BaseText>
6
6
  </div>
7
7
 
8
8
  <!-- body can be dropped down independently -->
@@ -1,26 +1,28 @@
1
1
  <template>
2
2
  <section class="relative" :class="showActiveTimer ? 'bg-primary' : ''">
3
- <!-- headers (if any) stay outside the card body -->
3
+
4
4
  <div v-if="roundName === 'Third Place'" class="flex justify-center border-accent border-b mb-4 mt-8 w-40">
5
- <BaseText size="sm" color="primary">{{roundName}}</BaseText>
5
+ <BaseText size="sm" color="primary">{{ roundName }}</BaseText>
6
6
  </div>
7
-
8
- <!-- CARD BODY: this is what the lines should attach to -->
9
7
  <div ref="cardBody" class="relative w-40">
10
-
11
- <section>
12
- <TableauFencerCard
13
- :fencer="person1Display"
14
- :score="score1"
15
- :position="dePosition1"
16
- :color="hostingClubColors.Color1"
17
- :scoreColor="getScoreColor(true)"
18
- :showScore="showScore1"
19
- />
20
- </section>
21
-
22
- <div class="relative h-[4px]">
23
- <!-- Flow flips depending on side -->
8
+ <section class="relative">
9
+ <div v-if="isActiveBout && bout?.RingName"
10
+ class="absolute -top-4 left-0 h-4 z-40 w-40 bg-primary px-5 pt-0.5 rounded-tl-md leading-none inline-flex items-center pointer-events-none" >
11
+ <BaseText size="xs" color="secondary" weight="bold" class="uppercase tracking-wide">
12
+ {{ bout.RingName }}
13
+ </BaseText>
14
+ </div>
15
+ <TableauFencerCard
16
+ :fencer="person1Display"
17
+ :score="score1"
18
+ :position="dePosition1"
19
+ :color="hostingClubColors.Color1"
20
+ :scoreColor="getScoreColor(true)"
21
+ :showScore="showScore1"
22
+ />
23
+ </section>
24
+
25
+ <div class="relative" :class="isCompact ? ' h-[0px]' : ' h-[4px]'">
24
26
  <div
25
27
  class="absolute top-1/2 -translate-y-1/2 w-px h-px opacity-0 pointer-events-none"
26
28
  :class="direction === 'rtl' ? 'right-0' : 'left-0'"
@@ -34,18 +36,18 @@
34
36
  ></div>
35
37
  </div>
36
38
 
37
- <section>
38
- <TableauFencerCard
39
- :fencer="person2Display"
40
- :score="score2"
41
- :position="dePosition2"
42
- :color="hostingClubColors.Color2"
43
- :scoreColor="getScoreColor(false)"
44
- :showScore="showScore2"
45
- />
46
- </section>
47
-
39
+ <section>
40
+ <TableauFencerCard
41
+ :fencer="person2Display"
42
+ :score="score2"
43
+ :position="dePosition2"
44
+ :color="hostingClubColors.Color2"
45
+ :scoreColor="getScoreColor(false)"
46
+ :showScore="showScore2"
47
+ />
48
+ </section>
48
49
  </div>
50
+
49
51
  <transition
50
52
  enter-active-class="transition duration-150"
51
53
  enter-from-class="opacity-0 translate-x-1"
@@ -54,11 +56,12 @@
54
56
  leave-from-class="opacity-100 translate-x-0"
55
57
  leave-to-class="opacity-0 translate-x-1"
56
58
  >
57
- <div v-if="showActiveTimer"
59
+ <div
60
+ v-if="isActiveBout "
58
61
  class="absolute top-1/2 -translate-y-1/2 z-50 pointer-events-none"
59
62
  :class="direction === 'rtl' ? '-left-10' : '-right-10'"
60
63
  >
61
- <div class="bg-primary rounded-r-md px-2 py-1 w-10 flex items-center justify-center" style="height: 44px">
64
+ <div class="bg-primary rounded-r-md px-2 py-1 w-10 flex items-center justify-center -mt-4" style="height: 60px">
62
65
  <BaseText size="xs" color="secondary" weight="bold" class="font-sourceCodePro">
63
66
  {{ displayTime }}
64
67
  </BaseText>
@@ -89,7 +92,6 @@ export default {
89
92
  bracketSize: { type: String, required: true },
90
93
  direction: { type: String, default: "ltr" },
91
94
  },
92
-
93
95
  data() {
94
96
  return {
95
97
  highlight: false,
@@ -97,15 +99,12 @@ export default {
97
99
  _timerId: null,
98
100
  };
99
101
  },
100
-
101
102
  mounted() {
102
103
  this.syncTimerFromBout();
103
104
  },
104
-
105
105
  beforeUnmount() {
106
106
  stopCountdown(this);
107
107
  },
108
-
109
108
  watch: {
110
109
  "bout.TimeLeft"(v) {
111
110
  this.remainingTime = v ?? 0;
@@ -118,67 +117,84 @@ export default {
118
117
  this.maybeStartOrStopTimer();
119
118
  },
120
119
  },
121
-
122
120
  computed: {
123
- dePosition1() { return this.bout.Person1?.EventPersons?.[0]?.DEPosition ?? ""; },
124
- score1() { return `${this.bout.Score1}`; },
125
- dePosition2() { return this.bout.Person2?.EventPersons?.[0]?.DEPosition ?? ""; },
126
- score2() { return `${this.bout.Score2}`; },
127
-
121
+ isActiveBout() {
122
+ return String(this.bout?.Status || '').toLowerCase() === 'active';
123
+ },
124
+ isTimerRunning() {
125
+ return String(this.bout?.TimerStatus || "").toLowerCase() === "running";
126
+ },
127
+ shouldTickTimer() {
128
+ return this.isTimerRunning && shouldTickBout(this.bout, this.remainingTime);
129
+ },
128
130
  showActiveTimer() {
129
- return shouldTickBout(this.bout, this.remainingTime);
131
+ return this.isActiveBout;
130
132
  },
131
-
133
+ isCompact() {
134
+ return ["Table of 64", "Table of 128", "Table of 256"].includes(this.bracketSize);
135
+ },
136
+ dePosition1() { return this.bout?.Person1?.EventPersons?.[0]?.DEPosition ?? ""; },
137
+ score1() { return `${this.bout?.Score1 ?? 0}`; },
138
+ dePosition2() { return this.bout?.Person2?.EventPersons?.[0]?.DEPosition ?? ""; },
139
+ score2() { return `${this.bout?.Score2 ?? 0}`; },
132
140
  displayTime() {
133
141
  return formatSecondsMMSS(this.remainingTime);
134
142
  },
135
-
136
143
  person1Display() {
137
- if (this.bout.Status === "Scheduled" && this.bout.Person1.DisplayName === "BYE") {
138
- return { ...this.bout.Person1, DisplayName: "", Club: { Name: "" } };
144
+ const p1 = this.bout?.Person1;
145
+ if (!p1) return { DisplayName: "", Club: { Name: "" } };
146
+
147
+ if (this.bout?.Status === "Scheduled" && p1.DisplayName === "BYE") {
148
+ return { ...p1, DisplayName: "", Club: { Name: "" } };
139
149
  }
140
- return this.bout.Person1;
150
+ return p1;
141
151
  },
142
152
  person2Display() {
143
- if (this.bout.Status === "Scheduled" && this.bout.Person2.DisplayName === "BYE") {
144
- return { ...this.bout.Person2, DisplayName: "", Club: { Name: "" } };
153
+ const p2 = this.bout?.Person2;
154
+ if (!p2) return { DisplayName: "", Club: { Name: "" } };
155
+
156
+ if (this.bout?.Status === "Scheduled" && p2.DisplayName === "BYE") {
157
+ return { ...p2, DisplayName: "", Club: { Name: "" } };
145
158
  }
146
- return this.bout.Person2;
159
+ return p2;
160
+ },
161
+ showScore1() { return (this.bout?.Person1?.DisplayName ?? "") !== "BYE"; },
162
+ showScore2() { return (this.bout?.Person2?.DisplayName ?? "") !== "BYE"; },
163
+ isFirstRound() {
164
+ return String(this.bout?.RoundLabel || "") === String(this.bracketSize || "");
147
165
  },
148
- showScore1() { return this.bout.Person1.DisplayName !== "BYE"; },
149
- showScore2() { return this.bout.Person2.DisplayName !== "BYE"; },
150
166
  },
151
-
152
167
  methods: {
153
168
  registerAnchor(kind, el) {
154
169
  if (!el) return;
155
170
  this.$emit("register-node-anchor", { id: this.bout?.DEBoutId, kind, el });
156
171
  },
157
-
158
172
  syncTimerFromBout() {
159
173
  this.remainingTime = this.bout?.TimeLeft ?? 0;
160
174
  this.maybeStartOrStopTimer();
161
175
  },
162
-
163
176
  maybeStartOrStopTimer() {
164
- const shouldTick = shouldTickBout(this.bout, this.remainingTime);
165
- if (shouldTick) startCountdown(this);
177
+ if (this.shouldTickTimer) startCountdown(this);
166
178
  else stopCountdown(this);
167
179
  },
168
-
169
180
  getScoreColor(isPerson1) {
181
+ const p1 = this.bout?.Person1?.DisplayName ?? "";
182
+ const p2 = this.bout?.Person2?.DisplayName ?? "";
183
+ const s1 = Number(this.bout?.Score1 ?? 0);
184
+ const s2 = Number(this.bout?.Score2 ?? 0);
185
+ const status = this.bout?.Status;
186
+
170
187
  if (isPerson1) {
171
- if (this.bout.Person2.DisplayName === "BYE" && !((this.bout.Person1.DisplayName === "BYE") && (this.bout.Person2.DisplayName === "BYE"))) return "bg-accent";
172
- if (this.bout.Score1 > this.bout.Score2) return "bg-accent";
173
- if (this.bout.Score1 < this.bout.Score2 && this.bout.Status === "Active") return "bg-secondary";
188
+ if (p2 === "BYE" && !(p1 === "BYE" && p2 === "BYE")) return "bg-accent";
189
+ if (s1 > s2) return "bg-accent";
190
+ if (s1 < s2 && status === "Active") return "bg-secondary";
174
191
  } else {
175
- if (this.bout.Person1.DisplayName === "BYE" && !((this.bout.Person1.DisplayName === "BYE") && (this.bout.Person2.DisplayName === "BYE"))) return "bg-accent";
176
- if (this.bout.Score1 < this.bout.Score2) return "bg-accent";
177
- if (this.bout.Score1 > this.bout.Score2 && this.bout.Status === "Active") return "bg-secondary";
192
+ if (p1 === "BYE" && !(p1 === "BYE" && p2 === "BYE")) return "bg-accent";
193
+ if (s1 < s2) return "bg-accent";
194
+ if (s1 > s2 && status === "Active") return "bg-secondary";
178
195
  }
179
196
  return "bg-secondary";
180
197
  },
181
198
  },
182
199
  };
183
-
184
200
  </script>
@@ -1,11 +1,10 @@
1
1
  <template>
2
- <section class="flex w-40 rounded-sm bg-secondary border border-primary h-5">
2
+ <section class="flex w-40 rounded-sm bg-secondary border border-primary" :class="isCompact ? 'h-3' : 'h-5'">
3
3
  <!-- Left position -->
4
4
  <div class="w-5 flex items-center justify-center px-1" :class="`bg-${color}`">
5
5
  <BaseText size="xs" color="secondary font-sourceCodePro">{{ position }}</BaseText>
6
6
  </div>
7
7
 
8
- <!-- Name (flexes, truncates properly) -->
9
8
  <div class="flex-1 min-w-0 flex items-center px-2">
10
9
  <BaseText
11
10
  size="xs"
@@ -66,20 +65,13 @@ export default {
66
65
  }
67
66
  },
68
67
  computed: {
69
- portraitURL() {
70
- const images = this.fencer.Images || [];
71
- const portrait = images.find(image => image.URL);
72
- return portrait ? portrait.URL : '';
68
+ isCompact() {
69
+ return ["Table of 64","Table of 128","Table of 256"].includes(this.bracketSize);
73
70
  },
74
71
  name() {
75
- //if (this.fencer.DisplayName === "BYE") return "";
76
72
  if (this.fencer.DisplayName.length > 19) return this.fencer.DisplayName.substring(0, 19);
77
73
  return this.fencer.DisplayName;
78
74
  },
79
- club() {
80
- if (this.fencer.DisplayName === "BYE" || this.fencer.DisplayName === "") return "";
81
- return this.fencer.Club.Name;
82
- },
83
75
  width() {
84
76
  return ' w-40';
85
77
  }
@@ -6,10 +6,21 @@ export const TableauLayoutTuning = {
6
6
  padByDepthPx: [0, 32, 96, 224, 480, 992, 2016],
7
7
  gapByDepthPx: [20, 84, 212, 468, 980, 2004, 0],
8
8
 
9
+ padByDepthPxCompact: [0, 26, 76, 176, 376, 777, 0],
10
+ gapByDepthPxCompact: [10, 60, 160, 360, 760, 1000, 0],
11
+
9
12
  finalPadBiasPx: 0,
10
13
  thirdPlacePadBiasPx: 120,
11
14
 
12
- splitFinalPadTopPx: 940,
15
+ splitFinalPadTopByBracket: {
16
+ "Table of 64": -45,
17
+ "Table of 128": 730,
18
+ "Table of 32": 0,
19
+ },
20
+ compact: {
21
+ enabledFor: ["Table of 64", "Table of 128", "Table of 256"],
22
+ interFencerGapPx: 1,
23
+ },
13
24
  splitFinalPadBiasPx: 0,
14
25
  };
15
26