@luceosports/play-rendering 2.5.5 → 2.5.7
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.
- package/dist/play-rendering.js +23 -23
- package/dist/play-rendering.js.map +1 -1
- package/dist/types/constants.d.ts +3 -0
- package/dist/types/models/AnimationModel.d.ts +1 -0
- package/dist/types/models/LineModel.d.ts +1 -0
- package/dist/types/models/PlayModel.d.ts +4 -0
- package/dist/types/types/index.d.ts +3 -2
- package/package.json +1 -1
- package/src/assets/balls/baseball.svg +2 -0
- package/src/assets/balls/basketball.svg +2 -0
- package/src/assets/balls/football.svg +2 -0
- package/src/assets/balls/hockey.svg +13 -0
- package/src/assets/balls/lacrosse.svg +21 -0
- package/src/assets/balls/soccer.svg +34 -0
- package/src/assets/balls/volleyball.svg +37 -0
- package/src/assets/sand_bg.png +0 -0
- package/src/ballConfig.ts +46 -0
- package/src/constants.ts +4 -0
- package/src/helpers/draw.ts +48 -0
- package/src/layers/LineLayer.ts +1 -0
- package/src/layers/PlayerLayer.ts +27 -1
- package/src/layers/court/index.ts +278 -261
- package/src/layers/court/layers/BEACH_VOLLEYBALL/constants.ts +5 -0
- package/src/layers/court/layers/BEACH_VOLLEYBALL/courtTypes/BEACH_VOLLEYBALL_NCAA/constants.ts +2 -0
- package/src/layers/court/layers/BEACH_VOLLEYBALL/courtTypes/BEACH_VOLLEYBALL_RECREATIONAL/constants.ts +2 -0
- package/src/layers/court/layers/BEACH_VOLLEYBALL/layers/BorderRectLayer.ts +10 -0
- package/src/layers/court/layers/BEACH_VOLLEYBALL/layers/CenterLineLayer.ts +20 -0
- package/src/layers/court/layers/BEACH_VOLLEYBALL/layers/HashMarkLayer.ts +24 -0
- package/src/layers/court/layers/BEACH_VOLLEYBALL/layers/index.ts +5 -0
- package/src/layers/line/base/InternalLineLayer.ts +17 -2
- package/src/layers/line/layers/DribbleLineLayer.ts +13 -3
- package/src/layers/line/layers/ShotLineLayer.ts +13 -2
- package/src/models/AnimationModel.ts +19 -0
- package/src/models/FrameModel.ts +15 -3
- package/src/models/LineModel.ts +12 -1
- package/src/models/Play/Options.ts +4 -0
- package/src/models/PlayModel.ts +13 -0
- package/src/traits/LineDrawOperationsTrait.ts +58 -3
- package/src/types/index.ts +4 -0
|
@@ -2,7 +2,7 @@ import InternalBaseLayer from '../../base/InternalBaseLayer';
|
|
|
2
2
|
import { adjustedBezierCurveWithExclusionZones } from '../../../math/LineDrawingMath';
|
|
3
3
|
import LineDrawOperationsTrait from '../../../traits/LineDrawOperationsTrait';
|
|
4
4
|
import LineLayer from '../../LineLayer';
|
|
5
|
-
import LineModel from '../../../models/LineModel';
|
|
5
|
+
import LineModel, { LinePartAdjusted } from '../../../models/LineModel';
|
|
6
6
|
import { CourtPoint, LinePart } from '../../../types';
|
|
7
7
|
|
|
8
8
|
export type MaskSettings = {
|
|
@@ -19,6 +19,13 @@ export default class InternalLineLayer extends InternalBaseLayer {
|
|
|
19
19
|
protected angleBetweenLastTwoPoints(): number {
|
|
20
20
|
return 0;
|
|
21
21
|
}
|
|
22
|
+
maybeDrawBallAtStartPoint(params: {
|
|
23
|
+
linePart: Pick<LinePartAdjusted, 'animationSegment'>;
|
|
24
|
+
controlPoints: CourtPoint[];
|
|
25
|
+
alreadyDrawn: boolean;
|
|
26
|
+
}): boolean {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
22
29
|
// ==============================================================
|
|
23
30
|
|
|
24
31
|
protected lineWidth: number;
|
|
@@ -62,7 +69,15 @@ export default class InternalLineLayer extends InternalBaseLayer {
|
|
|
62
69
|
const g = lineForPlayerInPosition ? 0 : Math.ceil(green * 255);
|
|
63
70
|
const b = lineForPlayerInPosition ? 255 : Math.ceil(blue * 255);
|
|
64
71
|
|
|
65
|
-
const
|
|
72
|
+
const hidePassLikeLinesDuringPlayback =
|
|
73
|
+
!!this.options.showBallMode &&
|
|
74
|
+
!!this.options.animationGlobalProgress &&
|
|
75
|
+
this.options.showPassLinesDuringPlayback === false &&
|
|
76
|
+
this.line.isBallTransferLine;
|
|
77
|
+
|
|
78
|
+
const effectiveAlpha = hidePassLikeLinesDuringPlayback ? 0 : alphaOverride ?? alpha;
|
|
79
|
+
|
|
80
|
+
const color = `rgba(${r}, ${g}, ${b}, ${effectiveAlpha})`;
|
|
66
81
|
|
|
67
82
|
this.ctx.fillStyle = color;
|
|
68
83
|
this.ctx.strokeStyle = color;
|
|
@@ -19,20 +19,30 @@ export default class DribbleLineLayer extends ActionLineLayer {
|
|
|
19
19
|
setLineOptions() {
|
|
20
20
|
const lineParts = [...this.line.getLineParts()];
|
|
21
21
|
const dribbleLineParts = this.convertLinePartsToDribble(lineParts);
|
|
22
|
+
|
|
22
23
|
this.line.setLinePartsAdjusted([]);
|
|
24
|
+
|
|
23
25
|
dribbleLineParts.forEach(lp => {
|
|
24
26
|
const { controlPoints, lpIndex } = lp;
|
|
25
27
|
const [firstPoint] = controlPoints;
|
|
26
|
-
|
|
28
|
+
|
|
29
|
+
let animationSegment: LinePartAdjusted['animationSegment'] | undefined = undefined;
|
|
30
|
+
|
|
27
31
|
if (this.options.animationGlobalProgress) {
|
|
28
32
|
const [start, end] = this.line.animationKeyTimeChunks[lpIndex!];
|
|
33
|
+
|
|
29
34
|
if (_.inRange(this.options.animationGlobalProgress, start, end)) {
|
|
30
35
|
if (animationProgress(this.options.animationGlobalProgress, start, end) > firstPoint.time!) {
|
|
31
|
-
|
|
36
|
+
animationSegment = 'processed';
|
|
37
|
+
} else {
|
|
38
|
+
animationSegment = 'active';
|
|
32
39
|
}
|
|
40
|
+
} else if (this.options.animationGlobalProgress > end) {
|
|
41
|
+
animationSegment = 'processed';
|
|
33
42
|
}
|
|
34
43
|
}
|
|
35
|
-
|
|
44
|
+
|
|
45
|
+
this.line.addLinePartAdjusted({ ...lp, controlPoints, animationSegment });
|
|
36
46
|
});
|
|
37
47
|
}
|
|
38
48
|
}
|
|
@@ -32,11 +32,13 @@ export default class ShotLineLayer extends ActionLineLayer {
|
|
|
32
32
|
|
|
33
33
|
this.ctx.lineJoin = 'round';
|
|
34
34
|
|
|
35
|
+
let isBallDrawn = false;
|
|
36
|
+
|
|
35
37
|
this.getProcessedLinePaths().forEach(linePart => {
|
|
36
38
|
this.ctx.save();
|
|
37
39
|
|
|
38
|
-
if (linePart.
|
|
39
|
-
this.setColor(
|
|
40
|
+
if (linePart.animationSegment === 'processed') {
|
|
41
|
+
this.setColor(0.1); // processed segment (before animation progress)
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
this.ctx.beginPath();
|
|
@@ -78,6 +80,15 @@ export default class ShotLineLayer extends ActionLineLayer {
|
|
|
78
80
|
this.ctx.stroke();
|
|
79
81
|
});
|
|
80
82
|
|
|
83
|
+
// Draw the ball AFTER the stroke so it overlaps the line.
|
|
84
|
+
if (!isBallDrawn) {
|
|
85
|
+
isBallDrawn = this.maybeDrawBallAtStartPoint({
|
|
86
|
+
linePart,
|
|
87
|
+
controlPoints: cp,
|
|
88
|
+
alreadyDrawn: isBallDrawn
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
81
92
|
this.ctx.restore();
|
|
82
93
|
});
|
|
83
94
|
|
|
@@ -30,6 +30,25 @@ export default class AnimationModel {
|
|
|
30
30
|
this.timeElapsedSaved = 0;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Extend current options (do not replace object) – same semantics as PlayModel.setOptions().
|
|
35
|
+
* This is needed because AnimationModel keeps an internal cloned PlayModel instance.
|
|
36
|
+
*/
|
|
37
|
+
setOptions(options: Partial<PlayModel['options']>) {
|
|
38
|
+
this.play.options = { ...this.play.options, ...options };
|
|
39
|
+
this.playBase.options = { ...this.playBase.options, ...options };
|
|
40
|
+
|
|
41
|
+
// Recreate frame so any layers/models that cached options are refreshed immediately.
|
|
42
|
+
this.animationFrame = new FrameModel(this.play, this.currentPlayPhase, this.globalProgress).setContext(this.ctx);
|
|
43
|
+
|
|
44
|
+
// If paused/stopped, force immediate visual update.
|
|
45
|
+
if (!this.running) {
|
|
46
|
+
this.animationFrame.setPhase(this.currentPlayPhase).setAnimationGlobalProgress(this.globalProgress).draw();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
33
52
|
get animationDuration() {
|
|
34
53
|
return this.play.playData.animationDuration / this.play.options.speed;
|
|
35
54
|
}
|
package/src/models/FrameModel.ts
CHANGED
|
@@ -252,14 +252,26 @@ export default class FrameModel {
|
|
|
252
252
|
if (line.type === 'DRIBBLE') {
|
|
253
253
|
return linePartsAdjusted.push({ ...line.getLineParts()[index] });
|
|
254
254
|
}
|
|
255
|
+
|
|
255
256
|
const linePathSplitted = splitBezierCurveAtTVal(
|
|
256
257
|
line.getLineParts()[index].controlPoints,
|
|
257
258
|
this.animationProgress(start, end)
|
|
258
259
|
) as LinePartAdjusted['controlPoints'][];
|
|
259
|
-
|
|
260
|
-
linePartsAdjusted.push({
|
|
260
|
+
|
|
261
|
+
linePartsAdjusted.push({
|
|
262
|
+
controlPoints: linePathSplitted[0],
|
|
263
|
+
animationSegment: 'processed'
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
linePartsAdjusted.push({
|
|
267
|
+
controlPoints: linePathSplitted[1],
|
|
268
|
+
animationSegment: 'active'
|
|
269
|
+
});
|
|
261
270
|
} else if (this.animationGlobalProgress > end) {
|
|
262
|
-
linePartsAdjusted.push({
|
|
271
|
+
linePartsAdjusted.push({
|
|
272
|
+
...line.getLineParts()[index],
|
|
273
|
+
animationSegment: 'processed'
|
|
274
|
+
});
|
|
263
275
|
} else {
|
|
264
276
|
linePartsAdjusted.push({ ...line.getLineParts()[index] });
|
|
265
277
|
}
|
package/src/models/LineModel.ts
CHANGED
|
@@ -9,7 +9,14 @@ export type LinePartAdjusted = {
|
|
|
9
9
|
| [CourtPointAdjusted, CourtPointAdjusted]
|
|
10
10
|
| [CourtPointAdjusted, CourtPointAdjusted, CourtPointAdjusted]
|
|
11
11
|
| [CourtPointAdjusted, CourtPointAdjusted, CourtPointAdjusted, CourtPointAdjusted];
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Indicates how this segment should be rendered during animation.
|
|
15
|
+
* - 'processed': segment is before current animation progress (dimmed)
|
|
16
|
+
* - 'active': segment is the currently "drawn" / normal colored segment
|
|
17
|
+
*/
|
|
18
|
+
animationSegment?: 'processed' | 'active';
|
|
19
|
+
|
|
13
20
|
lpIndex?: number;
|
|
14
21
|
};
|
|
15
22
|
|
|
@@ -97,6 +104,10 @@ export default class LineModel extends Model<LineData, LineAdjusted> {
|
|
|
97
104
|
return this._getAttr('type');
|
|
98
105
|
}
|
|
99
106
|
|
|
107
|
+
get isBallTransferLine() {
|
|
108
|
+
return ['PASS', 'HANDOFF', 'SHOT'].includes(this.type);
|
|
109
|
+
}
|
|
110
|
+
|
|
100
111
|
get phase() {
|
|
101
112
|
return this._getAttr('phase');
|
|
102
113
|
}
|
|
@@ -22,6 +22,10 @@ export function useDefaults(options?: Partial<PlayModelOptions>): PlayModelOptio
|
|
|
22
22
|
flipPlayerLabels: false,
|
|
23
23
|
legacyPrintStyle: false,
|
|
24
24
|
playerTokenScale: 1,
|
|
25
|
+
showBallMode: false,
|
|
26
|
+
// showBallMode sub-options (defaults match current behavior)
|
|
27
|
+
highlightPlayerPuck: true,
|
|
28
|
+
showPassLinesDuringPlayback: true,
|
|
25
29
|
// TODO: refactor NBA court type constants below
|
|
26
30
|
showHalfCourtCircle: true,
|
|
27
31
|
playersMap: [],
|
package/src/models/PlayModel.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
2
|
import playerHatsConfig from '../playerHatsConfig';
|
|
3
3
|
import shapesConfig from '../shapesConfig';
|
|
4
|
+
import ballConfig from '../ballConfig';
|
|
4
5
|
import { useDefaults } from './Play/Options';
|
|
5
6
|
|
|
6
7
|
import {
|
|
7
8
|
SPORT_TYPE_BASEBALL,
|
|
8
9
|
SPORT_TYPE_BASKETBALL,
|
|
10
|
+
SPORT_TYPE_BEACH_VOLLEYBALL,
|
|
9
11
|
SPORT_TYPE_FOOTBALL,
|
|
10
12
|
SPORT_TYPE_HOCKEY,
|
|
11
13
|
SPORT_TYPE_LACROSSE,
|
|
@@ -21,6 +23,7 @@ import hardwoodImageData from '../assets/wood_bg.png';
|
|
|
21
23
|
import grassImageData from '../assets/grass_bg.png';
|
|
22
24
|
import iceImageData from '../assets/ice_bg.png';
|
|
23
25
|
import concreteImageData from '../assets/concrete_bg.png';
|
|
26
|
+
import sandImageData from '../assets/sand_bg.png';
|
|
24
27
|
import { loadImage } from '../helpers/common';
|
|
25
28
|
|
|
26
29
|
const STORAGE_URL = 'https://playbooksstore.blob.core.windows.net/public';
|
|
@@ -44,6 +47,7 @@ export type PlayStaticData = {
|
|
|
44
47
|
watermark: typeof PlayModel.watermark;
|
|
45
48
|
playerHats: readonly ImageConfigItem[];
|
|
46
49
|
shapes: readonly ImageConfigItem[];
|
|
50
|
+
balls: readonly ImageConfigItem[];
|
|
47
51
|
playerHeadshots: typeof PlayModel.playerHeadshots;
|
|
48
52
|
teamPlayers: typeof PlayModel.teamPlayers;
|
|
49
53
|
};
|
|
@@ -88,6 +92,10 @@ export type PlayModelOptions = {
|
|
|
88
92
|
playersMap: PlayersMapItem[];
|
|
89
93
|
labelsOverrideType: 'Initials' | 'Jersey number' | 'Headshot' | null;
|
|
90
94
|
inDrawingState: boolean;
|
|
95
|
+
showBallMode: boolean;
|
|
96
|
+
// sub-options for showBallMode
|
|
97
|
+
highlightPlayerPuck: boolean;
|
|
98
|
+
showPassLinesDuringPlayback: boolean;
|
|
91
99
|
};
|
|
92
100
|
|
|
93
101
|
export default class PlayModel {
|
|
@@ -98,6 +106,7 @@ export default class PlayModel {
|
|
|
98
106
|
public static playerHeadshots: PlayerHeadshotItem[] = [];
|
|
99
107
|
public static playerHats: readonly ImageConfigItem[];
|
|
100
108
|
public static shapes: readonly ImageConfigItem[];
|
|
109
|
+
public static balls: readonly ImageConfigItem[];
|
|
101
110
|
public static watermark: { LuceoSports: HTMLImageElement; TeamLogo: HTMLImageElement | null };
|
|
102
111
|
public static backgroundOptions: Record<SportType, HTMLImageElement> & {
|
|
103
112
|
Hardwood: HTMLImageElement;
|
|
@@ -113,17 +122,20 @@ export default class PlayModel {
|
|
|
113
122
|
static async init({ teamLogoPath = '' } = {}) {
|
|
114
123
|
PlayModel.playerHats = await playerHatsConfig();
|
|
115
124
|
PlayModel.shapes = await shapesConfig();
|
|
125
|
+
PlayModel.balls = await ballConfig();
|
|
116
126
|
|
|
117
127
|
const hardwoodImage = await loadImage(hardwoodImageData);
|
|
118
128
|
const grassImage = await loadImage(grassImageData);
|
|
119
129
|
const iceImage = await loadImage(iceImageData);
|
|
120
130
|
const concreteImage = await loadImage(concreteImageData);
|
|
131
|
+
const sandImage = await loadImage(sandImageData);
|
|
121
132
|
|
|
122
133
|
PlayModel.backgroundOptions = {
|
|
123
134
|
Hardwood: hardwoodImage,
|
|
124
135
|
Concrete: concreteImage,
|
|
125
136
|
[SPORT_TYPE_BASKETBALL]: hardwoodImage,
|
|
126
137
|
[SPORT_TYPE_VOLLEYBALL]: hardwoodImage,
|
|
138
|
+
[SPORT_TYPE_BEACH_VOLLEYBALL]: sandImage,
|
|
127
139
|
[SPORT_TYPE_FOOTBALL]: grassImage,
|
|
128
140
|
[SPORT_TYPE_LACROSSE]: grassImage,
|
|
129
141
|
[SPORT_TYPE_LACROSSE_BOX]: grassImage,
|
|
@@ -190,6 +202,7 @@ export default class PlayModel {
|
|
|
190
202
|
watermark: PlayModel.watermark,
|
|
191
203
|
playerHats: PlayModel.playerHats,
|
|
192
204
|
shapes: PlayModel.shapes,
|
|
205
|
+
balls: PlayModel.balls,
|
|
193
206
|
playerHeadshots: PlayModel.playerHeadshots,
|
|
194
207
|
teamPlayers: PlayModel.teamPlayers
|
|
195
208
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { CourtPoint, LinePart } from '../types';
|
|
1
|
+
import { CourtPoint, LinePart, PlayData } from '../types';
|
|
2
2
|
import LineModel, { LinePartAdjusted } from '../models/LineModel';
|
|
3
3
|
import ShapeModel from '../models/ShapeModel';
|
|
4
|
+
import { FrameDataOptions } from '../models/FrameModel';
|
|
5
|
+
import { drawBallObject } from '../helpers/draw';
|
|
4
6
|
|
|
5
7
|
interface InheritedPropsAndMethods {
|
|
6
8
|
ctx: CanvasRenderingContext2D;
|
|
@@ -8,6 +10,13 @@ interface InheritedPropsAndMethods {
|
|
|
8
10
|
setColor: (alpha?: number) => void;
|
|
9
11
|
line?: LineModel;
|
|
10
12
|
shape?: ShapeModel;
|
|
13
|
+
// below present on layers using this trait (InternalBaseLayer)
|
|
14
|
+
options: FrameDataOptions;
|
|
15
|
+
playData: PlayData;
|
|
16
|
+
courtTypeConstants?: {
|
|
17
|
+
PLAYER_TOKEN_SCALE: number;
|
|
18
|
+
PLAYER_TOKEN_RADIUS: number;
|
|
19
|
+
};
|
|
11
20
|
}
|
|
12
21
|
|
|
13
22
|
interface LineDrawOperationsTrait extends InheritedPropsAndMethods {
|
|
@@ -21,9 +30,44 @@ interface LineDrawOperationsTrait extends InheritedPropsAndMethods {
|
|
|
21
30
|
angleBetweenTwoPoints: (cpFrom: CourtPoint, cpTo: CourtPoint) => number;
|
|
22
31
|
arrowTipPoint: () => CourtPoint;
|
|
23
32
|
setLineOptions: () => void;
|
|
33
|
+
maybeDrawBallAtStartPoint: (params: {
|
|
34
|
+
linePart: Pick<LinePartAdjusted, 'animationSegment'>;
|
|
35
|
+
controlPoints: CourtPoint[];
|
|
36
|
+
alreadyDrawn: boolean;
|
|
37
|
+
}) => boolean;
|
|
24
38
|
}
|
|
25
39
|
|
|
26
40
|
export default {
|
|
41
|
+
maybeDrawBallAtStartPoint(params: {
|
|
42
|
+
linePart: Pick<LinePartAdjusted, 'animationSegment'>;
|
|
43
|
+
controlPoints: CourtPoint[];
|
|
44
|
+
alreadyDrawn: boolean;
|
|
45
|
+
}): boolean {
|
|
46
|
+
const { linePart, controlPoints, alreadyDrawn } = params;
|
|
47
|
+
|
|
48
|
+
if (!this.options?.showBallMode) return alreadyDrawn;
|
|
49
|
+
if (alreadyDrawn) return true;
|
|
50
|
+
if (linePart.animationSegment !== 'active') return false;
|
|
51
|
+
if (!this.line?.isBallTransferLine) return false;
|
|
52
|
+
|
|
53
|
+
const startPoint = controlPoints[0];
|
|
54
|
+
if (!startPoint || !this.courtTypeConstants) return false;
|
|
55
|
+
|
|
56
|
+
const playerScale = this.courtTypeConstants.PLAYER_TOKEN_SCALE * (this.options?.playerTokenScale ?? 1);
|
|
57
|
+
const puckRadiusBase = this.courtTypeConstants.PLAYER_TOKEN_RADIUS * playerScale;
|
|
58
|
+
const radiusMultiplier = this.options?.legacyPrintStyle ? 1.2 : 1;
|
|
59
|
+
const puckRadius = radiusMultiplier * puckRadiusBase;
|
|
60
|
+
|
|
61
|
+
return drawBallObject({
|
|
62
|
+
sport: this.playData.sport,
|
|
63
|
+
ctx: this.ctx,
|
|
64
|
+
staticData: this.options?.staticData,
|
|
65
|
+
center: { x: startPoint.x, y: startPoint.y },
|
|
66
|
+
puckRadius,
|
|
67
|
+
placement: 'center'
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
|
|
27
71
|
drawLineFromControlPoints() {
|
|
28
72
|
this.ctx.save();
|
|
29
73
|
|
|
@@ -31,11 +75,13 @@ export default {
|
|
|
31
75
|
|
|
32
76
|
this.ctx.lineJoin = 'round';
|
|
33
77
|
|
|
78
|
+
let isBallDrawn = false;
|
|
79
|
+
|
|
34
80
|
this.getProcessedLinePaths().forEach(linePart => {
|
|
35
81
|
this.ctx.save();
|
|
36
82
|
|
|
37
|
-
if (linePart.
|
|
38
|
-
this.setColor(
|
|
83
|
+
if (linePart.animationSegment === 'processed') {
|
|
84
|
+
this.setColor(0.1); // processed segment (before animation progress)
|
|
39
85
|
}
|
|
40
86
|
|
|
41
87
|
this.ctx.beginPath();
|
|
@@ -57,6 +103,15 @@ export default {
|
|
|
57
103
|
|
|
58
104
|
this.ctx.stroke();
|
|
59
105
|
|
|
106
|
+
// Draw the ball AFTER the stroke so it overlaps the line.
|
|
107
|
+
if (!isBallDrawn) {
|
|
108
|
+
isBallDrawn = this.maybeDrawBallAtStartPoint({
|
|
109
|
+
linePart,
|
|
110
|
+
controlPoints: cp,
|
|
111
|
+
alreadyDrawn: isBallDrawn
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
60
115
|
this.ctx.restore();
|
|
61
116
|
});
|
|
62
117
|
|
package/src/types/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ export type SportType =
|
|
|
18
18
|
| 'FOOTBALL'
|
|
19
19
|
| 'BASKETBALL'
|
|
20
20
|
| 'VOLLEYBALL'
|
|
21
|
+
| 'BEACH_VOLLEYBALL'
|
|
21
22
|
| 'LACROSSE'
|
|
22
23
|
| 'LACROSSE_BOX'
|
|
23
24
|
| 'SOCCER'
|
|
@@ -37,6 +38,8 @@ export type CourtTypeSportBasketball =
|
|
|
37
38
|
|
|
38
39
|
export type CourtTypeSportVolleyball = 'VOLLEYBALL_INDOOR';
|
|
39
40
|
|
|
41
|
+
export type CourtTypeSportBeachVolleyball = 'BEACH_VOLLEYBALL_NCAA' | 'BEACH_VOLLEYBALL_RECREATIONAL';
|
|
42
|
+
|
|
40
43
|
export type CourtTypeSportSoccer =
|
|
41
44
|
| 'SOCCER_FIFA'
|
|
42
45
|
| 'SOCCER_NCAA'
|
|
@@ -62,6 +65,7 @@ export type CourtTypeSportFootballLegacy = 'FOOTBALL';
|
|
|
62
65
|
export type CourtType =
|
|
63
66
|
| CourtTypeSportBasketball
|
|
64
67
|
| CourtTypeSportVolleyball
|
|
68
|
+
| CourtTypeSportBeachVolleyball
|
|
65
69
|
| CourtTypeSportLacrosse
|
|
66
70
|
| CourtTypeSportLacrosseBox
|
|
67
71
|
| CourtTypeSportSoccer
|