canvasframework 0.6.3 → 0.7.1
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/components/AppBar.js +159 -99
- package/components/Avatar.js +142 -143
- package/components/BottomSheet.js +124 -106
- package/components/Button.js +207 -254
- package/components/Checkbox.js +98 -91
- package/components/Chip.js +137 -106
- package/components/Dialog.js +161 -146
- package/components/FAB.js +122 -195
- package/components/Switch.js +146 -112
- package/components/Text.js +104 -103
- package/components/Toast.js +132 -156
- package/components/VirtualList.js +88 -59
- package/core/CanvasFramework.js +115 -44
- package/core/CanvasUtils.js +141 -0
- package/core/Component.js +124 -66
- package/core/ThemeManager.js +162 -176
- package/core/WebGLCanvasAdapter.js +88 -39
- package/package.json +1 -1
package/components/Avatar.js
CHANGED
|
@@ -1,202 +1,201 @@
|
|
|
1
1
|
import Component from '../core/Component.js';
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
|
-
* Avatar
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* @property {string} bgColor - Couleur de fond
|
|
10
|
-
* @property {string} textColor - Couleur du texte
|
|
11
|
-
* @property {number} borderWidth - Épaisseur de la bordure
|
|
12
|
-
* @property {string} borderColor - Couleur de la bordure
|
|
13
|
-
* @property {string|null} status - Statut ('online', 'offline', 'away', 'busy')
|
|
14
|
-
* @property {ImageComponent|null} imageComponent - Composant image interne
|
|
4
|
+
* Avatar — photo de profil ou initiales.
|
|
5
|
+
*
|
|
6
|
+
* Corrections :
|
|
7
|
+
* - hexToRgb supprimé (non utilisé directement)
|
|
8
|
+
* - destroy() nettoie imageComponent
|
|
9
|
+
* - scrollOffset géré proprement dans isPointInside
|
|
15
10
|
*/
|
|
16
11
|
class Avatar extends Component {
|
|
17
12
|
/**
|
|
18
|
-
*
|
|
19
|
-
* @param {
|
|
20
|
-
* @param {
|
|
21
|
-
* @param {string}
|
|
22
|
-
* @param {
|
|
23
|
-
* @param {
|
|
24
|
-
* @param {string}
|
|
25
|
-
* @param {
|
|
26
|
-
* @param {
|
|
27
|
-
* @param {string}
|
|
28
|
-
* @param {
|
|
13
|
+
* @param {CanvasFramework} framework
|
|
14
|
+
* @param {Object} [options={}]
|
|
15
|
+
* @param {string} [options.imageUrl]
|
|
16
|
+
* @param {string} [options.initials='??']
|
|
17
|
+
* @param {number} [options.size=48]
|
|
18
|
+
* @param {string} [options.bgColor] - Auto-généré à partir des initiales si absent
|
|
19
|
+
* @param {string} [options.textColor='#FFFFFF']
|
|
20
|
+
* @param {number} [options.borderWidth=0]
|
|
21
|
+
* @param {string} [options.borderColor='#FFFFFF']
|
|
22
|
+
* @param {string} [options.status] - 'online'|'offline'|'away'|'busy'
|
|
23
|
+
* @param {Function}[options.onClick]
|
|
29
24
|
*/
|
|
30
25
|
constructor(framework, options = {}) {
|
|
31
26
|
super(framework, options);
|
|
32
|
-
|
|
33
|
-
this.
|
|
34
|
-
this.
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
27
|
+
|
|
28
|
+
this.imageUrl = options.imageUrl || null;
|
|
29
|
+
this.initials = options.initials || '??';
|
|
30
|
+
this.size = options.size || 48;
|
|
31
|
+
this.bgColor = options.bgColor || this._generateColor(this.initials);
|
|
32
|
+
this.textColor = options.textColor || '#FFFFFF';
|
|
37
33
|
this.borderWidth = options.borderWidth || 0;
|
|
38
34
|
this.borderColor = options.borderColor || '#FFFFFF';
|
|
39
|
-
this.status
|
|
40
|
-
|
|
41
|
-
this.width
|
|
35
|
+
this.status = options.status || null;
|
|
36
|
+
|
|
37
|
+
this.width = this.size;
|
|
42
38
|
this.height = this.size;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
|
|
40
|
+
this._imageElement = null;
|
|
41
|
+
|
|
46
42
|
if (this.imageUrl) {
|
|
47
|
-
this.
|
|
48
|
-
x: this.x,
|
|
49
|
-
y: this.y,
|
|
50
|
-
width: this.size,
|
|
51
|
-
height: this.size,
|
|
52
|
-
src: this.imageUrl,
|
|
53
|
-
fit: 'cover',
|
|
54
|
-
borderRadius: this.size / 2
|
|
55
|
-
});
|
|
43
|
+
this._loadImage(this.imageUrl);
|
|
56
44
|
}
|
|
57
45
|
}
|
|
58
|
-
|
|
46
|
+
|
|
47
|
+
// ─────────────────────────────────────────
|
|
48
|
+
// IMAGE
|
|
49
|
+
// ─────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
/** @private */
|
|
52
|
+
_loadImage(url) {
|
|
53
|
+
const img = new Image();
|
|
54
|
+
img.onload = () => { this._imageElement = img; this.markDirty(); };
|
|
55
|
+
img.onerror = () => { this._imageElement = null; };
|
|
56
|
+
img.src = url;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Change l'image de l'avatar.
|
|
61
|
+
* @param {string|null} url
|
|
62
|
+
*/
|
|
63
|
+
setImage(url) {
|
|
64
|
+
this.imageUrl = url;
|
|
65
|
+
this._imageElement = null;
|
|
66
|
+
if (url) this._loadImage(url);
|
|
67
|
+
else this.markDirty();
|
|
68
|
+
}
|
|
69
|
+
|
|
59
70
|
/**
|
|
60
|
-
*
|
|
61
|
-
* @param {
|
|
62
|
-
* @returns {string} Couleur hexadécimale
|
|
63
|
-
* @private
|
|
71
|
+
* Change le statut de l'avatar.
|
|
72
|
+
* @param {'online'|'offline'|'away'|'busy'|null} status
|
|
64
73
|
*/
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
setStatus(status) {
|
|
75
|
+
this.status = status;
|
|
76
|
+
this.markDirty();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─────────────────────────────────────────
|
|
80
|
+
// COULEUR AUTO
|
|
81
|
+
// ─────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
/** @private */
|
|
84
|
+
_generateColor(text) {
|
|
67
85
|
let hash = 0;
|
|
68
86
|
for (let i = 0; i < text.length; i++) {
|
|
69
87
|
hash = text.charCodeAt(i) + ((hash << 5) - hash);
|
|
70
88
|
}
|
|
71
|
-
|
|
72
|
-
const colors = [
|
|
89
|
+
const palette = [
|
|
73
90
|
'#E91E63', '#9C27B0', '#673AB7', '#3F51B5',
|
|
74
91
|
'#2196F3', '#00BCD4', '#009688', '#4CAF50',
|
|
75
|
-
'#FF9800', '#FF5722', '#795548', '#607D8B'
|
|
92
|
+
'#FF9800', '#FF5722', '#795548', '#607D8B',
|
|
76
93
|
];
|
|
77
|
-
|
|
78
|
-
return colors[Math.abs(hash) % colors.length];
|
|
94
|
+
return palette[Math.abs(hash) % palette.length];
|
|
79
95
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
|
|
97
|
+
// ─────────────────────────────────────────
|
|
98
|
+
// DESSIN
|
|
99
|
+
// ─────────────────────────────────────────
|
|
100
|
+
|
|
85
101
|
draw(ctx) {
|
|
86
102
|
ctx.save();
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
const
|
|
103
|
+
|
|
104
|
+
const cx = this.x + this.size / 2;
|
|
105
|
+
const cy = this.y + this.size / 2;
|
|
90
106
|
const radius = this.size / 2;
|
|
91
|
-
|
|
107
|
+
|
|
92
108
|
// Bordure
|
|
93
109
|
if (this.borderWidth > 0) {
|
|
94
110
|
ctx.fillStyle = this.borderColor;
|
|
95
111
|
ctx.beginPath();
|
|
96
|
-
ctx.arc(
|
|
112
|
+
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
|
|
97
113
|
ctx.fill();
|
|
98
114
|
}
|
|
99
|
-
|
|
100
|
-
//
|
|
115
|
+
|
|
116
|
+
// Clip circulaire
|
|
101
117
|
ctx.save();
|
|
102
118
|
ctx.beginPath();
|
|
103
|
-
ctx.arc(
|
|
119
|
+
ctx.arc(cx, cy, radius - this.borderWidth, 0, Math.PI * 2);
|
|
104
120
|
ctx.clip();
|
|
105
|
-
|
|
106
|
-
if (this.
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
this.
|
|
121
|
+
|
|
122
|
+
if (this._imageElement) {
|
|
123
|
+
// Dessin de l'image (cover)
|
|
124
|
+
const s = this.size - this.borderWidth * 2;
|
|
125
|
+
const sx = this.x + this.borderWidth;
|
|
126
|
+
const sy = this.y + this.borderWidth;
|
|
127
|
+
const imgW = this._imageElement.naturalWidth;
|
|
128
|
+
const imgH = this._imageElement.naturalHeight;
|
|
129
|
+
const scale = Math.max(s / imgW, s / imgH);
|
|
130
|
+
const drawW = imgW * scale;
|
|
131
|
+
const drawH = imgH * scale;
|
|
132
|
+
const drawX = sx + (s - drawW) / 2;
|
|
133
|
+
const drawY = sy + (s - drawH) / 2;
|
|
134
|
+
ctx.drawImage(this._imageElement, drawX, drawY, drawW, drawH);
|
|
113
135
|
} else {
|
|
114
|
-
//
|
|
136
|
+
// Fond coloré + initiales
|
|
115
137
|
ctx.fillStyle = this.bgColor;
|
|
116
138
|
ctx.fillRect(this.x, this.y, this.size, this.size);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
ctx.
|
|
120
|
-
ctx.
|
|
121
|
-
ctx.textAlign = 'center';
|
|
139
|
+
|
|
140
|
+
ctx.fillStyle = this.textColor;
|
|
141
|
+
ctx.font = `bold ${this.size / 2.5}px -apple-system, sans-serif`;
|
|
142
|
+
ctx.textAlign = 'center';
|
|
122
143
|
ctx.textBaseline = 'middle';
|
|
123
|
-
ctx.fillText(this.initials.toUpperCase(),
|
|
144
|
+
ctx.fillText(this.initials.toUpperCase(), cx, cy);
|
|
124
145
|
}
|
|
125
|
-
|
|
146
|
+
|
|
126
147
|
ctx.restore();
|
|
127
|
-
|
|
148
|
+
|
|
128
149
|
// Indicateur de statut
|
|
129
150
|
if (this.status) {
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
// Bordure blanche autour du statut
|
|
151
|
+
const dotSize = this.size / 4;
|
|
152
|
+
const dotX = this.x + this.size - dotSize;
|
|
153
|
+
const dotY = this.y + this.size - dotSize;
|
|
154
|
+
|
|
155
|
+
const statusColors = {
|
|
156
|
+
online: '#4CAF50',
|
|
157
|
+
offline: '#9E9E9E',
|
|
158
|
+
away: '#FF9800',
|
|
159
|
+
busy: '#F44336',
|
|
160
|
+
};
|
|
161
|
+
const dotColor = statusColors[this.status] || '#9E9E9E';
|
|
162
|
+
|
|
163
|
+
// Contour blanc
|
|
144
164
|
ctx.fillStyle = '#FFFFFF';
|
|
145
165
|
ctx.beginPath();
|
|
146
|
-
ctx.arc(
|
|
166
|
+
ctx.arc(dotX, dotY, dotSize / 2 + 1, 0, Math.PI * 2);
|
|
147
167
|
ctx.fill();
|
|
148
|
-
|
|
168
|
+
|
|
149
169
|
// Cercle de statut
|
|
150
|
-
ctx.fillStyle =
|
|
170
|
+
ctx.fillStyle = dotColor;
|
|
151
171
|
ctx.beginPath();
|
|
152
|
-
ctx.arc(
|
|
172
|
+
ctx.arc(dotX, dotY, dotSize / 2, 0, Math.PI * 2);
|
|
153
173
|
ctx.fill();
|
|
154
174
|
}
|
|
155
|
-
|
|
175
|
+
|
|
156
176
|
ctx.restore();
|
|
157
177
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
setImage(url) {
|
|
164
|
-
this.imageUrl = url;
|
|
165
|
-
if (url) {
|
|
166
|
-
this.imageComponent = new ImageComponent(this.framework, {
|
|
167
|
-
x: this.x,
|
|
168
|
-
y: this.y,
|
|
169
|
-
width: this.size,
|
|
170
|
-
height: this.size,
|
|
171
|
-
src: url,
|
|
172
|
-
fit: 'cover',
|
|
173
|
-
borderRadius: this.size / 2
|
|
174
|
-
});
|
|
175
|
-
} else {
|
|
176
|
-
this.imageComponent = null;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Change le statut
|
|
182
|
-
* @param {string} status - Nouveau statut
|
|
183
|
-
*/
|
|
184
|
-
setStatus(status) {
|
|
185
|
-
this.status = status;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Vérifie si un point est dans les limites
|
|
190
|
-
* @param {number} x - Coordonnée X
|
|
191
|
-
* @param {number} y - Coordonnée Y
|
|
192
|
-
* @returns {boolean} True si le point est dans l'avatar
|
|
193
|
-
*/
|
|
178
|
+
|
|
179
|
+
// ─────────────────────────────────────────
|
|
180
|
+
// HIT TEST
|
|
181
|
+
// ─────────────────────────────────────────
|
|
182
|
+
|
|
194
183
|
isPointInside(x, y) {
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
-
const
|
|
184
|
+
const scrollOffset = this.framework?.scrollOffset ?? 0;
|
|
185
|
+
const adjY = y + scrollOffset; // le framework soustrait le scroll dans les coords passées
|
|
186
|
+
const dx = x - (this.x + this.size / 2);
|
|
187
|
+
const dy = adjY - (this.y + this.size / 2);
|
|
198
188
|
return Math.sqrt(dx * dx + dy * dy) <= this.size / 2;
|
|
199
189
|
}
|
|
190
|
+
|
|
191
|
+
// ─────────────────────────────────────────
|
|
192
|
+
// DESTROY
|
|
193
|
+
// ─────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
destroy() {
|
|
196
|
+
this._imageElement = null;
|
|
197
|
+
super.destroy();
|
|
198
|
+
}
|
|
200
199
|
}
|
|
201
200
|
|
|
202
201
|
export default Avatar;
|