@luceosports/play-rendering 2.1.4 → 2.2.0

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.
@@ -30,10 +30,18 @@ export type PlayModelOptions = {
30
30
  legacyPrintStyle: boolean;
31
31
  showHalfCourtCircle: boolean;
32
32
  playersMap: PlayersMapItem[];
33
- labelsOverrideType: 'Headshot' | null;
33
+ labelsOverrideType: 'Initials' | 'Jersey number' | 'Headshot' | null;
34
34
  inDrawingState: boolean;
35
35
  }
36
36
 
37
+ export type TeamPlayer = {
38
+ id: string;
39
+ User_Name: string;
40
+ Initials: string;
41
+ jersey_number: number;
42
+ headshotUrl?: string;
43
+ };
44
+
37
45
  export class PlayModel {
38
46
  name: string;
39
47
  playData: PlayData;
@@ -42,7 +50,7 @@ export class PlayModel {
42
50
  static init({ teamLogoPath }?: {
43
51
  teamLogoPath?: string;
44
52
  }): Promise<void>;
45
- static loadPlayerHeadshots(data: { id: string; headshotUrl?: string | null }[]): Promise<void>
53
+ static setTeamPlayers(data: TeamPlayer[]): Promise<void>
46
54
  get totalPhasesCount(): number;
47
55
  get scale(): number;
48
56
  get width(): number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luceosports/play-rendering",
3
- "version": "2.1.4",
3
+ "version": "2.2.0",
4
4
  "main": "dist/play-rendering.js",
5
5
  "types": "dist/play-rendering.d.ts",
6
6
  "scripts": {
@@ -67,7 +67,10 @@ export default class NoteLayer extends BaseLayer {
67
67
  lines.forEach(({ words }, index) => {
68
68
  this.ctx.save();
69
69
 
70
- this.ctx.translate(NoteModel.NOTE_WRAP_PADDING, NoteModel.NOTE_WRAP_PADDING + note.lineHeight * index);
70
+ this.ctx.translate(
71
+ NoteModel.NOTE_WRAP_PADDING,
72
+ note.fontSize + NoteModel.NOTE_WRAP_PADDING + note.lineHeight * index
73
+ );
71
74
 
72
75
  let wordStartX = 0;
73
76
  words.forEach((word, i) => {
@@ -80,7 +83,17 @@ export default class NoteLayer extends BaseLayer {
80
83
 
81
84
  this.ctx.fillStyle = `rgba(0, 0, 0, ${this.animationAlpha})`;
82
85
  const wordText = matches.length ? word.text.replace(/(<player=(\d*)>)/gi, '') : word.text;
83
- this.ctx.fillText(wordText, wordStartX, 0);
86
+ this.ctx.fillText(wordText, wordStartX, -note.baseLineOffset);
87
+
88
+ // BASELINE FOR TESTING
89
+ // this.ctx.save();
90
+ // this.ctx.strokeStyle = 'red';
91
+ // this.ctx.beginPath();
92
+ // this.ctx.moveTo(wordStartX, 0);
93
+ // this.ctx.lineTo(word.width, 0);
94
+ // this.ctx.stroke();
95
+ // this.ctx.restore();
96
+
84
97
  wordStartX += word.width;
85
98
  if (matches.length) {
86
99
  wordStartX -= note.playerTokenRadius * 2;
@@ -92,7 +105,7 @@ export default class NoteLayer extends BaseLayer {
92
105
  }
93
106
 
94
107
  if (i !== words.length - 1) {
95
- this.ctx.fillText(' ', wordStartX, 0);
108
+ this.ctx.fillText(' ', wordStartX, -note.baseLineOffset);
96
109
  wordStartX += this.ctx.measureText(' ').width;
97
110
  }
98
111
  });
@@ -102,12 +115,12 @@ export default class NoteLayer extends BaseLayer {
102
115
  this.ctx.strokeStyle = `rgba(0, 0, 0, ${this.animationAlpha})`;
103
116
  this.ctx.beginPath();
104
117
  if (note.font.underline) {
105
- this.ctx.moveTo(0, note.fontSize - NoteModel.NOTE_LINE_HEIGHT_OFFSET / 2);
106
- this.ctx.lineTo(wordStartX, note.fontSize - NoteModel.NOTE_LINE_HEIGHT_OFFSET / 2);
118
+ this.ctx.moveTo(0, note.baseLineOffset);
119
+ this.ctx.lineTo(wordStartX, note.baseLineOffset);
107
120
  }
108
121
  if (note.font.strikethrough) {
109
- this.ctx.moveTo(0, note.lineHeight / 2 - NoteModel.NOTE_LINE_HEIGHT_OFFSET / 3);
110
- this.ctx.lineTo(wordStartX, note.lineHeight / 2 - NoteModel.NOTE_LINE_HEIGHT_OFFSET / 3);
122
+ this.ctx.moveTo(0, -note.fontSize / 2 + note.baseLineOffset);
123
+ this.ctx.lineTo(wordStartX, -note.fontSize / 2 + note.baseLineOffset);
111
124
  }
112
125
  this.ctx.stroke();
113
126
  }
@@ -124,14 +137,14 @@ export default class NoteLayer extends BaseLayer {
124
137
  this.ctx.beginPath();
125
138
  this.ctx.strokeStyle = `rgba(255, 255, 255, ${this.animationAlpha})`;
126
139
  this.ctx.fillStyle = `rgba(243, 111, 33, ${this.animationAlpha})`;
127
- this.ctx.arc(0, note.playerTokenRadius, note.playerTokenRadius, 0, Math.PI * 2);
140
+ this.ctx.arc(0, -note.fontSize / 2, note.playerTokenRadius, 0, Math.PI * 2);
128
141
  this.ctx.fill();
129
142
  this.ctx.stroke();
130
143
 
131
144
  const playerFontSize = note.fontSize * 0.8;
132
145
  this.ctx.font = `${playerFontSize}px Arial`;
133
146
  const textX = -this.ctx.measureText(label).width / 2;
134
- const textY = ((note.playerTokenRadius * 2 - playerFontSize) / 2) * (1 + NoteModel.NOTE_LINE_HEIGHT_OFFSET);
147
+ const textY = -playerFontSize / 4;
135
148
  this.ctx.fillStyle = `rgba(255, 255, 255, ${this.animationAlpha})`;
136
149
  this.ctx.fillText(label, textX, textY);
137
150
 
@@ -102,8 +102,9 @@ export default class PlayerLayer extends BaseLayer {
102
102
  setPlayerLabel(player: PlayerModel) {
103
103
  const { x, y } = player.location;
104
104
 
105
+ const playerMapItem = this.options.playersMap.find(item => item.position === player.position);
106
+
105
107
  if (this.staticData.playerHeadshots && this.options.labelsOverrideType === 'Headshot') {
106
- const playerMapItem = this.options.playersMap.find(item => item.position === player.position);
107
108
  const headshotImage = this.staticData.playerHeadshots.find(item => item.id === playerMapItem?.teamPlayerId);
108
109
 
109
110
  this.ctx.save();
@@ -148,13 +149,33 @@ export default class PlayerLayer extends BaseLayer {
148
149
  if (headshotImage) return;
149
150
  }
150
151
 
152
+ let playerTextLabel = player.textLabel;
153
+ if (playerMapItem) {
154
+ if (playerMapItem.textOverride) {
155
+ playerTextLabel = playerMapItem.textOverride;
156
+ }
157
+ if (playerMapItem.teamPlayerId) {
158
+ const teamPlayer = this.staticData.teamPlayers.find(p => p.id === playerMapItem.teamPlayerId);
159
+ if (teamPlayer) {
160
+ const fallbackLabel = teamPlayer.User_Name.split(' ')
161
+ .map(n => n[0])
162
+ .join('')
163
+ .slice(0, 2);
164
+ playerTextLabel = teamPlayer.Initials || fallbackLabel;
165
+ if (this.options.labelsOverrideType === 'Jersey number') {
166
+ playerTextLabel = `${teamPlayer.jersey_number}` || fallbackLabel;
167
+ }
168
+ }
169
+ }
170
+ }
171
+
151
172
  const { alpha } = player.color;
152
173
 
153
- const fontSizeLength = 3 - player.textLabel.length * 0.5;
174
+ const fontSizeLength = 3 - playerTextLabel.length * 0.5;
154
175
  const fontSizeMultiplier = this.options.legacyPrintStyle ? 1.3 : 1;
155
176
  const fontSize = fontSizeLength * fontSizeMultiplier * this.playerScale;
156
177
 
157
- const textVOffsetLength = 1 - player.textLabel.length * 0.15;
178
+ const textVOffsetLength = 1 - playerTextLabel.length * 0.15;
158
179
  const textVOffsetMultiplier = this.options.legacyPrintStyle ? 1.4 : 1;
159
180
  const textVerticalOffset = textVOffsetLength * textVOffsetMultiplier * this.playerScale;
160
181
 
@@ -181,7 +202,7 @@ export default class PlayerLayer extends BaseLayer {
181
202
  this.ctx.rotate(Math.PI); // 180
182
203
  }
183
204
 
184
- this.ctx.fillText(player.textLabel, px, py + textVerticalOffset);
205
+ this.ctx.fillText(playerTextLabel, px, py + textVerticalOffset);
185
206
 
186
207
  if (this.options.flipPlayerLabels) {
187
208
  this.ctx.restore();
@@ -2,6 +2,8 @@ import Model from './Base/InternalFrameModel';
2
2
  import { CourtPoint, Note as NoteData } from '../types';
3
3
 
4
4
  const NOTE_GREY_COLOR = { red: 0.502, green: 0.502, blue: 0.502, alpha: 0.35 };
5
+ const NOTE_LINE_HEIGHT_OFFSET = 0.3;
6
+ const NOTE_BASE_LINE_ALPHABETIC_OFFSET = 0.1;
5
7
 
6
8
  export default class NoteModel extends Model<NoteData, NoteData> {
7
9
  private internalCanvas: HTMLCanvasElement;
@@ -19,10 +21,6 @@ export default class NoteModel extends Model<NoteData, NoteData> {
19
21
  return 0.43;
20
22
  }
21
23
 
22
- static get NOTE_LINE_HEIGHT_OFFSET() {
23
- return 0.3;
24
- }
25
-
26
24
  get id() {
27
25
  return this._getAttr('id');
28
26
  }
@@ -68,7 +66,15 @@ export default class NoteModel extends Model<NoteData, NoteData> {
68
66
  }
69
67
 
70
68
  get lineHeight() {
71
- return this.fontSize + NoteModel.NOTE_LINE_HEIGHT_OFFSET;
69
+ return this.fontSize + this.lineHeightOffset;
70
+ }
71
+
72
+ get lineHeightOffset() {
73
+ return this.fontSize * NOTE_LINE_HEIGHT_OFFSET;
74
+ }
75
+
76
+ get baseLineOffset() {
77
+ return this.fontSize * NOTE_BASE_LINE_ALPHABETIC_OFFSET;
72
78
  }
73
79
 
74
80
  get playerTokenRadius() {
@@ -85,7 +91,7 @@ export default class NoteModel extends Model<NoteData, NoteData> {
85
91
  }
86
92
 
87
93
  get textBaseline(): CanvasTextBaseline {
88
- return 'top';
94
+ return 'alphabetic';
89
95
  }
90
96
 
91
97
  get internalCtx(): CanvasRenderingContext2D {
@@ -100,7 +106,7 @@ export default class NoteModel extends Model<NoteData, NoteData> {
100
106
  lines,
101
107
  box: {
102
108
  width: maxLineWidth + NoteModel.NOTE_WRAP_PADDING * 2,
103
- height: lines.length * this.lineHeight + NoteModel.NOTE_WRAP_PADDING * 2 - NoteModel.NOTE_LINE_HEIGHT_OFFSET
109
+ height: lines.length * this.lineHeight + NoteModel.NOTE_WRAP_PADDING * 2
104
110
  }
105
111
  };
106
112
  }
@@ -42,6 +42,7 @@ export type PlayStaticData = {
42
42
  playerHats: readonly ImageConfigItem[];
43
43
  shapes: readonly ImageConfigItem[];
44
44
  playerHeadshots: typeof PlayModel.playerHeadshots;
45
+ teamPlayers: typeof PlayModel.teamPlayers;
45
46
  };
46
47
 
47
48
  export type PlayersMapItem = {
@@ -51,6 +52,14 @@ export type PlayersMapItem = {
51
52
  playerHatKey?: string | null;
52
53
  };
53
54
 
55
+ export type TeamPlayer = {
56
+ id: string;
57
+ User_Name: string;
58
+ Initials: string;
59
+ jersey_number: number;
60
+ headshotUrl?: string;
61
+ };
62
+
54
63
  export type PlayModelOptions = {
55
64
  width: number;
56
65
  lineColor: string;
@@ -73,7 +82,7 @@ export type PlayModelOptions = {
73
82
  legacyPrintStyle: boolean;
74
83
  showHalfCourtCircle: boolean;
75
84
  playersMap: PlayersMapItem[];
76
- labelsOverrideType: 'Headshot' | null;
85
+ labelsOverrideType: 'Initials' | 'Jersey number' | 'Headshot' | null;
77
86
  inDrawingState: boolean;
78
87
  };
79
88
 
@@ -81,6 +90,7 @@ export default class PlayModel {
81
90
  public name: string;
82
91
  public playData: PlayData;
83
92
  public options: PlayModelOptions;
93
+ public static teamPlayers: TeamPlayer[] = [];
84
94
  public static playerHeadshots: PlayerHeadshotItem[] = [];
85
95
  public static playerHats: readonly ImageConfigItem[];
86
96
  public static shapes: readonly ImageConfigItem[];
@@ -120,7 +130,8 @@ export default class PlayModel {
120
130
  };
121
131
  }
122
132
 
123
- static async loadPlayerHeadshots(data: { id: string; headshotUrl?: string | null }[]) {
133
+ static async setTeamPlayers(data: TeamPlayer[]) {
134
+ PlayModel.teamPlayers = data;
124
135
  await Promise.all(
125
136
  data.map(async ({ id, headshotUrl }) => {
126
137
  try {
@@ -159,7 +170,8 @@ export default class PlayModel {
159
170
  watermark: PlayModel.watermark,
160
171
  playerHats: PlayModel.playerHats,
161
172
  shapes: PlayModel.shapes,
162
- playerHeadshots: PlayModel.playerHeadshots
173
+ playerHeadshots: PlayModel.playerHeadshots,
174
+ teamPlayers: PlayModel.teamPlayers
163
175
  };
164
176
  }
165
177