Skip to content

Commit 03af927

Browse files
committed
Init
1 parent 8f9b75e commit 03af927

13 files changed

+4530
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

dist/index.js

+100
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

+3,850
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "spacha",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "dist/index.js",
6+
"scripts": {
7+
"prebuild": "rimraf dist",
8+
"build": "npx webpack --config webpack.config.js"
9+
},
10+
"repository": {
11+
"type": "git",
12+
"url": "git+https://github.com/BonyChops/spacha.git"
13+
},
14+
"keywords": [],
15+
"author": "",
16+
"license": "ISC",
17+
"bugs": {
18+
"url": "https://github.com/BonyChops/spacha/issues"
19+
},
20+
"homepage": "https://github.com/BonyChops/spacha#readme",
21+
"devDependencies": {
22+
"@types/node": "^18.7.18",
23+
"rimraf": "^3.0.2",
24+
"ts-loader": "^9.3.1",
25+
"ts-node": "^10.9.1",
26+
"typescript": "^4.8.3",
27+
"webpack": "^5.74.0",
28+
"webpack-cli": "^4.10.0"
29+
},
30+
"files": [
31+
"dist"
32+
],
33+
"dependencies": {
34+
"canvas": "^2.10.1"
35+
}
36+
}

src/Spacha.ts

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { SpachaCore } from "./SpachaCore";
2+
import { SpachaOptions, themes } from "./define";
3+
4+
export class Spacha extends SpachaCore {
5+
private spc: HTMLElement;
6+
private upperDiv: HTMLElement;
7+
private baseDiv: HTMLElement;
8+
9+
constructor(spc: HTMLElement, options: SpachaOptions) {
10+
super(options);
11+
this.spc = spc;
12+
this.upperDiv = document.createElement("div");
13+
this.baseDiv = document.createElement("div");
14+
this.draw();
15+
}
16+
17+
private draw = (): void => {
18+
this.spc.innerHTML = '';
19+
const spcContainer = document.createElement("div");
20+
this.upperDiv = document.createElement("div");
21+
this.upperDiv.id = "spc-upper-div";
22+
this.setUpperStyle(this.upperDiv, Boolean(this.options.message));
23+
this.setUpperInner(this.upperDiv);
24+
25+
if (this.options.message) {
26+
this.baseDiv = document.createElement("div");
27+
this.baseDiv.id = "spc-base-div";
28+
this.baseDiv.innerHTML = this.options.message;
29+
this.setMessageStyle(this.baseDiv);
30+
}
31+
32+
spcContainer.appendChild(this.upperDiv);
33+
spcContainer.appendChild(this.baseDiv);
34+
35+
if(this.options.shaddow){
36+
spcContainer.style.boxShadow = "0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)";
37+
spcContainer.style.borderRadius = `${20 * this.scale}px`
38+
}
39+
this.spc.appendChild(spcContainer);
40+
41+
}
42+
43+
private setUpperStyle = (baseDiv: HTMLElement, upper: boolean): void => {
44+
const style = baseDiv.style;
45+
const r = 20 * this.scale;
46+
const pw = 30 * this.scale;
47+
const ph = 20 * this.scale;
48+
49+
style.backgroundColor = this.options.themeOption?.color ?? themes.blue.color;
50+
style.width = "100%";
51+
style.height = `${150 * this.scale}px`;
52+
style.display = "inline-flex";
53+
style.boxSizing = "border-box";
54+
style.borderRadius = upper ? `${r}px ${r}px 0px 0px` : `${r}px`;
55+
style.paddingLeft = `${pw}px`;
56+
style.paddingRight = `${pw}px`;
57+
style.paddingTop = `${ph}px`;
58+
style.paddingBottom = `${ph}px`;
59+
}
60+
61+
private setMessageStyle = (messageDiv: HTMLElement) => {
62+
const style = messageDiv.style;
63+
const r = 20 * this.scale;
64+
style.width = "100%";
65+
style.boxSizing = "border-box";
66+
style.backgroundColor = this.options.themeOption?.baseColor ?? themes.blue.baseColor;
67+
style.font = `${30 * this.scale}px 'sans-serif'`;
68+
style.padding = `${10 * this.scale}px`;
69+
style.borderRadius = `0px 0px ${r}px ${r}px`;
70+
style.textOverflow = "ellipsis";
71+
72+
style.color = this.options.themeOption?.txtColor ?? themes.blue.txtColor;
73+
}
74+
75+
private setUpperInner = (upperDiv: HTMLElement): void => {
76+
const iconDiv = document.createElement("div");
77+
const profileDiv = document.createElement("div");
78+
iconDiv.id = "spc-icon-div";
79+
profileDiv.id = "spc-profile-div";
80+
81+
iconDiv.style.width = `${110 * this.scale}px`;
82+
iconDiv.style.height = `${110 * this.scale}px`;
83+
84+
profileDiv.style.paddingLeft = `${10 * this.scale}px`;
85+
86+
this.setIconInner(iconDiv);
87+
this.setProfileInner(profileDiv);
88+
89+
upperDiv.appendChild(iconDiv);
90+
upperDiv.appendChild(profileDiv);
91+
}
92+
93+
private setIconInner = (iconDiv: HTMLElement): void => {
94+
const img = document.createElement("img");
95+
img.width = 110 * this.scale;
96+
img.height = 110 * this.scale;
97+
img.src = String(this.options.user?.img?.src);
98+
if(!this.options.imgOption?.square){
99+
img.style.borderRadius = `${110 * this.scale / 2}px`
100+
}
101+
102+
iconDiv.appendChild(img);
103+
}
104+
105+
private setProfileInner = (profileDiv: HTMLElement): void => {
106+
const nameP = document.createElement("p");
107+
const priceP = document.createElement("p");
108+
const txtColor = this.options.themeOption?.txtColor ?? themes.blue.txtColor;
109+
nameP.style.color = txtColor;
110+
priceP.style.color = txtColor;
111+
112+
nameP.style.opacity = "0.7";
113+
114+
nameP.style.marginTop = "10px";
115+
nameP.style.marginBottom = "0px";
116+
priceP.style.marginTop = "0px";
117+
118+
nameP.style.font = `${20 * this.scale}px 'sans-serif'`;
119+
priceP.style.font = `${50 * this.scale}px 'sans-serif'`;
120+
121+
nameP.innerHTML = this.options.user?.name ?? "YouTube User";
122+
priceP.innerHTML = this.options.customPriceString ?? this.generatePriceString(this.options.price ?? 0);
123+
124+
profileDiv.appendChild(nameP);
125+
profileDiv.appendChild(priceP);
126+
}
127+
}

src/SpachaCore.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
2+
import { SpachaOptions, Theme, themes, ThemeName } from "./define";
3+
export class SpachaCore {
4+
protected options: SpachaOptions;
5+
protected scale: number = 1.0;
6+
7+
constructor(options?: SpachaOptions) {
8+
this.options = options ?? {};
9+
this.init();
10+
}
11+
12+
private init = () => {
13+
const themeArray = Object.keys(themes).map(key => ({ ...themes[key], name: key }));
14+
const randomTheme = themeArray[Math.floor(Math.random() * themeArray.length)];
15+
console.log(themeArray);
16+
console.log(randomTheme);
17+
this.options.price ??= randomTheme.price;
18+
this.options.scale ??= 1.0;
19+
this.scale = this.options.scale;
20+
//this.options.theme ??= randomTheme.name;
21+
this.options.themeOption ??= themes[this.options.theme ?? randomTheme.name];
22+
this.options.user ??= {
23+
name: 'YouTube User'
24+
};
25+
this.options.user.name ??= 'YouTube User';
26+
const img = new Image;
27+
img.src = "https://github.com/youtube.png"
28+
this.options.user.img ??= img;
29+
this.options.shaddow ??= true;
30+
}
31+
32+
protected themeFromPrice = (price: number): Theme => {
33+
if (price < 0) {
34+
return themes.blue;
35+
}
36+
return Object.keys(themes).map(key => themes[key]).find(v => v.price <= price) ?? themes.blue;
37+
}
38+
39+
protected generatePriceString = (price: number): string => {
40+
return "¥" + Number(this.options.price).toLocaleString();
41+
}
42+
}

src/SpachaImage.ts

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import { SpachaCore } from "./SpachaCore";
2+
import { SpachaOptions, themes } from "./define";
3+
4+
export class SpachaImage extends SpachaCore {
5+
ctx: CanvasRenderingContext2D;
6+
7+
constructor(ctx: CanvasRenderingContext2D, options?: SpachaOptions) {
8+
super(options);
9+
this.ctx = ctx;
10+
this.job();
11+
}
12+
13+
job = (): void => {
14+
const canvas = this.ctx.canvas;
15+
this.options.width ??= canvas.clientWidth;
16+
if (this.options.width !== canvas.clientWidth) {
17+
canvas.width = this.options.width;
18+
}
19+
if (!this.options.height) {
20+
this.options.heightAutoFix ??= true;
21+
}
22+
this.options.height ??= canvas.clientHeight;
23+
if (this.options.height !== canvas.height) {
24+
canvas.height = this.options.height;
25+
}
26+
27+
this.draw();
28+
}
29+
30+
public draw = (): void => {
31+
const canvas = this.ctx.canvas;
32+
if (this.options.message) {
33+
let ty;
34+
if (this.options.heightAutoFix) {
35+
const y = 150 * this.scale;
36+
const offset = 10 * this.scale;
37+
ty = this.printString(this.options.message ?? 'error', 0, 0, 32 * this.scale, ((this.options.width ?? 600 ) - 10 ), false);
38+
this.options.height = y + ty + offset * 2;
39+
canvas.height = this.options.height;
40+
}
41+
42+
this.drawRounedSq(true);
43+
this.drawRoundedSqCm();
44+
} else {
45+
this.drawRounedSq(false);
46+
}
47+
}
48+
49+
private drawRounedSq = (cm: boolean): void => {
50+
const ctx = this.ctx;
51+
const x = 0, y = 0;
52+
const w = (this.options.width ?? 600) ;
53+
const h = (cm ? 150 * this.scale : ((this.options.height) ?? 150));
54+
const r = 20 * this.scale;
55+
const color = this.options.themeOption?.color ?? themes.blue.baseColor;
56+
57+
console.log(h);
58+
ctx.beginPath();
59+
ctx.lineWidth = 1;
60+
ctx.strokeStyle = color;
61+
ctx.fillStyle = color;
62+
ctx.moveTo(x, y + (r / 2));
63+
if (cm) {
64+
ctx.lineTo(x, y + h);
65+
ctx.lineTo(x + w, y + h);
66+
} else {
67+
ctx.arc(x + r, y + h - r, r, Math.PI, Math.PI * 0.5, true);
68+
ctx.arc(x + w - r, y + h - r, r, Math.PI * 0.5, 0, true);
69+
}
70+
ctx.arc(x + w - r, y + r, r, 0, Math.PI * 1.5, true);
71+
ctx.arc(x + r, y + r, r, Math.PI * 1.5, Math.PI, true);
72+
ctx.closePath();
73+
ctx.stroke();
74+
75+
ctx.fill();
76+
this.iconDraw();
77+
this.profileDraw();
78+
}
79+
80+
private drawRoundedSqCm = (): void => {
81+
const ctx = this.ctx;
82+
const x = 0, y = 150 * this.scale;
83+
const w = (this.options.width ?? 600);
84+
const h = (this.options.height ?? 150);
85+
const r = 20 * this.scale;
86+
87+
88+
ctx.lineWidth = 1;
89+
ctx.strokeStyle = this.options.themeOption?.color ?? themes.blue.color;
90+
ctx.fillStyle = this.options.themeOption?.baseColor ?? themes.blue.baseColor;
91+
ctx.beginPath();
92+
ctx.moveTo(x, y);
93+
ctx.arc(x + r, h - r, r, Math.PI, Math.PI * 0.5, true);
94+
ctx.arc(x + w - r, h - r, r, Math.PI * 0.5, 0, true);
95+
ctx.lineTo(x + w, y);
96+
ctx.closePath();
97+
ctx.stroke();
98+
ctx.fill();
99+
this.printString(this.options.message ?? 'error', x + (20 * this.scale), y + (32 * this.scale), 32 * this.scale, (this.options.width ?? 600) - 10 , true);
100+
}
101+
102+
private printString = (
103+
text: string,
104+
x: number,
105+
y: number,
106+
lineHeight: number,
107+
fitWidth: number,
108+
draw: boolean
109+
): number => {
110+
const ctx = this.ctx;
111+
ctx.font = `${30 * this.scale}px 'sans-serif'`;
112+
ctx.fillStyle = this.options.themeOption?.txtColor ?? 'white';
113+
fitWidth = fitWidth || 0;
114+
115+
if (fitWidth <= 0) {
116+
if (draw) {
117+
ctx.fillText(text, x, y);
118+
}
119+
return y + lineHeight;
120+
}
121+
122+
for (let idx = 1; idx <= text.length; idx++) {
123+
let str = text.substring(0, idx);
124+
if (ctx.measureText(str).width > fitWidth) {
125+
if (draw) {
126+
console.log(str, y);
127+
ctx.fillText(text.substring(0, idx - 1), x, y);
128+
}
129+
const r = this.printString(text.substring(idx - 1), x, y + lineHeight, lineHeight, fitWidth, draw);
130+
console.log(r);
131+
return r;
132+
}
133+
}
134+
if (draw) {
135+
console.log(text, y);
136+
ctx.fillText(text, x, y);
137+
}
138+
console.log(y + lineHeight);
139+
return y + lineHeight;
140+
}
141+
142+
private iconDraw = (): void => {
143+
const ctx = this.ctx;
144+
const img = this.options.user?.img;
145+
if (!img) {
146+
throw new Error("Img not set");
147+
}
148+
const w = 110 * this.scale;
149+
ctx.save();
150+
const x = 30 * this.scale;
151+
const y = 20 * this.scale;
152+
ctx.beginPath();
153+
ctx.arc(x + w / 2, y + w / 2, w / 2, 0, Math.PI * 2);
154+
ctx.closePath()
155+
ctx.clip();
156+
ctx.drawImage(img, x, y, w, w);
157+
ctx.restore();
158+
}
159+
160+
private profileDraw = (): void => {
161+
const ctx = this.ctx;
162+
ctx.fillStyle = this.options.themeOption?.txtColor ?? 'white';
163+
ctx.font = `${20 * this.scale}px 'sans-serif'`;
164+
ctx.globalAlpha = 0.7;
165+
console.log(this.options.user?.name);
166+
ctx.fillText(this.options.user?.name ?? 'error', 150 * this.scale, 45 * this.scale);
167+
ctx.globalAlpha = 1;
168+
ctx.font = `${50 * this.scale}px 'sans-serif'`;
169+
ctx.fillText(this.options.customPriceString ?? this.generatePriceString(this.options.price ?? 0), 150 * this.scale, 95 * this.scale);
170+
}
171+
}

0 commit comments

Comments
 (0)