fix(abut-rebar): 벽체 W9 ㄷ형 · W10 ㄴ형, 정착장 입력 (기본 330)
- 사용자 도면(2026-11-04): 끝마다 이웃 높낮이로 정함 — 높은 이웃 쪽은 경계 너머 정착장만큼 직선 겹침, 낮은 이웃 · 끝면 쪽은 경계 − c에서 연직 갈고리 정착장 - 번호는 모양으로: 양끝 갈고리 W9 (ㄷ), 한쪽 갈고리 W10 (ㄴ). A1: W9 6 (IP1 끝 구간), W10 24 - 벽체 카드에 "W9 · W10 정착장 (mm)" 입력 (기본 330, fck 연동 없음), 철근표 · 삽도 · 안내 문구 반영 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5e64a4f2ed
commit
3800a75b78
4 files changed
+69
-35
No files matched your search
+26
-13
@@ -2,7 +2,7 @@
|
||||
* 교대 벽체 배근 · AD4 (2026-11-04, 교대벽체철근.md, 결정 2026-11-02). 순수 함수, 길이는 mm.
|
||||
*
|
||||
* - W1 전면 연직 (cycle-1만) / W3 · W4 배면 연직 (cycle-1 / 2): 저판 격자 − (d_B13 + d_W)/2 (§4.3).
|
||||
* - W2 교좌면 상면 (교대축 직각, skew) / W9 · W10 계단 상면 (교대축 방향, 끝 계단 / 중간 계단).
|
||||
* - W2 교좌면 상면 (교대축 직각, skew) / W9 (ㄷ) · W10 (ㄴ) 계단 상면 (교대축 방향, 낮은 쪽 갈고리 · 높은 쪽 직선 겹침).
|
||||
* - W5 · W6 전면 · 배면 수평 (계단에서 끊음) / W7 간격재 / W8 끝면 수평 (ㄷ형) / W11 끝면 연직 (ㄷ형).
|
||||
* - 간격은 벽체 기본간격(s_L, s_W, s_v)에서 계산한다. 묶음별로 고치면 그 묶음만 연동이 끊긴다(RB-AB04).
|
||||
* - 좌표: u = 교대축을 따라 IP1 쪽 끝면에서 잰 skew 격자 위치 (끝면과 나란한 선이 u 일정), t = 교대축에서 전면 +,
|
||||
@@ -26,13 +26,15 @@ export interface AbutWallRebarSettings {
|
||||
/** W7 간격 배수 (교대축 / 연직), 정수 ≥ 1. */
|
||||
k7L: number;
|
||||
k7V: number;
|
||||
/** W9 · W10 정착장: 연직 갈고리 길이 = 높은 이웃 구간 철근과의 직선 겹침 길이 (사용자 도면 2026-11-04, 기본 330). */
|
||||
la: number;
|
||||
/** 묶음별 간격 수정 (없으면 기본간격에 연동): 연직 W1–W4 · W2 (교대축 실간격), 수평 W5 · W6 · W8 (연직), 계단 상면 W9 · W10 (폭). */
|
||||
sVert?: number;
|
||||
sHoriz?: number;
|
||||
sTop?: number;
|
||||
}
|
||||
|
||||
export const ABUT_WALL_REBAR_DEFAULT: AbutWallRebarSettings = { c: 100, eMin: 150, sL: 125, sW: 125, sV: 125, k7L: 8, k7V: 4 };
|
||||
export const ABUT_WALL_REBAR_DEFAULT: AbutWallRebarSettings = { c: 100, eMin: 150, sL: 125, sW: 125, sV: 125, k7L: 8, k7V: 4, la: 330 };
|
||||
|
||||
/** 규격 (기획서 §1.2). W11 H16은 제안값 (삽도에 규격 표기 없음). */
|
||||
export const ABUT_WALL_SIZES = {
|
||||
@@ -109,7 +111,7 @@ export function resolveWallRebar(changes?: Partial<AbutWallRebarSettings>): Abut
|
||||
/** Input problems in Korean (the edit refuses them). */
|
||||
export function wallRebarIssues(s: AbutWallRebarSettings): string[] {
|
||||
const out: string[] = [];
|
||||
for (const key of ['c', 'eMin', 'sL', 'sW', 'sV'] as const) if (!(s[key] > 0)) out.push(`${key}은(는) 0보다 커야 합니다.`);
|
||||
for (const key of ['c', 'eMin', 'sL', 'sW', 'sV', 'la'] as const) if (!(s[key] > 0)) out.push(`${key}은(는) 0보다 커야 합니다.`);
|
||||
for (const key of ['sVert', 'sHoriz', 'sTop'] as const) if (s[key] !== undefined && !((s[key] as number) > 0)) out.push('철근 간격은 0보다 커야 합니다.');
|
||||
if (!(Number.isInteger(s.k7L) && s.k7L >= 1 && Number.isInteger(s.k7V) && s.k7V >= 1)) out.push('W7 배수는 1 이상의 정수여야 합니다.');
|
||||
return out;
|
||||
@@ -157,8 +159,8 @@ export function abutWallRebar(g: AbutWallGeom, footing: AbutFootingRebar, change
|
||||
const sVert = s.sVert ?? s.sL;
|
||||
const sHoriz = s.sHoriz ?? s.sV;
|
||||
const sTop = s.sTop ?? 2 * s.sW;
|
||||
if (s.sVert !== undefined || s.sHoriz !== undefined || s.sTop !== undefined || s.k7L !== 8 || s.k7V !== 4) {
|
||||
add('RB-AB04', 'I', '철근 간격을 기본간격과 따로 고쳤거나 W7 배수가 기본값(8 × 4)이 아닙니다. [되돌리기]로 다시 연동합니다.');
|
||||
if (s.sVert !== undefined || s.sHoriz !== undefined || s.sTop !== undefined || s.k7L !== 8 || s.k7V !== 4 || s.la !== 330) {
|
||||
add('RB-AB04', 'I', '철근 간격을 기본간격과 따로 고쳤거나 W7 배수 · W9 · W10 정착장이 기본값(8 × 4 · 330)이 아닙니다. [되돌리기]로 다시 연동합니다.');
|
||||
}
|
||||
const fy = (size: BarSize) => fyOf(material, size);
|
||||
const ldOf = (size: BarSize) => Math.ceil(developmentLength(d(size), fy(size), material.fck) - 1e-9);
|
||||
@@ -242,14 +244,25 @@ export function abutWallRebar(g: AbutWallGeom, footing: AbutFootingRebar, change
|
||||
// 끝 여유 (§6.1): W5 · W6 · W9 · W10 끝은 W11 안쪽.
|
||||
const endIn = (size: BarSize) => (c + (d(SZ.W11) + d(size)) / 2) / sin;
|
||||
|
||||
// W9 · W10 (§5.2, 사용자 도면 2026-11-04): W2 바로 아래, 구간마다 독립된 ⊓형. 양끝은 계단 경계 − c (끝면 쪽은 W11 안쪽)에서
|
||||
// 아래로 l_d 꺾는다. 이웃 구간으로는 넘어가지 않는다.
|
||||
// W9 · W10 (§5.2, 사용자 도면 2026-11-04): W2 바로 아래, 구간마다 한 줄. 끝마다 이웃 높낮이로 정한다:
|
||||
// 높은 이웃 쪽 = 경계 너머 정착장 l_a만큼 직선 (그 구간 철근과 겹침), 낮은 이웃 · 끝면 쪽 = 경계 − c (끝면은 W11 안쪽)에서 연직 갈고리 l_a.
|
||||
// 양끝 갈고리 = W9 (ㄷ), 한쪽 갈고리 = W10 (ㄴ). 번호는 모양으로 정한다 (사용자 결정 2026-11-04).
|
||||
const la = s.la;
|
||||
segs.forEach((sg, k) => {
|
||||
const mark: WallMark = k === 0 || k === last ? 'W9' : 'W10';
|
||||
const z = g.HF + sg.H - c - (d(SZ.W2) + d(SZ.W9)) / 2;
|
||||
const a = k === 0 ? endIn(SZ.W9) : sg.from + c;
|
||||
const b = k === last ? g.L - endIn(SZ.W9) : sg.to - c;
|
||||
for (const x of xW9) put(mark, [[a, x, z - ld.H16], [a, x, z], [b, x, z], [b, x, z - ld.H16]], b - a + 2 * ld.H16, sg === one || mark === 'W10');
|
||||
const higher = (nb: (typeof segs)[number] | undefined) => nb !== undefined && nb.H >= sg.H;
|
||||
const lapA = higher(segs[k - 1]);
|
||||
const lapB = higher(segs[k + 1]);
|
||||
const a = lapA ? sg.from - la : k === 0 ? endIn(SZ.W9) : sg.from + c;
|
||||
const b = lapB ? sg.to + la : k === last ? g.L - endIn(SZ.W9) : sg.to - c;
|
||||
const hooks = (lapA ? 0 : 1) + (lapB ? 0 : 1);
|
||||
const mark: WallMark = hooks === 2 ? 'W9' : 'W10';
|
||||
for (const x of xW9) {
|
||||
const pts: [number, number, number][] = [[a, x, z], [b, x, z]];
|
||||
if (!lapA) pts.unshift([a, x, z - la]);
|
||||
if (!lapB) pts.push([b, x, z - la]);
|
||||
put(mark, pts, b - a + hooks * la, true);
|
||||
}
|
||||
});
|
||||
|
||||
// W5 · W6 (§5.3): 단 z_k = H_F + c + k·s_v, 들어가는 구간이 이어진 범위마다 한 줄. 계단 쪽 끝 = 경계 − c.
|
||||
@@ -334,8 +347,8 @@ export function abutWallRebar(g: AbutWallGeom, footing: AbutFootingRebar, change
|
||||
push({ mark: 'W7', size: SZ.W7, role: '간격재', spacing: `${lenText(s.k7L * sVert)} × ${lenText(s.k7V * sHoriz)}`, actual: '—', shape: `양끝 90° 갈고리 ${h13}`, position: `내측 간격재 · W3 선 ~ W5 선`, note: `${s.k7L}·s × ${s.k7V}·s · 철근표 H13 · skew 방향 (1/sin θ)`, basis: '규칙', modified: s.k7L !== 8 || s.k7V !== 4 });
|
||||
push({ mark: 'W8', size: SZ.W8, role: '수평', spacing: hSp, actual: hSp, shape: `ㄷ형 · 다리 l_d ${ld.H13}`, position: `양 끝면 (skew) · W11 안쪽 ${lenText(in8 * sin)}`, note: `낮은 끝 · 높은 끝 구간 높이로 단 수`, basis: '규칙', modified: hMod });
|
||||
const tSp = lenText(sTop);
|
||||
push({ mark: 'W9', size: SZ.W9, role: '계단 상면', spacing: tSp, actual: tSp, shape: `⊓ · 양끝 다리 l_d ${ld.H16} (경계 − ${lenText(c)})`, position: `끝 계단 상면 · 교대축 · 폭 ${xW9.length}개`, note: `W2 바로 아래 · 첫 줄 ${lenText(xW9[0] ?? 0)} (W1 선 피함)`, basis: '규칙', modified: tMod });
|
||||
push({ mark: 'W10', size: SZ.W10, role: '계단 상면', spacing: tSp, actual: tSp, shape: `⊓ · 양끝 다리 l_d ${ld.H16} (경계 − ${lenText(c)})`, position: `중간 계단 상면 · 교대축 · 폭 ${xW9.length}개`, note: '결정: W9 끝 계단 / W10 중간 계단', basis: '규칙', modified: tMod });
|
||||
push({ mark: 'W9', size: SZ.W9, role: '계단 상면', spacing: tSp, actual: tSp, shape: `ㄷ · 양끝 연직 갈고리 ${lenText(la)} (경계 − ${lenText(c)})`, position: `양쪽 이웃이 낮거나 끝면인 계단 상면 · 교대축 · 폭 ${xW9.length}개`, note: `W2 바로 아래 · 첫 줄 ${lenText(xW9[0] ?? 0)} (W1 선 피함) · 정착장 ${lenText(la)}`, basis: '규칙', modified: tMod || la !== 330 });
|
||||
push({ mark: 'W10', size: SZ.W10, role: '계단 상면', spacing: tSp, actual: tSp, shape: `ㄴ · 한쪽 연직 갈고리 ${lenText(la)} · 반대쪽 높은 구간 철근과 직선 겹침 ${lenText(la)}`, position: `높은 이웃이 있는 계단 상면 · 교대축 · 폭 ${xW9.length}개`, note: '번호는 모양으로: 양끝 갈고리 W9 (ㄷ) / 한쪽 갈고리 W10 (ㄴ)', basis: '규칙', modified: tMod || la !== 330 });
|
||||
push({ mark: 'W11', size: SZ.W11, role: '연직', spacing: `${lenText(s.sW)} / 300`, actual: '—', shape: `ㄷ형 · 아래 · 위 90° 갈고리 ${h16} (안쪽)`, position: `끝면 최외측 연직 · x 0 … ${lenText(xW5)} @${lenText(s.sW)} + ${xs11.map(lenText).join(' · ')}`, note: '규격 H16 제안 (삽도 표기 없음) · 흉벽 P11로 이어짐', basis: '가정', modified: false });
|
||||
|
||||
return {
|
||||
|
||||
+4
-2
@@ -3466,8 +3466,8 @@ function fillAbutWallRebarCard(box: HTMLDivElement, rec: AbutRecord, built: { ge
|
||||
caption:
|
||||
state.abutWallView === 'sec'
|
||||
? '교대축 직각 단면 · 구간 1 (IP3 끝) · 왼쪽 배면 → 오른쪽 전면 · 선 = 연직 · W2 · W7, 점 = W5 · W6 · W9 · W10'
|
||||
: '교대축 단면 · 전면 철근 · 왼쪽 IP3 = 구간 1 → 오른쪽 IP1 · W5는 계단에서 끊김, W9 · W10은 구간마다 ⊓ (양끝 경계 − 피복에서 다리 l_d)',
|
||||
memo: `벽체 높이 구간 ${[...geom.segments].sort((a, b) => a.no - b.no).map((sg) => `${sg.no} ${Math.round(sg.H)}`).join(' · ')} · 수평 W5는 높이가 들어가는 구간까지만 (위 계단 면 − ${lenText(s.c)}에서 끊음) · W9 · W10은 구간마다 ⊓, 양끝 다리 l_d ${r.ld.H16}`,
|
||||
: '교대축 단면 · 전면 철근 · 왼쪽 IP3 = 구간 1 → 오른쪽 IP1 · W5는 계단에서 끊김, W9 (ㄷ) · W10 (ㄴ)은 낮은 쪽 연직 갈고리 · 높은 쪽 직선 겹침',
|
||||
memo: `벽체 높이 구간 ${[...geom.segments].sort((a, b) => a.no - b.no).map((sg) => `${sg.no} ${Math.round(sg.H)}`).join(' · ')} · 수평 W5는 높이가 들어가는 구간까지만 (위 계단 면 − ${lenText(s.c)}에서 끊음) · W9 (ㄷ) · W10 (ㄴ): 낮은 이웃 · 끝면 쪽 연직 갈고리, 높은 이웃 쪽 직선 겹침, 정착장 ${lenText(s.la)}`,
|
||||
legendHtml: `<span style="color:${C.vertical}">━ 연직 (W1 · W3 · W4 · W11)</span> · <span style="color:${C.horizontal}">━ 수평 · 교좌면 (W2 · W5 · W6 · W8)</span> · <span style="color:${C.step}">━ 계단 상면 (W9 실선 · W10 파선)</span> · <span style="color:${C.tie}">┅ 간격재 W7</span>`,
|
||||
baseTitle: '기본간격 · 벽체 (부재별 입력)',
|
||||
base: [
|
||||
@@ -3479,6 +3479,8 @@ function fillAbutWallRebarCard(box: HTMLDivElement, rec: AbutRecord, built: { ge
|
||||
inputs: [
|
||||
['피복 (mm)', s.c, D.c, (v) => ({ c: v })],
|
||||
['첫 철근 최소 여유 (mm)', s.eMin, D.eMin, (v) => ({ eMin: v })],
|
||||
// 사용자 도면 2026-11-04: W9 · W10 갈고리 = 겹침 길이.
|
||||
['W9 · W10 정착장 (mm)', s.la, D.la, (v) => ({ la: v })],
|
||||
],
|
||||
pair: { label: '간격재 W7 배수 (교대축 × 연직)', value: [s.k7L, s.k7V], def: [D.k7L, D.k7V], sep: '×', error: 'W7 배수는 "8 × 4"처럼 두 정수로 입력하세요.', patch: (a, b) => ({ k7L: a, k7V: b }) },
|
||||
autos: [
|
||||
|
||||
@@ -113,7 +113,12 @@ export function abutWallElevationSvg(g: AbutWallGeom, r: AbutWallRebar, pick: Ab
|
||||
parts.push(text((X(0) + X(g.L)) / 2, Y(g.HF / 2), `저판 (연직철근 갈고리는 B6 · B8 위) · 전면 W1 x ${Math.round(front * 10) / 10}`, 'middle', MUTED, 10));
|
||||
parts.push(text(X(g.L), Y(0) + 14, 'IP3 (구간 1)', 'start', MUTED, 10), text(X(0), Y(0) + 14, 'IP1', 'end', MUTED, 10));
|
||||
const top = 20;
|
||||
parts.push(balloon(X(g.L * 0.85), top, 'W9', C.step, pick), balloon(X(g.L * 0.5), top, 'W10', C.step, pick));
|
||||
// W9 · W10 balloons over their own bars (the mark follows the shape: ㄷ / ㄴ).
|
||||
const midOf = (mark: WallMark, fallback: number) => {
|
||||
const p = r.paths.find((q) => q.mark === mark);
|
||||
return p ? (p.pts[0][0] + p.pts[p.pts.length - 1][0]) / 2 : fallback;
|
||||
};
|
||||
parts.push(balloon(X(midOf('W9', g.L * 0.85)), top, 'W9', C.step, pick), balloon(X(midOf('W10', g.L * 0.5)), top, 'W10', C.step, pick));
|
||||
parts.push(balloon(X(g.L * 0.3), top, 'W1', C.vertical, pick), balloon(X(g.L * 0.65), top, 'W5', C.horizontal, pick));
|
||||
parts.push(balloon(X(0) + 34, Y(g.HF + 1200), 'W11', C.vertical, pick), balloon(X(0) + 34, Y(g.HF + 600), 'W8', C.horizontal, pick));
|
||||
const W = X(0) + 60;
|
||||
|
||||
@@ -67,33 +67,47 @@ describe('교대 벽체 배근 A1 (AD4, 교대벽체철근.md §11)', () => {
|
||||
expect([lv(5).count, lv(5).text]).toEqual([20, '19@125=2.375 · 181']);
|
||||
});
|
||||
|
||||
it('W9 · W10: 5구간 → W9 2구간 × 6, W10 3구간 × 6; 구간 수에 따라 W9만 / W10 (m − 2)', () => {
|
||||
expect(bar(A1, 'W9').count).toBe(12);
|
||||
expect(bar(A1, 'W10').count).toBe(18);
|
||||
it('W9 · W10 (A1, IP1 쪽으로 높아지는 5구간): 가장 높은 IP1 끝 구간만 ㄷ (W9 6개), 나머지 4구간은 ㄴ (W10 24개)', () => {
|
||||
expect(bar(A1, 'W9').count).toBe(6);
|
||||
expect(bar(A1, 'W10').count).toBe(24);
|
||||
const withSegs = (n: number): AbutWallGeom => {
|
||||
const step = geom.L / n;
|
||||
return { ...geom, segments: Array.from({ length: n }, (_, i) => ({ from: i * step, to: (i + 1) * step, H: 2372 + 50 * i, no: n - i })) };
|
||||
};
|
||||
const marks = (n: number) => abutWallRebar(withSegs(n), footing, undefined, fck27).bars.map((b) => b.mark);
|
||||
expect(marks(1)).toContain('W9');
|
||||
expect(marks(1)).not.toContain('W10');
|
||||
expect(marks(2)).not.toContain('W10');
|
||||
expect(bar(abutWallRebar(withSegs(4), footing, undefined, fck27), 'W10').count).toBe(12);
|
||||
const count = (n: number, mark: string) => abutWallRebar(withSegs(n), footing, undefined, fck27).bars.find((b) => b.mark === mark)?.count ?? 0;
|
||||
expect([count(1, 'W9'), count(1, 'W10')]).toEqual([6, 0]); // 계단 없음: 양끝 끝면 → ㄷ
|
||||
expect([count(2, 'W9'), count(2, 'W10')]).toEqual([6, 6]);
|
||||
expect([count(4, 'W9'), count(4, 'W10')]).toEqual([6, 18]);
|
||||
});
|
||||
|
||||
it('W9 · W10: ⊓ per step, ends at the boundary − 100 (끝면 쪽은 W11 안쪽), legs down l_d 735 (사용자 도면 2026-11-04)', () => {
|
||||
const k = 2; // a middle step
|
||||
it('W9 ㄷ: 양끝 연직 갈고리 330 (끝면 쪽 W11 안쪽 · 낮은 쪽 경계 − 100); W10 ㄴ: 낮은 쪽 갈고리 330 · 높은 쪽 경계 너머 330 직선 겹침 (사용자 도면 2026-11-04)', () => {
|
||||
const sg0 = geom.segments[0]; // IP1 끝, 가장 높음
|
||||
const z0 = 1100 + sg0.H - 100 - 15.9;
|
||||
const w9 = A1.paths.find((q) => q.mark === 'W9')!;
|
||||
expect(w9.pts.length).toBe(4);
|
||||
expect(w9.pts[1][0]).toBeCloseTo((100 + 15.9) / angle.sin, 6);
|
||||
expect(w9.pts[2][0]).toBeCloseTo(sg0.to - 100, 6);
|
||||
expect(w9.pts[0][2]).toBeCloseTo(z0 - 330, 6);
|
||||
expect(w9.pts[3][2]).toBeCloseTo(z0 - 330, 6);
|
||||
const k = 2; // 중간 구간: IP1 쪽 이웃이 높고, IP3 쪽 이웃이 낮음
|
||||
const sg = geom.segments[k];
|
||||
const z = 1100 + sg.H - 100 - 15.9;
|
||||
const p = A1.paths.find((q) => q.mark === 'W10' && Math.abs(q.pts[1][2] - z) < 1e-6)!;
|
||||
expect(p.pts.map((q) => Math.round(q[0] * 10) / 10)).toEqual([sg.from + 100, sg.from + 100, sg.to - 100, sg.to - 100].map((v) => Math.round(v * 10) / 10));
|
||||
expect(p.pts[0][2]).toBeCloseTo(z - 735, 6);
|
||||
expect(p.pts[3][2]).toBeCloseTo(z - 735, 6);
|
||||
const w = A1.bars.find((b) => b.mark === 'W10')!;
|
||||
expect(w.length).toBe(Math.round(sg.to - sg.from - 200 + 2 * 735));
|
||||
// 끝 계단: 끝면 쪽은 W11 안쪽 (100 + (15.9 + 15.9)/2) / sin θ.
|
||||
const w9 = A1.paths.find((q) => q.mark === 'W9')!;
|
||||
expect(w9.pts[1][0]).toBeCloseTo((100 + 15.9) / angle.sin, 6);
|
||||
const p = A1.paths.find((q) => q.mark === 'W10' && Math.abs(q.pts[0][2] - z) < 1e-6)!;
|
||||
expect(p.pts.length).toBe(3); // ㄴ: 직선 겹침 끝 + 갈고리 끝
|
||||
expect(p.pts[0][0]).toBeCloseTo(sg.from - 330, 6);
|
||||
expect(p.pts[1][0]).toBeCloseTo(sg.to - 100, 6);
|
||||
expect(p.pts[2][2]).toBeCloseTo(z - 330, 6);
|
||||
expect(bar(A1, 'W10').count).toBe(24);
|
||||
const w10 = A1.bars.find((b) => b.mark === 'W10')!;
|
||||
expect([w10.length, w10.lengthMax].every((v) => v > 0)).toBe(true);
|
||||
});
|
||||
|
||||
it('정착장 입력: 400으로 바꾸면 갈고리 · 겹침이 함께 400, RB-AB04 "수정됨"', () => {
|
||||
const r = abutWallRebar(geom, footing, { la: 400 }, fck27);
|
||||
const p = r.paths.find((q) => q.mark === 'W9')!;
|
||||
expect(p.pts[1][2] - p.pts[0][2]).toBeCloseTo(400, 6);
|
||||
expect(r.issues.map((i) => i.code)).toContain('RB-AB04');
|
||||
expect(bar(r, 'W9').modified).toBe(true);
|
||||
});
|
||||
|
||||
it('W7: 12 × 5 = 60, 2200 / sin θ + 2 × 197 = 2603 (skew, 사용자 결정 2026-11-04)', () => {
|
||||
|
||||
Reference in new issue
Block a user