Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ba1d9df8d | |||
| 56e9b4e97a | |||
| 134f813073 | |||
| 9e2461b85a | |||
| ac43ddb55e | |||
| 2ce0b81483 | |||
| 2003e58ab1 | |||
| 151042ab81 | |||
| 7677007b49 | |||
| 1047d3089d | |||
| 24814f3c90 | |||
| faa7d4d0a9 | |||
| 37ca611675 | |||
| 63d0d383b0 | |||
| 19f99989b8 | |||
| 660d4b8136 | |||
| 4573d1d5fa | |||
| 16fc088319 | |||
| cd0e139b5e | |||
| ce1aee67d6 | |||
| 76f5b572e8 | |||
| 4a3a24e327 | |||
| 12a3711c0e | |||
| eaffea6ff6 | |||
| 019b404336 | |||
| f4123b158d | |||
| 84240f57a7 | |||
| 3f05073a5b | |||
| 561400843c | |||
| 694fed1b19 | |||
| 88b582a280 | |||
| a5f2544d11 | |||
| 107abc963f | |||
| 6033d59590 | |||
| ed8c7eacba | |||
| a996def67a | |||
| 69ca0d5ccf | |||
| f5352a945e | |||
| 1fe58b7b2c | |||
| c1004fe2b3 | |||
| 00a5507b68 | |||
| 06ce4c6634 | |||
| 9e7a978226 | |||
| 88389a5487 | |||
| 0bcc6dab3d | |||
| 1cd0b8cb37 | |||
| 4520c9445c | |||
| 73b6439702 | |||
| 01aa5dfe66 | |||
| 96c55f0a64 | |||
| 7b0b109fb4 | |||
| 5d2b372c14 | |||
| 9381d3fc0f | |||
| 4ce6b4dd68 | |||
| 32267893d5 | |||
| caddf36cf2 | |||
| ecbcaded1b | |||
| e1662130a5 | |||
| e150cea065 |
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
description: 개발방법론 사용
|
||||||
|
---
|
||||||
|
|
||||||
|
## 작업을 시작하기전에 해야할 일
|
||||||
|
1. 열려있는 모든 plan.md 파일을 확인하고, 항상 프로젝트 메인 플랜(/plan.md)에 내용을 병합해줘.
|
||||||
|
2. 작업이 시작되기 전에 메인 플랜에 Current Goal을 수정해서 플랜을 업데이트 해.
|
||||||
|
|
||||||
|
## 작업 방식
|
||||||
|
1. 작업을 시작하기 전에 해야할 일이 정상적으로 수행했는지 확인해.
|
||||||
|
2. 메인 플랜에 내용이 병합되었는지 직접 확인해.
|
||||||
|
3. 메인 플랜에 모든 플랜 내용이 병합되지 않았으면 작업을 중단하고 사용자에게 물어봐.
|
||||||
|
4. 사용자가 플랜을 확인할 수 있게 메인플랜에 업데이트를 지속적으로 수행 해.
|
||||||
|
5. TDD 개발 방법론을 항상 준수해.
|
||||||
|
6. 한개 작업이 끝나면 메인 플랜을 무조건 갱신해.
|
||||||
|
7. 린트 오류가 나면 넘어가지말고 즉시 수정해
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
# 볼링 매니저 (Bowling Manager)
|
||||||
|
|
||||||
|
볼링 매니저는 볼링장 운영자와 클럽 관리자를 위한 종합 관리 시스템입니다. 회원 관리, 이벤트 일정 관리, 점수 기록 및 통계, 구독 기반 프리미엄 기능을 제공합니다.
|
||||||
|
|
||||||
|
## 프로젝트 구조
|
||||||
|
|
||||||
|
이 프로젝트는 세 가지 주요 구성 요소로 이루어져 있습니다:
|
||||||
|
|
||||||
|
### 1. 백엔드 (Node.js/Express)
|
||||||
|
|
||||||
|
`/backend` 디렉토리에 위치한 서버 측 코드입니다.
|
||||||
|
|
||||||
|
- **기술 스택**: Node.js, Express, Sequelize ORM, MySQL
|
||||||
|
- **주요 기능**:
|
||||||
|
- RESTful API 제공
|
||||||
|
- 데이터베이스 관리
|
||||||
|
- 인증 및 권한 관리
|
||||||
|
- 구독 및 결제 처리
|
||||||
|
- 영수증 검증
|
||||||
|
|
||||||
|
### 2. 프론트엔드 (Vue.js)
|
||||||
|
|
||||||
|
`/frontend` 디렉토리에 위치한 웹 클라이언트 코드입니다.
|
||||||
|
|
||||||
|
- **기술 스택**: Vue 3, Vite, Vuex, Vue Router
|
||||||
|
- **주요 기능**:
|
||||||
|
- 관리자 대시보드
|
||||||
|
- 회원 및 이벤트 관리
|
||||||
|
- 통계 및 보고서
|
||||||
|
- 구독 관리
|
||||||
|
|
||||||
|
### 3. 모바일 앱 (Flutter)
|
||||||
|
|
||||||
|
`/mobile` 디렉토리에 위치한 크로스 플랫폼 모바일 앱 코드입니다.
|
||||||
|
|
||||||
|
- **기술 스택**: Flutter, Dart, Provider 상태 관리
|
||||||
|
- **주요 기능**:
|
||||||
|
- 회원 관리
|
||||||
|
- 이벤트 및 일정 관리
|
||||||
|
- 점수 기록 및 통계
|
||||||
|
- 인앱 결제 및 구독
|
||||||
|
- 오프라인 지원
|
||||||
|
|
||||||
|
## 설치 및 실행
|
||||||
|
|
||||||
|
### 백엔드
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
npm install
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
### 프론트엔드
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### 모바일 앱
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd mobile
|
||||||
|
flutter pub get
|
||||||
|
flutter run
|
||||||
|
```
|
||||||
|
|
||||||
|
## 구독 및 인앱 결제 기능
|
||||||
|
|
||||||
|
볼링 매니저는 다양한 구독 플랜을 제공합니다:
|
||||||
|
|
||||||
|
- **무료 플랜**: 기본 기능, 제한된 회원 및 이벤트 관리
|
||||||
|
- **기본 플랜**: 확장된 회원 및 이벤트 관리, 기본 통계
|
||||||
|
- **프리미엄 플랜**: 고급 통계, 커스터마이징 옵션
|
||||||
|
- **프로 플랜**: 모든 기능 무제한 이용, 우선 지원
|
||||||
|
|
||||||
|
구독 관련 문서:
|
||||||
|
- [구독 및 인앱 결제 요구사항 및 사용자 플로우](/docs/subscription_requirements_flow.md)
|
||||||
|
- [구독 및 인앱 결제 QA 체크리스트](/docs/subscription_qa_checklist.md)
|
||||||
|
- [배포 체크리스트](/docs/deployment_checklist.md)
|
||||||
|
|
||||||
|
## 개발 가이드
|
||||||
|
|
||||||
|
### 백엔드 API
|
||||||
|
|
||||||
|
백엔드 API는 RESTful 원칙을 따르며, JWT 기반 인증을 사용합니다. 주요 엔드포인트:
|
||||||
|
|
||||||
|
- `/api/auth`: 인증 관련 엔드포인트
|
||||||
|
- `/api/clubs`: 클럽 관리 엔드포인트
|
||||||
|
- `/api/members`: 회원 관리 엔드포인트
|
||||||
|
- `/api/events`: 이벤트 관리 엔드포인트
|
||||||
|
- `/api/scores`: 점수 관리 엔드포인트
|
||||||
|
- `/api/subscriptions`: 구독 관리 엔드포인트
|
||||||
|
|
||||||
|
### 모바일 앱 구조
|
||||||
|
|
||||||
|
모바일 앱은 다음과 같은 구조로 구성되어 있습니다:
|
||||||
|
|
||||||
|
- `lib/models`: 데이터 모델 클래스
|
||||||
|
- `lib/services`: API 통신 및 비즈니스 로직
|
||||||
|
- `lib/screens`: UI 화면
|
||||||
|
- `lib/widgets`: 재사용 가능한 UI 구성 요소
|
||||||
|
- `lib/utils`: 유틸리티 함수 및 상수
|
||||||
|
|
||||||
|
## 라이선스
|
||||||
|
|
||||||
|
이 프로젝트는 MIT 라이선스 하에 배포됩니다. 자세한 내용은 [LICENSE](LICENSE) 파일을 참조하세요.
|
||||||
|
|
||||||
|
## 기여
|
||||||
|
|
||||||
|
버그 신고, 기능 요청 및 풀 리퀘스트는 환영합니다. 기여하기 전에 프로젝트 관리자에게 문의하세요.
|
||||||
@@ -34,7 +34,7 @@ function getDomain(origin) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allowedDomains = ['lanebow.com', 'localhost'];
|
const allowedDomains = ['lanebow.com', 'www.lanebow.com', 'localhost'];
|
||||||
|
|
||||||
app.use(cors({
|
app.use(cors({
|
||||||
origin: function(origin, callback) {
|
origin: function(origin, callback) {
|
||||||
@@ -159,18 +159,17 @@ app.use('/api/users', userRoutes);
|
|||||||
app.use('/api/notifications', notificationRoutes);
|
app.use('/api/notifications', notificationRoutes);
|
||||||
app.use('/api/menus', menuRoutes);
|
app.use('/api/menus', menuRoutes);
|
||||||
|
|
||||||
// 정적 파일 제공 설정
|
// 정적 파일 제공 설정 (Flutter Web 빌드 산출물: mobile/build/web)
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
app.use(express.static(path.join(__dirname, '../frontend/dist')));
|
const WEB_ROOT = process.env.FRONTEND_ROOT || path.join(__dirname, '../mobile/build/web');
|
||||||
|
app.use(express.static(WEB_ROOT));
|
||||||
|
|
||||||
// API 요청이 아닌 모든 요청은 Vue 앱으로 라우팅
|
// API 요청이 아닌 모든 요청은 Flutter Web 앱으로 라우팅 (SPA Fallback)
|
||||||
app.get('*', (req, res, next) => {
|
app.get('*', (req, res, next) => {
|
||||||
// API 요청은 무시하고 다음 미들웨어로 전달
|
|
||||||
if (req.path.startsWith('/api/')) {
|
if (req.path.startsWith('/api/')) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
// Vue 앱의 index.html 반환
|
res.sendFile(path.join(WEB_ROOT, 'index.html'));
|
||||||
res.sendFile(path.join(__dirname, '../frontend/dist/index.html'));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 서버 시작
|
// 서버 시작
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const EventParticipant = require('../models/EventParticipant');
|
|||||||
const EventScore = require('../models/EventScore');
|
const EventScore = require('../models/EventScore');
|
||||||
const { Op } = require('sequelize');
|
const { Op } = require('sequelize');
|
||||||
const averageService = require('../services/averageService');
|
const averageService = require('../services/averageService');
|
||||||
|
const { sequelize } = require('../models/index');
|
||||||
|
|
||||||
// 관리자 권한 체크 함수
|
// 관리자 권한 체크 함수
|
||||||
const isAdmin = (user) => {
|
const isAdmin = (user) => {
|
||||||
@@ -229,15 +230,6 @@ exports.updateClub = async (req, res) => {
|
|||||||
newOwnerId = ownerId;
|
newOwnerId = ownerId;
|
||||||
ownerChanged = true;
|
ownerChanged = true;
|
||||||
}
|
}
|
||||||
// 이메일을 통한 모임장 변경 (기존 기능 유지)
|
|
||||||
else if (ownerEmail && ownerEmail !== club.ownerEmail) {
|
|
||||||
const newOwner = await User.findOne({ where: { email: ownerEmail } });
|
|
||||||
if (!newOwner) {
|
|
||||||
return res.status(404).json({ message: '해당 이메일로 등록된 사용자를 찾을 수 없습니다.' });
|
|
||||||
}
|
|
||||||
newOwnerId = newOwner.id;
|
|
||||||
ownerChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 클럽 정보 업데이트
|
// 클럽 정보 업데이트
|
||||||
await club.update({
|
await club.update({
|
||||||
@@ -434,7 +426,7 @@ exports.addClubMember = async (req, res) => {
|
|||||||
// 클럽 회원 정보 수정
|
// 클럽 회원 정보 수정
|
||||||
exports.updateClubMember = async (req, res) => {
|
exports.updateClubMember = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { clubId, memberId, name, gender, memberType, email, phone, handicap, status } = req.body;
|
const { clubId, memberId, name, gender, memberType, email, phone, handicap, status, joinDate } = req.body;
|
||||||
|
|
||||||
if (!clubId || !memberId) {
|
if (!clubId || !memberId) {
|
||||||
return res.status(400).json({ message: '잘못된 클럽 또는 회원 ID입니다.' });
|
return res.status(400).json({ message: '잘못된 클럽 또는 회원 ID입니다.' });
|
||||||
@@ -458,13 +450,14 @@ exports.updateClubMember = async (req, res) => {
|
|||||||
|
|
||||||
// 회원 정보 업데이트 (모임장 유지 또는 다른 유형으로 변경만 허용)
|
// 회원 정보 업데이트 (모임장 유지 또는 다른 유형으로 변경만 허용)
|
||||||
await member.update({
|
await member.update({
|
||||||
name: name || member.name,
|
name: name !== undefined ? name : member.name,
|
||||||
gender: gender || member.gender,
|
gender: gender !== undefined ? gender : member.gender,
|
||||||
memberType: (member.memberType === 'owner' && memberType !== 'owner') ? 'owner' : memberType || member.memberType,
|
memberType: (member.memberType === 'owner' && memberType !== 'owner') ? 'owner' : (memberType !== undefined ? memberType : member.memberType),
|
||||||
email: email || member.email,
|
email: email !== undefined ? email : member.email,
|
||||||
phone: phone || member.phone,
|
phone: phone !== undefined ? phone : member.phone,
|
||||||
handicap: handicap || member.handicap,
|
handicap: handicap !== undefined ? handicap : member.handicap,
|
||||||
status: status || member.status
|
status: status !== undefined ? status : member.status,
|
||||||
|
joinDate: joinDate !== undefined ? joinDate : member.joinDate
|
||||||
});
|
});
|
||||||
|
|
||||||
// 클럽의 회원 수 업데이트
|
// 클럽의 회원 수 업데이트
|
||||||
|
|||||||
@@ -159,7 +159,9 @@ exports.createEvent = async (req, res) => {
|
|||||||
location,
|
location,
|
||||||
maxParticipants,
|
maxParticipants,
|
||||||
eventType,
|
eventType,
|
||||||
|
participantFee,
|
||||||
entryFee,
|
entryFee,
|
||||||
|
fee,
|
||||||
gameCount,
|
gameCount,
|
||||||
laneCount,
|
laneCount,
|
||||||
status
|
status
|
||||||
@@ -196,6 +198,23 @@ exports.createEvent = async (req, res) => {
|
|||||||
const publicHash = await generateRandomHash();
|
const publicHash = await generateRandomHash();
|
||||||
const accessPassword = req.body.accessPassword || null;
|
const accessPassword = req.body.accessPassword || null;
|
||||||
|
|
||||||
|
// 금액 통합: 문자열("20000.00") 또는 숫자를 허용, 0은 유효값(무료), 빈값은 null 처리
|
||||||
|
const hasFeeField = Object.prototype.hasOwnProperty.call(req.body, 'participantFee')
|
||||||
|
|| Object.prototype.hasOwnProperty.call(req.body, 'entryFee')
|
||||||
|
|| Object.prototype.hasOwnProperty.call(req.body, 'fee');
|
||||||
|
let feeParsed = null;
|
||||||
|
if (hasFeeField) {
|
||||||
|
const feeRaw = fee ?? entryFee ?? participantFee;
|
||||||
|
if (feeRaw === undefined || feeRaw === null || `${feeRaw}`.trim() === '') {
|
||||||
|
feeParsed = null; // 명시적 빈값 => null 저장
|
||||||
|
} else {
|
||||||
|
const num = parseFloat(`${feeRaw}`);
|
||||||
|
feeParsed = Number.isFinite(num) ? num : null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
feeParsed = null; // 생성 시 미제공이면 null 유지
|
||||||
|
}
|
||||||
|
|
||||||
const event = await Event.create({
|
const event = await Event.create({
|
||||||
clubId,
|
clubId,
|
||||||
title,
|
title,
|
||||||
@@ -204,11 +223,11 @@ exports.createEvent = async (req, res) => {
|
|||||||
endDate: endDateVal,
|
endDate: endDateVal,
|
||||||
location,
|
location,
|
||||||
maxParticipants: maxParticipants || 0,
|
maxParticipants: maxParticipants || 0,
|
||||||
eventType: eventType || '일반',
|
eventType: eventType || 'regular',
|
||||||
entryFee: entryFee || 0,
|
participantFee: feeParsed, // allowNull=true이므로 null 저장 허용
|
||||||
gameCount: gameCount || 3,
|
gameCount: gameCount || 3,
|
||||||
laneCount: laneCount || 1,
|
laneCount: laneCount || 1,
|
||||||
status: status || '활성',
|
status: status || 'active',
|
||||||
publicHash,
|
publicHash,
|
||||||
accessPassword
|
accessPassword
|
||||||
});
|
});
|
||||||
@@ -252,7 +271,7 @@ exports.getEventByPublicHash = async (req, res) => {
|
|||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
if (!event) return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
if (!event) return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
||||||
if (event.status === '취소') return res.status(403).json({ message: '취소된 이벤트입니다.' });
|
if (event.status === 'canceled') return res.status(403).json({ message: '취소된 이벤트입니다.' });
|
||||||
|
|
||||||
// 2. 비밀번호 필요 여부 및 인증
|
// 2. 비밀번호 필요 여부 및 인증
|
||||||
if (event.accessPassword) {
|
if (event.accessPassword) {
|
||||||
@@ -287,6 +306,7 @@ exports.getEventByPublicHash = async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
console.log('allMembersWithAverage', allMembersWithAverage);
|
||||||
|
|
||||||
// 4. 참가자 명단 추출 (참가자 테이블 + Member 조인)
|
// 4. 참가자 명단 추출 (참가자 테이블 + Member 조인)
|
||||||
const participants = await EventParticipant.findAll({
|
const participants = await EventParticipant.findAll({
|
||||||
@@ -567,10 +587,13 @@ exports.updateEvent = async (req, res) => {
|
|||||||
location,
|
location,
|
||||||
maxParticipants,
|
maxParticipants,
|
||||||
eventType,
|
eventType,
|
||||||
|
participantFee,
|
||||||
entryFee,
|
entryFee,
|
||||||
|
fee,
|
||||||
gameCount,
|
gameCount,
|
||||||
laneCount,
|
laneCount,
|
||||||
status,
|
status,
|
||||||
|
registrationDeadline,
|
||||||
publicHash,
|
publicHash,
|
||||||
accessPassword
|
accessPassword
|
||||||
} = req.body;
|
} = req.body;
|
||||||
@@ -595,6 +618,21 @@ exports.updateEvent = async (req, res) => {
|
|||||||
if(endDate == 'Invalid Date') {
|
if(endDate == 'Invalid Date') {
|
||||||
endDate = null;
|
endDate = null;
|
||||||
}
|
}
|
||||||
|
// 금액 통합: 문자열("20000.00") 또는 숫자를 허용, 0은 유효값(무료), 빈값은 null 처리
|
||||||
|
const hasFeeField = Object.prototype.hasOwnProperty.call(req.body, 'participantFee')
|
||||||
|
|| Object.prototype.hasOwnProperty.call(req.body, 'entryFee')
|
||||||
|
|| Object.prototype.hasOwnProperty.call(req.body, 'fee');
|
||||||
|
let feeParsed = undefined; // 업데이트에서 undefined면 필드 미변경
|
||||||
|
if (hasFeeField) {
|
||||||
|
const feeRaw = fee ?? entryFee ?? participantFee;
|
||||||
|
if (feeRaw === undefined || feeRaw === null || `${feeRaw}`.trim() === '') {
|
||||||
|
feeParsed = null; // 명시적 빈값 => null로 클리어
|
||||||
|
} else {
|
||||||
|
const num = parseFloat(`${feeRaw}`);
|
||||||
|
feeParsed = Number.isFinite(num) ? num : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const updateData = {
|
const updateData = {
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
@@ -602,11 +640,12 @@ exports.updateEvent = async (req, res) => {
|
|||||||
endDate: endDate || null,
|
endDate: endDate || null,
|
||||||
location,
|
location,
|
||||||
maxParticipants: maxParticipants || event.maxParticipants,
|
maxParticipants: maxParticipants || event.maxParticipants,
|
||||||
eventType: eventType || event.eventType,
|
eventType: eventType || event.eventType || 'regular',
|
||||||
entryFee: entryFee !== undefined ? entryFee : event.entryFee,
|
...(feeParsed !== undefined ? { participantFee: feeParsed } : {}),
|
||||||
gameCount: gameCount || event.gameCount,
|
gameCount: gameCount || event.gameCount,
|
||||||
laneCount: laneCount || event.laneCount,
|
laneCount: laneCount || event.laneCount,
|
||||||
status: status || event.status,
|
status: status || event.status || 'active',
|
||||||
|
registrationDeadline: registrationDeadline || event.registrationDeadline,
|
||||||
publicHash: publicHash || event.publicHash,
|
publicHash: publicHash || event.publicHash,
|
||||||
accessPassword: accessPassword || null
|
accessPassword: accessPassword || null
|
||||||
};
|
};
|
||||||
@@ -648,7 +687,7 @@ exports.updateEvent = async (req, res) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 이벤트 삭제 (소프트 삭제)
|
// 이벤트 삭제 (소프트 삭제: 숨김 처리)
|
||||||
exports.deleteEvent = async (req, res) => {
|
exports.deleteEvent = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const eventId = req.params.id;
|
const eventId = req.params.id;
|
||||||
@@ -677,9 +716,9 @@ exports.deleteEvent = async (req, res) => {
|
|||||||
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 참가자 정보도 함께 삭제 처리
|
// 참가자 소프트 삭제(취소 상태로 표시)
|
||||||
await EventParticipant.update(
|
await EventParticipant.update(
|
||||||
{ status: '취소' },
|
{ status: 'canceled' },
|
||||||
{
|
{
|
||||||
where: {
|
where: {
|
||||||
eventId,
|
eventId,
|
||||||
@@ -688,10 +727,11 @@ exports.deleteEvent = async (req, res) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
await event.update({ status: '삭제' });
|
// 이벤트 숨김 처리(표준 상태값 사용)
|
||||||
|
await event.update({ status: 'deleted' });
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
message: '이벤트가 삭제되었습니다.',
|
message: '이벤트가 숨김(소프트 삭제)되었습니다.',
|
||||||
event: {
|
event: {
|
||||||
id: event.id,
|
id: event.id,
|
||||||
title: event.title,
|
title: event.title,
|
||||||
@@ -705,6 +745,50 @@ exports.deleteEvent = async (req, res) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 이벤트 완전 삭제 (하드 삭제): 팀배정, 점수, 참가자, 팀, 이벤트 순서로 물리 삭제
|
||||||
|
exports.deleteEventPermanently = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const eventId = Number(req.params.id);
|
||||||
|
const { clubId } = req.body || {};
|
||||||
|
const cid = Number(clubId);
|
||||||
|
|
||||||
|
if (!eventId || !cid) {
|
||||||
|
return res.status(400).json({ message: '이벤트 ID와 클럽 ID가 필요합니다.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
await sequelize.transaction(async (t) => {
|
||||||
|
const event = await Event.findOne({ where: { id: eventId, clubId: cid }, transaction: t });
|
||||||
|
if (!event) {
|
||||||
|
throw new Error('이벤트를 찾을 수 없습니다.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 팀멤버 삭제
|
||||||
|
const teams = await EventTeam.findAll({ where: { eventId }, transaction: t });
|
||||||
|
const teamIds = teams.map((tm) => tm.id);
|
||||||
|
if (teamIds.length > 0) {
|
||||||
|
await EventTeamMember.destroy({ where: { eventTeamId: teamIds }, transaction: t });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 점수 삭제
|
||||||
|
await EventScore.destroy({ where: { eventId }, transaction: t });
|
||||||
|
|
||||||
|
// 참가자 삭제
|
||||||
|
await EventParticipant.destroy({ where: { eventId }, transaction: t });
|
||||||
|
|
||||||
|
// 팀 삭제
|
||||||
|
await EventTeam.destroy({ where: { eventId }, transaction: t });
|
||||||
|
|
||||||
|
// 이벤트 삭제
|
||||||
|
await Event.destroy({ where: { id: eventId, clubId: cid }, transaction: t });
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({ message: '이벤트 및 관련 데이터가 완전히 삭제되었습니다.' });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('이벤트 완전 삭제 오류:', error);
|
||||||
|
res.status(500).json({ message: '이벤트 완전 삭제 중 오류가 발생했습니다.' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 이벤트 점수 조회
|
// 이벤트 점수 조회
|
||||||
exports.getEventScores = async (req, res) => {
|
exports.getEventScores = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@@ -936,8 +1020,27 @@ exports.updateEventScore = async (req, res) => {
|
|||||||
totalScore: score + handicap
|
totalScore: score + handicap
|
||||||
});
|
});
|
||||||
|
|
||||||
// 캐시 무효화 추가
|
// 캐시 무효화: participantId -> memberId로 변환 후 무효화 및 워밍업
|
||||||
await averageService.invalidateMemberAverageCache(scoreRecord.participantId);
|
try {
|
||||||
|
const ep = await EventParticipant.findOne({
|
||||||
|
where: { id: scoreRecord.participantId },
|
||||||
|
attributes: ['memberId'],
|
||||||
|
});
|
||||||
|
if (ep && ep.memberId) {
|
||||||
|
await averageService.invalidateMemberAverageCache(ep.memberId);
|
||||||
|
// clubId 조회 후 워밍업
|
||||||
|
try {
|
||||||
|
const ev = await Event.findOne({ where: { id: eventId }, attributes: ['clubId'] });
|
||||||
|
if (ev && ev.clubId) {
|
||||||
|
await averageService.calculateMemberAverageByClubSetting(ep.memberId, ev.clubId);
|
||||||
|
}
|
||||||
|
} catch (e2) {
|
||||||
|
console.error('에버리지 워밍업 실패(updateEventScore):', e2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('에버리지 캐시 무효화 실패(updateEventScore):', e);
|
||||||
|
}
|
||||||
|
|
||||||
// 업데이트된 레코드 조회
|
// 업데이트된 레코드 조회
|
||||||
const updatedScore = await EventScore.findOne({
|
const updatedScore = await EventScore.findOne({
|
||||||
@@ -1009,15 +1112,24 @@ exports.deleteEventScore = async (req, res) => {
|
|||||||
|
|
||||||
await score.destroy();
|
await score.destroy();
|
||||||
|
|
||||||
// 점수 삭제 시 해당 회원의 에버리지 캡시 무효화
|
// 점수 삭제 시 해당 회원의 에버리지 캐시 무효화 및 워밍업
|
||||||
if (score.participant && score.participant.Member) {
|
|
||||||
try {
|
try {
|
||||||
await averageService.invalidateMemberAverageCache(score.participant.Member.id);
|
const memberId = score.EventParticipant?.Member?.id;
|
||||||
console.log(`회원 ${score.participant.Member.id}의 에버리지 캡시 무효화 완료`);
|
if (memberId) {
|
||||||
} catch (error) {
|
await averageService.invalidateMemberAverageCache(memberId);
|
||||||
console.error(`회원 ${score.participant.Member.id}의 에버리지 캡시 무효화 오류:`, error);
|
console.log(`회원 ${memberId}의 에버리지 캐시 무효화 완료`);
|
||||||
|
// clubId는 상단 검증 단계의 req.body에서 확보됨
|
||||||
|
if (clubId) {
|
||||||
|
try {
|
||||||
|
await averageService.calculateMemberAverageByClubSetting(memberId, clubId);
|
||||||
|
} catch (e2) {
|
||||||
|
console.error('에버리지 워밍업 실패(deleteEventScore):', e2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('에버리지 캐시 무효화 오류(deleteEventScore):', error);
|
||||||
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
message: '점수가 삭제되었습니다.',
|
message: '점수가 삭제되었습니다.',
|
||||||
@@ -1047,6 +1159,12 @@ exports.deleteParticipantScores = async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 삭제 전 memberId 조회 (캐시 무효화용)
|
||||||
|
const ep = await EventParticipant.findOne({
|
||||||
|
where: { id: participantId },
|
||||||
|
attributes: ['memberId']
|
||||||
|
});
|
||||||
|
|
||||||
// 점수 삭제
|
// 점수 삭제
|
||||||
await EventScore.destroy({
|
await EventScore.destroy({
|
||||||
where: {
|
where: {
|
||||||
@@ -1055,6 +1173,23 @@ exports.deleteParticipantScores = async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 캐시 무효화: 해당 참가자의 회원 평균 캐시 제거 및 워밍업
|
||||||
|
try {
|
||||||
|
if (ep && ep.memberId) {
|
||||||
|
await averageService.invalidateMemberAverageCache(ep.memberId);
|
||||||
|
try {
|
||||||
|
const ev = await Event.findOne({ where: { id: eventId }, attributes: ['clubId'] });
|
||||||
|
if (ev && ev.clubId) {
|
||||||
|
await averageService.calculateMemberAverageByClubSetting(ep.memberId, ev.clubId);
|
||||||
|
}
|
||||||
|
} catch (e2) {
|
||||||
|
console.error('에버리지 워밍업 오류(deleteParticipantScores):', e2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('에버리지 캐시 무효화 오류(deleteParticipantScores):', e);
|
||||||
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
message: '점수가 삭제되었습니다.'
|
message: '점수가 삭제되었습니다.'
|
||||||
});
|
});
|
||||||
@@ -1366,7 +1501,7 @@ exports.getEventParticipants = async (req, res) => {
|
|||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: Member,
|
model: Member,
|
||||||
attributes: ['id', 'name', 'email', 'phone', 'memberType']
|
attributes: ['id', 'name', 'email', 'phone', 'memberType', 'gender', 'handicap']
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
order: [
|
order: [
|
||||||
@@ -1484,6 +1619,29 @@ exports.addEventParticipant = async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 입력 상태/결제값을 DB Enum에 맞추어 정규화
|
||||||
|
const normalizeStatus = (s) => {
|
||||||
|
if (!s) return undefined;
|
||||||
|
const map = {
|
||||||
|
'참가예정': 'pending',
|
||||||
|
'참가확정': 'confirmed',
|
||||||
|
'취소': 'canceled',
|
||||||
|
};
|
||||||
|
return ['pending', 'confirmed', 'canceled'].includes(s) ? s : (map[s] || undefined);
|
||||||
|
};
|
||||||
|
const normalizePayment = (p) => {
|
||||||
|
if (!p) return undefined;
|
||||||
|
const map = {
|
||||||
|
'미납': 'unpaid',
|
||||||
|
'납부완료': 'paid',
|
||||||
|
'완납': 'paid',
|
||||||
|
};
|
||||||
|
return ['unpaid', 'paid'].includes(p) ? p : (map[p] || undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
const normStatus = normalizeStatus(status);
|
||||||
|
const normPayment = normalizePayment(paymentStatus);
|
||||||
|
|
||||||
let participant;
|
let participant;
|
||||||
let isUpdate = false;
|
let isUpdate = false;
|
||||||
if (existingParticipant) {
|
if (existingParticipant) {
|
||||||
@@ -1493,8 +1651,8 @@ exports.addEventParticipant = async (req, res) => {
|
|||||||
eventId,
|
eventId,
|
||||||
memberId,
|
memberId,
|
||||||
clubId,
|
clubId,
|
||||||
status: status || existingParticipant.status,
|
status: normStatus || existingParticipant.status,
|
||||||
paymentStatus: paymentStatus || existingParticipant.paymentStatus,
|
paymentStatus: normPayment || existingParticipant.paymentStatus,
|
||||||
comment: comment || existingParticipant.comment
|
comment: comment || existingParticipant.comment
|
||||||
});
|
});
|
||||||
participant = await EventParticipant.findOne({
|
participant = await EventParticipant.findOne({
|
||||||
@@ -1512,8 +1670,8 @@ exports.addEventParticipant = async (req, res) => {
|
|||||||
participant = await EventParticipant.create({
|
participant = await EventParticipant.create({
|
||||||
eventId,
|
eventId,
|
||||||
memberId,
|
memberId,
|
||||||
status: status || '참가예정',
|
status: normStatus || 'pending',
|
||||||
paymentStatus: paymentStatus || '미납',
|
paymentStatus: normPayment || 'unpaid',
|
||||||
comment: comment || null,
|
comment: comment || null,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date()
|
updatedAt: new Date()
|
||||||
|
|||||||
@@ -50,6 +50,79 @@ const fileFilter = (req, file, cb) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 업로드 저장 롤백: 이벤트 및 관련 데이터 삭제 (옵션: 게스트 정리)
|
||||||
|
exports.rollbackEventImport = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { eventId, clubId, includeGuests } = req.body || {};
|
||||||
|
const eid = Number(eventId);
|
||||||
|
const cid = Number(clubId);
|
||||||
|
if (!eid || !cid) {
|
||||||
|
return res.status(400).json({ message: '유효한 eventId, clubId가 필요합니다.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
await sequelize.transaction(async (t) => {
|
||||||
|
// 이벤트 검증 (동일 클럽)
|
||||||
|
const event = await Event.findOne({ where: { id: eid, clubId: cid }, transaction: t });
|
||||||
|
if (!event) {
|
||||||
|
throw new Error('대상 이벤트를 찾을 수 없습니다.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 관련 팀멤버 삭제
|
||||||
|
const teams = await EventTeam.findAll({ where: { eventId: eid }, transaction: t });
|
||||||
|
const teamIds = teams.map((tm) => tm.id);
|
||||||
|
if (teamIds.length > 0) {
|
||||||
|
await EventTeamMember.destroy({ where: { eventTeamId: teamIds }, transaction: t });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 점수 삭제
|
||||||
|
await EventScore.destroy({ where: { eventId: eid }, transaction: t });
|
||||||
|
|
||||||
|
// 참가자 삭제 전, 게스트 후보 수집
|
||||||
|
let guestMemberIds = [];
|
||||||
|
if (includeGuests) {
|
||||||
|
const participants = await EventParticipant.findAll({ where: { eventId: eid }, transaction: t });
|
||||||
|
const memberIds = participants.map((p) => p.memberId).filter(Boolean);
|
||||||
|
if (memberIds.length > 0) {
|
||||||
|
const members = await Member.findAll({ where: { id: memberIds, clubId: cid, memberType: 'guest' }, transaction: t });
|
||||||
|
guestMemberIds = members.map((m) => m.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 참가자 삭제
|
||||||
|
await EventParticipant.destroy({ where: { eventId: eid }, transaction: t });
|
||||||
|
|
||||||
|
// 팀 삭제
|
||||||
|
await EventTeam.destroy({ where: { eventId: eid }, transaction: t });
|
||||||
|
|
||||||
|
// 이벤트 삭제
|
||||||
|
await Event.destroy({ where: { id: eid, clubId: cid }, transaction: t });
|
||||||
|
|
||||||
|
// 게스트 정리: 다른 참가 기록이 없고, 동일 클럽의 조인만 있는 경우 제거
|
||||||
|
if (includeGuests && guestMemberIds.length > 0) {
|
||||||
|
// 다른 참가 기록이 없는 멤버만 필터
|
||||||
|
const others = await EventParticipant.findAll({ where: { memberId: guestMemberIds }, transaction: t });
|
||||||
|
const stillUsed = new Set(others.map((p) => p.memberId));
|
||||||
|
const deletable = guestMemberIds.filter((id) => !stillUsed.has(id));
|
||||||
|
if (deletable.length > 0) {
|
||||||
|
// 클럽 조인 제거
|
||||||
|
const club = await Club.findOne({ where: { id: cid }, transaction: t });
|
||||||
|
if (club) {
|
||||||
|
// removeMember 지원 여부에 따라 destroy 직접 수행
|
||||||
|
await sequelize.models.ClubMembers?.destroy?.({ where: { clubId: cid, memberId: deletable }, transaction: t }).catch(() => {});
|
||||||
|
}
|
||||||
|
// 멤버 삭제
|
||||||
|
await Member.destroy({ where: { id: deletable, clubId: cid, memberType: 'guest' }, transaction: t });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({ message: '롤백이 완료되었습니다.' });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('롤백 오류:', error);
|
||||||
|
res.status(500).json({ message: `롤백 중 오류가 발생했습니다: ${error.message}` });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const upload = multer({
|
const upload = multer({
|
||||||
storage: storage,
|
storage: storage,
|
||||||
fileFilter: fileFilter,
|
fileFilter: fileFilter,
|
||||||
@@ -183,7 +256,7 @@ exports.parseEventFile = async (req, res) => {
|
|||||||
console.error(`회원 ${member.id}의 에버리지 계산 오류:`, error);
|
console.error(`회원 ${member.id}의 에버리지 계산 오류:`, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
memberMap.set(member.name, {
|
memberMap.set(member.name.trim(), {
|
||||||
id: member.id,
|
id: member.id,
|
||||||
name: member.name,
|
name: member.name,
|
||||||
gender: member.gender,
|
gender: member.gender,
|
||||||
@@ -340,7 +413,7 @@ function processRawData(rawData, memberMap) {
|
|||||||
const row = rawData[i];
|
const row = rawData[i];
|
||||||
if (row.length < 2) continue;
|
if (row.length < 2) continue;
|
||||||
// 이름: 한글 2자 이상
|
// 이름: 한글 2자 이상
|
||||||
const name = colMap.name !== undefined ? row[colMap.name] : row.find(cell => /^[가-힣a-z]{2,}$/i.test(cell));
|
const name = (colMap.name !== undefined ? row[colMap.name] : row.find(cell => /^[가-힣a-z]{2,}$/i.test(cell)))?.toString().trim();
|
||||||
if (!name) continue;
|
if (!name) continue;
|
||||||
const teamNumber = colMap.team !== undefined ? row[colMap.team] : '';
|
const teamNumber = colMap.team !== undefined ? row[colMap.team] : '';
|
||||||
const scores = (colMap.scores || []).map(idx => parseInt(row[idx], 10)).filter(n => !isNaN(n));
|
const scores = (colMap.scores || []).map(idx => parseInt(row[idx], 10)).filter(n => !isNaN(n));
|
||||||
@@ -348,7 +421,7 @@ function processRawData(rawData, memberMap) {
|
|||||||
const total = colMap.total !== undefined ? parseInt(row[colMap.total], 10) : null;
|
const total = colMap.total !== undefined ? parseInt(row[colMap.total], 10) : null;
|
||||||
const avg = colMap.avg !== undefined ? parseFloat(row[colMap.avg]) : null;
|
const avg = colMap.avg !== undefined ? parseFloat(row[colMap.avg]) : null;
|
||||||
// 회원 매핑 (이름 기준)
|
// 회원 매핑 (이름 기준)
|
||||||
const memberMatch = memberMap && memberMap.get && name ? memberMap.get(name) : null;
|
const memberMatch = memberMap && memberMap.get && name ? memberMap.get(name.trim()) : null;
|
||||||
participants.push({
|
participants.push({
|
||||||
name,
|
name,
|
||||||
teamNumber,
|
teamNumber,
|
||||||
@@ -522,16 +595,49 @@ exports.saveEventFileData = async (req, res) => {
|
|||||||
if (!eventData || !participants || !clubId) {
|
if (!eventData || !participants || !clubId) {
|
||||||
return res.status(400).json({ message: '이벤트 정보, 참가자 정보, 클럽 ID가 필요합니다.' });
|
return res.status(400).json({ message: '이벤트 정보, 참가자 정보, 클럽 ID가 필요합니다.' });
|
||||||
}
|
}
|
||||||
|
// clubId 타입 보정 (문자열로 들어온 경우 Number로 변환)
|
||||||
|
const cid = Number(clubId);
|
||||||
|
if (!cid || Number.isNaN(cid)) {
|
||||||
|
return res.status(400).json({ message: '유효한 클럽 ID가 필요합니다.' });
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 트랜잭션 시작
|
// 트랜잭션 시작
|
||||||
const result = await sequelize.transaction(async (t) => {
|
const result = await sequelize.transaction(async (t) => {
|
||||||
|
// 프런트에서 전달할 수 있는 핸디 포함 여부 플래그 (기본: true)
|
||||||
|
const scoresIncludeHandicap = (
|
||||||
|
(typeof req.body.scoresIncludeHandicap === 'boolean') ? req.body.scoresIncludeHandicap
|
||||||
|
: (eventData && typeof eventData.scoresIncludeHandicap === 'boolean') ? eventData.scoresIncludeHandicap
|
||||||
|
: true
|
||||||
|
);
|
||||||
|
// 이벤트 타입/상태 정규화 (모델 enum 호환)
|
||||||
|
const normalizeEventType = (v) => {
|
||||||
|
if (!v) return 'regular';
|
||||||
|
const s = String(v).toLowerCase();
|
||||||
|
if (['regular','일반'].includes(s)) return 'regular';
|
||||||
|
if (['lightning','번개'].includes(s)) return 'lightning';
|
||||||
|
if (['practice','연습'].includes(s)) return 'practice';
|
||||||
|
if (['exchange','교류전'].includes(s)) return 'exchange';
|
||||||
|
if (['event','행사'].includes(s)) return 'event';
|
||||||
|
return 'other';
|
||||||
|
};
|
||||||
|
const normalizeStatus = (v) => {
|
||||||
|
if (!v) return 'completed';
|
||||||
|
const s = String(v).toLowerCase();
|
||||||
|
if (['active','활성'].includes(s)) return 'active';
|
||||||
|
if (['ready','대기','준비','draft'].includes(s)) return 'ready';
|
||||||
|
if (['completed','완료','done','finished'].includes(s)) return 'completed';
|
||||||
|
if (['canceled','cancelled','취소'].includes(s)) return 'canceled';
|
||||||
|
if (['deleted','삭제'].includes(s)) return 'deleted';
|
||||||
|
return 'completed';
|
||||||
|
};
|
||||||
|
|
||||||
// 1. 이벤트 생성 또는 업데이트
|
// 1. 이벤트 생성 또는 업데이트
|
||||||
let event;
|
let event;
|
||||||
if (eventData.id) {
|
if (eventData.id) {
|
||||||
// 기존 이벤트 업데이트
|
// 기존 이벤트 업데이트
|
||||||
event = await Event.findOne({
|
event = await Event.findOne({
|
||||||
where: { id: eventData.id, clubId },
|
where: { id: eventData.id, clubId: cid },
|
||||||
transaction: t
|
transaction: t
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -542,30 +648,37 @@ exports.saveEventFileData = async (req, res) => {
|
|||||||
await event.update({
|
await event.update({
|
||||||
title: eventData.title,
|
title: eventData.title,
|
||||||
description: eventData.description || '',
|
description: eventData.description || '',
|
||||||
eventType: eventData.eventType,
|
// 비즈니스 규칙: 이벤트 유형은 정기전, 상태는 완료로 고정
|
||||||
|
eventType: 'regular',
|
||||||
startDate: new Date(eventData.startDate),
|
startDate: new Date(eventData.startDate),
|
||||||
endDate: eventData.endDate ? new Date(eventData.endDate) : null,
|
endDate: eventData.endDate ? new Date(eventData.endDate) : null,
|
||||||
location: eventData.location || '',
|
location: eventData.location || '',
|
||||||
gameCount: eventData.gameCount || 3,
|
gameCount: eventData.gameCount || 3,
|
||||||
maxParticipants: eventData.maxParticipants || 0,
|
maxParticipants: eventData.maxParticipants || 0,
|
||||||
status: eventData.status || '완료'
|
status: 'completed'
|
||||||
}, { transaction: t });
|
}, { transaction: t });
|
||||||
} else {
|
} else {
|
||||||
// 새 이벤트 생성
|
// 새 이벤트 생성
|
||||||
event = await Event.create({
|
event = await Event.create({
|
||||||
clubId,
|
clubId: cid,
|
||||||
title: eventData.title,
|
title: eventData.title,
|
||||||
description: eventData.description || '',
|
description: eventData.description || '',
|
||||||
eventType: eventData.eventType,
|
// 비즈니스 규칙: 이벤트 유형은 정기전으로 저장
|
||||||
|
eventType: 'regular',
|
||||||
startDate: new Date(eventData.startDate),
|
startDate: new Date(eventData.startDate),
|
||||||
endDate: eventData.endDate ? new Date(eventData.endDate) : null,
|
endDate: eventData.endDate ? new Date(eventData.endDate) : null,
|
||||||
location: eventData.location || '',
|
location: eventData.location || '',
|
||||||
gameCount: eventData.gameCount || 3,
|
gameCount: eventData.gameCount || 3,
|
||||||
maxParticipants: eventData.maxParticipants || 0,
|
maxParticipants: eventData.maxParticipants || 0,
|
||||||
status: eventData.status || '완료',
|
// 비즈니스 규칙: 상태는 완료로 저장
|
||||||
|
status: 'completed',
|
||||||
publicHash: generateRandomHash()
|
publicHash: generateRandomHash()
|
||||||
}, { transaction: t });
|
}, { transaction: t });
|
||||||
}
|
}
|
||||||
|
// 이벤트에서 최종 클럽ID 확정(추가 안정성)
|
||||||
|
const effectiveClubId = Number(event.clubId);
|
||||||
|
// 클럽 인스턴스 로드 (조인 테이블 조작용)
|
||||||
|
const clubInstance = await Club.findOne({ where: { id: effectiveClubId, status: 'active' }, transaction: t });
|
||||||
|
|
||||||
// 1.5 팀 정보 처리 - 참가자 처리 전에 팀 생성
|
// 1.5 팀 정보 처리 - 참가자 처리 전에 팀 생성
|
||||||
// 팀 번호별로 그룹화
|
// 팀 번호별로 그룹화
|
||||||
@@ -605,6 +718,7 @@ exports.saveEventFileData = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 참가자 및 점수 처리
|
// 2. 참가자 및 점수 처리
|
||||||
|
const affectedMemberIds = new Set();
|
||||||
for (const participant of participants) {
|
for (const participant of participants) {
|
||||||
let eventParticipant;
|
let eventParticipant;
|
||||||
|
|
||||||
@@ -635,28 +749,55 @@ exports.saveEventFileData = async (req, res) => {
|
|||||||
eventParticipant = await EventParticipant.create({
|
eventParticipant = await EventParticipant.create({
|
||||||
eventId: event.id,
|
eventId: event.id,
|
||||||
memberId: participant.memberId,
|
memberId: participant.memberId,
|
||||||
status: '참가확정',
|
status: 'confirmed',
|
||||||
paymentStatus: '납부완료',
|
paymentStatus: 'paid',
|
||||||
teamId: teamId,
|
teamId: teamId,
|
||||||
comment: ''
|
comment: ''
|
||||||
}, { transaction: t });
|
}, { transaction: t });
|
||||||
}
|
}
|
||||||
|
affectedMemberIds.add(Number(participant.memberId));
|
||||||
} else {
|
} else {
|
||||||
// 새 회원 생성 및 참가자 등록
|
// 새 회원 생성 및 참가자 등록
|
||||||
|
// 클럽 여성 기본 핸디캡 기준으로 성별/핸디캡 보정
|
||||||
|
const femaleBase = Number(clubInstance?.femaleHandicap ?? 0) || 0;
|
||||||
|
let parsedHandicap = null;
|
||||||
|
if (participant.handicap !== undefined && participant.handicap !== null) {
|
||||||
|
const ph = Number(participant.handicap);
|
||||||
|
if (Number.isFinite(ph)) parsedHandicap = ph;
|
||||||
|
}
|
||||||
|
// 점수 객체에 handicap이 개별 제공되는 경우 최댓값을 사용
|
||||||
|
if (parsedHandicap == null && Array.isArray(participant.scores)) {
|
||||||
|
for (const s of participant.scores) {
|
||||||
|
if (s && typeof s === 'object' && s.handicap !== undefined) {
|
||||||
|
const sh = Number(s.handicap);
|
||||||
|
if (Number.isFinite(sh)) {
|
||||||
|
parsedHandicap = parsedHandicap == null ? sh : Math.max(parsedHandicap, sh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const isFemaleByRule = (parsedHandicap != null && parsedHandicap >= femaleBase) ||
|
||||||
|
(Array.isArray(participant.scores) && participant.scores.some((s) => Number(s?.handicap) >= femaleBase));
|
||||||
|
const genderNormalized = (participant.gender === 'female' || participant.gender === '여성') ? 'female' : 'male';
|
||||||
|
const finalGender = isFemaleByRule ? 'female' : genderNormalized;
|
||||||
|
|
||||||
const newMember = await Member.create({
|
const newMember = await Member.create({
|
||||||
|
clubId: effectiveClubId, // required by Member model
|
||||||
name: participant.name,
|
name: participant.name,
|
||||||
gender: participant.gender || '남성',
|
gender: finalGender,
|
||||||
memberType: '정회원',
|
memberType: 'guest',
|
||||||
status: '활성'
|
status: 'active',
|
||||||
|
handicap: parsedHandicap ?? 0
|
||||||
}, { transaction: t });
|
}, { transaction: t });
|
||||||
|
|
||||||
// 클럽 회원으로 등록
|
// 클럽 회원으로 등록 (조인 테이블을 association으로 처리)
|
||||||
await ClubMember.create({
|
if (!clubInstance) {
|
||||||
clubId,
|
throw new Error('클럽을 찾을 수 없습니다.');
|
||||||
memberId: newMember.id,
|
}
|
||||||
joinDate: new Date(),
|
await clubInstance.addMember(newMember, {
|
||||||
status: 'active'
|
through: { joinDate: new Date(), status: 'active' },
|
||||||
}, { transaction: t });
|
transaction: t,
|
||||||
|
});
|
||||||
|
|
||||||
// 팀 ID 가져오기
|
// 팀 ID 가져오기
|
||||||
let teamId = null;
|
let teamId = null;
|
||||||
@@ -667,10 +808,12 @@ exports.saveEventFileData = async (req, res) => {
|
|||||||
eventParticipant = await EventParticipant.create({
|
eventParticipant = await EventParticipant.create({
|
||||||
eventId: event.id,
|
eventId: event.id,
|
||||||
memberId: newMember.id,
|
memberId: newMember.id,
|
||||||
status: '참가확정',
|
status: 'confirmed',
|
||||||
|
paymentStatus: 'paid',
|
||||||
teamId: teamId,
|
teamId: teamId,
|
||||||
comment: ''
|
comment: ''
|
||||||
}, { transaction: t });
|
}, { transaction: t });
|
||||||
|
affectedMemberIds.add(Number(newMember.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 팀에 속한 참가자인 경우 팀 멤버로 등록
|
// 팀에 속한 참가자인 경우 팀 멤버로 등록
|
||||||
@@ -705,36 +848,67 @@ exports.saveEventFileData = async (req, res) => {
|
|||||||
transaction: t
|
transaction: t
|
||||||
});
|
});
|
||||||
|
|
||||||
// 새 점수 등록
|
// 새 점수 등록 (숫자 배열/객체 배열 모두 지원)
|
||||||
for (const scoreData of participant.scores) {
|
const scoreItems = Array.isArray(participant.scores)
|
||||||
// 핵디캡 값이 없는 경우 참가자의 핵디캡 값 사용
|
? participant.scores.map((s, idx) => (
|
||||||
const handicap = scoreData.handicap !== undefined ? scoreData.handicap : (participant.handicap || 0);
|
typeof s === 'number'
|
||||||
// 총점수가 없는 경우 원점수 사용
|
? { gameNumber: idx + 1, score: s }
|
||||||
const totalScore = scoreData.totalScore !== undefined ? scoreData.totalScore : scoreData.score;
|
: s
|
||||||
|
))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
for (const s of scoreItems) {
|
||||||
|
const gameNumber = Number(s.gameNumber ?? 0) || 0;
|
||||||
|
const handicap = s.handicap !== undefined ? Number(s.handicap) : Number(participant.handicap || 0);
|
||||||
|
// scoresIncludeHandicap 플래그에 따라 저장 방식 결정
|
||||||
|
const provided = s.totalScore !== undefined ? Number(s.totalScore) : Number(s.score ?? 0) || 0;
|
||||||
|
const totalScore = scoresIncludeHandicap
|
||||||
|
? provided
|
||||||
|
: Math.max(0, provided + (Number.isFinite(handicap) ? handicap : 0));
|
||||||
|
const baseScore = scoresIncludeHandicap
|
||||||
|
? Math.max(0, provided - (Number.isFinite(handicap) ? handicap : 0))
|
||||||
|
: provided;
|
||||||
|
if (gameNumber <= 0) continue;
|
||||||
|
|
||||||
await EventScore.create({
|
await EventScore.create({
|
||||||
eventId: event.id,
|
eventId: event.id,
|
||||||
participantId: eventParticipant.id,
|
participantId: eventParticipant.id,
|
||||||
gameNumber: scoreData.gameNumber,
|
gameNumber,
|
||||||
score: scoreData.score,
|
score: baseScore,
|
||||||
handicap: handicap,
|
handicap,
|
||||||
totalScore: totalScore,
|
totalScore,
|
||||||
teamNumber: participant.teamNumber || null
|
teamNumber: participant.teamNumber || null
|
||||||
}, { transaction: t });
|
}, { transaction: t });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return event;
|
// 트랜잭션 내부에서는 목록만 반환 (커밋 후 워밍업)
|
||||||
|
return { event, affectedMemberIds: Array.from(affectedMemberIds), effectiveClubId };
|
||||||
});
|
});
|
||||||
|
// 커밋 이후 평균 캐시 무효화 및 워밍업 (신규 점수가 커밋된 뒤 계산되도록)
|
||||||
|
if (result && Array.isArray(result.affectedMemberIds)) {
|
||||||
|
for (const mid of result.affectedMemberIds) {
|
||||||
|
try {
|
||||||
|
await averageService.invalidateMemberAverageCache(mid);
|
||||||
|
await averageService.calculateMemberAverageByClubSetting(mid, result.effectiveClubId);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('평균 캐시 갱신 실패:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
message: '이벤트 데이터가 성공적으로 저장되었습니다.',
|
message: '이벤트 데이터가 성공적으로 저장되었습니다.',
|
||||||
event: {
|
event: {
|
||||||
id: result.id,
|
id: result.event.id,
|
||||||
title: result.title,
|
title: result.event.title,
|
||||||
eventType: result.eventType,
|
eventType: result.event.eventType,
|
||||||
startDate: result.startDate
|
startDate: result.event.startDate
|
||||||
|
},
|
||||||
|
rollbackToken: {
|
||||||
|
eventId: result.event.id,
|
||||||
|
clubId: Number(result.event.clubId)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ exports.publicEventEntry = async (req, res) => {
|
|||||||
if (!event) {
|
if (!event) {
|
||||||
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
||||||
}
|
}
|
||||||
if (event.status === '취소') {
|
if (event.status === 'canceled') {
|
||||||
return res.status(403).json({ message: '취소된 이벤트입니다.' });
|
return res.status(403).json({ message: '취소된 이벤트입니다.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ exports.getEventByPublicHash = async (event, token = null) => {
|
|||||||
attributes: [[sequelize.col('id'), 'memberId'], 'name', 'memberType', 'gender', 'handicap']
|
attributes: [[sequelize.col('id'), 'memberId'], 'name', 'memberType', 'gender', 'handicap']
|
||||||
});
|
});
|
||||||
const participants = await EventParticipant.findAll({
|
const participants = await EventParticipant.findAll({
|
||||||
where: { eventId: event.id, status: { [Op.ne]: '취소' } },
|
where: { eventId: event.id, status: { [Op.ne]: 'canceled' } },
|
||||||
include: [ { model: Member, attributes: [[sequelize.col('id'), 'memberId'], 'name', 'memberType', 'gender', 'handicap'] } ]
|
include: [ { model: Member, attributes: [[sequelize.col('id'), 'memberId'], 'name', 'memberType', 'gender', 'handicap'] } ]
|
||||||
});
|
});
|
||||||
// 모든 회원에 대한 에버리지 계산
|
// 모든 회원에 대한 에버리지 계산
|
||||||
@@ -117,11 +117,11 @@ exports.getEventByPublicHash = async (event, token = null) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const participantMemberIds = [
|
const participantMemberIds = [
|
||||||
...new Set(participants.filter(p => p.dataValues.status !== '취소').map(p => parseInt(p.dataValues.memberId)))
|
...new Set(participants.filter(p => p.dataValues.status !== 'canceled').map(p => parseInt(p.dataValues.memberId)))
|
||||||
];
|
];
|
||||||
// 상태별로 미참가자/게스트 추가 제한
|
// 상태별로 미참가자/게스트 추가 제한
|
||||||
let availableMembers = [];
|
let availableMembers = [];
|
||||||
if (event.status === '준비') {
|
if (event.status === 'ready') {
|
||||||
availableMembers = allMemberObjs.filter(m => !participantMemberIds.includes(parseInt(m.memberId)));
|
availableMembers = allMemberObjs.filter(m => !participantMemberIds.includes(parseInt(m.memberId)));
|
||||||
} // 준비 아닐 땐 빈 배열(신청/게스트 추가 불가)
|
} // 준비 아닐 땐 빈 배열(신청/게스트 추가 불가)
|
||||||
// 팀 정보
|
// 팀 정보
|
||||||
@@ -132,7 +132,7 @@ exports.getEventByPublicHash = async (event, token = null) => {
|
|||||||
model: EventTeamMember,
|
model: EventTeamMember,
|
||||||
include: [{
|
include: [{
|
||||||
model: EventParticipant,
|
model: EventParticipant,
|
||||||
where: { status: { [Op.ne]: '취소' } },
|
where: { status: { [Op.ne]: 'canceled' } },
|
||||||
include: [{ model: Member, attributes: [[sequelize.col('id'), 'memberId'], 'name', 'memberType', 'gender', 'handicap'] }]
|
include: [{ model: Member, attributes: [[sequelize.col('id'), 'memberId'], 'name', 'memberType', 'gender', 'handicap'] }]
|
||||||
}]
|
}]
|
||||||
}]
|
}]
|
||||||
@@ -280,18 +280,65 @@ exports.registerPublicParticipant = async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const event = await Event.findOne({ where: { publicHash } });
|
const event = await Event.findOne({ where: { publicHash } });
|
||||||
if (!event) return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
if (!event) return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
||||||
if (event.status === '취소') return res.status(403).json({ message: '취소된 이벤트입니다.' });
|
if (event.status === 'canceled') return res.status(403).json({ message: '취소된 이벤트입니다.' });
|
||||||
let member = await Member.findOne({ where: { id: memberId } });
|
|
||||||
let participant = await EventParticipant.findOne({ where: { id: participantId, memberId: member.id } });
|
// 참가 마감시간 체크: 마감 후에는 신규 참가 불가, 기존 참가자 수정만 허용
|
||||||
|
const now = new Date();
|
||||||
|
const regDeadline = event.registrationDeadline ? new Date(event.registrationDeadline) : null;
|
||||||
|
|
||||||
|
// 입력값 정규화 및 검증
|
||||||
|
const toInt = (v) => {
|
||||||
|
if (v === undefined || v === null || v === '') return null;
|
||||||
|
const n = Number(v);
|
||||||
|
return Number.isFinite(n) ? n : null;
|
||||||
|
};
|
||||||
|
const pid = toInt(participantId);
|
||||||
|
const mid = toInt(memberId);
|
||||||
|
if (!mid) {
|
||||||
|
return res.status(400).json({ message: '유효한 회원 ID가 필요합니다.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 상태/납부 검증 (프런트가 백엔드 값에 맞춰 보냄)
|
||||||
|
const allowedStatus = ['pending', 'confirmed', 'canceled'];
|
||||||
|
const allowedPayment = ['unpaid', 'paid'];
|
||||||
|
const normStatus = status || 'pending';
|
||||||
|
const normPayment = paymentStatus || 'unpaid';
|
||||||
|
if (!allowedStatus.includes(normStatus)) {
|
||||||
|
return res.status(400).json({ message: `유효하지 않은 참가 상태입니다. 허용값: ${allowedStatus.join(', ')}` });
|
||||||
|
}
|
||||||
|
if (!allowedPayment.includes(normPayment)) {
|
||||||
|
return res.status(400).json({ message: `유효하지 않은 납부 상태입니다. 허용값: ${allowedPayment.join(', ')}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 멤버 소속 검증 (이벤트 클럽 소속)
|
||||||
|
const member = await Member.findOne({ where: { id: mid, clubId: event.clubId } });
|
||||||
|
if (!member) {
|
||||||
|
return res.status(404).json({ message: '클럽 소속 회원을 찾을 수 없습니다.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
let participant = null;
|
||||||
|
if (pid) {
|
||||||
|
participant = await EventParticipant.findOne({ where: { id: pid, memberId: member.id } });
|
||||||
|
}
|
||||||
if (!participant) {
|
if (!participant) {
|
||||||
participant = await EventParticipant.create({ eventId: event.id, memberId: member.id, comment, status, paymentStatus });
|
// 신규 생성 전에 마감 체크 수행
|
||||||
|
if (regDeadline && now > regDeadline) {
|
||||||
|
return res.status(403).json({ message: '참가 신청이 마감되었습니다. 기존 참가 정보만 수정할 수 있습니다.' });
|
||||||
|
}
|
||||||
|
// 중복 참가 방지: 동일 eventId/memberId의 취소되지 않은 레코드가 있으면 업데이트로 처리
|
||||||
|
const existing = await EventParticipant.findOne({ where: { eventId: event.id, memberId: member.id, status: { [Op.ne]: 'canceled' } } });
|
||||||
|
if (existing) {
|
||||||
|
await existing.update({ comment: comment ?? existing.comment, status: normStatus || existing.status, paymentStatus: normPayment || existing.paymentStatus });
|
||||||
} else {
|
} else {
|
||||||
await participant.update({ comment, status, paymentStatus });
|
await EventParticipant.create({ eventId: event.id, memberId: member.id, comment: comment || null, status: normStatus, paymentStatus: normPayment });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await participant.update({ comment, status: normStatus, paymentStatus: normPayment });
|
||||||
}
|
}
|
||||||
res.json({ message: '참가 신청/수정이 완료되었습니다.' });
|
res.json({ message: '참가 신청/수정이 완료되었습니다.' });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('공개 참가자 등록 오류:', e);
|
console.error('공개 참가자 등록 오류:', { error: e?.message, stack: e?.stack });
|
||||||
res.status(500).json({ message: '참가 신청/수정 중 오류가 발생했습니다.' });
|
res.status(500).json({ message: '참가 신청/수정 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -303,6 +350,12 @@ exports.addPublicGuest = async (req, res) => {
|
|||||||
const event = await Event.findOne({ where: { publicHash } });
|
const event = await Event.findOne({ where: { publicHash } });
|
||||||
if (!event) return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
if (!event) return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
||||||
if (event.status === '취소') return res.status(403).json({ message: '취소된 이벤트입니다.' });
|
if (event.status === '취소') return res.status(403).json({ message: '취소된 이벤트입니다.' });
|
||||||
|
// 참가 마감시간 체크: 마감 후에는 게스트 추가 불가
|
||||||
|
const now = new Date();
|
||||||
|
const regDeadline = event.registrationDeadline ? new Date(event.registrationDeadline) : null;
|
||||||
|
if (regDeadline && now > regDeadline) {
|
||||||
|
return res.status(403).json({ message: '참가 신청이 마감되었습니다. 게스트를 추가할 수 없습니다.' });
|
||||||
|
}
|
||||||
|
|
||||||
// 클럽의 회원 수와 구독 플랜의 최대 회원 수 확인
|
// 클럽의 회원 수와 구독 플랜의 최대 회원 수 확인
|
||||||
const memberCount = await Member.count({ where: { clubId: event.clubId } });
|
const memberCount = await Member.count({ where: { clubId: event.clubId } });
|
||||||
@@ -319,7 +372,6 @@ exports.addPublicGuest = async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 구독이 없거나 만료된 경우 접근 제한
|
// 구독이 없거나 만료된 경우 접근 제한
|
||||||
const now = new Date();
|
|
||||||
if (!clubSubscription || new Date(clubSubscription.endDate) < now) {
|
if (!clubSubscription || new Date(clubSubscription.endDate) < now) {
|
||||||
return res.status(403).json({ message: '클럽의 구독이 만료되어 이벤트에 접근할 수 없습니다. 클럽 관리자에게 문의하세요.' });
|
return res.status(403).json({ message: '클럽의 구독이 만료되어 이벤트에 접근할 수 없습니다. 클럽 관리자에게 문의하세요.' });
|
||||||
}
|
}
|
||||||
@@ -327,7 +379,7 @@ exports.addPublicGuest = async (req, res) => {
|
|||||||
// 회원 수가 최대치를 초과하는 경우
|
// 회원 수가 최대치를 초과하는 경우
|
||||||
if (clubSubscription.SubscriptionPlan) {
|
if (clubSubscription.SubscriptionPlan) {
|
||||||
const maxMembers = clubSubscription.SubscriptionPlan.maxMembers;
|
const maxMembers = clubSubscription.SubscriptionPlan.maxMembers;
|
||||||
if (memberCount >= maxMembers) {
|
if (maxMembers > 0 && memberCount >= maxMembers) {
|
||||||
return res.status(403).json({
|
return res.status(403).json({
|
||||||
message: `회원 수가 구독 플랜의 최대 허용 인원(${maxMembers}명)을 초과했습니다. 구독 업그레이드가 필요합니다.`
|
message: `회원 수가 구독 플랜의 최대 허용 인원(${maxMembers}명)을 초과했습니다. 구독 업그레이드가 필요합니다.`
|
||||||
});
|
});
|
||||||
@@ -417,3 +469,24 @@ exports.updatePublicScore = async (req, res) => {
|
|||||||
res.status(500).json({ message: '점수 저장 중 오류가 발생했습니다.' });
|
res.status(500).json({ message: '점수 저장 중 오류가 발생했습니다.' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 팀 핸디캡 수정 (top-level export)
|
||||||
|
exports.updateTeamHandicap = async (req, res) => {
|
||||||
|
const { publicHash } = req.params;
|
||||||
|
const { teamNumber, handicap } = req.body || {};
|
||||||
|
try {
|
||||||
|
const event = await Event.findOne({ where: { publicHash } });
|
||||||
|
if (!event) return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
||||||
|
if (event.status !== 'ready' && event.status !== 'active') return res.status(403).json({ message: '팀 핸디캡 수정이 불가능한 상태입니다.' });
|
||||||
|
|
||||||
|
const team = await EventTeam.findOne({ where: { eventId: event.id, teamNumber } });
|
||||||
|
if (!team) return res.status(404).json({ message: '팀을 찾을 수 없습니다.' });
|
||||||
|
|
||||||
|
const hc = Number(handicap) || 0;
|
||||||
|
await team.update({ handicap: hc });
|
||||||
|
return res.json({ message: '팀 핸디캡이 저장되었습니다.', data: { teamNumber, handicap: hc } });
|
||||||
|
} catch (e) {
|
||||||
|
console.error('팀 핸디캡 수정 오류:', e);
|
||||||
|
res.status(500).json({ message: '팀 핸디캡 저장 중 오류가 발생했습니다.' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -685,3 +685,141 @@ exports.getPlanSubscriptions = async (req, res) => {
|
|||||||
res.status(500).json({ message: '플랜별 구독 목록 조회 중 오류가 발생했습니다.' });
|
res.status(500).json({ message: '플랜별 구독 목록 조회 중 오류가 발생했습니다.' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 인앱 결제 영수증 검증
|
||||||
|
exports.verifyReceipt = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { receipt, transactionId, productId, platform } = req.body;
|
||||||
|
const userId = req.user.id;
|
||||||
|
const clubId = req.body.clubId;
|
||||||
|
|
||||||
|
if (!receipt) {
|
||||||
|
return res.status(400).json({ message: '영수증 데이터가 없습니다.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 플랫폼별 검증 로직
|
||||||
|
let verificationResult = false;
|
||||||
|
let purchaseData = {};
|
||||||
|
|
||||||
|
if (platform === 'ios') {
|
||||||
|
// iOS 영수증 검증
|
||||||
|
verificationResult = await verifyIosReceipt(receipt);
|
||||||
|
purchaseData = {
|
||||||
|
platform: 'ios',
|
||||||
|
transactionId,
|
||||||
|
productId,
|
||||||
|
receipt
|
||||||
|
};
|
||||||
|
} else if (platform === 'android') {
|
||||||
|
// Android 영수증 검증
|
||||||
|
verificationResult = await verifyAndroidReceipt(receipt, productId);
|
||||||
|
purchaseData = {
|
||||||
|
platform: 'android',
|
||||||
|
transactionId,
|
||||||
|
productId,
|
||||||
|
receipt
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return res.status(400).json({ message: '지원하지 않는 플랫폼입니다.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!verificationResult) {
|
||||||
|
return res.status(400).json({ message: '영수증 검증에 실패했습니다.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 구독 플랜 타입 확인
|
||||||
|
const planType = getPlanTypeFromProductId(productId);
|
||||||
|
if (!planType) {
|
||||||
|
return res.status(400).json({ message: '유효하지 않은 상품 ID입니다.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 해당 플랜 정보 가져오기
|
||||||
|
const plan = await SubscriptionPlan.findOne({
|
||||||
|
where: { type: planType }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!plan) {
|
||||||
|
return res.status(404).json({ message: '해당 플랜을 찾을 수 없습니다.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 기존 구독 정보 확인
|
||||||
|
let subscription = await ClubSubscription.findOne({
|
||||||
|
where: { clubId }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 구독 생성 또는 업데이트
|
||||||
|
const startDate = new Date();
|
||||||
|
const endDate = new Date();
|
||||||
|
endDate.setMonth(endDate.getMonth() + 1); // 기본 1개월 구독
|
||||||
|
|
||||||
|
if (subscription) {
|
||||||
|
// 기존 구독 업데이트
|
||||||
|
await subscription.update({
|
||||||
|
planId: plan.id,
|
||||||
|
status: 'active',
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
lastPaymentDate: startDate,
|
||||||
|
transactionId,
|
||||||
|
receiptData: JSON.stringify(purchaseData)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 새 구독 생성
|
||||||
|
subscription = await ClubSubscription.create({
|
||||||
|
clubId,
|
||||||
|
planId: plan.id,
|
||||||
|
status: 'active',
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
lastPaymentDate: startDate,
|
||||||
|
transactionId,
|
||||||
|
receiptData: JSON.stringify(purchaseData)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 검증 결과 및 구독 정보 반환
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: '영수증 검증 및 구독 처리가 완료되었습니다.',
|
||||||
|
subscription: {
|
||||||
|
id: subscription.id,
|
||||||
|
planId: subscription.planId,
|
||||||
|
planType: planType,
|
||||||
|
status: subscription.status,
|
||||||
|
startDate: subscription.startDate,
|
||||||
|
endDate: subscription.endDate,
|
||||||
|
lastPaymentDate: subscription.lastPaymentDate
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in verifyReceipt:', error);
|
||||||
|
res.status(500).json({ message: '영수증 검증 중 오류가 발생했습니다.' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// iOS 영수증 검증 함수 (실제 구현에서는 Apple 서버와 통신 필요)
|
||||||
|
async function verifyIosReceipt(receipt) {
|
||||||
|
// 실제 구현에서는 Apple 서버에 영수증 검증 요청
|
||||||
|
// 테스트를 위해 현재는 항상 true 반환
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Android 영수증 검증 함수 (실제 구현에서는 Google Play API와 통신 필요)
|
||||||
|
async function verifyAndroidReceipt(receipt, productId) {
|
||||||
|
// 실제 구현에서는 Google Play API를 통해 영수증 검증
|
||||||
|
// 테스트를 위해 현재는 항상 true 반환
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 상품 ID에서 플랜 타입 추출
|
||||||
|
function getPlanTypeFromProductId(productId) {
|
||||||
|
if (productId.includes('basic')) {
|
||||||
|
return 'basic';
|
||||||
|
} else if (productId.includes('premium')) {
|
||||||
|
return 'premium';
|
||||||
|
} else if (productId.includes('pro')) {
|
||||||
|
return 'pro';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const { clubHasFeature } = require('../utils/featureAccess');
|
|||||||
function requireFeature(featureCode) {
|
function requireFeature(featureCode) {
|
||||||
return async function(req, res, next) {
|
return async function(req, res, next) {
|
||||||
try {
|
try {
|
||||||
const clubId = req.session.clubId || req.clubId || req.body.clubId || req.params.clubId;
|
const clubId = req.session.clubId || req.clubId || req.body.clubId || req.params.clubId || req.query.clubId;
|
||||||
// console.log('backendClubId:', clubId);
|
// console.log('backendClubId:', clubId);
|
||||||
if (!clubId) {
|
if (!clubId) {
|
||||||
return res.status(400).json({ error: '클럽 정보가 필요합니다.' });
|
return res.status(400).json({ error: '클럽 정보가 필요합니다.' });
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ const Event = sequelize.define('Event', {
|
|||||||
},
|
},
|
||||||
participantFee: {
|
participantFee: {
|
||||||
type: DataTypes.DECIMAL(10, 2),
|
type: DataTypes.DECIMAL(10, 2),
|
||||||
allowNull: false,
|
allowNull: true,
|
||||||
defaultValue: 0,
|
defaultValue: null,
|
||||||
comment: '참가비'
|
comment: '참가비'
|
||||||
},
|
},
|
||||||
maxParticipants: {
|
maxParticipants: {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ router.get('/:id', authenticateJWT, requireFeature('event_management'), eventCon
|
|||||||
router.post('/create', authenticateJWT, requireFeature('event_management'), eventController.createEvent);
|
router.post('/create', authenticateJWT, requireFeature('event_management'), eventController.createEvent);
|
||||||
router.put('/:id', authenticateJWT, requireFeature('event_management'), eventController.updateEvent);
|
router.put('/:id', authenticateJWT, requireFeature('event_management'), eventController.updateEvent);
|
||||||
router.delete('/:id', authenticateJWT, requireFeature('event_management'), eventController.deleteEvent);
|
router.delete('/:id', authenticateJWT, requireFeature('event_management'), eventController.deleteEvent);
|
||||||
|
// 이벤트 완전 삭제 (하드 삭제)
|
||||||
|
router.delete('/:id/permanent', authenticateJWT, requireFeature('event_management'), eventController.deleteEventPermanently);
|
||||||
|
|
||||||
// 이벤트 참가자 관리
|
// 이벤트 참가자 관리
|
||||||
router.post('/:id/participants/list', authenticateJWT, requireFeature('event_management'), eventController.getEventParticipants);
|
router.post('/:id/participants/list', authenticateJWT, requireFeature('event_management'), eventController.getEventParticipants);
|
||||||
@@ -42,5 +44,7 @@ router.post('/upload', authenticateJWT, requireFeature('event_management'), file
|
|||||||
router.post('/parse-preview', authenticateJWT, requireFeature('event_management'), fileUploadController.parseEventFile);
|
router.post('/parse-preview', authenticateJWT, requireFeature('event_management'), fileUploadController.parseEventFile);
|
||||||
router.post('/save-file-data', authenticateJWT, requireFeature('event_management'), fileUploadController.saveEventFileData);
|
router.post('/save-file-data', authenticateJWT, requireFeature('event_management'), fileUploadController.saveEventFileData);
|
||||||
router.post('/delete-file', authenticateJWT, requireFeature('event_management'), fileUploadController.deleteUploadedFile);
|
router.post('/delete-file', authenticateJWT, requireFeature('event_management'), fileUploadController.deleteUploadedFile);
|
||||||
|
// 업로드 저장 롤백
|
||||||
|
router.post('/rollback-import', authenticateJWT, requireFeature('event_management'), fileUploadController.rollbackEventImport);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@@ -12,4 +12,7 @@ router.post('/:publicHash/guest', publicAuth, publicController.addPublicGuest);
|
|||||||
// 공개 점수 수정
|
// 공개 점수 수정
|
||||||
router.put('/:publicHash/score', publicAuth, publicController.updatePublicScore);
|
router.put('/:publicHash/score', publicAuth, publicController.updatePublicScore);
|
||||||
|
|
||||||
|
// 팀 핸디캡 수정
|
||||||
|
router.put('/:publicHash/team/:teamNumber/handicap', publicAuth, publicController.updateTeamHandicap);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -29,4 +29,7 @@ router.post('/payment/request', authenticateJWT, subscriptionController.requestP
|
|||||||
router.post('/payment/success', subscriptionController.processPaymentSuccess);
|
router.post('/payment/success', subscriptionController.processPaymentSuccess);
|
||||||
router.post('/payment/fail', subscriptionController.processPaymentFail);
|
router.post('/payment/fail', subscriptionController.processPaymentFail);
|
||||||
|
|
||||||
|
// 인앱 결제 영수증 검증 엔드포인트
|
||||||
|
router.post('/verify-receipt', authenticateJWT, subscriptionController.verifyReceipt);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# Dark Mode Audit Checklist (Mobile)
|
||||||
|
|
||||||
|
본 문서는 Flutter 앱의 다크 모드 대비(가독성/명암비/아이콘 가시성)를 점검하기 위한 실기 체크리스트입니다. 점검 결과에 따라 팔레트 미세 보정 또는 위젯 레벨 스타일 수정을 권장합니다.
|
||||||
|
|
||||||
|
## 점검 범위
|
||||||
|
- 화면: `events_screen.dart`, `event_details_screen.dart`, `event_form_screen.dart`, `members_screen.dart`, `member_statistics_screen.dart`, `dashboard_screen.dart`, `club_settings_screen.dart`
|
||||||
|
- 공통 컴포넌트: `theme/badges.dart`, `widgets/dialog_actions.dart`, Chip/Button/Card/Input/SnackBar/Tooltip
|
||||||
|
|
||||||
|
## 공통 체크 항목
|
||||||
|
- [ ] 배경/전경 색상 대비가 충분한가 (텍스트/아이콘 최소 4.5:1 권장)
|
||||||
|
- [ ] 텍스트가 배경색과 혼동되지 않는가 (세컨더리/보조 텍스트 포함)
|
||||||
|
- [ ] 아이콘 색이 배경과 충분히 구분되는가 (특히 회색 계열)
|
||||||
|
- [ ] 링크/버튼/상태 배지의 의미가 색만으로 전달되지 않는가 (아이콘/레이블 병행)
|
||||||
|
- [ ] elevation/shadow가 다크 모드에서 과도하거나 부족하지 않은가
|
||||||
|
- [ ] 오버레이(다이얼로그/바텀시트/팝업) 배경과 콘텐츠 대비가 적절한가
|
||||||
|
- [ ] focus/hover/pressed 상태의 컬러가 다크 모드에서도 인지 가능한가
|
||||||
|
|
||||||
|
## 위젯별 체크 항목
|
||||||
|
- Buttons (Elevated/Outlined/Text)
|
||||||
|
- [ ] foreground/background/disabled 상태 대비
|
||||||
|
- [ ] OutlinedButton의 border 대비 (다크 테마 기준)
|
||||||
|
- Chips/FilterChip
|
||||||
|
- [ ] 선택/비선택 상태의 labelColor/backgroundColor 대비
|
||||||
|
- Cards/ListTiles
|
||||||
|
- [ ] 카드 배경/경계/섀도우 과도 여부
|
||||||
|
- [ ] 타이틀/서브타이틀 텍스트 대비 (`overflow: TextOverflow.ellipsis` 유지)
|
||||||
|
- Inputs/TextFields
|
||||||
|
- [ ] label/hint/helper/error 컬러 대비 및 가독성
|
||||||
|
- [ ] prefix/suffix 아이콘 대비
|
||||||
|
- SnackBars/Tooltips
|
||||||
|
- [ ] 텍스트/아이콘 대비, action 버튼 가독성
|
||||||
|
- Badges (`theme/badges.dart`)
|
||||||
|
- [ ] 자동 전경색 선택 로직이 다크 모드에서도 적절한가
|
||||||
|
- [ ] 상태(활성/대기/취소/완료, 회원유형/참가상태/결제상태) 배지 대비 검증
|
||||||
|
|
||||||
|
## 화면별 포인트
|
||||||
|
- EventsScreen
|
||||||
|
- [ ] 카드 타이틀/위치 ellipsis 유지 + 대비 확인
|
||||||
|
- [ ] 상태 배지 대비/간격/줄바꿈(Wrap) 확인
|
||||||
|
- EventDetailsScreen
|
||||||
|
- [ ] 기본정보 섹션 텍스트/아이콘 대비
|
||||||
|
- [ ] 탭/탭바/콘텐츠 배경 대비
|
||||||
|
- EventFormScreen
|
||||||
|
- [ ] 필수/보조 텍스트 컬러 대비 (실시간 유효성 스타일 포함)
|
||||||
|
- [ ] 공개 접근 섹션(공개 URL/비밀번호) 라벨/아이콘 대비
|
||||||
|
- Members/Metrics/Dashboard/Settings
|
||||||
|
- [ ] 배지/보조 텍스트/아이콘 전반 대비 확인
|
||||||
|
|
||||||
|
## 수행 절차(권장)
|
||||||
|
1) iOS 시뮬레이터에서 라이트/다크 전환 후 각 화면을 순회 점검
|
||||||
|
2) 스크린샷 취득 및 문제 구간 표시
|
||||||
|
3) 빠른 보정은 테마의 `colorScheme` 튜닝 또는 `BadgeStyles` 전경 색상 보정으로 처리
|
||||||
|
4) 복잡한 케이스는 위젯 로컬 스타일 적용 (ex. `Theme.of(context).brightness` 분기)
|
||||||
|
|
||||||
|
## 보정 가이드
|
||||||
|
- Material 3 ColorScheme 다크 프리셋을 기본으로 사용
|
||||||
|
- 배지 전경색: 배경 대비를 계산해 흰/검정 중 대비가 큰 색을 선택 (`theme/badges.dart` 참고)
|
||||||
|
- 회색 글자: 다크 모드에서는 명도 차이를 더 키워 가독성 확보
|
||||||
|
|
||||||
|
## 결과 기록 템플릿
|
||||||
|
```
|
||||||
|
화면: events_screen.dart
|
||||||
|
- 카드 타이틀/서브: OK
|
||||||
|
- 상태 배지: 대기 상태 배지 전경 대비 부족 → BadgeStyles.eventStatus 대기 색상 전경색 보정 필요
|
||||||
|
- 파일 업로드 다이얼로그: OK
|
||||||
|
```
|
||||||
|
|
||||||
|
## 현장 점검 기록 (2025-09-10T07:43:19+09:00 시작)
|
||||||
|
- 대상 화면
|
||||||
|
- [ ] `lib/screens/club/events_screen.dart`
|
||||||
|
- [ ] `lib/screens/club/event_details_screen.dart`
|
||||||
|
- [ ] `lib/screens/club/event_form_screen.dart`
|
||||||
|
|
||||||
|
- 초기 관찰(1차)
|
||||||
|
- events_screen.dart
|
||||||
|
- 카드 타이틀/서브: 대비 양호(OK)
|
||||||
|
- 상태 배지: 일부 회색 배경의 전경 대비 약함 → BadgeStyles.eventStatus 전경색 계산 재검토 필요
|
||||||
|
- 카드 쉐도우: 다크에서 살짝 강함 → elevation 단계 -1 검토
|
||||||
|
- event_details_screen.dart
|
||||||
|
- 섹션 타이틀/아이콘: 대비 양호(OK)
|
||||||
|
- 탭바/선택 배경: 대비 양호(OK)
|
||||||
|
- 보조 텍스트(secondary): 명도 부족 구간 존재 → Typography.secondary 톤 상향 검토
|
||||||
|
- event_form_screen.dart
|
||||||
|
- TextField label/hint: 대비 양호(OK)
|
||||||
|
- prefix/suffix 아이콘: 일부 아이콘 대비 약함 → IconTheme 다크 톤 상향 검토
|
||||||
|
- OutlinedButton border: 배경과의 대비 약함 → 테마의 outline 색상 톤 업 필요
|
||||||
|
|
||||||
|
개선 후보(누적)
|
||||||
|
- [ ] `lib/theme/badges.dart`: 배지 전경 대비 계산 로직 검토(다크 모드에서 회색계 배경 대비 강화)
|
||||||
|
- [ ] `SnackBar` action 텍스트 대비 확인 및 필요 시 컬러 보정
|
||||||
|
- [ ] 보조 텍스트(secondary) 명도 상향 검토
|
||||||
|
- [ ] OutlinedButton 다크 테마 border 색상 톤 업
|
||||||
|
- [ ] IconTheme(다크) 기본 톤 상향으로 prefix/suffix 대비 강화
|
||||||
|
- [ ] 카드 elevation 다크 모드 단계 -1 검토
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# 볼링 매니저 앱 배포 체크리스트
|
||||||
|
|
||||||
|
## 1. 앱 준비
|
||||||
|
|
||||||
|
### 1.1 코드 준비
|
||||||
|
- [ ] 모든 디버그 코드 및 로그 제거
|
||||||
|
- [ ] 릴리스 모드로 빌드 설정 확인
|
||||||
|
- [ ] 앱 버전 및 빌드 번호 업데이트
|
||||||
|
- [ ] 프로덕션 API 엔드포인트 설정 확인
|
||||||
|
- [ ] 코드 최적화 및 성능 검토 완료
|
||||||
|
|
||||||
|
### 1.2 자산 및 리소스
|
||||||
|
- [ ] 모든 이미지 및 아이콘 최적화
|
||||||
|
- [ ] 앱 아이콘 모든 크기 준비 완료
|
||||||
|
- [ ] 스플래시 화면 준비 완료
|
||||||
|
- [ ] 다국어 지원 텍스트 검토 완료
|
||||||
|
|
||||||
|
### 1.3 인앱 결제 설정
|
||||||
|
- [ ] App Store Connect에 인앱 결제 상품 등록 완료
|
||||||
|
- [ ] Google Play Console에 구독 상품 등록 완료
|
||||||
|
- [ ] 샌드박스 테스트 완료
|
||||||
|
- [ ] 실제 결제 테스트 완료 (필요시)
|
||||||
|
|
||||||
|
## 2. iOS 배포 준비
|
||||||
|
|
||||||
|
### 2.1 App Store Connect 설정
|
||||||
|
- [ ] 앱 정보 입력 완료 (이름, 설명, 키워드 등)
|
||||||
|
- [ ] 스크린샷 및 프리뷰 비디오 업로드
|
||||||
|
- [ ] 개인정보 처리방침 URL 등록
|
||||||
|
- [ ] 앱 리뷰 정보 준비 (테스트 계정 등)
|
||||||
|
- [ ] 연령 등급 설정
|
||||||
|
|
||||||
|
### 2.2 인증서 및 프로비저닝
|
||||||
|
- [ ] 배포용 인증서 유효성 확인
|
||||||
|
- [ ] 프로비저닝 프로파일 설정 확인
|
||||||
|
- [ ] 앱 ID 및 기능 활성화 확인
|
||||||
|
|
||||||
|
### 2.3 빌드 및 제출
|
||||||
|
- [ ] Archive 빌드 생성
|
||||||
|
- [ ] TestFlight 업로드 및 테스트
|
||||||
|
- [ ] 앱 심사 제출 준비
|
||||||
|
|
||||||
|
## 3. Android 배포 준비
|
||||||
|
|
||||||
|
### 3.1 Google Play Console 설정
|
||||||
|
- [ ] 스토어 등록정보 입력 완료 (이름, 설명, 카테고리 등)
|
||||||
|
- [ ] 스크린샷 및 그래픽 자산 업로드
|
||||||
|
- [ ] 개인정보 처리방침 URL 등록
|
||||||
|
- [ ] 콘텐츠 등급 설정
|
||||||
|
- [ ] 가격 및 배포 국가 설정
|
||||||
|
|
||||||
|
### 3.2 서명 및 보안
|
||||||
|
- [ ] 앱 서명 키 확인
|
||||||
|
- [ ] Play App Signing 설정 확인
|
||||||
|
- [ ] ProGuard 설정 확인 (코드 난독화)
|
||||||
|
|
||||||
|
### 3.3 빌드 및 제출
|
||||||
|
- [ ] 릴리스 APK/AAB 생성
|
||||||
|
- [ ] 내부/외부 테스트 트랙 업로드 및 테스트
|
||||||
|
- [ ] 프로덕션 출시 준비
|
||||||
|
|
||||||
|
## 4. 백엔드 배포 준비
|
||||||
|
|
||||||
|
### 4.1 서버 환경 설정
|
||||||
|
- [ ] 프로덕션 서버 환경 구성 완료
|
||||||
|
- [ ] 데이터베이스 마이그레이션 계획 수립
|
||||||
|
- [ ] 백업 및 복구 전략 확인
|
||||||
|
|
||||||
|
### 4.2 API 및 보안
|
||||||
|
- [ ] 모든 API 엔드포인트 프로덕션 환경에서 테스트 완료
|
||||||
|
- [ ] HTTPS 설정 확인
|
||||||
|
- [ ] API 키 및 인증 설정 검토
|
||||||
|
- [ ] 속도 제한 및 보안 설정 확인
|
||||||
|
|
||||||
|
### 4.3 모니터링 및 로깅
|
||||||
|
- [ ] 서버 모니터링 도구 설정
|
||||||
|
- [ ] 오류 로깅 및 알림 설정
|
||||||
|
- [ ] 성능 모니터링 설정
|
||||||
|
|
||||||
|
## 5. 법적 요구사항
|
||||||
|
|
||||||
|
### 5.1 개인정보 보호
|
||||||
|
- [ ] 개인정보 처리방침 최신화
|
||||||
|
- [ ] 데이터 수집 및 사용에 대한 동의 절차 확인
|
||||||
|
- [ ] GDPR/CCPA 등 관련 법규 준수 확인
|
||||||
|
|
||||||
|
### 5.2 이용 약관
|
||||||
|
- [ ] 이용 약관 최신화
|
||||||
|
- [ ] 구독 및 결제 관련 조항 포함 확인
|
||||||
|
- [ ] 환불 정책 명시 확인
|
||||||
|
|
||||||
|
### 5.3 지적 재산권
|
||||||
|
- [ ] 사용된 모든 라이브러리 및 자산의 라이선스 확인
|
||||||
|
- [ ] 제3자 콘텐츠 사용 권한 확인
|
||||||
|
|
||||||
|
## 6. 마케팅 및 출시 준비
|
||||||
|
|
||||||
|
### 6.1 마케팅 자료
|
||||||
|
- [ ] 보도 자료 준비
|
||||||
|
- [ ] 소셜 미디어 콘텐츠 준비
|
||||||
|
- [ ] 프로모션 계획 수립
|
||||||
|
|
||||||
|
### 6.2 사용자 지원
|
||||||
|
- [ ] 고객 지원 채널 설정
|
||||||
|
- [ ] FAQ 및 도움말 문서 준비
|
||||||
|
- [ ] 사용자 피드백 수집 방법 설정
|
||||||
|
|
||||||
|
### 6.3 출시 계획
|
||||||
|
- [ ] 출시 일정 확정
|
||||||
|
- [ ] 단계적 출시 전략 수립 (필요시)
|
||||||
|
- [ ] 출시 후 모니터링 계획 수립
|
||||||
|
|
||||||
|
## 7. 최종 검토
|
||||||
|
|
||||||
|
### 7.1 품질 보증
|
||||||
|
- [ ] 모든 주요 기능 최종 테스트 완료
|
||||||
|
- [ ] 다양한 기기 및 OS 버전에서 테스트 완료
|
||||||
|
- [ ] 성능 및 배터리 사용량 테스트 완료
|
||||||
|
|
||||||
|
### 7.2 사용자 경험
|
||||||
|
- [ ] 온보딩 프로세스 검토
|
||||||
|
- [ ] 오류 처리 및 피드백 검토
|
||||||
|
- [ ] 접근성 기능 검토
|
||||||
|
|
||||||
|
### 7.3 최종 승인
|
||||||
|
- [ ] 모든 이해관계자의 최종 승인 획득
|
||||||
|
- [ ] 출시 결정 확인
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
# PrimeVue 유사 디자인 가이드 (적용 준비 문서)
|
||||||
|
---
|
||||||
|
|
||||||
|
## 목표
|
||||||
|
- PrimeVue의 Badge/Tag, Dialog, DataTable, Toolbar, Button 스타일 특징을 분석하여 Flutter 머티리얼 디자인에 이식 가능한 규칙 수립
|
||||||
|
- 앱 전반의 시각적 일관성 및 사용성 향상
|
||||||
|
|
||||||
|
## 핵심 포인트 요약
|
||||||
|
- 색상 계층: Primary/Success/Warning/Danger + Soft Background 톤 병행
|
||||||
|
- 아이콘+텍스트 조합, Pill 형태의 배지, 여백 8px 단위 그리드
|
||||||
|
- 테이블 밀도 조절, 헤더 강조, 정렬/필터 affordance 명확화
|
||||||
|
- 다이얼로그 버튼 정렬, 파괴적 액션 시각 구분
|
||||||
|
|
||||||
|
## Flutter 적용 원칙
|
||||||
|
- Material 3 스펙을 기본으로, 색상/여백만 PrimeVue 유사 톤으로 튜닝
|
||||||
|
- 공통 위젯(배지, 다이얼로그 액션, 테이블 래퍼)로 재사용성 확보
|
||||||
|
|
||||||
|
## 컴포넌트 매핑
|
||||||
|
- Badge/Tag → Chip
|
||||||
|
- Dialog → AlertDialog/BottomSheet
|
||||||
|
- DataTable → DataTable/PaginatedDataTable(또는 community DataTable2)
|
||||||
|
- Toolbar → AppBar/SliverAppBar + Actions
|
||||||
|
- Button → Elevated/Outlined/Text
|
||||||
|
|
||||||
|
## 체크리스트
|
||||||
|
- [x] 배지 스타일 클래스 설계 및 적용 시작(`BadgeStyles`)
|
||||||
|
- [ ] 다이얼로그 액션 유틸 설계(`DialogActions`)
|
||||||
|
- [ ] 데이터 테이블 래퍼 설계
|
||||||
|
- [ ] 프리뷰 스크린/스토리북 추가
|
||||||
|
|
||||||
|
## 다음 단계
|
||||||
|
1) DialogActions 유틸 구현 및 2개 화면에 시범 적용
|
||||||
|
2) 데이터 테이블 래퍼 설계 초안 작성 및 샘플 테이블 구현
|
||||||
|
3) UI Preview 스크린에 샘플 컴포넌트 카탈로그 구성
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
# 볼링 매니저 구독 및 인앱 결제 QA 체크리스트
|
||||||
|
|
||||||
|
## 1. 구독 기능 테스트
|
||||||
|
|
||||||
|
### 1.1 구독 정보 조회
|
||||||
|
- [ ] 앱 시작 시 현재 구독 정보 로드 확인
|
||||||
|
- [ ] 구독이 없는 경우 기본(무료) 플랜 표시 확인
|
||||||
|
- [ ] 구독 상세 정보 표시 확인 (플랜 유형, 시작일, 만료일, 자동 갱신 상태 등)
|
||||||
|
|
||||||
|
### 1.2 구독 플랜 목록
|
||||||
|
- [ ] 사용 가능한 모든 구독 플랜 표시 확인
|
||||||
|
- [ ] 각 플랜의 가격, 기능, 제한 사항 등 정확히 표시 확인
|
||||||
|
- [ ] 월간/연간 요금제 전환 기능 확인
|
||||||
|
|
||||||
|
### 1.3 구독 상태별 기능 제한
|
||||||
|
- [ ] 무료 플랜 사용자의 기능 제한 확인
|
||||||
|
- [ ] 기본 플랜 사용자의 기능 접근 확인
|
||||||
|
- [ ] 프리미엄 플랜 사용자의 기능 접근 확인
|
||||||
|
- [ ] 프로 플랜 사용자의 기능 접근 확인
|
||||||
|
- [ ] 제한된 기능 접근 시 업그레이드 안내 대화상자 표시 확인
|
||||||
|
|
||||||
|
### 1.4 회원 및 이벤트 제한
|
||||||
|
- [ ] 플랜별 최대 회원 수 제한 확인
|
||||||
|
- [ ] 플랜별 최대 이벤트 수 제한 확인
|
||||||
|
- [ ] 제한 초과 시 적절한 안내 메시지 표시 확인
|
||||||
|
|
||||||
|
## 2. 인앱 결제 테스트
|
||||||
|
|
||||||
|
### 2.1 구독 구매
|
||||||
|
- [ ] 각 플랜별 구독 구매 버튼 작동 확인
|
||||||
|
- [ ] 인앱 결제 다이얼로그 표시 확인
|
||||||
|
- [ ] 결제 진행 중 로딩 표시 확인
|
||||||
|
- [ ] 결제 성공 시 구독 정보 업데이트 확인
|
||||||
|
- [ ] 결제 실패 시 적절한 오류 메시지 표시 확인
|
||||||
|
|
||||||
|
### 2.2 구독 복원
|
||||||
|
- [ ] 구독 복원 버튼 작동 확인
|
||||||
|
- [ ] 복원 진행 중 로딩 표시 확인
|
||||||
|
- [ ] 복원 성공 시 구독 정보 업데이트 확인
|
||||||
|
- [ ] 복원할 구독이 없는 경우 적절한 메시지 표시 확인
|
||||||
|
|
||||||
|
### 2.3 플랜 변경
|
||||||
|
- [ ] 현재 구독에서 다른 플랜으로 변경 기능 확인
|
||||||
|
- [ ] 업그레이드/다운그레이드 시 적절한 안내 메시지 표시 확인
|
||||||
|
- [ ] 플랜 변경 후 구독 정보 업데이트 확인
|
||||||
|
|
||||||
|
### 2.4 자동 갱신 설정
|
||||||
|
- [ ] 자동 갱신 설정 변경 기능 확인
|
||||||
|
- [ ] 설정 변경 후 상태 업데이트 확인
|
||||||
|
|
||||||
|
## 3. 백엔드 API 테스트
|
||||||
|
|
||||||
|
### 3.1 구독 관련 API
|
||||||
|
- [ ] `/subscriptions/current` - 현재 구독 정보 조회 API 테스트
|
||||||
|
- [ ] `/subscriptions/plans` - 구독 플랜 목록 조회 API 테스트
|
||||||
|
- [ ] `/subscriptions/subscribe` - 구독 생성 API 테스트
|
||||||
|
- [ ] `/subscriptions/change-with-features` - 플랜 변경 API 테스트
|
||||||
|
|
||||||
|
### 3.2 인앱 결제 검증 API
|
||||||
|
- [ ] `/subscriptions/verify-receipt` - 영수증 검증 API 테스트
|
||||||
|
- [ ] iOS 영수증 검증 테스트
|
||||||
|
- [ ] Android 영수증 검증 테스트
|
||||||
|
- [ ] 잘못된 영수증 데이터 처리 테스트
|
||||||
|
|
||||||
|
## 4. 샌드박스 테스트
|
||||||
|
|
||||||
|
### 4.1 iOS 샌드박스 테스트
|
||||||
|
- [ ] App Store Connect에서 샌드박스 테스터 계정 설정
|
||||||
|
- [ ] 샌드박스 환경에서 구독 구매 테스트
|
||||||
|
- [ ] 샌드박스 환경에서 구독 복원 테스트
|
||||||
|
- [ ] 샌드박스 환경에서 플랜 변경 테스트
|
||||||
|
|
||||||
|
### 4.2 Android 샌드박스 테스트
|
||||||
|
- [ ] Google Play Console에서 테스트 트랙 설정
|
||||||
|
- [ ] 테스트 환경에서 구독 구매 테스트
|
||||||
|
- [ ] 테스트 환경에서 구독 복원 테스트
|
||||||
|
- [ ] 테스트 환경에서 플랜 변경 테스트
|
||||||
|
|
||||||
|
## 5. 오류 처리 테스트
|
||||||
|
|
||||||
|
### 5.1 네트워크 오류
|
||||||
|
- [ ] 오프라인 상태에서 구독 관련 기능 동작 확인
|
||||||
|
- [ ] 네트워크 재연결 시 상태 복구 확인
|
||||||
|
|
||||||
|
### 5.2 서버 오류
|
||||||
|
- [ ] 서버 응답 오류 시 적절한 메시지 표시 확인
|
||||||
|
- [ ] 재시도 메커니즘 확인
|
||||||
|
|
||||||
|
### 5.3 결제 오류
|
||||||
|
- [ ] 결제 취소 시 적절한 처리 확인
|
||||||
|
- [ ] 잘못된 결제 정보 입력 시 오류 처리 확인
|
||||||
|
|
||||||
|
## 6. UI/UX 테스트
|
||||||
|
|
||||||
|
### 6.1 구독 화면 UI
|
||||||
|
- [ ] 구독 화면의 레이아웃 및 디자인 확인
|
||||||
|
- [ ] 다양한 화면 크기에서의 반응형 디자인 확인
|
||||||
|
- [ ] 다크 모드 지원 확인
|
||||||
|
|
||||||
|
### 6.2 사용자 피드백
|
||||||
|
- [ ] 로딩 상태 표시 확인
|
||||||
|
- [ ] 오류 메시지 명확성 확인
|
||||||
|
- [ ] 성공 메시지 표시 확인
|
||||||
|
|
||||||
|
## 7. 보안 테스트
|
||||||
|
|
||||||
|
### 7.1 영수증 검증
|
||||||
|
- [ ] 위조된 영수증 거부 확인
|
||||||
|
- [ ] 만료된 영수증 처리 확인
|
||||||
|
|
||||||
|
### 7.2 인증 및 권한
|
||||||
|
- [ ] 인증되지 않은 사용자의 구독 API 접근 차단 확인
|
||||||
|
- [ ] 권한이 없는 사용자의 관리자 기능 접근 차단 확인
|
||||||
|
|
||||||
|
## 8. 통합 테스트
|
||||||
|
|
||||||
|
### 8.1 전체 구독 흐름
|
||||||
|
- [ ] 구독 생성 → 플랜 변경 → 자동 갱신 설정 → 구독 취소의 전체 흐름 테스트
|
||||||
|
|
||||||
|
### 8.2 다중 기기 동기화
|
||||||
|
- [ ] 여러 기기에서 동일한 계정으로 로그인 시 구독 정보 동기화 확인
|
||||||
|
|
||||||
|
## 9. 배포 준비
|
||||||
|
|
||||||
|
### 9.1 스토어 설정
|
||||||
|
- [ ] App Store 인앱 결제 상품 설정 확인
|
||||||
|
- [ ] Google Play 구독 상품 설정 확인
|
||||||
|
- [ ] 스토어 스크린샷 및 설명에 구독 정보 포함 확인
|
||||||
|
|
||||||
|
### 9.2 법적 요구사항
|
||||||
|
- [ ] 개인정보 처리방침에 구독 관련 내용 포함 확인
|
||||||
|
- [ ] 이용 약관에 구독 조건 포함 확인
|
||||||
|
- [ ] 환불 정책 명시 확인
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
# 볼링 매니저 구독 및 인앱 결제 요구사항 및 사용자 플로우
|
||||||
|
|
||||||
|
## 1. 요구사항
|
||||||
|
|
||||||
|
### 1.1 기능적 요구사항
|
||||||
|
|
||||||
|
#### 구독 관리
|
||||||
|
- 사용자는 다양한 구독 플랜(기본, 프리미엄, 프로) 중에서 선택할 수 있어야 함
|
||||||
|
- 사용자는 월간 또는 연간 구독 옵션을 선택할 수 있어야 함
|
||||||
|
- 사용자는 현재 구독 상태를 확인할 수 있어야 함
|
||||||
|
- 사용자는 구독을 업그레이드/다운그레이드할 수 있어야 함
|
||||||
|
- 사용자는 자동 갱신 설정을 변경할 수 있어야 함
|
||||||
|
- 사용자는 이전에 구매한 구독을 복원할 수 있어야 함
|
||||||
|
|
||||||
|
#### 인앱 결제
|
||||||
|
- 앱은 플랫폼별(iOS, Android) 인앱 결제를 지원해야 함
|
||||||
|
- 결제 과정은 안전하고 사용자 친화적이어야 함
|
||||||
|
- 결제 오류 발생 시 적절한 피드백을 제공해야 함
|
||||||
|
- 결제 완료 후 즉시 구독 상태가 업데이트되어야 함
|
||||||
|
|
||||||
|
#### 기능 제한
|
||||||
|
- 구독 플랜에 따라 사용 가능한 기능이 차등 적용되어야 함
|
||||||
|
- 무료 사용자는 기본 기능만 사용할 수 있어야 함
|
||||||
|
- 구독 만료 시 접근 가능한 기능이 제한되어야 함
|
||||||
|
- 제한된 기능 접근 시 업그레이드 안내가 표시되어야 함
|
||||||
|
|
||||||
|
#### 백엔드 연동
|
||||||
|
- 구독 정보는 서버에 안전하게 저장되어야 함
|
||||||
|
- 인앱 결제 영수증은 서버에서 검증되어야 함
|
||||||
|
- 구독 상태는 여러 기기에서 동기화되어야 함
|
||||||
|
- 구독 만료 및 갱신은 서버에서 처리되어야 함
|
||||||
|
|
||||||
|
### 1.2 비기능적 요구사항
|
||||||
|
|
||||||
|
#### 보안
|
||||||
|
- 결제 정보는 안전하게 처리되어야 함
|
||||||
|
- 영수증 검증은 서버 측에서 수행되어야 함
|
||||||
|
- 사용자 인증 및 권한 관리가 적절히 구현되어야 함
|
||||||
|
|
||||||
|
#### 성능
|
||||||
|
- 구독 상태 확인은 빠르게 이루어져야 함
|
||||||
|
- 결제 처리는 최소한의 지연으로 완료되어야 함
|
||||||
|
- 오프라인 상태에서도 기본적인 기능은 사용 가능해야 함
|
||||||
|
|
||||||
|
#### 사용성
|
||||||
|
- 구독 및 결제 UI는 직관적이고 사용하기 쉬워야 함
|
||||||
|
- 오류 메시지는 명확하고 이해하기 쉬워야 함
|
||||||
|
- 구독 혜택 및 제한 사항이 명확하게 표시되어야 함
|
||||||
|
|
||||||
|
#### 확장성
|
||||||
|
- 새로운 구독 플랜 추가가 용이해야 함
|
||||||
|
- 새로운 기능 및 제한 사항 추가가 용이해야 함
|
||||||
|
- 다양한 결제 방식 추가가 가능해야 함
|
||||||
|
|
||||||
|
## 2. 사용자 플로우
|
||||||
|
|
||||||
|
### 2.1 구독 구매 플로우
|
||||||
|
|
||||||
|
1. **구독 화면 접근**
|
||||||
|
- 사용자가 앱에서 '구독' 또는 '프리미엄' 메뉴를 탭함
|
||||||
|
- 또는 제한된 기능 접근 시 표시되는 업그레이드 안내에서 '구독하기' 버튼을 탭함
|
||||||
|
|
||||||
|
2. **구독 플랜 선택**
|
||||||
|
- 사용 가능한 구독 플랜(기본, 프리미엄, 프로) 목록이 표시됨
|
||||||
|
- 각 플랜의 가격, 기능, 제한 사항 등이 표시됨
|
||||||
|
- 사용자가 원하는 플랜을 선택함
|
||||||
|
|
||||||
|
3. **월간/연간 선택**
|
||||||
|
- 선택한 플랜의 월간 또는 연간 구독 옵션이 표시됨
|
||||||
|
- 사용자가 원하는 구독 기간을 선택함
|
||||||
|
|
||||||
|
4. **결제 진행**
|
||||||
|
- 플랫폼별 인앱 결제 다이얼로그가 표시됨
|
||||||
|
- 사용자가 결제 정보를 입력하고 결제를 확인함
|
||||||
|
- 결제 진행 중 로딩 표시가 나타남
|
||||||
|
|
||||||
|
5. **결제 완료**
|
||||||
|
- 결제가 성공적으로 완료되면 성공 메시지가 표시됨
|
||||||
|
- 구독 정보가 업데이트되고 구독 상세 화면으로 이동함
|
||||||
|
- 결제 실패 시 오류 메시지가 표시되고 재시도 옵션이 제공됨
|
||||||
|
|
||||||
|
### 2.2 구독 관리 플로우
|
||||||
|
|
||||||
|
1. **구독 정보 확인**
|
||||||
|
- 사용자가 '내 구독' 또는 '계정' 메뉴를 탭함
|
||||||
|
- 현재 구독 상태, 플랜, 시작일, 만료일, 자동 갱신 상태 등이 표시됨
|
||||||
|
|
||||||
|
2. **플랜 변경**
|
||||||
|
- 사용자가 '플랜 변경' 버튼을 탭함
|
||||||
|
- 사용 가능한 다른 플랜 목록이 표시됨
|
||||||
|
- 사용자가 새 플랜을 선택하고 확인함
|
||||||
|
- 플랜 변경이 처리되고 구독 정보가 업데이트됨
|
||||||
|
|
||||||
|
3. **자동 갱신 설정**
|
||||||
|
- 사용자가 '자동 갱신' 토글 버튼을 탭함
|
||||||
|
- 설정 변경 확인 다이얼로그가 표시됨
|
||||||
|
- 사용자가 확인하면 설정이 변경되고 구독 정보가 업데이트됨
|
||||||
|
|
||||||
|
4. **구독 취소**
|
||||||
|
- 사용자가 '구독 취소' 버튼을 탭함
|
||||||
|
- 취소 확인 다이얼로그가 표시됨
|
||||||
|
- 사용자가 확인하면 구독이 취소되고 만료일까지 사용 가능함을 안내함
|
||||||
|
|
||||||
|
### 2.3 구독 복원 플로우
|
||||||
|
|
||||||
|
1. **복원 시작**
|
||||||
|
- 사용자가 '구독 복원' 버튼을 탭함
|
||||||
|
- 복원 진행 중 로딩 표시가 나타남
|
||||||
|
|
||||||
|
2. **복원 결과**
|
||||||
|
- 복원 가능한 구독이 있으면 구독이 복원되고 성공 메시지가 표시됨
|
||||||
|
- 구독 정보가 업데이트되고 구독 상세 화면으로 이동함
|
||||||
|
- 복원할 구독이 없으면 안내 메시지가 표시됨
|
||||||
|
|
||||||
|
### 2.4 제한된 기능 접근 플로우
|
||||||
|
|
||||||
|
1. **기능 접근 시도**
|
||||||
|
- 사용자가 현재 구독 플랜에서 제한된 기능에 접근을 시도함
|
||||||
|
|
||||||
|
2. **업그레이드 안내**
|
||||||
|
- 해당 기능에 접근하기 위해 필요한 구독 플랜 정보가 포함된 대화상자가 표시됨
|
||||||
|
- '구독하기' 버튼과 '취소' 버튼이 제공됨
|
||||||
|
|
||||||
|
3. **사용자 선택**
|
||||||
|
- 사용자가 '구독하기'를 선택하면 구독 화면으로 이동함
|
||||||
|
- 사용자가 '취소'를 선택하면 이전 화면으로 돌아감
|
||||||
|
|
||||||
|
## 3. 구독 플랜 및 기능
|
||||||
|
|
||||||
|
### 3.1 무료 플랜
|
||||||
|
- **기능**:
|
||||||
|
- 기본 볼링 점수 추적
|
||||||
|
- 최대 10명의 회원 관리
|
||||||
|
- 최대 5개의 이벤트 관리
|
||||||
|
|
||||||
|
### 3.2 기본 플랜
|
||||||
|
- **기능**:
|
||||||
|
- 모든 무료 기능
|
||||||
|
- 최대 30명의 회원 관리
|
||||||
|
- 최대 10개의 이벤트 관리
|
||||||
|
- 기본 통계 분석
|
||||||
|
|
||||||
|
### 3.3 프리미엄 플랜
|
||||||
|
- **기능**:
|
||||||
|
- 모든 기본 기능
|
||||||
|
- 최대 100명의 회원 관리
|
||||||
|
- 최대 50개의 이벤트 관리
|
||||||
|
- 고급 통계 분석
|
||||||
|
- 커스터마이징 옵션
|
||||||
|
|
||||||
|
### 3.4 프로 플랜
|
||||||
|
- **기능**:
|
||||||
|
- 모든 프리미엄 기능
|
||||||
|
- 최대 500명의 회원 관리
|
||||||
|
- 최대 200개의 이벤트 관리
|
||||||
|
- 우선 지원
|
||||||
|
- 모든 고급 기능 접근
|
||||||
|
|
||||||
|
## 4. 오류 처리 시나리오
|
||||||
|
|
||||||
|
### 4.1 결제 오류
|
||||||
|
- **시나리오**: 사용자가 결제를 시도했으나 결제가 실패함
|
||||||
|
- **처리**:
|
||||||
|
- 오류 원인에 따른 명확한 메시지 표시
|
||||||
|
- 재시도 옵션 제공
|
||||||
|
- 문제 해결을 위한 안내 제공
|
||||||
|
|
||||||
|
### 4.2 네트워크 오류
|
||||||
|
- **시나리오**: 구독 정보 로드 또는 결제 중 네트워크 연결이 끊김
|
||||||
|
- **처리**:
|
||||||
|
- 네트워크 오류 메시지 표시
|
||||||
|
- 자동 재연결 시도
|
||||||
|
- 오프라인 모드로 전환 (가능한 경우)
|
||||||
|
|
||||||
|
### 4.3 서버 오류
|
||||||
|
- **시나리오**: 서버에서 구독 정보 처리 중 오류 발생
|
||||||
|
- **처리**:
|
||||||
|
- 서버 오류 메시지 표시
|
||||||
|
- 관리자에게 오류 보고
|
||||||
|
- 재시도 옵션 제공
|
||||||
|
|
||||||
|
### 4.4 영수증 검증 오류
|
||||||
|
- **시나리오**: 인앱 결제는 성공했으나 영수증 검증에 실패함
|
||||||
|
- **처리**:
|
||||||
|
- 검증 오류 메시지 표시
|
||||||
|
- 자동 재검증 시도
|
||||||
|
- 수동 복원 옵션 제공
|
||||||
|
|
||||||
|
## 5. 배포 및 유지보수 계획
|
||||||
|
|
||||||
|
### 5.1 배포 준비
|
||||||
|
- App Store 및 Google Play에 인앱 결제 상품 등록
|
||||||
|
- 스토어 설명 및 스크린샷에 구독 정보 포함
|
||||||
|
- 개인정보 처리방침 및 이용 약관 업데이트
|
||||||
|
|
||||||
|
### 5.2 모니터링 및 분석
|
||||||
|
- 구독 전환율 모니터링
|
||||||
|
- 구독 취소율 및 원인 분석
|
||||||
|
- 플랜별 사용자 행동 분석
|
||||||
|
|
||||||
|
### 5.3 향후 개선 계획
|
||||||
|
- 사용자 피드백에 기반한 구독 플랜 조정
|
||||||
|
- 새로운 프리미엄 기능 추가
|
||||||
|
- 추가 결제 방식 통합 검토
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
# Flutter UI 스타일링 초안 (Material 3 기반)
|
||||||
|
---
|
||||||
|
|
||||||
|
## 목적
|
||||||
|
- Flutter 앱에서 배지/다이얼로그/데이터테이블 등 핵심 UI 컴포넌트의 일관된 스타일 가이드를 수립한다.
|
||||||
|
- Flutter Material 3 디자인을 기본으로, 앱의 브랜드 톤에 맞게 경량 튜닝한다.
|
||||||
|
|
||||||
|
## 공통 디자인 원칙
|
||||||
|
- 그림자/여백: Card, Dialog, Menu에 적절한 elevation(1~4)과 8~16px 패딩 적용
|
||||||
|
- 컬러 시스템: Primary(Blue 600/700), Success(Green 600), Warning(Amber 600), Danger(Red 600)
|
||||||
|
- 라운딩: 기본 8px, Badge(12px), 버튼 Pill 옵션은 상황별 적용
|
||||||
|
- 접근성: 대비비(AA) 확보, 의미색 + 아이콘 병행
|
||||||
|
|
||||||
|
## 배지(Badge)
|
||||||
|
- 형태: Chip + 아이콘 + 텍스트
|
||||||
|
- 높이: 24~28
|
||||||
|
- 색상 매핑 예시
|
||||||
|
- 회원유형: {정회원=Blue 100, 준회원=Indigo 100, 게스트=Grey 200}
|
||||||
|
- 참가상태: {등록됨=Grey 200, 확인됨=Green 100, 취소됨=Red 100, 참석함=Blue 100}
|
||||||
|
- 결제상태: {미결제=Red 100, 결제완료=Green 100}
|
||||||
|
- 아이콘 매핑 예시
|
||||||
|
- 정회원=person, 준회원=person_outline, 게스트=person_add_alt
|
||||||
|
- 등록됨=how_to_reg, 확인됨=check_circle, 취소됨=cancel, 참석함=event_available
|
||||||
|
- 미결제=cancel, 결제완료=check_circle
|
||||||
|
|
||||||
|
## 다이얼로그(Dialog)
|
||||||
|
- AppBar 없는 AlertDialog/BottomSheet 권장
|
||||||
|
- 타이틀은 18~20, 본문은 14~16
|
||||||
|
- 버튼 영역 Right-aligned, 기본/파괴적 버튼 구분
|
||||||
|
- 하단 액션: TextButton(취소)/ElevatedButton(확인)
|
||||||
|
|
||||||
|
## 데이터 테이블(DataTable)
|
||||||
|
- 밀도(dense) 모드: 행 높이 44~48
|
||||||
|
- 정렬 화살표, 고정 폭 컬럼 최소화, 긴 텍스트 ellipsis + Tooltip
|
||||||
|
- 헤더 강조(반투명 배경), zebra striping(선택)
|
||||||
|
|
||||||
|
## 적용 우선순위
|
||||||
|
1) Badge 컴포넌트 공통화: `_buildBadge(text, color, icon)` 유지 + 색상/아이콘 매핑 상수화
|
||||||
|
2) Dialog 일관화: 확인/취소 액션 정렬, 파괴적 액션 색상 구분
|
||||||
|
3) 리스트 아이템 overflow 방지: `Row` → `Wrap/Flexible` 전환 규칙 정립
|
||||||
|
4) 표형 데이터: `PaginatedDataTable`/`DataTable2` 검토 및 공통 위젯 래퍼 도입
|
||||||
|
|
||||||
|
## 구현 체크리스트
|
||||||
|
- [ ] `_buildBadge` 색상/아이콘 매핑 상수 분리(`lib/theme/badges.dart`)
|
||||||
|
- [ ] 다이얼로그 액션 빌더 유틸(`lib/widgets/dialog_actions.dart`)
|
||||||
|
- [ ] 리스트 아이템 텍스트/뱃지 Wrap 적용 가이드(`docs/guides/list_wrap_rules.md`)
|
||||||
|
- [ ] 데이터 테이블 공용 래퍼(`lib/widgets/data_table_wrappers.dart`)
|
||||||
|
- [ ] 샘플 스크린/스토리북(`lib/screens/dev/ui_preview_screen.dart`)
|
||||||
|
|
||||||
|
## 참고
|
||||||
|
- Material 3 디자인 가이드
|
||||||
|
- (선택) 타 UI 라이브러리에서 유용한 패턴이 있을 경우 제한적으로 참고
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
<meta property="og:url" content="https://lanebow.com">
|
<meta property="og:url" content="https://lanebow.com">
|
||||||
<meta property="og:image" content="/LaneBowLogo.png">
|
<meta property="og:image" content="/LaneBowLogo.png">
|
||||||
|
<meta name="google-adsense-account" content="ca-pub-4239250687600488">
|
||||||
<title>LaneBow-레인보우</title>
|
<title>LaneBow-레인보우</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
"vee-validate": "^4.12.5",
|
"vee-validate": "^4.12.5",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-chartjs": "^5.3.2",
|
"vue-chartjs": "^5.3.2",
|
||||||
|
"vue-gtag": "^3.5.1",
|
||||||
|
"vue-gtag-next": "^1.14.0",
|
||||||
"vue-router": "^4.5.0",
|
"vue-router": "^4.5.0",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
"xlsx": "^0.18.5",
|
"xlsx": "^0.18.5",
|
||||||
@@ -3748,6 +3750,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-gtag": {
|
||||||
|
"version": "3.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-gtag/-/vue-gtag-3.5.1.tgz",
|
||||||
|
"integrity": "sha512-fyXQeIIc2cMibgggCbIZKfP0NvVbMupi4yl7CIwnD2feL9MGVVjzN/3nb5mX9AmZhIrkYri9dmBX6jY0lRCKDg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.5.13",
|
||||||
|
"vue-router": "^4.5.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"vue-router": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vue-gtag-next": {
|
||||||
|
"version": "1.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-gtag-next/-/vue-gtag-next-1.14.0.tgz",
|
||||||
|
"integrity": "sha512-iJl+cOG2GU5NuxqzSSIpt03WVOvZqyKB9TOy7d55KiuvRklcnb2nlqxW5B/a3/sbIt7fla+XEkRyMCcoz0zAHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.0.0-rc.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue-router": {
|
"node_modules/vue-router": {
|
||||||
"version": "4.5.1",
|
"version": "4.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz",
|
||||||
|
|||||||
@@ -35,6 +35,8 @@
|
|||||||
"vee-validate": "^4.12.5",
|
"vee-validate": "^4.12.5",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-chartjs": "^5.3.2",
|
"vue-chartjs": "^5.3.2",
|
||||||
|
"vue-gtag": "^3.5.1",
|
||||||
|
"vue-gtag-next": "^1.14.0",
|
||||||
"vue-router": "^4.5.0",
|
"vue-router": "^4.5.0",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
"xlsx": "^0.18.5",
|
"xlsx": "^0.18.5",
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
google.com, pub-4239250687600488, DIRECT, f08c47fec0942fa0
|
||||||
@@ -0,0 +1,845 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="description" content="볼링 구속 계산기 - 스톱워치, 영상으로 볼링 구속을 측정해보세요.">
|
||||||
|
<meta property="og:title" content="레인보우 - 볼링 구속 계산기">
|
||||||
|
<meta property="og:description" content="볼링 구속 계산기 - 스톱워치, 영상으로 볼링 구속을 측정해보세요.">
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:url" content="https://lanebow.com/bowlspeed">
|
||||||
|
<meta property="og:image" content="/LaneBowLogo.png">
|
||||||
|
<title>레인보우 - 볼링 구속 계산기</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Nanum Gothic', Arial, sans-serif;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
color: #333;
|
||||||
|
touch-action: pan-y;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #2c3e50;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
background-color: #eaf2f8;
|
||||||
|
padding: 7px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.description p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.calculator {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
padding: 7px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
.input-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
input[type="text"], input[type="number"] {
|
||||||
|
width: 100px;
|
||||||
|
padding: 6px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-input {
|
||||||
|
width: 100px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.distance-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.distance-half {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
margin: 20px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: #3498db;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: #2980b9;
|
||||||
|
}
|
||||||
|
.results {
|
||||||
|
margin-top: 20px;
|
||||||
|
background-color: #f0f8ff;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #b3d8ff;
|
||||||
|
}
|
||||||
|
.result-item {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-item span:first-child {
|
||||||
|
min-width: 120px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-item .unit {
|
||||||
|
min-width: 40px;
|
||||||
|
text-align: left;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.result-item {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-item span:first-child {
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"], input[type="number"], .result-input {
|
||||||
|
width: 90px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-value {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2980b9;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
margin-top: 30px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
.stopwatch {
|
||||||
|
background-color: #e8f4fc;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid #b3d8ff;
|
||||||
|
}
|
||||||
|
.stopwatch-display {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
margin: 10px 0;
|
||||||
|
color: #2c3e50;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
.stopwatch-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.toggle-btn {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #27ae60;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.toggle-btn.running {
|
||||||
|
background-color: #e74c3c;
|
||||||
|
}
|
||||||
|
.toggle-btn:hover {
|
||||||
|
background-color: #219653;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
.toggle-btn.running:hover {
|
||||||
|
background-color: #c0392b;
|
||||||
|
}
|
||||||
|
.reset-btn {
|
||||||
|
background-color: #f39c12;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.reset-btn:hover {
|
||||||
|
background-color: #d35400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
.tab-btn {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px 0;
|
||||||
|
font-size: 17px;
|
||||||
|
background: #eaf2f8;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
|
color: #2980b9;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s, color 0.2s;
|
||||||
|
}
|
||||||
|
.tab-btn.active {
|
||||||
|
background: #fff;
|
||||||
|
color: #222;
|
||||||
|
border-bottom: 2px solid #2980b9;
|
||||||
|
}
|
||||||
|
.tab-content {
|
||||||
|
display: none;
|
||||||
|
animation: fadein 0.25s;
|
||||||
|
}
|
||||||
|
.tab-content.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.video-section {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
.video-controls {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.video-upload-btn {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
background: #3498db;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 1.08em;
|
||||||
|
padding: 12px 32px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.18s;
|
||||||
|
box-shadow: 0 1px 4px rgba(52,152,219,0.08);
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.video-upload-btn:hover, .video-upload-btn:focus {
|
||||||
|
background: #217dbb;
|
||||||
|
}
|
||||||
|
.video-preview {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.video-preview.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.video-preview-large {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.video-preview-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.timeline-btns {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.timeline-btn {
|
||||||
|
flex: 1;
|
||||||
|
background: #3498db;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 1.08em;
|
||||||
|
padding: 12px 32px;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.18s;
|
||||||
|
box-shadow: 0 1px 4px rgba(52,152,219,0.08);
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.timeline-btn:hover, .timeline-btn:focus {
|
||||||
|
background: #217dbb;
|
||||||
|
}
|
||||||
|
.reset-frame-btn {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin: 12px 0 0 0;
|
||||||
|
}
|
||||||
|
.frame-time-info {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #2980b9;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!-- Google Tag Manager -->
|
||||||
|
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
||||||
|
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||||
|
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||||
|
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||||
|
})(window,document,'script','dataLayer','GTM-PPDRKT7Q');</script>
|
||||||
|
<!-- End Google Tag Manager -->
|
||||||
|
<!-- Google AdSense -->
|
||||||
|
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-4239250687600488"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<!-- End Google AdSense -->
|
||||||
|
<script>
|
||||||
|
// 페이지 진입 시 GA 페이지뷰 이벤트 전송
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
window.dataLayer.push({
|
||||||
|
event: 'page_view',
|
||||||
|
page_title: document.title,
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
page_location: window.location.href
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Google Tag Manager (noscript) -->
|
||||||
|
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-PPDRKT7Q"
|
||||||
|
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
|
||||||
|
<!-- End Google Tag Manager (noscript) -->
|
||||||
|
<div class="container">
|
||||||
|
<h1>볼링공 속도 계산기</h1>
|
||||||
|
|
||||||
|
<div class="description">
|
||||||
|
<p>이 계산기는 볼링공이 파울라인에서 헤드핀까지 이동하는 속도를 계산합니다.</p>
|
||||||
|
<p>시간 입력 또는 원하는 속도 입력으로 양방향 계산이 가능합니다. 볼링 레인의 기본 거리는 60피트(18.288미터)입니다.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-container">
|
||||||
|
<button class="tab-btn active" id="tab-stopwatch">스톱워치</button>
|
||||||
|
<button class="tab-btn" id="tab-video">영상</button>
|
||||||
|
</div>
|
||||||
|
<div id="tab-content-stopwatch" class="tab-content active">
|
||||||
|
<h3>스톱워치로 측정</h3>
|
||||||
|
<p>볼링공이 파울라인을 지날 때 '시작'을 누르고, 헤드핀에 도달할 때 '정지'를 누르세요.</p>
|
||||||
|
<div class="stopwatch-display" id="stopwatch-display">00:00.000</div>
|
||||||
|
<div class="stopwatch-container">
|
||||||
|
<button class="toggle-btn" id="toggle-btn"
|
||||||
|
onclick="dataLayer.push({event:'button_click', event_category:'볼스피드', event_label:'스톱워치'});">
|
||||||
|
시작
|
||||||
|
</button> <!-- [GA] 시작 버튼 클릭 추적 -->
|
||||||
|
<button class="reset-btn" id="reset-btn"
|
||||||
|
onclick="dataLayer.push({event:'button_click', event_category:'볼스피드', event_label:'초기화'});">
|
||||||
|
초기화
|
||||||
|
</button> <!-- [GA] 초기화 버튼 클릭 추적 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 영상 업로드/촬영 및 프레임 선택 UI -->
|
||||||
|
<div id="tab-content-video" class="video-section tab-content">
|
||||||
|
<h2>볼링 영상으로 측정</h2>
|
||||||
|
<div class="video-controls">
|
||||||
|
<label class="video-upload-btn">
|
||||||
|
영상 업로드/촬영
|
||||||
|
<input type="file" id="video-upload" accept="video/*" style="display:none;">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="video-preview-wrapper">
|
||||||
|
<video id="video-preview" class="video-preview-large" controls></video>
|
||||||
|
</div>
|
||||||
|
<p class="frame-time-info">영상의 타임라인을 이동하여 "시작/종료 지점 선택" 버튼을 눌러주세요.</p>
|
||||||
|
<div class="timeline-btns">
|
||||||
|
<button id="set-start-btn" class="timeline-btn" type="button">시작 지점 선택</button>
|
||||||
|
<button id="set-end-btn" class="timeline-btn" type="button">종료 지점 선택</button>
|
||||||
|
</div>
|
||||||
|
<button id="reset-frame-btn" class="timeline-btn reset-frame-btn" type="button">구간 초기화</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="calculator">
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="time">볼링공 이동 시간:</label>
|
||||||
|
<input type="number" id="time" step="0.001" value="2.000" min="0.001"> 초
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group distance-container">
|
||||||
|
<div class="distance-half">
|
||||||
|
<label for="distance-meters">거리 (미터):</label>
|
||||||
|
<input type="number" id="distance-meters" value="18.288" step="0.001" min="0.1" onchange="updateFeet()"> 미터
|
||||||
|
</div>
|
||||||
|
<div class="distance-half">
|
||||||
|
<label for="distance-feet">거리 (피트):</label>
|
||||||
|
<input type="number" id="distance-feet" value="60.00" step="0.01" min="1" onchange="updateMeters()"> 피트
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<button onclick="dataLayer.push({event:'button_click', event_category:'볼스피드', event_label:'계산하기'}); calculateSpeed()">계산하기</button> <!-- [GA] 계산하기 버튼 클릭 추적 -->
|
||||||
|
<button onclick="dataLayer.push({event:'button_click', event_category:'볼스피드', event_label:'계산초기화'}); resetValues()">초기화</button> <!-- [GA] 계산초기화 버튼 클릭 추적 -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="results" id="results">
|
||||||
|
<div class="result-item">
|
||||||
|
<span>시속 (킬로미터):</span>
|
||||||
|
<input type="number" class="result-input" id="kph" value="0" step="0.01" min="0" onchange="calculateTimeFromKPH()">
|
||||||
|
<span class="unit">KPH</span>
|
||||||
|
</div>
|
||||||
|
<div class="result-item">
|
||||||
|
<span>시속 (마일):</span>
|
||||||
|
<input type="number" class="result-input" id="mph" value="0" step="0.01" min="0" onchange="calculateTimeFromMPH()">
|
||||||
|
<span class="unit">MPH</span>
|
||||||
|
</div>
|
||||||
|
<div class="result-item">
|
||||||
|
<span>초속 (피트):</span>
|
||||||
|
<input type="number" class="result-input" id="fps" value="0" step="0.01" min="0" onchange="calculateTimeFromFPS()">
|
||||||
|
<span class="unit">FPS</span>
|
||||||
|
</div>
|
||||||
|
<div class="result-item">
|
||||||
|
<span>초속 (미터):</span>
|
||||||
|
<input type="number" class="result-input" id="mps" value="0" step="0.01" min="0" onchange="calculateTimeFromMPS()">
|
||||||
|
<span class="unit">MPS</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- display -->
|
||||||
|
<ins class="adsbygoogle"
|
||||||
|
style="display:block"
|
||||||
|
data-ad-client="ca-pub-4239250687600488"
|
||||||
|
data-ad-slot="3858930866"
|
||||||
|
data-ad-format="auto"
|
||||||
|
data-full-width-responsive="true"></ins>
|
||||||
|
<script>
|
||||||
|
(adsbygoogle = window.adsbygoogle || []).push({});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p> 2025 볼링 구속 계산기 © <a href="https://lanebow.com" target="_blank">레인보우 - 볼링클럽 매니저</a></p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 소수점 자릿수 반올림 함수
|
||||||
|
function perRound(num, precision) {
|
||||||
|
precision = precision || 3;
|
||||||
|
precision = parseInt(precision);
|
||||||
|
var result1 = num * Math.pow(10, precision);
|
||||||
|
var result2 = Math.round(result1);
|
||||||
|
var result3 = result2 / Math.pow(10, precision);
|
||||||
|
return zerosPad(result3, precision);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 소수점 자릿수 0 채우기 함수
|
||||||
|
function zerosPad(rndVal, decPlaces) {
|
||||||
|
var valStrg = rndVal.toString();
|
||||||
|
var decLoc = valStrg.indexOf(".");
|
||||||
|
|
||||||
|
if (decLoc == -1) {
|
||||||
|
decPartLen = 0;
|
||||||
|
valStrg += decPlaces > 0 ? "." : "";
|
||||||
|
} else {
|
||||||
|
decPartLen = valStrg.length - decLoc - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalPad = decPlaces - decPartLen;
|
||||||
|
if (totalPad > 0) {
|
||||||
|
for (var i = 1; i <= totalPad; i++) {
|
||||||
|
valStrg += "0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return valStrg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 미터를 피트로 변환
|
||||||
|
function updateFeet() {
|
||||||
|
var meters = document.getElementById("distance-meters").value;
|
||||||
|
var feet = perRound(meters * 3.2808399, 2);
|
||||||
|
document.getElementById("distance-feet").value = feet;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 피트를 미터로 변환
|
||||||
|
function updateMeters() {
|
||||||
|
var feet = document.getElementById("distance-feet").value;
|
||||||
|
var meters = perRound(feet * 0.3048, 3);
|
||||||
|
document.getElementById("distance-meters").value = meters;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 속도 계산 함수
|
||||||
|
function calculateSpeed() {
|
||||||
|
var time = parseFloat(document.getElementById("time").value);
|
||||||
|
var distance = parseFloat(document.getElementById("distance-feet").value);
|
||||||
|
|
||||||
|
if (time <= 0) {
|
||||||
|
alert("시간은 0보다 커야 합니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (distance <= 0) {
|
||||||
|
alert("거리는 0보다 커야 합니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 속도 계산 (원본 코드의 공식 사용)
|
||||||
|
var mph = perRound((distance / time) * (3600 / 5280), 2);
|
||||||
|
var kph = perRound((distance / time) * (3600 * 0.0003048), 2);
|
||||||
|
var fps = perRound(distance / time, 2);
|
||||||
|
var mps = perRound((distance * 0.3048) / time, 2);
|
||||||
|
|
||||||
|
// 결과 표시 (입력 필드에 값 설정)
|
||||||
|
document.getElementById("mph").value = mph;
|
||||||
|
document.getElementById("kph").value = kph;
|
||||||
|
document.getElementById("fps").value = fps;
|
||||||
|
document.getElementById("mps").value = mps;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 시속(KPH)에서 시간 계산
|
||||||
|
function calculateTimeFromKPH() {
|
||||||
|
var kph = parseFloat(document.getElementById("kph").value);
|
||||||
|
var distance = parseFloat(document.getElementById("distance-meters").value);
|
||||||
|
|
||||||
|
if (kph <= 0) {
|
||||||
|
alert("속도는 0보다 커야 합니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// KPH에서 시간(초) 계산: 거리(m) / 속도(m/s) = 시간(s)
|
||||||
|
// KPH를 m/s로 변환: kph * 1000 / 3600
|
||||||
|
var speedInMps = kph * 1000 / 3600;
|
||||||
|
var time = distance / speedInMps;
|
||||||
|
|
||||||
|
// 시간 설정 및 다른 속도 값 업데이트
|
||||||
|
document.getElementById("time").value = perRound(time, 3);
|
||||||
|
|
||||||
|
// 다른 속도 단위 계산
|
||||||
|
var distanceFeet = parseFloat(document.getElementById("distance-feet").value);
|
||||||
|
var mph = perRound((distanceFeet / time) * (3600 / 5280), 2);
|
||||||
|
var fps = perRound(distanceFeet / time, 2);
|
||||||
|
var mps = perRound(speedInMps, 2);
|
||||||
|
|
||||||
|
// 다른 속도 필드 업데이트 (무한 루프 방지를 위해 이벤트 발생 없이)
|
||||||
|
document.getElementById("mph").value = mph;
|
||||||
|
document.getElementById("fps").value = fps;
|
||||||
|
document.getElementById("mps").value = mps;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 시속(MPH)에서 시간 계산
|
||||||
|
function calculateTimeFromMPH() {
|
||||||
|
var mph = parseFloat(document.getElementById("mph").value);
|
||||||
|
var distance = parseFloat(document.getElementById("distance-feet").value);
|
||||||
|
|
||||||
|
if (mph <= 0) {
|
||||||
|
alert("속도는 0보다 커야 합니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MPH에서 시간(초) 계산: 거리(feet) / 속도(feet/s) = 시간(s)
|
||||||
|
// MPH를 feet/s로 변환: mph * 5280 / 3600
|
||||||
|
var speedInFps = mph * 5280 / 3600;
|
||||||
|
var time = distance / speedInFps;
|
||||||
|
|
||||||
|
// 시간 설정 및 다른 속도 값 업데이트
|
||||||
|
document.getElementById("time").value = perRound(time, 3);
|
||||||
|
|
||||||
|
// 다른 속도 단위 계산
|
||||||
|
var kph = perRound(mph * 1.60934, 2);
|
||||||
|
var fps = perRound(speedInFps, 2);
|
||||||
|
var mps = perRound(speedInFps * 0.3048, 2);
|
||||||
|
|
||||||
|
// 다른 속도 필드 업데이트
|
||||||
|
document.getElementById("kph").value = kph;
|
||||||
|
document.getElementById("fps").value = fps;
|
||||||
|
document.getElementById("mps").value = mps;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 초속(FPS)에서 시간 계산
|
||||||
|
function calculateTimeFromFPS() {
|
||||||
|
var fps = parseFloat(document.getElementById("fps").value);
|
||||||
|
var distance = parseFloat(document.getElementById("distance-feet").value);
|
||||||
|
|
||||||
|
if (fps <= 0) {
|
||||||
|
alert("속도는 0보다 커야 합니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FPS에서 시간(초) 계산: 거리(feet) / 속도(feet/s) = 시간(s)
|
||||||
|
var time = distance / fps;
|
||||||
|
|
||||||
|
// 시간 설정 및 다른 속도 값 업데이트
|
||||||
|
document.getElementById("time").value = perRound(time, 3);
|
||||||
|
|
||||||
|
// 다른 속도 단위 계산
|
||||||
|
var mph = perRound(fps * 3600 / 5280, 2);
|
||||||
|
var kph = perRound(fps * 0.3048 * 3.6, 2);
|
||||||
|
var mps = perRound(fps * 0.3048, 2);
|
||||||
|
|
||||||
|
// 다른 속도 필드 업데이트
|
||||||
|
document.getElementById("mph").value = mph;
|
||||||
|
document.getElementById("kph").value = kph;
|
||||||
|
document.getElementById("mps").value = mps;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 초속(MPS)에서 시간 계산
|
||||||
|
function calculateTimeFromMPS() {
|
||||||
|
var mps = parseFloat(document.getElementById("mps").value);
|
||||||
|
var distance = parseFloat(document.getElementById("distance-meters").value);
|
||||||
|
|
||||||
|
if (mps <= 0) {
|
||||||
|
alert("속도는 0보다 커야 합니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MPS에서 시간(초) 계산: 거리(m) / 속도(m/s) = 시간(s)
|
||||||
|
var time = distance / mps;
|
||||||
|
|
||||||
|
// 시간 설정 및 다른 속도 값 업데이트
|
||||||
|
document.getElementById("time").value = perRound(time, 3);
|
||||||
|
|
||||||
|
// 다른 속도 단위 계산
|
||||||
|
var distanceFeet = parseFloat(document.getElementById("distance-feet").value);
|
||||||
|
var fps = perRound(mps / 0.3048, 2);
|
||||||
|
var mph = perRound(mps * 2.23694, 2);
|
||||||
|
var kph = perRound(mps * 3.6, 2);
|
||||||
|
|
||||||
|
// 다른 속도 필드 업데이트
|
||||||
|
document.getElementById("mph").value = mph;
|
||||||
|
document.getElementById("kph").value = kph;
|
||||||
|
document.getElementById("fps").value = fps;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 초기화 함수
|
||||||
|
function resetValues() {
|
||||||
|
document.getElementById("time").value = "2.000";
|
||||||
|
document.getElementById("distance-feet").value = "60.00";
|
||||||
|
document.getElementById("distance-meters").value = "18.288";
|
||||||
|
document.getElementById("mph").value = "0";
|
||||||
|
document.getElementById("kph").value = "0";
|
||||||
|
document.getElementById("fps").value = "0";
|
||||||
|
document.getElementById("mps").value = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 스톱워치 관련 변수
|
||||||
|
let stopwatchInterval;
|
||||||
|
let wStartTime;
|
||||||
|
let wElapsedTime = 0;
|
||||||
|
let wIsRunning = false;
|
||||||
|
|
||||||
|
// 스톱워치 시작/정지 토글 함수
|
||||||
|
function toggleStopwatch() {
|
||||||
|
const toggleBtn = document.getElementById('toggle-btn');
|
||||||
|
|
||||||
|
if (!wIsRunning) {
|
||||||
|
// 시작
|
||||||
|
wStartTime = Date.now() - wElapsedTime;
|
||||||
|
stopwatchInterval = setInterval(updateStopwatch, 10);
|
||||||
|
wIsRunning = true;
|
||||||
|
|
||||||
|
// 버튼 상태 변경
|
||||||
|
toggleBtn.textContent = '정지';
|
||||||
|
toggleBtn.classList.add('running');
|
||||||
|
} else {
|
||||||
|
// 정지
|
||||||
|
clearInterval(stopwatchInterval);
|
||||||
|
wElapsedTime = Date.now() - wStartTime;
|
||||||
|
wIsRunning = false;
|
||||||
|
|
||||||
|
// 버튼 상태 변경
|
||||||
|
toggleBtn.textContent = '시작';
|
||||||
|
toggleBtn.classList.remove('running');
|
||||||
|
|
||||||
|
// 스톱워치 정지 시 정확한 시간 계산
|
||||||
|
updateStopwatchDisplay(); // 스톱워치 표시 갱신
|
||||||
|
|
||||||
|
// 스톱워치와 동일한 값을 입력 필드에 설정
|
||||||
|
const exactSeconds = Math.floor((wElapsedTime % 60000) / 1000) + ((wElapsedTime % 1000) / 1000);
|
||||||
|
document.getElementById('time').value = exactSeconds.toFixed(3);
|
||||||
|
|
||||||
|
calculateSpeed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 스톱워치 초기화 함수
|
||||||
|
function resetStopwatch() {
|
||||||
|
clearInterval(stopwatchInterval);
|
||||||
|
wElapsedTime = 0;
|
||||||
|
wIsRunning = false;
|
||||||
|
updateStopwatchDisplay();
|
||||||
|
|
||||||
|
// 버튼 상태 변경
|
||||||
|
const toggleBtn = document.getElementById('toggle-btn');
|
||||||
|
toggleBtn.textContent = '시작';
|
||||||
|
toggleBtn.classList.remove('running');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 스톱워치 표시 업데이트 함수
|
||||||
|
function updateStopwatch() {
|
||||||
|
wElapsedTime = Date.now() - wStartTime;
|
||||||
|
updateStopwatchDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 스톱워치 시간 표시 형식 변환 함수
|
||||||
|
function updateStopwatchDisplay() {
|
||||||
|
const minutes = Math.floor(wElapsedTime / 60000);
|
||||||
|
const seconds = Math.floor((wElapsedTime % 60000) / 1000);
|
||||||
|
const milliseconds = wElapsedTime % 1000;
|
||||||
|
|
||||||
|
const display = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${milliseconds.toString().padStart(3, '0')}`;
|
||||||
|
document.getElementById('stopwatch-display').textContent = display;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 페이지 로드 시 초기 계산 및 이벤트 리스너 설정
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
calculateSpeed();
|
||||||
|
|
||||||
|
// 스톱워치 버튼에 이벤트 리스너 추가
|
||||||
|
const toggleBtn = document.getElementById('toggle-btn');
|
||||||
|
const resetBtn = document.getElementById('reset-btn');
|
||||||
|
|
||||||
|
// mousedown과 touchstart 이벤트를 모두 추가하여 모바일과 데스크톱 모두 지원
|
||||||
|
toggleBtn.addEventListener('mousedown', toggleStopwatch);
|
||||||
|
toggleBtn.addEventListener('touchstart', function(e) {
|
||||||
|
e.preventDefault(); // 화면 스크롤 방지
|
||||||
|
toggleStopwatch();
|
||||||
|
});
|
||||||
|
|
||||||
|
resetBtn.addEventListener('mousedown', resetStopwatch);
|
||||||
|
resetBtn.addEventListener('touchstart', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
resetStopwatch();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// --- 탭 전환 기능 개선 ---
|
||||||
|
const tabStopwatch = document.getElementById('tab-stopwatch');
|
||||||
|
const tabVideo = document.getElementById('tab-video');
|
||||||
|
const tabContentStopwatch = document.getElementById('tab-content-stopwatch');
|
||||||
|
const tabContentVideo = document.getElementById('tab-content-video');
|
||||||
|
function activateTab(tab) {
|
||||||
|
[tabStopwatch, tabVideo].forEach(btn => btn.classList.remove('active'));
|
||||||
|
[tabContentStopwatch, tabContentVideo].forEach(c => c.classList.remove('active'));
|
||||||
|
if(tab === 'stopwatch') {
|
||||||
|
tabStopwatch.classList.add('active');
|
||||||
|
tabContentStopwatch.classList.add('active');
|
||||||
|
} else {
|
||||||
|
tabVideo.classList.add('active');
|
||||||
|
tabContentVideo.classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tabStopwatch.onclick = () => activateTab('stopwatch');
|
||||||
|
tabVideo.onclick = () => activateTab('video');
|
||||||
|
// 최초 로드 시
|
||||||
|
activateTab('stopwatch');
|
||||||
|
|
||||||
|
// --- 영상 업로드/촬영 & 구간(프레임) 선택 기능 (시커바 기반) ---
|
||||||
|
const videoUpload = document.getElementById('video-upload');
|
||||||
|
const videoPreview = document.getElementById('video-preview');
|
||||||
|
const fileNameLabel = document.getElementById('file-name-label');
|
||||||
|
|
||||||
|
const setStartBtn = document.getElementById('set-start-btn');
|
||||||
|
const setEndBtn = document.getElementById('set-end-btn');
|
||||||
|
const resetFrameBtn = document.getElementById('reset-frame-btn');
|
||||||
|
let mediaRecorder, stream = null;
|
||||||
|
let videoFile = null;
|
||||||
|
let duration = 0;
|
||||||
|
|
||||||
|
// startTime, endTime은 오직 여기서만 선언
|
||||||
|
let startTime = null;
|
||||||
|
let endTime = null;
|
||||||
|
const DIST_M = 18.288;
|
||||||
|
|
||||||
|
// 파일 업로드/촬영
|
||||||
|
videoUpload.onchange = function(e) {
|
||||||
|
if (!e.target.files || !e.target.files[0]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
videoFile = e.target.files[0];
|
||||||
|
videoPreview.src = URL.createObjectURL(videoFile);
|
||||||
|
videoPreview.classList.add('active');
|
||||||
|
videoPreview.onloadedmetadata = function() {
|
||||||
|
duration = videoPreview.duration;
|
||||||
|
startTime = null;
|
||||||
|
endTime = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 비디오 컨트롤(내장 시커바) 기반 시작/종료 지점 선택
|
||||||
|
setStartBtn.onclick = function() {
|
||||||
|
if (duration === 0) return;
|
||||||
|
startTime = videoPreview.currentTime;
|
||||||
|
setStartBtn.textContent = `시작 지점: ${startTime.toFixed(3)}초`;
|
||||||
|
if (endTime !== null && endTime <= startTime) {
|
||||||
|
endTime = null;
|
||||||
|
setEndBtn.textContent = '종료 지점 선택';
|
||||||
|
}
|
||||||
|
updateSection();
|
||||||
|
};
|
||||||
|
setEndBtn.onclick = function() {
|
||||||
|
if (duration === 0) return;
|
||||||
|
if (startTime === null) {
|
||||||
|
alert('먼저 시작 지점을 선택하세요.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
endTime = videoPreview.currentTime;
|
||||||
|
if (endTime <= startTime) {
|
||||||
|
alert('종료 지점은 시작 지점보다 뒤여야 합니다.');
|
||||||
|
endTime = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setEndBtn.textContent = `종료 지점: ${endTime.toFixed(3)}초`;
|
||||||
|
updateSection();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 구간 초기화 버튼 클릭 시 버튼 텍스트도 초기화
|
||||||
|
resetFrameBtn.onclick = function() {
|
||||||
|
startTime = null;
|
||||||
|
endTime = null;
|
||||||
|
setStartBtn.textContent = '시작 지점 선택';
|
||||||
|
setEndBtn.textContent = '종료 지점 선택';
|
||||||
|
document.getElementById('distance-meters').value = DIST_M;
|
||||||
|
document.getElementById('distance-feet').value = perRound(DIST_M * 3.2808399, 2);
|
||||||
|
document.getElementById('time').value = '2.000';
|
||||||
|
calculateSpeed();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 구간 시간 및 속도 계산
|
||||||
|
function updateSection() {
|
||||||
|
if (startTime !== null && endTime !== null) {
|
||||||
|
const dt = endTime - startTime;
|
||||||
|
document.getElementById('time').value = dt.toFixed(3);
|
||||||
|
calculateSpeed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
User-agent: Googlebot
|
||||||
|
Disallow: /admin/
|
||||||
|
Disallow: /private/
|
||||||
|
|
||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
@@ -94,6 +94,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { getEventTypeLabel, getEventStatusLabel } from '@/utils/enumMappings';
|
import { getEventTypeLabel, getEventStatusLabel } from '@/utils/enumMappings';
|
||||||
import { ref, computed, watch } from 'vue';
|
import { ref, computed, watch } from 'vue';
|
||||||
|
import eventService from '../../services/eventService';
|
||||||
import TeamGeneration from './TeamGeneration.vue';
|
import TeamGeneration from './TeamGeneration.vue';
|
||||||
import Dialog from 'primevue/dialog';
|
import Dialog from 'primevue/dialog';
|
||||||
import ParticipantManagement from './ParticipantManagement.vue';
|
import ParticipantManagement from './ParticipantManagement.vue';
|
||||||
@@ -157,10 +158,34 @@ watch(() => eventModel.value.id, async (eventId) => {
|
|||||||
|
|
||||||
// 참가확정 참가자 목록을 계산하는 computed 속성
|
// 참가확정 참가자 목록을 계산하는 computed 속성
|
||||||
const confirmedParticipants = ref([]);
|
const confirmedParticipants = ref([]);
|
||||||
function updateConfirmedParticipants() {
|
async function updateConfirmedParticipants() {
|
||||||
|
try {
|
||||||
|
// 이벤트 참가자 필터링
|
||||||
const arr = eventModel.value.EventParticipants?.filter(p => p.status === 'confirmed' || p.status === 'pending') || [];
|
const arr = eventModel.value.EventParticipants?.filter(p => p.status === 'confirmed' || p.status === 'pending') || [];
|
||||||
|
|
||||||
|
// 참가자 데이터에 에버리지 정보 추가
|
||||||
|
if (arr.length > 0 && eventModel.value.id) {
|
||||||
|
// 최신 참가자 데이터 가져오기
|
||||||
|
const latestParticipants = await eventService.getEventParticipants(eventModel.value.id);
|
||||||
|
|
||||||
|
// 참가자 데이터 매핑 (에버리지 정보 포함)
|
||||||
|
confirmedParticipants.value = arr.map(p => {
|
||||||
|
// 최신 참가자 데이터에서 해당 참가자 찾기
|
||||||
|
const latestData = latestParticipants.find(lp => lp.id === p.id);
|
||||||
|
if (latestData) {
|
||||||
|
// 최신 데이터로 업데이트 (에버리지 정보 포함)
|
||||||
|
return latestData;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
confirmedParticipants.value = arr;
|
confirmedParticipants.value = arr;
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('참가자 데이터 업데이트 중 오류:', error);
|
||||||
|
confirmedParticipants.value = eventModel.value.EventParticipants?.filter(p => p.status === 'confirmed' || p.status === 'pending') || [];
|
||||||
|
}
|
||||||
|
}
|
||||||
// 최초 및 eventModel 변경 시 동기화
|
// 최초 및 eventModel 변경 시 동기화
|
||||||
watch(() => eventModel.value.EventParticipants, updateConfirmedParticipants, { immediate: true, deep: true });
|
watch(() => eventModel.value.EventParticipants, updateConfirmedParticipants, { immediate: true, deep: true });
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,8 @@
|
|||||||
<span class="badge bg-primary text-white mr-1">{{ mIdx + 1 }}</span>
|
<span class="badge bg-primary text-white mr-1">{{ mIdx + 1 }}</span>
|
||||||
<span>{{ member.name }}</span>
|
<span>{{ member.name }}</span>
|
||||||
<span v-if="getMemberAverage(member) !== null" class="text-xs text-gray-700 ml-1">({{ getMemberAverage(member) }})</span>
|
<span v-if="getMemberAverage(member) !== null" class="text-xs text-gray-700 ml-1">({{ getMemberAverage(member) }})</span>
|
||||||
|
<span v-if="getMemberGender(member) === 'male'" class="gender-icon male ml-1" title="남성">♂</span>
|
||||||
|
<span v-else-if="getMemberGender(member) === 'female'" class="gender-icon female ml-1" title="여성">♀</span>
|
||||||
<Button icon="pi pi-times" class="p-button-text p-button-danger p-0" @click="removeMemberFromTeam(idx, mIdx)" v-if="team.members.length > 0" />
|
<Button icon="pi pi-times" class="p-button-text p-button-danger p-0" @click="removeMemberFromTeam(idx, mIdx)" v-if="team.members.length > 0" />
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
@@ -62,6 +64,8 @@
|
|||||||
<span v-if="getMemberAverage(slotProps.option) !== null" class="text-xs text-gray-700 ml-1">
|
<span v-if="getMemberAverage(slotProps.option) !== null" class="text-xs text-gray-700 ml-1">
|
||||||
({{ getMemberAverage(slotProps.option) }})
|
({{ getMemberAverage(slotProps.option) }})
|
||||||
</span>
|
</span>
|
||||||
|
<span v-if="getMemberGender(slotProps.option) === 'male'" class="gender-icon male ml-1" title="남성">♂</span>
|
||||||
|
<span v-else-if="getMemberGender(slotProps.option) === 'female'" class="gender-icon female ml-1" title="여성">♀</span>
|
||||||
</template>
|
</template>
|
||||||
<template #value="slotProps">
|
<template #value="slotProps">
|
||||||
<span v-if="slotProps.value">
|
<span v-if="slotProps.value">
|
||||||
@@ -69,6 +73,8 @@
|
|||||||
<span v-if="getMemberAverage(slotProps.value) !== null" class="text-xs text-gray-700 ml-1">
|
<span v-if="getMemberAverage(slotProps.value) !== null" class="text-xs text-gray-700 ml-1">
|
||||||
({{ getMemberAverage(slotProps.value) }})
|
({{ getMemberAverage(slotProps.value) }})
|
||||||
</span>
|
</span>
|
||||||
|
<span v-if="getMemberGender(slotProps.value) === 'male'" class="gender-icon male ml-1" title="남성">♂</span>
|
||||||
|
<span v-else-if="getMemberGender(slotProps.value) === 'female'" class="gender-icon female ml-1" title="여성">♀</span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</Select>
|
</Select>
|
||||||
@@ -102,19 +108,62 @@ import { ref, computed, watch, unref } from 'vue';
|
|||||||
|
|
||||||
// 참가자 에버리지 구하기
|
// 참가자 에버리지 구하기
|
||||||
function getMemberAverage(member) {
|
function getMemberAverage(member) {
|
||||||
if (!member.id) return null;
|
if (!member || !member.id) return null;
|
||||||
|
|
||||||
|
// 객체 자체에서 에버리지 값 추출 시도
|
||||||
|
if (member.average !== undefined && member.average !== null) {
|
||||||
|
return Number(member.average);
|
||||||
|
}
|
||||||
|
|
||||||
// 원본 참가자 객체에서 id로 찾아서 평균값 추출
|
// 원본 참가자 객체에서 id로 찾아서 평균값 추출
|
||||||
const arr = unref(props.participants);
|
const arr = unref(props.participants);
|
||||||
const p = arr.find(x => x.id === member.id);
|
const p = arr.find(x => x.id === member.id || x.participantId === member.id || x.memberId === member.id);
|
||||||
|
|
||||||
if (!p) return null;
|
if (!p) return null;
|
||||||
if (p.Member && typeof p.Member.average === 'number') return p.Member.average;
|
|
||||||
if (typeof p.average === 'number') return p.average;
|
// 다양한 구조에서 값 추출 시도
|
||||||
if (typeof p.averageScore === 'number') return p.averageScore;
|
if (p.Member && p.Member.average !== undefined && p.Member.average !== null) {
|
||||||
|
return Number(p.Member.average);
|
||||||
|
}
|
||||||
|
if (p.average !== undefined && p.average !== null) {
|
||||||
|
return Number(p.average);
|
||||||
|
}
|
||||||
|
if (p.averageScore !== undefined && p.averageScore !== null) {
|
||||||
|
return Number(p.averageScore);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// 팀 에버리지(평균)
|
|
||||||
|
// 참가자 성별 정보 가져오기
|
||||||
|
function getMemberGender(member) {
|
||||||
|
if (!member) return null;
|
||||||
|
|
||||||
|
// 객체 자체에서 성별 정보 추출 시도
|
||||||
|
if (member.gender) {
|
||||||
|
return member.gender;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 원본 참가자 객체에서 찾기
|
||||||
|
const arr = unref(props.participants);
|
||||||
|
const p = arr.find(x => x.id === member.id || x.participantId === member.id || x.memberId === member.id);
|
||||||
|
|
||||||
|
if (!p) return null;
|
||||||
|
|
||||||
|
// Member 객체에서 성별 정보 확인
|
||||||
|
if (p.Member && p.Member.gender) {
|
||||||
|
return p.Member.gender;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 팀 에버리지(평균) - 에버리지가 0인 사람은 제외
|
||||||
function getTeamAverage(team) {
|
function getTeamAverage(team) {
|
||||||
const avgs = team.members.map(getMemberAverage).filter(v => typeof v === 'number');
|
// 에버리지가 0보다 큰 값만 필터링
|
||||||
|
const avgs = team.members
|
||||||
|
.map(getMemberAverage)
|
||||||
|
.filter(v => typeof v === 'number' && v > 0);
|
||||||
|
|
||||||
if (!avgs.length) return 0;
|
if (!avgs.length) return 0;
|
||||||
return avgs.reduce((a, b) => a + b, 0) / avgs.length;
|
return avgs.reduce((a, b) => a + b, 0) / avgs.length;
|
||||||
}
|
}
|
||||||
@@ -345,5 +394,14 @@ watch(() => props.participants, () => {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
margin-right: 0.25em;
|
margin-right: 0.25em;
|
||||||
}
|
}
|
||||||
|
.gender-icon {
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.gender-icon.male {
|
||||||
|
color: #2196F3;
|
||||||
|
}
|
||||||
|
.gender-icon.female {
|
||||||
|
color: #E91E63;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -7,7 +7,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
// 별도 로직 없음
|
import { useGtag } from 'vue-gtag-next'
|
||||||
|
|
||||||
|
// Google Analytics(GA) 추적용 gtag 인스턴스 사용
|
||||||
|
const gtag = useGtag()
|
||||||
|
|
||||||
|
// 컴포넌트 마운트 시 페이지뷰 이벤트 발생
|
||||||
|
// (퍼블릭 레이아웃 진입 시 추적)
|
||||||
|
gtag.pageview({
|
||||||
|
page_title: '퍼블릭 레이아웃',
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
page_location: window.location.href
|
||||||
|
})
|
||||||
|
|
||||||
|
// 주요 버튼 클릭 등 액션 추적용 함수 예시
|
||||||
|
const trackPublicAction = (actionName) => {
|
||||||
|
gtag.event('button_click', {
|
||||||
|
event_category: '퍼블릭',
|
||||||
|
event_label: actionName,
|
||||||
|
value: 1
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -5,8 +5,33 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { useGtag } from 'vue-gtag-next'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ClubLayout'
|
name: 'ClubLayout',
|
||||||
|
setup() {
|
||||||
|
const gtag = useGtag()
|
||||||
|
|
||||||
|
// 컴포넌트 마운트 시 페이지뷰 이벤트 발생
|
||||||
|
gtag.pageview({
|
||||||
|
page_title: '클럽 레이아웃',
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
page_location: window.location.href
|
||||||
|
})
|
||||||
|
|
||||||
|
// 이벤트 추적 메서드 예시
|
||||||
|
const trackButtonClick = (buttonName) => {
|
||||||
|
gtag.event('button_click', {
|
||||||
|
'event_category': '클럽',
|
||||||
|
'event_label': buttonName,
|
||||||
|
'value': 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
trackButtonClick
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import App from './App.vue'
|
|||||||
import router from './router'
|
import router from './router'
|
||||||
import './assets/base.css'
|
import './assets/base.css'
|
||||||
|
|
||||||
|
// Google Tag Manager 설정
|
||||||
|
import VueGtag from 'vue-gtag-next'
|
||||||
|
|
||||||
// dayjs configuration
|
// dayjs configuration
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import timezone from 'dayjs/plugin/timezone'
|
import timezone from 'dayjs/plugin/timezone'
|
||||||
@@ -79,6 +82,16 @@ app.use(ToastService)
|
|||||||
app.use(createPinia())
|
app.use(createPinia())
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
|
||||||
|
// Google Analytics(GA) vue-gtag-next 플러그인 등록
|
||||||
|
app.use(VueGtag, {
|
||||||
|
property: {
|
||||||
|
id: 'GTM-PPDRKT7Q'
|
||||||
|
},
|
||||||
|
appName: '볼링매니저',
|
||||||
|
pageTrackerScreenviewEnabled: true
|
||||||
|
}, router)
|
||||||
|
// GTM-PPDRKT7Q: 구글 태그매니저(GA4) ID
|
||||||
|
|
||||||
// Register custom components
|
// Register custom components
|
||||||
app.component('AppHeader', AppHeader);
|
app.component('AppHeader', AppHeader);
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,14 @@ const routes = [
|
|||||||
path: '/',
|
path: '/',
|
||||||
redirect: '/login'
|
redirect: '/login'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/bowlspeed',
|
||||||
|
name: 'BowlingSpeed',
|
||||||
|
beforeEnter() {
|
||||||
|
window.location.href = '/bowlspeed/index.html';
|
||||||
|
},
|
||||||
|
meta: { title: '볼링공 속도 계산기', requiresAuth: false }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/public/:publicHash',
|
path: '/public/:publicHash',
|
||||||
name: 'EventPublicPage',
|
name: 'EventPublicPage',
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { useGtag } from 'vue-gtag-next';
|
||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
import adminService from '@/services/adminService';
|
import adminService from '@/services/adminService';
|
||||||
|
|
||||||
@@ -58,7 +59,14 @@ const currentPage = ref(1);
|
|||||||
const rowsPerPage = ref(20);
|
const rowsPerPage = ref(20);
|
||||||
|
|
||||||
// 페이지 로드 시 활동 로그 목록 가져오기
|
// 페이지 로드 시 활동 로그 목록 가져오기
|
||||||
|
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.pageview({
|
||||||
|
page_title: '활동로그관리',
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
page_location: window.location.href
|
||||||
|
});
|
||||||
await fetchLogs();
|
await fetchLogs();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -248,8 +248,10 @@ import { ref, reactive, onMounted } from 'vue';
|
|||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import adminService from '@/services/adminService';
|
import adminService from '@/services/adminService';
|
||||||
|
import { useGtag } from 'vue-gtag-next';
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
const gtag = useGtag();
|
||||||
|
|
||||||
// 상태 변수
|
// 상태 변수
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
@@ -319,7 +321,14 @@ const member = reactive({
|
|||||||
const canChangeOwner = ref(true);
|
const canChangeOwner = ref(true);
|
||||||
|
|
||||||
// 페이지 로드 시 클럽 목록 가져오기
|
// 페이지 로드 시 클럽 목록 가져오기
|
||||||
|
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.pageview({
|
||||||
|
page_title: '클럽관리',
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
page_location: window.location.href
|
||||||
|
});
|
||||||
await fetchClubs();
|
await fetchClubs();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -129,6 +129,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed } from 'vue';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
|
import { useGtag } from 'vue-gtag-next';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@@ -206,7 +207,14 @@ const chartOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 컴포넌트 마운트 시 데이터 로드
|
// 컴포넌트 마운트 시 데이터 로드
|
||||||
|
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.pageview({
|
||||||
|
page_title: '관리자대시보드',
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
page_location: window.location.href
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
await loadDashboardData();
|
await loadDashboardData();
|
||||||
|
|||||||
@@ -202,12 +202,14 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { getStatusLabel, getStatusSeverity, getSubscriptionStatusLabel, getSubscriptionStatusSeverity, SUBSCRIPTION_STATUS_OPTIONS, SUBSCRIPTION_STATUS_LABELS } from '@/utils/enumMappings';
|
import { getStatusLabel, getStatusSeverity, getSubscriptionStatusLabel, getSubscriptionStatusSeverity, SUBSCRIPTION_STATUS_OPTIONS, SUBSCRIPTION_STATUS_LABELS } from '@/utils/enumMappings';
|
||||||
import { ref, onMounted, computed } from 'vue';
|
import { ref, onMounted, computed, watch } from 'vue';
|
||||||
|
import { useGtag } from 'vue-gtag-next';
|
||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import adminService from '@/services/adminService';
|
import adminService from '@/services/adminService';
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
const gtag = useGtag();
|
||||||
const API_URL = 'http://localhost:3030/api';
|
const API_URL = 'http://localhost:3030/api';
|
||||||
|
|
||||||
// 데이터 정의
|
// 데이터 정의
|
||||||
@@ -272,7 +274,14 @@ const fetchPlanSubscriptions = async (planId) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 컴포넌트 마운트 시 데이터 로드
|
// 컴포넌트 마운트 시 데이터 로드
|
||||||
|
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.pageview({
|
||||||
|
page_title: '구독플랜관리',
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
page_location: window.location.href
|
||||||
|
});
|
||||||
await fetchPlans();
|
await fetchPlans();
|
||||||
await fetchFeatures();
|
await fetchFeatures();
|
||||||
await fetchClubs();
|
await fetchClubs();
|
||||||
|
|||||||
@@ -125,6 +125,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { getStatusLabel, getRoleName, getRoleSeverity, getStatusSeverity, STATUS_OPTIONS, ROLE_OPTIONS } from '@/utils/enumMappings';
|
import { getStatusLabel, getRoleName, getRoleSeverity, getStatusSeverity, STATUS_OPTIONS, ROLE_OPTIONS } from '@/utils/enumMappings';
|
||||||
import { ref, onMounted, reactive, computed } from 'vue';
|
import { ref, onMounted, reactive, computed } from 'vue';
|
||||||
|
import { useGtag } from 'vue-gtag-next';
|
||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
import adminService from '@/services/adminService';
|
import adminService from '@/services/adminService';
|
||||||
|
|
||||||
@@ -153,7 +154,14 @@ const dialogTitle = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 페이지 로드 시 사용자 목록 가져오기
|
// 페이지 로드 시 사용자 목록 가져오기
|
||||||
|
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.pageview({
|
||||||
|
page_title: '사용자관리',
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
page_location: window.location.href
|
||||||
|
});
|
||||||
await fetchUsers();
|
await fetchUsers();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,13 @@
|
|||||||
<form @submit.prevent="handleLogin" v-if="!showRegisterForm">
|
<form @submit.prevent="handleLogin" v-if="!showRegisterForm">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="username">사용자명</label>
|
<label for="username">사용자명</label>
|
||||||
<InputText id="username" v-model="username" class="w-full" :class="{'p-invalid': usernameError}" />
|
<InputText id="username" v-model="username" class="w-full" />
|
||||||
<small v-if="usernameError" class="p-error">{{ usernameError }}</small>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password">비밀번호</label>
|
<label for="password">비밀번호</label>
|
||||||
<InputText id="password" v-model="password" type="password" class="w-full" :class="{'p-invalid': passwordError}" />
|
<InputText id="password" v-model="password" type="password" class="w-full" />
|
||||||
<small v-if="passwordError" class="p-error">{{ passwordError }}</small>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-options">
|
<div class="form-options">
|
||||||
<div class="remember-me">
|
<div class="remember-me">
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<Button type="submit" label="로그인" class="w-full" :loading="loading" />
|
<Button type="submit" label="로그인" class="w-full" :loading="loading" />
|
||||||
<div class="register-link">
|
<div class="register-link">
|
||||||
<p>계정이 없으신가요? <a href="#" @click.prevent="showRegisterForm = true">회원가입</a></p>
|
<p>계정이 없으신가요? <a href="#" @click.prevent="showRegisterForm = true; gtag.event('show_register_form', { 'event_category': '인증', 'event_label': '회원가입 폼 표시' });">회원가입</a></p>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@@ -32,42 +32,40 @@
|
|||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="registerUsername">사용자명(로그인 아이디)</label>
|
<label for="registerUsername">사용자명(로그인 아이디)</label>
|
||||||
<InputText id="registerUsername" v-model="registerUsername" class="w-full" :class="{'p-invalid': registerUsernameError}" />
|
<InputText id="registerUsername" v-model="registerUsername" class="w-full" />
|
||||||
<small v-if="registerUsernameError" class="p-error">{{ registerUsernameError }}</small>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="registerName">이름</label>
|
<label for="registerName">이름</label>
|
||||||
<InputText id="registerName" v-model="registerName" class="w-full" :class="{'p-invalid': registerNameError}" />
|
<InputText id="registerName" v-model="registerName" class="w-full" />
|
||||||
<small v-if="registerNameError" class="p-error">{{ registerNameError }}</small>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="registerEmail">이메일</label>
|
<label for="registerEmail">이메일</label>
|
||||||
<InputText id="registerEmail" v-model="registerEmail" class="w-full" :class="{'p-invalid': registerEmailError}" />
|
<InputText id="registerEmail" v-model="registerEmail" class="w-full" />
|
||||||
<small v-if="registerEmailError" class="p-error">{{ registerEmailError }}</small>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="registerPassword">비밀번호</label>
|
<label for="registerPassword">비밀번호</label>
|
||||||
<Password id="registerPassword" v-model="registerPassword" :feedback="true" toggleMask
|
<Password id="registerPassword" v-model="registerPassword" :feedback="true" toggleMask
|
||||||
:class="{'p-invalid': registerPasswordError}"
|
|
||||||
:promptLabel="'비밀번호 강도'"
|
:promptLabel="'비밀번호 강도'"
|
||||||
:weakLabel="'약함'"
|
:weakLabel="'약함'"
|
||||||
:mediumLabel="'중간'"
|
:mediumLabel="'중간'"
|
||||||
:strongLabel="'강함'"
|
:strongLabel="'강함'"
|
||||||
class="w-full" />
|
class="w-full" />
|
||||||
<small v-if="registerPasswordError" class="p-error">{{ registerPasswordError }}</small>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="registerPasswordConfirm">비밀번호 확인</label>
|
<label for="registerPasswordConfirm">비밀번호 확인</label>
|
||||||
<Password id="registerPasswordConfirm" v-model="registerPasswordConfirm" toggleMask
|
<Password id="registerPasswordConfirm" v-model="registerPasswordConfirm" toggleMask
|
||||||
:class="{'p-invalid': registerPasswordConfirmError}"
|
|
||||||
:feedback="false"
|
:feedback="false"
|
||||||
class="w-full" />
|
class="w-full" />
|
||||||
<small v-if="registerPasswordConfirmError" class="p-error">{{ registerPasswordConfirmError }}</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button type="submit" label="회원가입" class="w-full" :loading="loading" />
|
<Button type="submit" label="회원가입" class="w-full" :loading="loading" />
|
||||||
<div class="login-link">
|
<div class="login-link">
|
||||||
<p>이미 계정이 있으신가요? <a href="#" @click.prevent="showRegisterForm = false">로그인</a></p>
|
<p>이미 계정이 있으신가요? <a href="#" @click.prevent="showRegisterForm = false; gtag.event('show_login_form', { 'event_category': '인증', 'event_label': '로그인 폼 표시' });">로그인</a></p>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -76,21 +74,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, inject } from 'vue';
|
import { ref, computed, inject, onMounted } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
|
import { useGtag } from 'vue-gtag-next';
|
||||||
import authService from '@/services/authService';
|
import authService from '@/services/authService';
|
||||||
|
|
||||||
const user = inject('user');
|
const user = inject('user');
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
const gtag = useGtag();
|
||||||
|
|
||||||
const username = ref('');
|
const username = ref('');
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
const rememberMe = ref(false);
|
const rememberMe = ref(false);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const usernameError = ref('');
|
|
||||||
const passwordError = ref('');
|
|
||||||
|
|
||||||
// 회원가입 폼 상태
|
// 회원가입 폼 상태
|
||||||
const showRegisterForm = ref(false);
|
const showRegisterForm = ref(false);
|
||||||
@@ -99,11 +97,6 @@ const registerUsername = ref('');
|
|||||||
const registerEmail = ref('');
|
const registerEmail = ref('');
|
||||||
const registerPassword = ref('');
|
const registerPassword = ref('');
|
||||||
const registerPasswordConfirm = ref('');
|
const registerPasswordConfirm = ref('');
|
||||||
const registerNameError = ref('');
|
|
||||||
const registerUsernameError = ref('');
|
|
||||||
const registerEmailError = ref('');
|
|
||||||
const registerPasswordError = ref('');
|
|
||||||
const registerPasswordConfirmError = ref('');
|
|
||||||
|
|
||||||
// 비밀번호 강도 검사
|
// 비밀번호 강도 검사
|
||||||
const passwordHasMinLength = computed(() => registerPassword.value.length >= 8);
|
const passwordHasMinLength = computed(() => registerPassword.value.length >= 8);
|
||||||
@@ -118,18 +111,14 @@ const validateForm = () => {
|
|||||||
|
|
||||||
// 사용자명 검증
|
// 사용자명 검증
|
||||||
if (!username.value) {
|
if (!username.value) {
|
||||||
usernameError.value = '사용자명을 입력해주세요.';
|
toast.add({ severity: 'error', summary: '입력 오류', detail: '사용자명을 입력해주세요.', life: 3000 });
|
||||||
isValid = false;
|
isValid = false;
|
||||||
} else {
|
|
||||||
usernameError.value = '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 비밀번호 유효성 검사
|
// 비밀번호 유효성 검사
|
||||||
if (!password.value) {
|
if (!password.value) {
|
||||||
passwordError.value = '비밀번호를 입력해주세요.';
|
toast.add({ severity: 'error', summary: '입력 오류', detail: '비밀번호를 입력해주세요.', life: 3000 });
|
||||||
isValid = false;
|
isValid = false;
|
||||||
} else {
|
|
||||||
passwordError.value = '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return isValid;
|
return isValid;
|
||||||
@@ -137,71 +126,66 @@ const validateForm = () => {
|
|||||||
|
|
||||||
// 회원가입 폼 유효성 검사
|
// 회원가입 폼 유효성 검사
|
||||||
const validateRegisterForm = () => {
|
const validateRegisterForm = () => {
|
||||||
let isValid = true;
|
|
||||||
|
|
||||||
// 이름 검증
|
// 이름 검증
|
||||||
if (!registerName.value) {
|
if (!registerName.value) {
|
||||||
registerNameError.value = '이름을 입력해주세요.';
|
toast.add({ severity: 'error', summary: '입력 오류', detail: '이름을 입력해주세요.', life: 3000 });
|
||||||
isValid = false;
|
return false;
|
||||||
} else {
|
|
||||||
registerNameError.value = '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 사용자명 검증
|
// 사용자명 검증
|
||||||
if (!registerUsername.value) {
|
if (!registerUsername.value) {
|
||||||
registerUsernameError.value = '사용자명을 입력해주세요.';
|
toast.add({ severity: 'error', summary: '입력 오류', detail: '사용자명을 입력해주세요.', life: 3000 });
|
||||||
isValid = false;
|
return false;
|
||||||
} else if (registerUsername.value.length < 4) {
|
} else if (registerUsername.value.length < 4) {
|
||||||
registerUsernameError.value = '사용자명은 최소 4자 이상이어야 합니다.';
|
toast.add({ severity: 'error', summary: '입력 오류', detail: '사용자명은 최소 4자 이상이어야 합니다.', life: 3000 });
|
||||||
isValid = false;
|
return false;
|
||||||
} else {
|
|
||||||
registerUsernameError.value = '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 이메일 검증
|
// 이메일 검증
|
||||||
if (!registerEmail.value) {
|
if (!registerEmail.value) {
|
||||||
registerEmailError.value = '이메일을 입력해주세요.';
|
toast.add({ severity: 'error', summary: '입력 오류', detail: '이메일을 입력해주세요.', life: 3000 });
|
||||||
isValid = false;
|
return false;
|
||||||
} else if (!/^\S+@\S+\.\S+$/.test(registerEmail.value)) {
|
} else if (!/^\S+@\S+\.\S+$/.test(registerEmail.value)) {
|
||||||
registerEmailError.value = '유효한 이메일 주소를 입력해주세요.';
|
toast.add({ severity: 'error', summary: '입력 오류', detail: '유효한 이메일 주소를 입력해주세요.', life: 3000 });
|
||||||
isValid = false;
|
return false;
|
||||||
} else {
|
|
||||||
registerEmailError.value = '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 비밀번호 유효성 검사
|
// 비밀번호 유효성 검사
|
||||||
if (!registerPassword.value) {
|
if (!registerPassword.value) {
|
||||||
registerPasswordError.value = '비밀번호를 입력해주세요.';
|
toast.add({ severity: 'error', summary: '입력 오류', detail: '비밀번호를 입력해주세요.', life: 3000 });
|
||||||
isValid = false;
|
return false;
|
||||||
} else if (registerPassword.value.length < 8) {
|
} else if (registerPassword.value.length < 8) {
|
||||||
registerPasswordError.value = '비밀번호는 최소 8자 이상이어야 합니다.';
|
toast.add({ severity: 'error', summary: '입력 오류', detail: '비밀번호는 최소 8자 이상이어야 합니다.', life: 3000 });
|
||||||
isValid = false;
|
return false;
|
||||||
} else if (!passwordHasUppercase.value || !passwordHasLowercase.value ||
|
} else if (!passwordHasUppercase.value || !passwordHasLowercase.value ||
|
||||||
!passwordHasNumber.value || !passwordHasSpecial.value) {
|
!passwordHasNumber.value || !passwordHasSpecial.value) {
|
||||||
registerPasswordError.value = '비밀번호는 대문자, 소문자, 숫자, 특수문자를 모두 포함해야 합니다.';
|
toast.add({ severity: 'error', summary: '입력 오류', detail: '비밀번호는 대문자, 소문자, 숫자, 특수문자를 모두 포함해야 합니다.', life: 3000 });
|
||||||
isValid = false;
|
return false;
|
||||||
} else {
|
|
||||||
registerPasswordError.value = '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 비밀번호 확인 유효성 검사
|
// 비밀번호 확인 유효성 검사
|
||||||
if (!registerPasswordConfirm.value) {
|
if (!registerPasswordConfirm.value) {
|
||||||
registerPasswordConfirmError.value = '비밀번호 확인을 입력해주세요.';
|
toast.add({ severity: 'error', summary: '입력 오류', detail: '비밀번호 확인을 입력해주세요.', life: 3000 });
|
||||||
isValid = false;
|
return false;
|
||||||
} else if (registerPassword.value !== registerPasswordConfirm.value) {
|
} else if (registerPassword.value !== registerPasswordConfirm.value) {
|
||||||
registerPasswordConfirmError.value = '비밀번호가 일치하지 않습니다.';
|
toast.add({ severity: 'error', summary: '입력 오류', detail: '비밀번호가 일치하지 않습니다.', life: 3000 });
|
||||||
isValid = false;
|
return false;
|
||||||
} else {
|
|
||||||
registerPasswordConfirmError.value = '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return isValid;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 로그인 처리 함수
|
// 로그인 처리 함수
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
if (!validateForm()) return;
|
if (!validateForm()) return;
|
||||||
|
|
||||||
|
// Google Analytics 이벤트 추적 - 로그인 시도
|
||||||
|
gtag.event('login_attempt', {
|
||||||
|
'event_category': '인증',
|
||||||
|
'event_label': '로그인 시도',
|
||||||
|
'value': 1
|
||||||
|
});
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -223,6 +207,12 @@ const handleLogin = async () => {
|
|||||||
// 로그인 성공 메시지
|
// 로그인 성공 메시지
|
||||||
toast.add({ severity: 'success', summary: '성공', detail: '로그인에 성공했습니다.', life: 3000 });
|
toast.add({ severity: 'success', summary: '성공', detail: '로그인에 성공했습니다.', life: 3000 });
|
||||||
|
|
||||||
|
// Google Analytics 이벤트 추적 - 로그인 성공
|
||||||
|
gtag.event('login', {
|
||||||
|
'method': 'id_password',
|
||||||
|
'user_role': userData.role
|
||||||
|
});
|
||||||
|
|
||||||
// 사용자 정보 및 메뉴 아이템 로드 이벤트 발생
|
// 사용자 정보 및 메뉴 아이템 로드 이벤트 발생
|
||||||
window.dispatchEvent(new CustomEvent('user-logged-in'));
|
window.dispatchEvent(new CustomEvent('user-logged-in'));
|
||||||
|
|
||||||
@@ -235,6 +225,13 @@ const handleLogin = async () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('로그인 오류:', error);
|
console.error('로그인 오류:', error);
|
||||||
|
|
||||||
|
// Google Analytics 이벤트 추적 - 로그인 실패
|
||||||
|
gtag.event('login_failed', {
|
||||||
|
'event_category': '인증',
|
||||||
|
'event_label': error.response?.data?.message || '인증 실패',
|
||||||
|
'value': 1
|
||||||
|
});
|
||||||
|
|
||||||
// 오류 메시지 처리
|
// 오류 메시지 처리
|
||||||
const errorMessage = error.response?.data?.message || '로그인에 실패했습니다. 이메일과 비밀번호를 확인해주세요.';
|
const errorMessage = error.response?.data?.message || '로그인에 실패했습니다. 이메일과 비밀번호를 확인해주세요.';
|
||||||
toast.add({ severity: 'error', summary: '오류', detail: errorMessage, life: 3000 });
|
toast.add({ severity: 'error', summary: '오류', detail: errorMessage, life: 3000 });
|
||||||
@@ -247,6 +244,13 @@ const handleLogin = async () => {
|
|||||||
const handleRegister = async () => {
|
const handleRegister = async () => {
|
||||||
if (!validateRegisterForm()) return;
|
if (!validateRegisterForm()) return;
|
||||||
|
|
||||||
|
// Google Analytics 이벤트 추적 - 회원가입 시도
|
||||||
|
gtag.event('sign_up_attempt', {
|
||||||
|
'event_category': '인증',
|
||||||
|
'event_label': '회원가입 시도',
|
||||||
|
'value': 1
|
||||||
|
});
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -260,6 +264,11 @@ const handleRegister = async () => {
|
|||||||
// 회원가입 성공 메시지
|
// 회원가입 성공 메시지
|
||||||
toast.add({ severity: 'success', summary: '성공', detail: '회원가입에 성공했습니다. 로그인해주세요.', life: 3000 });
|
toast.add({ severity: 'success', summary: '성공', detail: '회원가입에 성공했습니다. 로그인해주세요.', life: 3000 });
|
||||||
|
|
||||||
|
// Google Analytics 이벤트 추적 - 회원가입 성공
|
||||||
|
gtag.event('sign_up', {
|
||||||
|
'method': 'form'
|
||||||
|
});
|
||||||
|
|
||||||
// 로그인 폼으로 전환
|
// 로그인 폼으로 전환
|
||||||
showRegisterForm.value = false;
|
showRegisterForm.value = false;
|
||||||
|
|
||||||
@@ -276,6 +285,13 @@ const handleRegister = async () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('회원가입 오류:', error);
|
console.error('회원가입 오류:', error);
|
||||||
|
|
||||||
|
// Google Analytics 이벤트 추적 - 회원가입 실패
|
||||||
|
gtag.event('sign_up_failed', {
|
||||||
|
'event_category': '인증',
|
||||||
|
'event_label': error.response?.data?.message || '회원가입 실패',
|
||||||
|
'value': 1
|
||||||
|
});
|
||||||
|
|
||||||
// 오류 메시지 처리
|
// 오류 메시지 처리
|
||||||
const errorMessage = error.response?.data?.message || '회원가입에 실패했습니다. 다시 시도해주세요.';
|
const errorMessage = error.response?.data?.message || '회원가입에 실패했습니다. 다시 시도해주세요.';
|
||||||
toast.add({ severity: 'error', summary: '오류', detail: errorMessage, life: 3000 });
|
toast.add({ severity: 'error', summary: '오류', detail: errorMessage, life: 3000 });
|
||||||
|
|||||||
@@ -83,6 +83,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, onMounted } from 'vue';
|
import { ref, reactive, computed, onMounted } from 'vue';
|
||||||
|
import { useGtag } from 'vue-gtag-next';
|
||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
import { useVuelidate } from '@vuelidate/core';
|
import { useVuelidate } from '@vuelidate/core';
|
||||||
import { required, email, minLength, sameAs, helpers } from '@vuelidate/validators';
|
import { required, email, minLength, sameAs, helpers } from '@vuelidate/validators';
|
||||||
@@ -134,7 +135,14 @@ const rules = computed(() => ({
|
|||||||
const v$ = useVuelidate(rules, form);
|
const v$ = useVuelidate(rules, form);
|
||||||
|
|
||||||
// 페이지 로드 시 사용자 정보 가져오기
|
// 페이지 로드 시 사용자 정보 가져오기
|
||||||
|
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.pageview({
|
||||||
|
page_title: '프로필',
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
page_location: window.location.href
|
||||||
|
});
|
||||||
await fetchUserProfile();
|
await fetchUserProfile();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { AVERAGE_CALCULATION_PERIOD_OPTIONS } from '@/utils/enumMappings';
|
import { AVERAGE_CALCULATION_PERIOD_OPTIONS } from '@/utils/enumMappings';
|
||||||
import { ref, computed, onMounted, inject } from 'vue';
|
import { ref, computed, onMounted, inject } from 'vue';
|
||||||
|
import { useGtag } from 'vue-gtag-next';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
import clubService from '@/services/clubService';
|
import clubService from '@/services/clubService';
|
||||||
@@ -55,7 +56,14 @@ const loading = ref(false);
|
|||||||
const clubNameError = ref('');
|
const clubNameError = ref('');
|
||||||
const clubDescriptionError = ref('');
|
const clubDescriptionError = ref('');
|
||||||
|
|
||||||
|
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.pageview({
|
||||||
|
page_title: '클럽생성',
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
page_location: window.location.href
|
||||||
|
});
|
||||||
authStore.loadUserInfo();
|
authStore.loadUserInfo();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -81,7 +89,13 @@ const validateForm = () => {
|
|||||||
return isValid;
|
return isValid;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 클럽 생성 시도/성공/실패 GA 이벤트 추적
|
||||||
const handleCreateClub = async () => {
|
const handleCreateClub = async () => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.event('create_club_attempt', {
|
||||||
|
event_category: '클럽생성',
|
||||||
|
event_label: '클럽 생성 시도'
|
||||||
|
});
|
||||||
if (!validateForm()) return;
|
if (!validateForm()) return;
|
||||||
|
|
||||||
if (!authStore.user) {
|
if (!authStore.user) {
|
||||||
|
|||||||
@@ -130,6 +130,7 @@
|
|||||||
import { AVERAGE_CALCULATION_PERIOD_OPTIONS, getMemberTypeLabel } from '@/utils/enumMappings';
|
import { AVERAGE_CALCULATION_PERIOD_OPTIONS, getMemberTypeLabel } from '@/utils/enumMappings';
|
||||||
|
|
||||||
import { ref, onMounted, computed, inject } from 'vue';
|
import { ref, onMounted, computed, inject } from 'vue';
|
||||||
|
import { useGtag } from 'vue-gtag-next';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
import { useClubStore } from '@/stores/clubStore';
|
import { useClubStore } from '@/stores/clubStore';
|
||||||
@@ -309,7 +310,13 @@ const getFeatureIcon = (featureType) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 클럽 정보 저장
|
// 클럽 정보 저장
|
||||||
|
// 클럽 정보 저장 버튼 클릭 시 GA 이벤트 추적
|
||||||
const saveClubInfo = async () => {
|
const saveClubInfo = async () => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.event('save_club_info', {
|
||||||
|
event_category: '클럽설정',
|
||||||
|
event_label: '클럽 정보 저장'
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
if (!clubId) {
|
if (!clubId) {
|
||||||
router.push('/club');
|
router.push('/club');
|
||||||
|
|||||||
@@ -146,9 +146,11 @@ import { useRouter } from 'vue-router';
|
|||||||
import apiClient from '@/services/api';
|
import apiClient from '@/services/api';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
|
import { useGtag } from 'vue-gtag-next';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
const gtag = useGtag();
|
||||||
|
|
||||||
// 데이터 상태 변수들
|
// 데이터 상태 변수들
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
@@ -260,6 +262,12 @@ const loadDashboardData = async () => {
|
|||||||
|
|
||||||
// 페이지 이동 함수
|
// 페이지 이동 함수
|
||||||
const navigateTo = (path) => {
|
const navigateTo = (path) => {
|
||||||
|
// Google Analytics 이벤트 추적
|
||||||
|
gtag.event('navigation', {
|
||||||
|
'event_category': '대시보드',
|
||||||
|
'event_label': path,
|
||||||
|
'value': 1
|
||||||
|
});
|
||||||
router.push(path);
|
router.push(path);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -267,6 +275,13 @@ const navigateTo = (path) => {
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
clubId.value = localStorage.getItem('clubId') || null;
|
clubId.value = localStorage.getItem('clubId') || null;
|
||||||
await loadDashboardData();
|
await loadDashboardData();
|
||||||
|
|
||||||
|
// Google Analytics 페이지뷰 추적
|
||||||
|
gtag.pageview({
|
||||||
|
page_title: '클럽 대시보드',
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
page_location: window.location.href
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
// getEventTypeLabel 중복 import 제거. eventUtils.js의 getEventTypeLabel만 사용.
|
// getEventTypeLabel 중복 import 제거. eventUtils.js의 getEventTypeLabel만 사용.
|
||||||
import { ref, onMounted, computed } from 'vue';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
|
import { useGtag } from 'vue-gtag-next';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
import FullCalendar from '@fullcalendar/vue3';
|
import FullCalendar from '@fullcalendar/vue3';
|
||||||
@@ -56,7 +57,14 @@ const loading = ref(false);
|
|||||||
const clubId = ref(localStorage.getItem('clubId') || null);
|
const clubId = ref(localStorage.getItem('clubId') || null);
|
||||||
|
|
||||||
// 페이지 로드 시 데이터 가져오기
|
// 페이지 로드 시 데이터 가져오기
|
||||||
|
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.pageview({
|
||||||
|
page_title: '이벤트캘린더',
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
page_location: window.location.href
|
||||||
|
});
|
||||||
await fetchEvents();
|
await fetchEvents();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -107,13 +115,27 @@ const getEventColor = (eventType) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 캘린더 이벤트 클릭 핸들러
|
// 캘린더 이벤트 클릭 핸들러
|
||||||
|
// 캘린더 이벤트 클릭 시 GA 이벤트 추적
|
||||||
const handleEventClick = (info) => {
|
const handleEventClick = (info) => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.event('calendar_event_click', {
|
||||||
|
event_category: '이벤트캘린더',
|
||||||
|
event_label: '캘린더 이벤트 클릭',
|
||||||
|
value: info.event.id
|
||||||
|
});
|
||||||
const eventId = info.event.id;
|
const eventId = info.event.id;
|
||||||
viewEventDetails(eventId);
|
viewEventDetails(eventId);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 이벤트 상세 보기
|
// 이벤트 상세 보기
|
||||||
|
// 이벤트 상세보기 클릭 시 GA 이벤트 추적
|
||||||
const viewEventDetails = async (eventId) => {
|
const viewEventDetails = async (eventId) => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.event('view_event_details', {
|
||||||
|
event_category: '이벤트캘린더',
|
||||||
|
event_label: '이벤트 상세보기',
|
||||||
|
value: eventId
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
selectedEvent.value = await eventService.getEventById(eventId);
|
selectedEvent.value = await eventService.getEventById(eventId);
|
||||||
eventDetailsDialog.value = true;
|
eventDetailsDialog.value = true;
|
||||||
@@ -140,7 +162,13 @@ const removeParticipant = async (participant) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 이벤트 관리 페이지로 이동
|
// 이벤트 관리 페이지로 이동
|
||||||
|
// 이벤트관리 이동 버튼 클릭 시 GA 이벤트 추적
|
||||||
const navigateToEventManagement = () => {
|
const navigateToEventManagement = () => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.event('go_to_event_management', {
|
||||||
|
event_category: '이벤트캘린더',
|
||||||
|
event_label: '이벤트관리 이동'
|
||||||
|
});
|
||||||
router.push('/club/events');
|
router.push('/club/events');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { getEventTypeLabel, getEventStatusLabel, getStatusSeverity } from '@/utils/enumMappings';
|
import { getEventTypeLabel, getEventStatusLabel, getStatusSeverity } from '@/utils/enumMappings';
|
||||||
import { ref, onMounted, computed } from 'vue';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
|
import { useGtag } from 'vue-gtag-next';
|
||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useClubStore } from '@/stores/clubStore';
|
import { useClubStore } from '@/stores/clubStore';
|
||||||
@@ -203,7 +204,14 @@ const fetchClubMembers = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 컴포넌트 마운트 시 데이터 로드
|
// 컴포넌트 마운트 시 데이터 로드
|
||||||
|
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.pageview({
|
||||||
|
page_title: '이벤트관리',
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
page_location: window.location.href
|
||||||
|
});
|
||||||
if (!clubId.value) {
|
if (!clubId.value) {
|
||||||
toast.add({
|
toast.add({
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
@@ -265,7 +273,14 @@ const editEvent = (eventData) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 이벤트 상세 정보 보기
|
// 이벤트 상세 정보 보기
|
||||||
|
// 이벤트 상세보기 클릭 시 GA 이벤트 추적
|
||||||
const viewEventDetails = async (eventData) => {
|
const viewEventDetails = async (eventData) => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.event('view_event_details', {
|
||||||
|
event_category: '이벤트관리',
|
||||||
|
event_label: '이벤트 상세보기',
|
||||||
|
value: eventData.id
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
const response = await eventService.getEventById(eventData.id);
|
const response = await eventService.getEventById(eventData.id);
|
||||||
event.value = response;
|
event.value = response;
|
||||||
@@ -302,9 +317,16 @@ const confirmDeleteEvent = (eventData) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 이벤트 저장
|
// 이벤트 저장
|
||||||
|
// 이벤트 저장(생성/수정) 시 GA 이벤트 추적
|
||||||
const saveEvent = async (eventData) => {
|
const saveEvent = async (eventData) => {
|
||||||
|
const gtag = useGtag();
|
||||||
try {
|
try {
|
||||||
if (eventData.id) {
|
if (eventData.id) {
|
||||||
|
gtag.event('edit_event', {
|
||||||
|
event_category: '이벤트관리',
|
||||||
|
event_label: '이벤트 수정',
|
||||||
|
value: eventData.id
|
||||||
|
});
|
||||||
// 이벤트 수정
|
// 이벤트 수정
|
||||||
const response = await eventService.updateEvent({
|
const response = await eventService.updateEvent({
|
||||||
...eventData,
|
...eventData,
|
||||||
@@ -312,6 +334,10 @@ const saveEvent = async (eventData) => {
|
|||||||
});
|
});
|
||||||
await fetchEvents(); // 목록 갱신
|
await fetchEvents(); // 목록 갱신
|
||||||
} else {
|
} else {
|
||||||
|
gtag.event('create_event', {
|
||||||
|
event_category: '이벤트관리',
|
||||||
|
event_label: '이벤트 생성'
|
||||||
|
});
|
||||||
// 이벤트 생성
|
// 이벤트 생성
|
||||||
const response = await eventService.createEvent({
|
const response = await eventService.createEvent({
|
||||||
...eventData,
|
...eventData,
|
||||||
@@ -327,6 +353,10 @@ const saveEvent = async (eventData) => {
|
|||||||
});
|
});
|
||||||
hideDialog();
|
hideDialog();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
gtag.event('event_save_fail', {
|
||||||
|
event_category: '이벤트관리',
|
||||||
|
event_label: eventData.id ? '이벤트 수정 실패' : '이벤트 생성 실패'
|
||||||
|
});
|
||||||
toast.add({
|
toast.add({
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
summary: '오류',
|
summary: '오류',
|
||||||
@@ -337,17 +367,22 @@ const saveEvent = async (eventData) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 이벤트 삭제
|
// 이벤트 삭제
|
||||||
|
// 이벤트 삭제 시 GA 이벤트 추적
|
||||||
const deleteEvent = async () => {
|
const deleteEvent = async () => {
|
||||||
|
const gtag = useGtag();
|
||||||
try {
|
try {
|
||||||
await eventService.deleteEvent(clubId.value, event.value.id);
|
await eventService.deleteEvent(clubId.value, event.value.id);
|
||||||
|
gtag.event('delete_event', {
|
||||||
|
event_category: '이벤트관리',
|
||||||
|
event_label: '이벤트 삭제',
|
||||||
|
value: event.value.id
|
||||||
|
});
|
||||||
toast.add({
|
toast.add({
|
||||||
severity: 'success',
|
severity: 'success',
|
||||||
summary: '성공',
|
summary: '성공',
|
||||||
detail: '이벤트가 삭제되었습니다.',
|
detail: '이벤트가 삭제되었습니다.',
|
||||||
life: 3000
|
life: 3000
|
||||||
});
|
});
|
||||||
|
|
||||||
deleteEventDialog.value = false;
|
deleteEventDialog.value = false;
|
||||||
const index = events.value.findIndex(e => e.id === event.value.id);
|
const index = events.value.findIndex(e => e.id === event.value.id);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
@@ -355,6 +390,11 @@ const deleteEvent = async () => {
|
|||||||
}
|
}
|
||||||
event.value = {};
|
event.value = {};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
gtag.event('delete_event_fail', {
|
||||||
|
event_category: '이벤트관리',
|
||||||
|
event_label: '이벤트 삭제 실패',
|
||||||
|
value: event.value.id
|
||||||
|
});
|
||||||
toast.add({
|
toast.add({
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
summary: '오류',
|
summary: '오류',
|
||||||
@@ -398,7 +438,13 @@ const goToScoreEntry = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 이벤트 캘린더 페이지로 이동
|
// 이벤트 캘린더 페이지로 이동
|
||||||
|
// 캘린더 이동 버튼 클릭 시 GA 이벤트 추적
|
||||||
const navigateToCalendar = () => {
|
const navigateToCalendar = () => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.event('go_to_calendar', {
|
||||||
|
event_category: '이벤트관리',
|
||||||
|
event_label: '캘린더 이동'
|
||||||
|
});
|
||||||
const url = `/club/calendar?clubId=${clubId.value}`;
|
const url = `/club/calendar?clubId=${clubId.value}`;
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field col-6 mb-3">
|
<div class="field col-6 mb-3">
|
||||||
<label for="memberType" class="mb-1">회원 유형</label>
|
<label for="memberType" class="mb-1">회원 유형</label>
|
||||||
<Select id="memberType" v-model="member.memberType" :options="filteredMemberTypeOptions" optionLabel="name" optionValue="code" placeholder="회원 유형 선택" class="w-full" />
|
<Select id="memberType" v-model="member.memberType" :options="filteredMemberTypeOptions" optionLabel="name" optionValue="value" placeholder="회원 유형 선택" class="w-full" :disabled="member.memberType === 'owner'" />
|
||||||
</div>
|
</div>
|
||||||
<div class="field col-6 mb-3">
|
<div class="field col-6 mb-3">
|
||||||
<label for="phone" class="mb-1">전화번호</label>
|
<label for="phone" class="mb-1">전화번호</label>
|
||||||
@@ -190,6 +190,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { useGtag } from 'vue-gtag-next';
|
||||||
import { ref, reactive, computed, onMounted, watch } from 'vue';
|
import { ref, reactive, computed, onMounted, watch } from 'vue';
|
||||||
import clubService from '@/services/clubService';
|
import clubService from '@/services/clubService';
|
||||||
import { useClubStore } from '@/stores/clubStore';
|
import { useClubStore } from '@/stores/clubStore';
|
||||||
@@ -202,9 +203,13 @@ import {
|
|||||||
getMemberTypeLabel,
|
getMemberTypeLabel,
|
||||||
getStatusLabel
|
getStatusLabel
|
||||||
} from '@/utils/enumMappings';
|
} from '@/utils/enumMappings';
|
||||||
// 모임장(owner) 옵션을 제외한 회원 유형 옵션만 노출
|
// 회원 유형 옵션 - 현재 회원이 owner인 경우 모임장 옵션 포함, 그렇지 않은 경우 제외
|
||||||
const filteredMemberTypeOptions = computed(() => MEMBER_TYPE_OPTIONS.filter(opt => opt.value !== 'owner' && opt.code !== 'owner'));
|
const filteredMemberTypeOptions = computed(() => {
|
||||||
|
if (member.value?.memberType === 'owner') {
|
||||||
|
return MEMBER_TYPE_OPTIONS;
|
||||||
|
}
|
||||||
|
return MEMBER_TYPE_OPTIONS.filter(opt => opt.value !== 'owner');
|
||||||
|
});
|
||||||
const clubStore = useClubStore();
|
const clubStore = useClubStore();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
@@ -364,6 +369,7 @@ const loadMembers = async () => {
|
|||||||
|
|
||||||
// 새 회원 추가 다이얼로그 열기
|
// 새 회원 추가 다이얼로그 열기
|
||||||
const openNewMemberDialog = () => {
|
const openNewMemberDialog = () => {
|
||||||
|
dialogMode.value = 'new';
|
||||||
member.value = {
|
member.value = {
|
||||||
name: '',
|
name: '',
|
||||||
gender: null,
|
gender: null,
|
||||||
@@ -372,7 +378,6 @@ const openNewMemberDialog = () => {
|
|||||||
email: '',
|
email: '',
|
||||||
handicap: 0
|
handicap: 0
|
||||||
};
|
};
|
||||||
dialogMode.value = 'new';
|
|
||||||
memberDialog.value = true;
|
memberDialog.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -388,6 +393,8 @@ watch(() => member.value.gender, (newGender) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 회원 수정 다이얼로그 열기
|
// 회원 수정 다이얼로그 열기
|
||||||
|
const gtag = useGtag();
|
||||||
|
|
||||||
const editMember = (data) => {
|
const editMember = (data) => {
|
||||||
member.value = {
|
member.value = {
|
||||||
...data
|
...data
|
||||||
@@ -406,12 +413,18 @@ const hideDialog = () => {
|
|||||||
const saveMember = async () => {
|
const saveMember = async () => {
|
||||||
try {
|
try {
|
||||||
const memberData = { ...member.value };
|
const memberData = { ...member.value };
|
||||||
|
|
||||||
const isNew = dialogMode.value === 'new';
|
const isNew = dialogMode.value === 'new';
|
||||||
const response = isNew
|
const response = isNew
|
||||||
? await clubService.addClubMember(clubId.value, memberData)
|
? await clubService.addClubMember(clubId.value, memberData)
|
||||||
: await clubService.updateClubMember(clubId.value, memberData.id, memberData);
|
: await clubService.updateClubMember(clubId.value, memberData.id, memberData);
|
||||||
|
|
||||||
|
// Google Analytics 이벤트 추적 - 회원 저장
|
||||||
|
gtag.event(isNew ? 'add_member' : 'edit_member', {
|
||||||
|
'event_category': '회원관리',
|
||||||
|
'event_label': isNew ? '새 회원 추가' : '회원 정보 수정',
|
||||||
|
'value': 1
|
||||||
|
});
|
||||||
|
|
||||||
toast.add({
|
toast.add({
|
||||||
severity: 'success',
|
severity: 'success',
|
||||||
summary: '성공',
|
summary: '성공',
|
||||||
@@ -440,6 +453,12 @@ const deleteMember = async () => {
|
|||||||
try {
|
try {
|
||||||
await clubService.deleteClubMember(clubId.value, selectedMember.value.id);
|
await clubService.deleteClubMember(clubId.value, selectedMember.value.id);
|
||||||
deleteDialog.value = false;
|
deleteDialog.value = false;
|
||||||
|
// Google Analytics 이벤트 추적 - 회원 삭제
|
||||||
|
gtag.event('delete_member', {
|
||||||
|
'event_category': '회원관리',
|
||||||
|
'event_label': '회원 삭제',
|
||||||
|
'value': 1
|
||||||
|
});
|
||||||
toast.add({ severity: 'success', summary: '성공', detail: '회원이 삭제되었습니다.', life: 3000 });
|
toast.add({ severity: 'success', summary: '성공', detail: '회원이 삭제되었습니다.', life: 3000 });
|
||||||
await loadMembers();
|
await loadMembers();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -449,6 +468,14 @@ const deleteMember = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// Google Analytics 페이지뷰 추적
|
||||||
|
gtag.pageview({
|
||||||
|
page_title: '회원 관리',
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
page_location: window.location.href
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ import { ref, reactive, computed, onMounted } from 'vue';
|
|||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
import scoreService from '@/services/scoreService';
|
import scoreService from '@/services/scoreService';
|
||||||
|
import { useGtag } from 'vue-gtag-next';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -411,7 +412,13 @@ const calculateTotal = (participant) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 모든 점수 저장
|
// 모든 점수 저장
|
||||||
|
// 점수 저장 버튼 클릭 시 GA 이벤트 추적
|
||||||
const saveAllScores = async () => {
|
const saveAllScores = async () => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.event('save_scores', {
|
||||||
|
event_category: '점수관리',
|
||||||
|
event_label: '점수 저장'
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
await scoreService.saveScores(meetingId.value, participants.value);
|
await scoreService.saveScores(meetingId.value, participants.value);
|
||||||
@@ -425,7 +432,13 @@ const saveAllScores = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 점수 내보내기 다이얼로그 열기
|
// 점수 내보내기 다이얼로그 열기
|
||||||
|
// 점수 내보내기 버튼 클릭 시 GA 이벤트 추적
|
||||||
const exportScores = () => {
|
const exportScores = () => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.event('export_scores', {
|
||||||
|
event_category: '점수관리',
|
||||||
|
event_label: '점수 내보내기'
|
||||||
|
});
|
||||||
exportContent.value = [exportContentOptions.value[0]];
|
exportContent.value = [exportContentOptions.value[0]];
|
||||||
exportDialog.value = true;
|
exportDialog.value = true;
|
||||||
};
|
};
|
||||||
@@ -446,12 +459,25 @@ const downloadExport = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 뒤로 가기
|
// 뒤로 가기
|
||||||
|
// 뒤로가기 버튼 클릭 시 GA 이벤트 추적
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.event('go_back', {
|
||||||
|
event_category: '점수관리',
|
||||||
|
event_label: '뒤로가기'
|
||||||
|
});
|
||||||
router.push('/clubs/meetings');
|
router.push('/clubs/meetings');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 컴포넌트 마운트 시 데이터 로드
|
// 컴포넌트 마운트 시 데이터 로드
|
||||||
|
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.pageview({
|
||||||
|
page_title: '점수관리',
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
page_location: window.location.href
|
||||||
|
});
|
||||||
if (!clubId.value) {
|
if (!clubId.value) {
|
||||||
toast.add({ severity: 'error', summary: '오류', detail: '클럽 ID가 설정되지 않았습니다.', life: 3000 });
|
toast.add({ severity: 'error', summary: '오류', detail: '클럽 ID가 설정되지 않았습니다.', life: 3000 });
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div class="period-selector">
|
<div class="period-selector">
|
||||||
<select v-model="selectedPeriod" @change="loadStatistics">
|
<select v-model="selectedPeriod" @change="onPeriodChange">
|
||||||
<option value="30">최근 30일</option>
|
<option value="30">최근 30일</option>
|
||||||
<option value="90">최근 90일</option>
|
<option value="90">최근 90일</option>
|
||||||
<option value="180">최근 180일</option>
|
<option value="180">최근 180일</option>
|
||||||
@@ -184,6 +184,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ref, onMounted, computed } from 'vue';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
|
import { useGtag } from 'vue-gtag-next';
|
||||||
import { Bar as BarChart, Line as LineChart } from 'vue-chartjs';
|
import { Bar as BarChart, Line as LineChart } from 'vue-chartjs';
|
||||||
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, LineElement, PointElement, Title, Tooltip, Legend } from 'chart.js';
|
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, LineElement, PointElement, Title, Tooltip, Legend } from 'chart.js';
|
||||||
import apiClient from '@/services/api';
|
import apiClient from '@/services/api';
|
||||||
@@ -194,6 +195,7 @@ export default {
|
|||||||
name: 'Statistics',
|
name: 'Statistics',
|
||||||
components: { BarChart, LineChart },
|
components: { BarChart, LineChart },
|
||||||
setup() {
|
setup() {
|
||||||
|
const gtag = useGtag();
|
||||||
const stats = ref({
|
const stats = ref({
|
||||||
summary: {},
|
summary: {},
|
||||||
scoreDistribution: [],
|
scoreDistribution: [],
|
||||||
@@ -300,9 +302,25 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
// Google Analytics 페이지뷰 추적
|
||||||
|
gtag.pageview({
|
||||||
|
page_title: '통계',
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
page_location: window.location.href
|
||||||
|
});
|
||||||
loadStatistics();
|
loadStatistics();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 기간 변경 시 GA 이벤트
|
||||||
|
const onPeriodChange = () => {
|
||||||
|
gtag.event('change_period', {
|
||||||
|
'event_category': '통계',
|
||||||
|
'event_label': selectedPeriod.value + '일',
|
||||||
|
'value': Number(selectedPeriod.value)
|
||||||
|
});
|
||||||
|
loadStatistics();
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
stats,
|
stats,
|
||||||
selectedPeriod,
|
selectedPeriod,
|
||||||
@@ -314,7 +332,8 @@ export default {
|
|||||||
attendanceTrendChartData,
|
attendanceTrendChartData,
|
||||||
attendanceTrendChartOptions,
|
attendanceTrendChartOptions,
|
||||||
participantTrendChartData,
|
participantTrendChartData,
|
||||||
getHandicapTrendChartData
|
getHandicapTrendChartData,
|
||||||
|
onPeriodChange
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,11 +50,11 @@
|
|||||||
<div v-if="event.location" class="info-row">
|
<div v-if="event.location" class="info-row">
|
||||||
<i class="pi pi-map-marker mr-1" /><strong>장소:</strong> {{ event.location }}
|
<i class="pi pi-map-marker mr-1" /><strong>장소:</strong> {{ event.location }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="event.participantFee" class="info-row">
|
<div v-if="event.participantFee > 0" class="info-row">
|
||||||
<i class="pi pi-credit-card mr-1" /><strong>참가비:</strong> {{
|
<i class="pi pi-credit-card mr-1" /><strong>참가비:</strong> {{
|
||||||
Number(event.participantFee).toLocaleString('ko-KR') }}원
|
Number(event.participantFee).toLocaleString('ko-KR') }}원
|
||||||
</div>
|
</div>
|
||||||
<div v-if="event.maxParticipants" class="info-row">
|
<div v-if="event.maxParticipants > 0" class="info-row">
|
||||||
<i class="pi pi-user mr-1" /><strong>참여:</strong> {{ participants.length }}/{{ event.maxParticipants
|
<i class="pi pi-user mr-1" /><strong>참여:</strong> {{ participants.length }}/{{ event.maxParticipants
|
||||||
}}명
|
}}명
|
||||||
</div>
|
</div>
|
||||||
@@ -765,7 +765,14 @@ function handleGuestAdded(guest) {
|
|||||||
reload();
|
reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.pageview({
|
||||||
|
page_title: '이벤트공개페이지',
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
page_location: window.location.href
|
||||||
|
});
|
||||||
const token = sessionStorage.getItem('event_token_' + publicHash);
|
const token = sessionStorage.getItem('event_token_' + publicHash);
|
||||||
if (token) {
|
if (token) {
|
||||||
fetchEventData();
|
fetchEventData();
|
||||||
|
|||||||
@@ -16,13 +16,21 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { useGtag } from 'vue-gtag-next';
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const message = ref('');
|
const message = ref('');
|
||||||
|
|
||||||
|
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
const gtag = useGtag();
|
||||||
|
gtag.pageview({
|
||||||
|
page_title: '결제실패',
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
page_location: window.location.href
|
||||||
|
});
|
||||||
// URL에서 메시지 가져오기
|
// URL에서 메시지 가져오기
|
||||||
message.value = route.query.message || '결제가 취소되었습니다.';
|
message.value = route.query.message || '결제가 취소되었습니다.';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
name: Flutter 테스트
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, develop ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main, develop ]
|
||||||
|
workflow_dispatch: # 수동 실행 옵션
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: 테스트 실행
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15 # CI 전체 타임아웃 설정
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 코드 체크아웃
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Flutter 설정
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: '3.10.0'
|
||||||
|
channel: 'stable'
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: 의존성 설치
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: 테스트 디렉토리 생성
|
||||||
|
run: mkdir -p test_diagnostics
|
||||||
|
|
||||||
|
- name: 테스트 실행 권한 설정
|
||||||
|
run: |
|
||||||
|
chmod +x ./scripts/run_tests.sh
|
||||||
|
chmod +x ./scripts/reproduce_test.sh
|
||||||
|
|
||||||
|
- name: Flutter 테스트 실행
|
||||||
|
id: run_tests
|
||||||
|
continue-on-error: true
|
||||||
|
run: ./scripts/run_tests.sh ci --verbose
|
||||||
|
env:
|
||||||
|
FLUTTER_TEST_IS_CI: true
|
||||||
|
|
||||||
|
- name: 플래키 자동 재실행 (실패 시만)
|
||||||
|
if: steps.run_tests.outcome == 'failure'
|
||||||
|
run: |
|
||||||
|
echo "1차 테스트 실패 - 플래키 자동 재실행 시작"
|
||||||
|
chmod +x ./scripts/run_and_rerun_failed.sh
|
||||||
|
# 전체 스위트를 재실행하며, 내부적으로 per-file 폴백을 수행
|
||||||
|
set +e
|
||||||
|
./scripts/run_and_rerun_failed.sh
|
||||||
|
RERUN_STATUS=$?
|
||||||
|
set -e
|
||||||
|
if [ "$RERUN_STATUS" -eq 0 ]; then
|
||||||
|
echo "재실행 성공 - 플래키로 간주하고 워크플로를 성공 처리합니다."
|
||||||
|
echo "::set-output name=flaky_recovered::true"
|
||||||
|
else
|
||||||
|
echo "재실행 실패 - 실패를 유지합니다."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: 임시 flutter 테스트 로그 수집 (/tmp)
|
||||||
|
if: always()
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "임시 flutter 테스트 로그를 test_diagnostics/로 복사합니다"
|
||||||
|
mkdir -p test_diagnostics
|
||||||
|
# run_and_rerun_failed.sh가 출력한 /tmp 로그를 수집
|
||||||
|
cp -v /tmp/flutter_test_*.log test_diagnostics/ 2>/dev/null || true
|
||||||
|
cp -v /tmp/flutter_test_*.log.clean test_diagnostics/ 2>/dev/null || true
|
||||||
|
# machine 리포터 로그 등 기타 진단 파일 수집 시도
|
||||||
|
cp -v /tmp/*flutter*test*.* test_diagnostics/ 2>/dev/null || true
|
||||||
|
|
||||||
|
- name: 테스트 진단 정보 수집
|
||||||
|
if: failure()
|
||||||
|
run: |
|
||||||
|
echo "테스트 실패 - 진단 정보 수집 중..."
|
||||||
|
ls -la test_diagnostics/
|
||||||
|
cat test_diagnostics/*.log || echo "로그 파일 없음"
|
||||||
|
cat test_diagnostics/*.txt || echo "분석 파일 없음"
|
||||||
|
|
||||||
|
- name: 테스트 진단 정보 업로드
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: test-diagnostics
|
||||||
|
path: test_diagnostics/
|
||||||
|
retention-days: 7
|
||||||
|
if-no-files-found: ignore
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# CI 환경 테스트 설정
|
||||||
|
test_settings:
|
||||||
|
# 비동기 작업 타임아웃 설정 (밀리초)
|
||||||
|
async_timeout: 1500
|
||||||
|
# 테스트 간 지연 시간 (밀리초)
|
||||||
|
test_delay: 200
|
||||||
|
# 테스트 재시도 횟수
|
||||||
|
retry_count: 3
|
||||||
|
# 테스트 병렬 실행 여부
|
||||||
|
parallel: false
|
||||||
|
# 메모리 제한 (MB)
|
||||||
|
memory_limit: 2048
|
||||||
|
# 로깅 레벨 (1: 최소, 5: 최대)
|
||||||
|
log_level: 4
|
||||||
|
# 테스트 격리 모드 (strict, normal, relaxed)
|
||||||
|
isolation_mode: strict
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.build/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
.swiftpm/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/**
|
||||||
|
!/build/web/
|
||||||
|
!/build/web/**
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Android Studio will place build artifacts here
|
||||||
|
/android/app/debug
|
||||||
|
/android/app/profile
|
||||||
|
/android/app/release
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: "fcf2c11572af6f390246c056bc905eca609533a0"
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
- platform: android
|
||||||
|
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
- platform: ios
|
||||||
|
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
- platform: linux
|
||||||
|
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
- platform: macos
|
||||||
|
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
- platform: web
|
||||||
|
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
- platform: windows
|
||||||
|
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# mobile
|
||||||
|
|
||||||
|
A new Flutter project.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
This project is a starting point for a Flutter application.
|
||||||
|
|
||||||
|
A few resources to get you started if this is your first Flutter project:
|
||||||
|
|
||||||
|
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||||
|
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||||
|
|
||||||
|
For help getting started with Flutter development, view the
|
||||||
|
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||||
|
samples, guidance on mobile development, and a full API reference.
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# This file configures the analyzer, which statically analyzes Dart code to
|
||||||
|
# check for errors, warnings, and lints.
|
||||||
|
#
|
||||||
|
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||||
|
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||||
|
# invoked from the command line by running `flutter analyze`.
|
||||||
|
|
||||||
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
# Exclude particularly noisy legacy integration test fixtures from analysis
|
||||||
|
# to keep CI green while retaining analysis for the rest of the test suite.
|
||||||
|
exclude:
|
||||||
|
- test/integration/subscription_service_integration_test_fixed2.dart
|
||||||
|
- test/integration/subscription_service_integration_test_fixed3.dart
|
||||||
|
- test/integration/subscription_service_integration_test_backup.dart
|
||||||
|
# Exclude test-only helpers incorrectly placed under lib/
|
||||||
|
- lib/utils/test_config.dart
|
||||||
|
- lib/utils/test_diagnostics.dart
|
||||||
|
- lib/utils/test_utils.dart
|
||||||
|
# Downgrade or ignore some common test-oriented lints to avoid failing
|
||||||
|
# analysis on non-production patterns used in tests.
|
||||||
|
errors:
|
||||||
|
avoid_print: ignore
|
||||||
|
annotate_overrides: ignore
|
||||||
|
prefer_final_fields: ignore
|
||||||
|
unnecessary_getters_setters: ignore
|
||||||
|
override_on_non_overriding_member: ignore
|
||||||
|
use_function_type_syntax_for_parameters: ignore
|
||||||
|
use_super_parameters: ignore
|
||||||
|
no_leading_underscores_for_local_identifiers: ignore
|
||||||
|
|
||||||
|
linter:
|
||||||
|
# The lint rules applied to this project can be customized in the
|
||||||
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
# included above or to enable additional rules. A list of all available lints
|
||||||
|
# and their documentation is published at https://dart.dev/lints.
|
||||||
|
#
|
||||||
|
# Instead of disabling a lint rule for the entire project in the
|
||||||
|
# section below, it can also be suppressed for a single line of code
|
||||||
|
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||||
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
|
# producing the lint.
|
||||||
|
rules:
|
||||||
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
.cxx/
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/to/reference-keystore
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.application")
|
||||||
|
id("kotlin-android")
|
||||||
|
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||||
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.example.mobile"
|
||||||
|
compileSdk = flutter.compileSdkVersion
|
||||||
|
ndkVersion = "27.0.12077973"
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
// 코어 라이브러리 desugaring 활성화
|
||||||
|
isCoreLibraryDesugaringEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
|
applicationId = "com.example.mobile"
|
||||||
|
// You can update the following values to match your application needs.
|
||||||
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
|
minSdk = flutter.minSdkVersion
|
||||||
|
targetSdk = flutter.targetSdkVersion
|
||||||
|
versionCode = flutter.versionCode
|
||||||
|
versionName = flutter.versionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// TODO: Add your own signing config for the release build.
|
||||||
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source = "../.."
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// 코어 라이브러리 desugaring 의존성 추가
|
||||||
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<application
|
||||||
|
android:label="Lanebow"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
</application>
|
||||||
|
<!-- Required to query activities that can process text, see:
|
||||||
|
https://developer.android.com/training/package-visibility and
|
||||||
|
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||||
|
|
||||||
|
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
|
<data android:mimeType="text/plain"/>
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.example.mobile
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity : FlutterActivity()
|
||||||
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 131 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 41 KiB |
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#FFFFFF</color>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
|
||||||
|
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||||
|
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
project.evaluationDependsOn(":app")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register<Delete>("clean") {
|
||||||
|
delete(rootProject.layout.buildDirectory)
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
pluginManagement {
|
||||||
|
val flutterSdkPath = run {
|
||||||
|
val properties = java.util.Properties()
|
||||||
|
file("local.properties").inputStream().use { properties.load(it) }
|
||||||
|
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||||
|
flutterSdkPath
|
||||||
|
}
|
||||||
|
|
||||||
|
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
|
id("com.android.application") version "8.7.3" apply false
|
||||||
|
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
include(":app")
|
||||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 101 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
title,startDate,endDate,description,location,status,type,maxParticipants
|
||||||
|
볼링 대회,2025-10-01,2025-10-01,가을 정기 대회,서울볼링장,활성,개인전,32
|
||||||
|
연습 모임,2025-10-12,,월간 연습 모임,우리동네볼링장,대기,연습,
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
13a19403b2f3e4a1d3f2824c427aaade
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
assets/images/logo.png
assetassets/images/logo.png*assets/templates/event_import_template.csv
asset*assets/templates/event_import_template.csv2packages/cupertino_icons/assets/CupertinoIcons.ttf
asset2packages/cupertino_icons/assets/CupertinoIcons.ttf
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"DQMHFmFzc2V0cy9pbWFnZXMvbG9nby5wbmcMAQ0BBwVhc3NldAcWYXNzZXRzL2ltYWdlcy9sb2dvLnBuZwcqYXNzZXRzL3RlbXBsYXRlcy9ldmVudF9pbXBvcnRfdGVtcGxhdGUuY3N2DAENAQcFYXNzZXQHKmFzc2V0cy90ZW1wbGF0ZXMvZXZlbnRfaW1wb3J0X3RlbXBsYXRlLmNzdgcycGFja2FnZXMvY3VwZXJ0aW5vX2ljb25zL2Fzc2V0cy9DdXBlcnRpbm9JY29ucy50dGYMAQ0BBwVhc3NldAcycGFja2FnZXMvY3VwZXJ0aW5vX2ljb25zL2Fzc2V0cy9DdXBlcnRpbm9JY29ucy50dGY="
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"assets/images/logo.png":["assets/images/logo.png"],"assets/templates/event_import_template.csv":["assets/templates/event_import_template.csv"],"packages/cupertino_icons/assets/CupertinoIcons.ttf":["packages/cupertino_icons/assets/CupertinoIcons.ttf"]}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
[{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]},{"family":"packages/cupertino_icons/CupertinoIcons","fonts":[{"asset":"packages/cupertino_icons/assets/CupertinoIcons.ttf"}]}]
|
||||||
|
After Width: | Height: | Size: 101 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
title,startDate,endDate,description,location,status,type,maxParticipants
|
||||||
|
볼링 대회,2025-10-01,2025-10-01,가을 정기 대회,서울볼링장,활성,개인전,32
|
||||||
|
연습 모임,2025-10-12,,월간 연습 모임,우리동네볼링장,대기,연습,
|
||||||
|
@@ -0,0 +1,192 @@
|
|||||||
|
|
||||||
|
var CanvasKitInit = (() => {
|
||||||
|
var _scriptName = import.meta.url;
|
||||||
|
|
||||||
|
return (
|
||||||
|
function(moduleArg = {}) {
|
||||||
|
var moduleRtn;
|
||||||
|
|
||||||
|
var r=moduleArg,ba,ca,da=new Promise((a,b)=>{ba=a;ca=b}),fa="object"==typeof window,ia="function"==typeof importScripts;
|
||||||
|
(function(a){a.ce=a.ce||[];a.ce.push(function(){a.MakeSWCanvasSurface=function(b){var c=b,e="undefined"!==typeof OffscreenCanvas&&c instanceof OffscreenCanvas;if(!("undefined"!==typeof HTMLCanvasElement&&c instanceof HTMLCanvasElement||e||(c=document.getElementById(b),c)))throw"Canvas with id "+b+" was not found";if(b=a.MakeSurface(c.width,c.height))b.Ae=c;return b};a.MakeCanvasSurface||(a.MakeCanvasSurface=a.MakeSWCanvasSurface);a.MakeSurface=function(b,c){var e={width:b,height:c,colorType:a.ColorType.RGBA_8888,
|
||||||
|
alphaType:a.AlphaType.Unpremul,colorSpace:a.ColorSpace.SRGB},f=b*c*4,k=a._malloc(f);if(e=a.Surface._makeRasterDirect(e,k,4*b))e.Ae=null,e.$e=b,e.Xe=c,e.Ye=f,e.He=k,e.getCanvas().clear(a.TRANSPARENT);return e};a.MakeRasterDirectSurface=function(b,c,e){return a.Surface._makeRasterDirect(b,c.byteOffset,e)};a.Surface.prototype.flush=function(b){a.$d(this.Zd);this._flush();if(this.Ae){var c=new Uint8ClampedArray(a.HEAPU8.buffer,this.He,this.Ye);c=new ImageData(c,this.$e,this.Xe);b?this.Ae.getContext("2d").putImageData(c,
|
||||||
|
0,0,b[0],b[1],b[2]-b[0],b[3]-b[1]):this.Ae.getContext("2d").putImageData(c,0,0)}};a.Surface.prototype.dispose=function(){this.He&&a._free(this.He);this.delete()};a.$d=a.$d||function(){};a.Be=a.Be||function(){return null}})})(r);
|
||||||
|
(function(a){a.ce=a.ce||[];a.ce.push(function(){function b(l,p,v){return l&&l.hasOwnProperty(p)?l[p]:v}function c(l){var p=ja(ka);ka[p]=l;return p}function e(l){return l.naturalHeight||l.videoHeight||l.displayHeight||l.height}function f(l){return l.naturalWidth||l.videoWidth||l.displayWidth||l.width}function k(l,p,v,w){l.bindTexture(l.TEXTURE_2D,p);w||v.alphaType!==a.AlphaType.Premul||l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!0);return p}function n(l,p,v){v||p.alphaType!==a.AlphaType.Premul||
|
||||||
|
l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1);l.bindTexture(l.TEXTURE_2D,null)}a.GetWebGLContext=function(l,p){if(!l)throw"null canvas passed into makeWebGLContext";var v={alpha:b(p,"alpha",1),depth:b(p,"depth",1),stencil:b(p,"stencil",8),antialias:b(p,"antialias",0),premultipliedAlpha:b(p,"premultipliedAlpha",1),preserveDrawingBuffer:b(p,"preserveDrawingBuffer",0),preferLowPowerToHighPerformance:b(p,"preferLowPowerToHighPerformance",0),failIfMajorPerformanceCaveat:b(p,"failIfMajorPerformanceCaveat",
|
||||||
|
0),enableExtensionsByDefault:b(p,"enableExtensionsByDefault",1),explicitSwapControl:b(p,"explicitSwapControl",0),renderViaOffscreenBackBuffer:b(p,"renderViaOffscreenBackBuffer",0)};v.majorVersion=p&&p.majorVersion?p.majorVersion:"undefined"!==typeof WebGL2RenderingContext?2:1;if(v.explicitSwapControl)throw"explicitSwapControl is not supported";l=na(l,v);if(!l)return 0;oa(l);z.le.getExtension("WEBGL_debug_renderer_info");return l};a.deleteContext=function(l){z===pa[l]&&(z=null);"object"==typeof JSEvents&&
|
||||||
|
JSEvents.Af(pa[l].le.canvas);pa[l]&&pa[l].le.canvas&&(pa[l].le.canvas.Ve=void 0);pa[l]=null};a._setTextureCleanup({deleteTexture:function(l,p){var v=ka[p];v&&pa[l].le.deleteTexture(v);ka[p]=null}});a.MakeWebGLContext=function(l){if(!this.$d(l))return null;var p=this._MakeGrContext();if(!p)return null;p.Zd=l;var v=p.delete.bind(p);p["delete"]=function(){a.$d(this.Zd);v()}.bind(p);return z.Je=p};a.MakeGrContext=a.MakeWebGLContext;a.GrDirectContext.prototype.getResourceCacheLimitBytes=function(){a.$d(this.Zd);
|
||||||
|
this._getResourceCacheLimitBytes()};a.GrDirectContext.prototype.getResourceCacheUsageBytes=function(){a.$d(this.Zd);this._getResourceCacheUsageBytes()};a.GrDirectContext.prototype.releaseResourcesAndAbandonContext=function(){a.$d(this.Zd);this._releaseResourcesAndAbandonContext()};a.GrDirectContext.prototype.setResourceCacheLimitBytes=function(l){a.$d(this.Zd);this._setResourceCacheLimitBytes(l)};a.MakeOnScreenGLSurface=function(l,p,v,w,A,D){if(!this.$d(l.Zd))return null;p=void 0===A||void 0===D?
|
||||||
|
this._MakeOnScreenGLSurface(l,p,v,w):this._MakeOnScreenGLSurface(l,p,v,w,A,D);if(!p)return null;p.Zd=l.Zd;return p};a.MakeRenderTarget=function(){var l=arguments[0];if(!this.$d(l.Zd))return null;if(3===arguments.length){var p=this._MakeRenderTargetWH(l,arguments[1],arguments[2]);if(!p)return null}else if(2===arguments.length){if(p=this._MakeRenderTargetII(l,arguments[1]),!p)return null}else return null;p.Zd=l.Zd;return p};a.MakeWebGLCanvasSurface=function(l,p,v){p=p||null;var w=l,A="undefined"!==
|
||||||
|
typeof OffscreenCanvas&&w instanceof OffscreenCanvas;if(!("undefined"!==typeof HTMLCanvasElement&&w instanceof HTMLCanvasElement||A||(w=document.getElementById(l),w)))throw"Canvas with id "+l+" was not found";l=this.GetWebGLContext(w,v);if(!l||0>l)throw"failed to create webgl context: err "+l;l=this.MakeWebGLContext(l);p=this.MakeOnScreenGLSurface(l,w.width,w.height,p);return p?p:(p=w.cloneNode(!0),w.parentNode.replaceChild(p,w),p.classList.add("ck-replaced"),a.MakeSWCanvasSurface(p))};a.MakeCanvasSurface=
|
||||||
|
a.MakeWebGLCanvasSurface;a.Surface.prototype.makeImageFromTexture=function(l,p){a.$d(this.Zd);l=c(l);if(p=this._makeImageFromTexture(this.Zd,l,p))p.ue=l;return p};a.Surface.prototype.makeImageFromTextureSource=function(l,p,v){p||={height:e(l),width:f(l),colorType:a.ColorType.RGBA_8888,alphaType:v?a.AlphaType.Premul:a.AlphaType.Unpremul};p.colorSpace||(p.colorSpace=a.ColorSpace.SRGB);a.$d(this.Zd);var w=z.le;v=k(w,w.createTexture(),p,v);2===z.version?w.texImage2D(w.TEXTURE_2D,0,w.RGBA,p.width,p.height,
|
||||||
|
0,w.RGBA,w.UNSIGNED_BYTE,l):w.texImage2D(w.TEXTURE_2D,0,w.RGBA,w.RGBA,w.UNSIGNED_BYTE,l);n(w,p);this._resetContext();return this.makeImageFromTexture(v,p)};a.Surface.prototype.updateTextureFromSource=function(l,p,v){if(l.ue){a.$d(this.Zd);var w=l.getImageInfo(),A=z.le,D=k(A,ka[l.ue],w,v);2===z.version?A.texImage2D(A.TEXTURE_2D,0,A.RGBA,f(p),e(p),0,A.RGBA,A.UNSIGNED_BYTE,p):A.texImage2D(A.TEXTURE_2D,0,A.RGBA,A.RGBA,A.UNSIGNED_BYTE,p);n(A,w,v);this._resetContext();ka[l.ue]=null;l.ue=c(D);w.colorSpace=
|
||||||
|
l.getColorSpace();p=this._makeImageFromTexture(this.Zd,l.ue,w);v=l.Yd.ae;A=l.Yd.ee;l.Yd.ae=p.Yd.ae;l.Yd.ee=p.Yd.ee;p.Yd.ae=v;p.Yd.ee=A;p.delete();w.colorSpace.delete()}};a.MakeLazyImageFromTextureSource=function(l,p,v){p||={height:e(l),width:f(l),colorType:a.ColorType.RGBA_8888,alphaType:v?a.AlphaType.Premul:a.AlphaType.Unpremul};p.colorSpace||(p.colorSpace=a.ColorSpace.SRGB);var w={makeTexture:function(){var A=z,D=A.le,I=k(D,D.createTexture(),p,v);2===A.version?D.texImage2D(D.TEXTURE_2D,0,D.RGBA,
|
||||||
|
p.width,p.height,0,D.RGBA,D.UNSIGNED_BYTE,l):D.texImage2D(D.TEXTURE_2D,0,D.RGBA,D.RGBA,D.UNSIGNED_BYTE,l);n(D,p,v);return c(I)},freeSrc:function(){}};"VideoFrame"===l.constructor.name&&(w.freeSrc=function(){l.close()});return a.Image._makeFromGenerator(p,w)};a.$d=function(l){return l?oa(l):!1};a.Be=function(){return z&&z.Je&&!z.Je.isDeleted()?z.Je:null}})})(r);
|
||||||
|
(function(a){function b(g){return(f(255*g[3])<<24|f(255*g[0])<<16|f(255*g[1])<<8|f(255*g[2])<<0)>>>0}function c(g){if(g&&g._ck)return g;if(g instanceof Float32Array){for(var d=Math.floor(g.length/4),h=new Uint32Array(d),m=0;m<d;m++)h[m]=b(g.slice(4*m,4*(m+1)));return h}if(g instanceof Uint32Array)return g;if(g instanceof Array&&g[0]instanceof Float32Array)return g.map(b)}function e(g){if(void 0===g)return 1;var d=parseFloat(g);return g&&-1!==g.indexOf("%")?d/100:d}function f(g){return Math.round(Math.max(0,
|
||||||
|
Math.min(g||0,255)))}function k(g,d){d&&d._ck||a._free(g)}function n(g,d,h){if(!g||!g.length)return 0;if(g&&g._ck)return g.byteOffset;var m=a[d].BYTES_PER_ELEMENT;h||=a._malloc(g.length*m);a[d].set(g,h/m);return h}function l(g){var d={he:0,count:g.length,colorType:a.ColorType.RGBA_F32};if(g instanceof Float32Array)d.he=n(g,"HEAPF32"),d.count=g.length/4;else if(g instanceof Uint32Array)d.he=n(g,"HEAPU32"),d.colorType=a.ColorType.RGBA_8888;else if(g instanceof Array){if(g&&g.length){for(var h=a._malloc(16*
|
||||||
|
g.length),m=0,t=h/4,u=0;u<g.length;u++)for(var x=0;4>x;x++)a.HEAPF32[t+m]=g[u][x],m++;g=h}else g=0;d.he=g}else throw"Invalid argument to copyFlexibleColorArray, Not a color array "+typeof g;return d}function p(g){if(!g)return 0;var d=aa.toTypedArray();if(g.length){if(6===g.length||9===g.length)return n(g,"HEAPF32",P),6===g.length&&a.HEAPF32.set(Vc,6+P/4),P;if(16===g.length)return d[0]=g[0],d[1]=g[1],d[2]=g[3],d[3]=g[4],d[4]=g[5],d[5]=g[7],d[6]=g[12],d[7]=g[13],d[8]=g[15],P;throw"invalid matrix size";
|
||||||
|
}if(void 0===g.m11)throw"invalid matrix argument";d[0]=g.m11;d[1]=g.m21;d[2]=g.m41;d[3]=g.m12;d[4]=g.m22;d[5]=g.m42;d[6]=g.m14;d[7]=g.m24;d[8]=g.m44;return P}function v(g){if(!g)return 0;var d=X.toTypedArray();if(g.length){if(16!==g.length&&6!==g.length&&9!==g.length)throw"invalid matrix size";if(16===g.length)return n(g,"HEAPF32",la);d.fill(0);d[0]=g[0];d[1]=g[1];d[3]=g[2];d[4]=g[3];d[5]=g[4];d[7]=g[5];d[10]=1;d[12]=g[6];d[13]=g[7];d[15]=g[8];6===g.length&&(d[12]=0,d[13]=0,d[15]=1);return la}if(void 0===
|
||||||
|
g.m11)throw"invalid matrix argument";d[0]=g.m11;d[1]=g.m21;d[2]=g.m31;d[3]=g.m41;d[4]=g.m12;d[5]=g.m22;d[6]=g.m32;d[7]=g.m42;d[8]=g.m13;d[9]=g.m23;d[10]=g.m33;d[11]=g.m43;d[12]=g.m14;d[13]=g.m24;d[14]=g.m34;d[15]=g.m44;return la}function w(g,d){return n(g,"HEAPF32",d||ha)}function A(g,d,h,m){var t=Ea.toTypedArray();t[0]=g;t[1]=d;t[2]=h;t[3]=m;return ha}function D(g){for(var d=new Float32Array(4),h=0;4>h;h++)d[h]=a.HEAPF32[g/4+h];return d}function I(g,d){return n(g,"HEAPF32",d||V)}function Q(g,d){return n(g,
|
||||||
|
"HEAPF32",d||tb)}a.Color=function(g,d,h,m){void 0===m&&(m=1);return a.Color4f(f(g)/255,f(d)/255,f(h)/255,m)};a.ColorAsInt=function(g,d,h,m){void 0===m&&(m=255);return(f(m)<<24|f(g)<<16|f(d)<<8|f(h)<<0&268435455)>>>0};a.Color4f=function(g,d,h,m){void 0===m&&(m=1);return Float32Array.of(g,d,h,m)};Object.defineProperty(a,"TRANSPARENT",{get:function(){return a.Color4f(0,0,0,0)}});Object.defineProperty(a,"BLACK",{get:function(){return a.Color4f(0,0,0,1)}});Object.defineProperty(a,"WHITE",{get:function(){return a.Color4f(1,
|
||||||
|
1,1,1)}});Object.defineProperty(a,"RED",{get:function(){return a.Color4f(1,0,0,1)}});Object.defineProperty(a,"GREEN",{get:function(){return a.Color4f(0,1,0,1)}});Object.defineProperty(a,"BLUE",{get:function(){return a.Color4f(0,0,1,1)}});Object.defineProperty(a,"YELLOW",{get:function(){return a.Color4f(1,1,0,1)}});Object.defineProperty(a,"CYAN",{get:function(){return a.Color4f(0,1,1,1)}});Object.defineProperty(a,"MAGENTA",{get:function(){return a.Color4f(1,0,1,1)}});a.getColorComponents=function(g){return[Math.floor(255*
|
||||||
|
g[0]),Math.floor(255*g[1]),Math.floor(255*g[2]),g[3]]};a.parseColorString=function(g,d){g=g.toLowerCase();if(g.startsWith("#")){d=255;switch(g.length){case 9:d=parseInt(g.slice(7,9),16);case 7:var h=parseInt(g.slice(1,3),16);var m=parseInt(g.slice(3,5),16);var t=parseInt(g.slice(5,7),16);break;case 5:d=17*parseInt(g.slice(4,5),16);case 4:h=17*parseInt(g.slice(1,2),16),m=17*parseInt(g.slice(2,3),16),t=17*parseInt(g.slice(3,4),16)}return a.Color(h,m,t,d/255)}return g.startsWith("rgba")?(g=g.slice(5,
|
||||||
|
-1),g=g.split(","),a.Color(+g[0],+g[1],+g[2],e(g[3]))):g.startsWith("rgb")?(g=g.slice(4,-1),g=g.split(","),a.Color(+g[0],+g[1],+g[2],e(g[3]))):g.startsWith("gray(")||g.startsWith("hsl")||!d||(g=d[g],void 0===g)?a.BLACK:g};a.multiplyByAlpha=function(g,d){g=g.slice();g[3]=Math.max(0,Math.min(g[3]*d,1));return g};a.Malloc=function(g,d){var h=a._malloc(d*g.BYTES_PER_ELEMENT);return{_ck:!0,length:d,byteOffset:h,qe:null,subarray:function(m,t){m=this.toTypedArray().subarray(m,t);m._ck=!0;return m},toTypedArray:function(){if(this.qe&&
|
||||||
|
this.qe.length)return this.qe;this.qe=new g(a.HEAPU8.buffer,h,d);this.qe._ck=!0;return this.qe}}};a.Free=function(g){a._free(g.byteOffset);g.byteOffset=0;g.toTypedArray=null;g.qe=null};var P=0,aa,la=0,X,ha=0,Ea,ea,V=0,Ub,Aa=0,Vb,ub=0,Wb,vb=0,$a,Ma=0,Xb,tb=0,Yb,Zb=0,Vc=Float32Array.of(0,0,1);a.onRuntimeInitialized=function(){function g(d,h,m,t,u,x,C){x||(x=4*t.width,t.colorType===a.ColorType.RGBA_F16?x*=2:t.colorType===a.ColorType.RGBA_F32&&(x*=4));var G=x*t.height;var F=u?u.byteOffset:a._malloc(G);
|
||||||
|
if(C?!d._readPixels(t,F,x,h,m,C):!d._readPixels(t,F,x,h,m))return u||a._free(F),null;if(u)return u.toTypedArray();switch(t.colorType){case a.ColorType.RGBA_8888:case a.ColorType.RGBA_F16:d=(new Uint8Array(a.HEAPU8.buffer,F,G)).slice();break;case a.ColorType.RGBA_F32:d=(new Float32Array(a.HEAPU8.buffer,F,G)).slice();break;default:return null}a._free(F);return d}Ea=a.Malloc(Float32Array,4);ha=Ea.byteOffset;X=a.Malloc(Float32Array,16);la=X.byteOffset;aa=a.Malloc(Float32Array,9);P=aa.byteOffset;Xb=a.Malloc(Float32Array,
|
||||||
|
12);tb=Xb.byteOffset;Yb=a.Malloc(Float32Array,12);Zb=Yb.byteOffset;ea=a.Malloc(Float32Array,4);V=ea.byteOffset;Ub=a.Malloc(Float32Array,4);Aa=Ub.byteOffset;Vb=a.Malloc(Float32Array,3);ub=Vb.byteOffset;Wb=a.Malloc(Float32Array,3);vb=Wb.byteOffset;$a=a.Malloc(Int32Array,4);Ma=$a.byteOffset;a.ColorSpace.SRGB=a.ColorSpace._MakeSRGB();a.ColorSpace.DISPLAY_P3=a.ColorSpace._MakeDisplayP3();a.ColorSpace.ADOBE_RGB=a.ColorSpace._MakeAdobeRGB();a.GlyphRunFlags={IsWhiteSpace:a._GlyphRunFlags_isWhiteSpace};a.Path.MakeFromCmds=
|
||||||
|
function(d){var h=n(d,"HEAPF32"),m=a.Path._MakeFromCmds(h,d.length);k(h,d);return m};a.Path.MakeFromVerbsPointsWeights=function(d,h,m){var t=n(d,"HEAPU8"),u=n(h,"HEAPF32"),x=n(m,"HEAPF32"),C=a.Path._MakeFromVerbsPointsWeights(t,d.length,u,h.length,x,m&&m.length||0);k(t,d);k(u,h);k(x,m);return C};a.Path.prototype.addArc=function(d,h,m){d=I(d);this._addArc(d,h,m);return this};a.Path.prototype.addCircle=function(d,h,m,t){this._addCircle(d,h,m,!!t);return this};a.Path.prototype.addOval=function(d,h,m){void 0===
|
||||||
|
m&&(m=1);d=I(d);this._addOval(d,!!h,m);return this};a.Path.prototype.addPath=function(){var d=Array.prototype.slice.call(arguments),h=d[0],m=!1;"boolean"===typeof d[d.length-1]&&(m=d.pop());if(1===d.length)this._addPath(h,1,0,0,0,1,0,0,0,1,m);else if(2===d.length)d=d[1],this._addPath(h,d[0],d[1],d[2],d[3],d[4],d[5],d[6]||0,d[7]||0,d[8]||1,m);else if(7===d.length||10===d.length)this._addPath(h,d[1],d[2],d[3],d[4],d[5],d[6],d[7]||0,d[8]||0,d[9]||1,m);else return null;return this};a.Path.prototype.addPoly=
|
||||||
|
function(d,h){var m=n(d,"HEAPF32");this._addPoly(m,d.length/2,h);k(m,d);return this};a.Path.prototype.addRect=function(d,h){d=I(d);this._addRect(d,!!h);return this};a.Path.prototype.addRRect=function(d,h){d=Q(d);this._addRRect(d,!!h);return this};a.Path.prototype.addVerbsPointsWeights=function(d,h,m){var t=n(d,"HEAPU8"),u=n(h,"HEAPF32"),x=n(m,"HEAPF32");this._addVerbsPointsWeights(t,d.length,u,h.length,x,m&&m.length||0);k(t,d);k(u,h);k(x,m)};a.Path.prototype.arc=function(d,h,m,t,u,x){d=a.LTRBRect(d-
|
||||||
|
m,h-m,d+m,h+m);u=(u-t)/Math.PI*180-360*!!x;x=new a.Path;x.addArc(d,t/Math.PI*180,u);this.addPath(x,!0);x.delete();return this};a.Path.prototype.arcToOval=function(d,h,m,t){d=I(d);this._arcToOval(d,h,m,t);return this};a.Path.prototype.arcToRotated=function(d,h,m,t,u,x,C){this._arcToRotated(d,h,m,!!t,!!u,x,C);return this};a.Path.prototype.arcToTangent=function(d,h,m,t,u){this._arcToTangent(d,h,m,t,u);return this};a.Path.prototype.close=function(){this._close();return this};a.Path.prototype.conicTo=
|
||||||
|
function(d,h,m,t,u){this._conicTo(d,h,m,t,u);return this};a.Path.prototype.computeTightBounds=function(d){this._computeTightBounds(V);var h=ea.toTypedArray();return d?(d.set(h),d):h.slice()};a.Path.prototype.cubicTo=function(d,h,m,t,u,x){this._cubicTo(d,h,m,t,u,x);return this};a.Path.prototype.dash=function(d,h,m){return this._dash(d,h,m)?this:null};a.Path.prototype.getBounds=function(d){this._getBounds(V);var h=ea.toTypedArray();return d?(d.set(h),d):h.slice()};a.Path.prototype.lineTo=function(d,
|
||||||
|
h){this._lineTo(d,h);return this};a.Path.prototype.moveTo=function(d,h){this._moveTo(d,h);return this};a.Path.prototype.offset=function(d,h){this._transform(1,0,d,0,1,h,0,0,1);return this};a.Path.prototype.quadTo=function(d,h,m,t){this._quadTo(d,h,m,t);return this};a.Path.prototype.rArcTo=function(d,h,m,t,u,x,C){this._rArcTo(d,h,m,t,u,x,C);return this};a.Path.prototype.rConicTo=function(d,h,m,t,u){this._rConicTo(d,h,m,t,u);return this};a.Path.prototype.rCubicTo=function(d,h,m,t,u,x){this._rCubicTo(d,
|
||||||
|
h,m,t,u,x);return this};a.Path.prototype.rLineTo=function(d,h){this._rLineTo(d,h);return this};a.Path.prototype.rMoveTo=function(d,h){this._rMoveTo(d,h);return this};a.Path.prototype.rQuadTo=function(d,h,m,t){this._rQuadTo(d,h,m,t);return this};a.Path.prototype.stroke=function(d){d=d||{};d.width=d.width||1;d.miter_limit=d.miter_limit||4;d.cap=d.cap||a.StrokeCap.Butt;d.join=d.join||a.StrokeJoin.Miter;d.precision=d.precision||1;return this._stroke(d)?this:null};a.Path.prototype.transform=function(){if(1===
|
||||||
|
arguments.length){var d=arguments[0];this._transform(d[0],d[1],d[2],d[3],d[4],d[5],d[6]||0,d[7]||0,d[8]||1)}else if(6===arguments.length||9===arguments.length)d=arguments,this._transform(d[0],d[1],d[2],d[3],d[4],d[5],d[6]||0,d[7]||0,d[8]||1);else throw"transform expected to take 1 or 9 arguments. Got "+arguments.length;return this};a.Path.prototype.trim=function(d,h,m){return this._trim(d,h,!!m)?this:null};a.Image.prototype.encodeToBytes=function(d,h){var m=a.Be();d=d||a.ImageFormat.PNG;h=h||100;
|
||||||
|
return m?this._encodeToBytes(d,h,m):this._encodeToBytes(d,h)};a.Image.prototype.makeShaderCubic=function(d,h,m,t,u){u=p(u);return this._makeShaderCubic(d,h,m,t,u)};a.Image.prototype.makeShaderOptions=function(d,h,m,t,u){u=p(u);return this._makeShaderOptions(d,h,m,t,u)};a.Image.prototype.readPixels=function(d,h,m,t,u){var x=a.Be();return g(this,d,h,m,t,u,x)};a.Canvas.prototype.clear=function(d){a.$d(this.Zd);d=w(d);this._clear(d)};a.Canvas.prototype.clipRRect=function(d,h,m){a.$d(this.Zd);d=Q(d);this._clipRRect(d,
|
||||||
|
h,m)};a.Canvas.prototype.clipRect=function(d,h,m){a.$d(this.Zd);d=I(d);this._clipRect(d,h,m)};a.Canvas.prototype.concat=function(d){a.$d(this.Zd);d=v(d);this._concat(d)};a.Canvas.prototype.drawArc=function(d,h,m,t,u){a.$d(this.Zd);d=I(d);this._drawArc(d,h,m,t,u)};a.Canvas.prototype.drawAtlas=function(d,h,m,t,u,x,C){if(d&&t&&h&&m&&h.length===m.length){a.$d(this.Zd);u||(u=a.BlendMode.SrcOver);var G=n(h,"HEAPF32"),F=n(m,"HEAPF32"),S=m.length/4,T=n(c(x),"HEAPU32");if(C&&"B"in C&&"C"in C)this._drawAtlasCubic(d,
|
||||||
|
F,G,T,S,u,C.B,C.C,t);else{let q=a.FilterMode.Linear,y=a.MipmapMode.None;C&&(q=C.filter,"mipmap"in C&&(y=C.mipmap));this._drawAtlasOptions(d,F,G,T,S,u,q,y,t)}k(G,h);k(F,m);k(T,x)}};a.Canvas.prototype.drawCircle=function(d,h,m,t){a.$d(this.Zd);this._drawCircle(d,h,m,t)};a.Canvas.prototype.drawColor=function(d,h){a.$d(this.Zd);d=w(d);void 0!==h?this._drawColor(d,h):this._drawColor(d)};a.Canvas.prototype.drawColorInt=function(d,h){a.$d(this.Zd);this._drawColorInt(d,h||a.BlendMode.SrcOver)};a.Canvas.prototype.drawColorComponents=
|
||||||
|
function(d,h,m,t,u){a.$d(this.Zd);d=A(d,h,m,t);void 0!==u?this._drawColor(d,u):this._drawColor(d)};a.Canvas.prototype.drawDRRect=function(d,h,m){a.$d(this.Zd);d=Q(d,tb);h=Q(h,Zb);this._drawDRRect(d,h,m)};a.Canvas.prototype.drawImage=function(d,h,m,t){a.$d(this.Zd);this._drawImage(d,h,m,t||null)};a.Canvas.prototype.drawImageCubic=function(d,h,m,t,u,x){a.$d(this.Zd);this._drawImageCubic(d,h,m,t,u,x||null)};a.Canvas.prototype.drawImageOptions=function(d,h,m,t,u,x){a.$d(this.Zd);this._drawImageOptions(d,
|
||||||
|
h,m,t,u,x||null)};a.Canvas.prototype.drawImageNine=function(d,h,m,t,u){a.$d(this.Zd);h=n(h,"HEAP32",Ma);m=I(m);this._drawImageNine(d,h,m,t,u||null)};a.Canvas.prototype.drawImageRect=function(d,h,m,t,u){a.$d(this.Zd);I(h,V);I(m,Aa);this._drawImageRect(d,V,Aa,t,!!u)};a.Canvas.prototype.drawImageRectCubic=function(d,h,m,t,u,x){a.$d(this.Zd);I(h,V);I(m,Aa);this._drawImageRectCubic(d,V,Aa,t,u,x||null)};a.Canvas.prototype.drawImageRectOptions=function(d,h,m,t,u,x){a.$d(this.Zd);I(h,V);I(m,Aa);this._drawImageRectOptions(d,
|
||||||
|
V,Aa,t,u,x||null)};a.Canvas.prototype.drawLine=function(d,h,m,t,u){a.$d(this.Zd);this._drawLine(d,h,m,t,u)};a.Canvas.prototype.drawOval=function(d,h){a.$d(this.Zd);d=I(d);this._drawOval(d,h)};a.Canvas.prototype.drawPaint=function(d){a.$d(this.Zd);this._drawPaint(d)};a.Canvas.prototype.drawParagraph=function(d,h,m){a.$d(this.Zd);this._drawParagraph(d,h,m)};a.Canvas.prototype.drawPatch=function(d,h,m,t,u){if(24>d.length)throw"Need 12 cubic points";if(h&&4>h.length)throw"Need 4 colors";if(m&&8>m.length)throw"Need 4 shader coordinates";
|
||||||
|
a.$d(this.Zd);const x=n(d,"HEAPF32"),C=h?n(c(h),"HEAPU32"):0,G=m?n(m,"HEAPF32"):0;t||(t=a.BlendMode.Modulate);this._drawPatch(x,C,G,t,u);k(G,m);k(C,h);k(x,d)};a.Canvas.prototype.drawPath=function(d,h){a.$d(this.Zd);this._drawPath(d,h)};a.Canvas.prototype.drawPicture=function(d){a.$d(this.Zd);this._drawPicture(d)};a.Canvas.prototype.drawPoints=function(d,h,m){a.$d(this.Zd);var t=n(h,"HEAPF32");this._drawPoints(d,t,h.length/2,m);k(t,h)};a.Canvas.prototype.drawRRect=function(d,h){a.$d(this.Zd);d=Q(d);
|
||||||
|
this._drawRRect(d,h)};a.Canvas.prototype.drawRect=function(d,h){a.$d(this.Zd);d=I(d);this._drawRect(d,h)};a.Canvas.prototype.drawRect4f=function(d,h,m,t,u){a.$d(this.Zd);this._drawRect4f(d,h,m,t,u)};a.Canvas.prototype.drawShadow=function(d,h,m,t,u,x,C){a.$d(this.Zd);var G=n(u,"HEAPF32"),F=n(x,"HEAPF32");h=n(h,"HEAPF32",ub);m=n(m,"HEAPF32",vb);this._drawShadow(d,h,m,t,G,F,C);k(G,u);k(F,x)};a.getShadowLocalBounds=function(d,h,m,t,u,x,C){d=p(d);m=n(m,"HEAPF32",ub);t=n(t,"HEAPF32",vb);if(!this._getShadowLocalBounds(d,
|
||||||
|
h,m,t,u,x,V))return null;h=ea.toTypedArray();return C?(C.set(h),C):h.slice()};a.Canvas.prototype.drawTextBlob=function(d,h,m,t){a.$d(this.Zd);this._drawTextBlob(d,h,m,t)};a.Canvas.prototype.drawVertices=function(d,h,m){a.$d(this.Zd);this._drawVertices(d,h,m)};a.Canvas.prototype.getDeviceClipBounds=function(d){this._getDeviceClipBounds(Ma);var h=$a.toTypedArray();d?d.set(h):d=h.slice();return d};a.Canvas.prototype.quickReject=function(d){d=I(d);return this._quickReject(d)};a.Canvas.prototype.getLocalToDevice=
|
||||||
|
function(){this._getLocalToDevice(la);for(var d=la,h=Array(16),m=0;16>m;m++)h[m]=a.HEAPF32[d/4+m];return h};a.Canvas.prototype.getTotalMatrix=function(){this._getTotalMatrix(P);for(var d=Array(9),h=0;9>h;h++)d[h]=a.HEAPF32[P/4+h];return d};a.Canvas.prototype.makeSurface=function(d){d=this._makeSurface(d);d.Zd=this.Zd;return d};a.Canvas.prototype.readPixels=function(d,h,m,t,u){a.$d(this.Zd);return g(this,d,h,m,t,u)};a.Canvas.prototype.saveLayer=function(d,h,m,t,u){h=I(h);return this._saveLayer(d||
|
||||||
|
null,h,m||null,t||0,u||a.TileMode.Clamp)};a.Canvas.prototype.writePixels=function(d,h,m,t,u,x,C,G){if(d.byteLength%(h*m))throw"pixels length must be a multiple of the srcWidth * srcHeight";a.$d(this.Zd);var F=d.byteLength/(h*m);x=x||a.AlphaType.Unpremul;C=C||a.ColorType.RGBA_8888;G=G||a.ColorSpace.SRGB;var S=F*h;F=n(d,"HEAPU8");h=this._writePixels({width:h,height:m,colorType:C,alphaType:x,colorSpace:G},F,S,t,u);k(F,d);return h};a.ColorFilter.MakeBlend=function(d,h,m){d=w(d);m=m||a.ColorSpace.SRGB;
|
||||||
|
return a.ColorFilter._MakeBlend(d,h,m)};a.ColorFilter.MakeMatrix=function(d){if(!d||20!==d.length)throw"invalid color matrix";var h=n(d,"HEAPF32"),m=a.ColorFilter._makeMatrix(h);k(h,d);return m};a.ContourMeasure.prototype.getPosTan=function(d,h){this._getPosTan(d,V);d=ea.toTypedArray();return h?(h.set(d),h):d.slice()};a.ImageFilter.prototype.getOutputBounds=function(d,h,m){d=I(d,V);h=p(h);this._getOutputBounds(d,h,Ma);h=$a.toTypedArray();return m?(m.set(h),m):h.slice()};a.ImageFilter.MakeDropShadow=
|
||||||
|
function(d,h,m,t,u,x){u=w(u,ha);return a.ImageFilter._MakeDropShadow(d,h,m,t,u,x)};a.ImageFilter.MakeDropShadowOnly=function(d,h,m,t,u,x){u=w(u,ha);return a.ImageFilter._MakeDropShadowOnly(d,h,m,t,u,x)};a.ImageFilter.MakeImage=function(d,h,m,t){m=I(m,V);t=I(t,Aa);if("B"in h&&"C"in h)return a.ImageFilter._MakeImageCubic(d,h.B,h.C,m,t);const u=h.filter;let x=a.MipmapMode.None;"mipmap"in h&&(x=h.mipmap);return a.ImageFilter._MakeImageOptions(d,u,x,m,t)};a.ImageFilter.MakeMatrixTransform=function(d,h,
|
||||||
|
m){d=p(d);if("B"in h&&"C"in h)return a.ImageFilter._MakeMatrixTransformCubic(d,h.B,h.C,m);const t=h.filter;let u=a.MipmapMode.None;"mipmap"in h&&(u=h.mipmap);return a.ImageFilter._MakeMatrixTransformOptions(d,t,u,m)};a.Paint.prototype.getColor=function(){this._getColor(ha);return D(ha)};a.Paint.prototype.setColor=function(d,h){h=h||null;d=w(d);this._setColor(d,h)};a.Paint.prototype.setColorComponents=function(d,h,m,t,u){u=u||null;d=A(d,h,m,t);this._setColor(d,u)};a.Path.prototype.getPoint=function(d,
|
||||||
|
h){this._getPoint(d,V);d=ea.toTypedArray();return h?(h[0]=d[0],h[1]=d[1],h):d.slice(0,2)};a.Picture.prototype.makeShader=function(d,h,m,t,u){t=p(t);u=I(u);return this._makeShader(d,h,m,t,u)};a.Picture.prototype.cullRect=function(d){this._cullRect(V);var h=ea.toTypedArray();return d?(d.set(h),d):h.slice()};a.PictureRecorder.prototype.beginRecording=function(d,h){d=I(d);return this._beginRecording(d,!!h)};a.Surface.prototype.getCanvas=function(){var d=this._getCanvas();d.Zd=this.Zd;return d};a.Surface.prototype.makeImageSnapshot=
|
||||||
|
function(d){a.$d(this.Zd);d=n(d,"HEAP32",Ma);return this._makeImageSnapshot(d)};a.Surface.prototype.makeSurface=function(d){a.$d(this.Zd);d=this._makeSurface(d);d.Zd=this.Zd;return d};a.Surface.prototype.Ze=function(d,h){this.te||(this.te=this.getCanvas());return requestAnimationFrame(function(){a.$d(this.Zd);d(this.te);this.flush(h)}.bind(this))};a.Surface.prototype.requestAnimationFrame||(a.Surface.prototype.requestAnimationFrame=a.Surface.prototype.Ze);a.Surface.prototype.We=function(d,h){this.te||
|
||||||
|
(this.te=this.getCanvas());requestAnimationFrame(function(){a.$d(this.Zd);d(this.te);this.flush(h);this.dispose()}.bind(this))};a.Surface.prototype.drawOnce||(a.Surface.prototype.drawOnce=a.Surface.prototype.We);a.PathEffect.MakeDash=function(d,h){h||=0;if(!d.length||1===d.length%2)throw"Intervals array must have even length";var m=n(d,"HEAPF32");h=a.PathEffect._MakeDash(m,d.length,h);k(m,d);return h};a.PathEffect.MakeLine2D=function(d,h){h=p(h);return a.PathEffect._MakeLine2D(d,h)};a.PathEffect.MakePath2D=
|
||||||
|
function(d,h){d=p(d);return a.PathEffect._MakePath2D(d,h)};a.Shader.MakeColor=function(d,h){h=h||null;d=w(d);return a.Shader._MakeColor(d,h)};a.Shader.Blend=a.Shader.MakeBlend;a.Shader.Color=a.Shader.MakeColor;a.Shader.MakeLinearGradient=function(d,h,m,t,u,x,C,G){G=G||null;var F=l(m),S=n(t,"HEAPF32");C=C||0;x=p(x);var T=ea.toTypedArray();T.set(d);T.set(h,2);d=a.Shader._MakeLinearGradient(V,F.he,F.colorType,S,F.count,u,C,x,G);k(F.he,m);t&&k(S,t);return d};a.Shader.MakeRadialGradient=function(d,h,m,
|
||||||
|
t,u,x,C,G){G=G||null;var F=l(m),S=n(t,"HEAPF32");C=C||0;x=p(x);d=a.Shader._MakeRadialGradient(d[0],d[1],h,F.he,F.colorType,S,F.count,u,C,x,G);k(F.he,m);t&&k(S,t);return d};a.Shader.MakeSweepGradient=function(d,h,m,t,u,x,C,G,F,S){S=S||null;var T=l(m),q=n(t,"HEAPF32");C=C||0;G=G||0;F=F||360;x=p(x);d=a.Shader._MakeSweepGradient(d,h,T.he,T.colorType,q,T.count,u,G,F,C,x,S);k(T.he,m);t&&k(q,t);return d};a.Shader.MakeTwoPointConicalGradient=function(d,h,m,t,u,x,C,G,F,S){S=S||null;var T=l(u),q=n(x,"HEAPF32");
|
||||||
|
F=F||0;G=p(G);var y=ea.toTypedArray();y.set(d);y.set(m,2);d=a.Shader._MakeTwoPointConicalGradient(V,h,t,T.he,T.colorType,q,T.count,C,F,G,S);k(T.he,u);x&&k(q,x);return d};a.Vertices.prototype.bounds=function(d){this._bounds(V);var h=ea.toTypedArray();return d?(d.set(h),d):h.slice()};a.ce&&a.ce.forEach(function(d){d()})};a.computeTonalColors=function(g){var d=n(g.ambient,"HEAPF32"),h=n(g.spot,"HEAPF32");this._computeTonalColors(d,h);var m={ambient:D(d),spot:D(h)};k(d,g.ambient);k(h,g.spot);return m};
|
||||||
|
a.LTRBRect=function(g,d,h,m){return Float32Array.of(g,d,h,m)};a.XYWHRect=function(g,d,h,m){return Float32Array.of(g,d,g+h,d+m)};a.LTRBiRect=function(g,d,h,m){return Int32Array.of(g,d,h,m)};a.XYWHiRect=function(g,d,h,m){return Int32Array.of(g,d,g+h,d+m)};a.RRectXY=function(g,d,h){return Float32Array.of(g[0],g[1],g[2],g[3],d,h,d,h,d,h,d,h)};a.MakeAnimatedImageFromEncoded=function(g){g=new Uint8Array(g);var d=a._malloc(g.byteLength);a.HEAPU8.set(g,d);return(g=a._decodeAnimatedImage(d,g.byteLength))?
|
||||||
|
g:null};a.MakeImageFromEncoded=function(g){g=new Uint8Array(g);var d=a._malloc(g.byteLength);a.HEAPU8.set(g,d);return(g=a._decodeImage(d,g.byteLength))?g:null};var ab=null;a.MakeImageFromCanvasImageSource=function(g){var d=g.width,h=g.height;ab||=document.createElement("canvas");ab.width=d;ab.height=h;var m=ab.getContext("2d",{willReadFrequently:!0});m.drawImage(g,0,0);g=m.getImageData(0,0,d,h);return a.MakeImage({width:d,height:h,alphaType:a.AlphaType.Unpremul,colorType:a.ColorType.RGBA_8888,colorSpace:a.ColorSpace.SRGB},
|
||||||
|
g.data,4*d)};a.MakeImage=function(g,d,h){var m=a._malloc(d.length);a.HEAPU8.set(d,m);return a._MakeImage(g,m,d.length,h)};a.MakeVertices=function(g,d,h,m,t,u){var x=t&&t.length||0,C=0;h&&h.length&&(C|=1);m&&m.length&&(C|=2);void 0===u||u||(C|=4);g=new a._VerticesBuilder(g,d.length/2,x,C);n(d,"HEAPF32",g.positions());g.texCoords()&&n(h,"HEAPF32",g.texCoords());g.colors()&&n(c(m),"HEAPU32",g.colors());g.indices()&&n(t,"HEAPU16",g.indices());return g.detach()};(function(g){g.ce=g.ce||[];g.ce.push(function(){function d(q){q&&
|
||||||
|
(q.dir=0===q.dir?g.TextDirection.RTL:g.TextDirection.LTR);return q}function h(q){if(!q||!q.length)return[];for(var y=[],M=0;M<q.length;M+=5){var W=g.LTRBRect(q[M],q[M+1],q[M+2],q[M+3]),wa=g.TextDirection.LTR;0===q[M+4]&&(wa=g.TextDirection.RTL);y.push({rect:W,dir:wa})}g._free(q.byteOffset);return y}function m(q){q=q||{};void 0===q.weight&&(q.weight=g.FontWeight.Normal);q.width=q.width||g.FontWidth.Normal;q.slant=q.slant||g.FontSlant.Upright;return q}function t(q){if(!q||!q.length)return 0;for(var y=
|
||||||
|
[],M=0;M<q.length;M++){var W=u(q[M]);y.push(W)}return n(y,"HEAPU32")}function u(q){if(G[q])return G[q];var y=qa(q)+1,M=g._malloc(y);ra(q,M,y);return G[q]=M}function x(q){q._colorPtr=w(q.color);q._foregroundColorPtr=0;q._backgroundColorPtr=0;q._decorationColorPtr=0;q.foregroundColor&&(q._foregroundColorPtr=w(q.foregroundColor,F));q.backgroundColor&&(q._backgroundColorPtr=w(q.backgroundColor,S));q.decorationColor&&(q._decorationColorPtr=w(q.decorationColor,T));Array.isArray(q.fontFamilies)&&q.fontFamilies.length?
|
||||||
|
(q._fontFamiliesPtr=t(q.fontFamilies),q._fontFamiliesLen=q.fontFamilies.length):(q._fontFamiliesPtr=0,q._fontFamiliesLen=0);if(q.locale){var y=q.locale;q._localePtr=u(y);q._localeLen=qa(y)}else q._localePtr=0,q._localeLen=0;if(Array.isArray(q.shadows)&&q.shadows.length){y=q.shadows;var M=y.map(function(ma){return ma.color||g.BLACK}),W=y.map(function(ma){return ma.blurRadius||0});q._shadowLen=y.length;for(var wa=g._malloc(8*y.length),wb=wa/4,xb=0;xb<y.length;xb++){var $b=y[xb].offset||[0,0];g.HEAPF32[wb]=
|
||||||
|
$b[0];g.HEAPF32[wb+1]=$b[1];wb+=2}q._shadowColorsPtr=l(M).he;q._shadowOffsetsPtr=wa;q._shadowBlurRadiiPtr=n(W,"HEAPF32")}else q._shadowLen=0,q._shadowColorsPtr=0,q._shadowOffsetsPtr=0,q._shadowBlurRadiiPtr=0;Array.isArray(q.fontFeatures)&&q.fontFeatures.length?(y=q.fontFeatures,M=y.map(function(ma){return ma.name}),W=y.map(function(ma){return ma.value}),q._fontFeatureLen=y.length,q._fontFeatureNamesPtr=t(M),q._fontFeatureValuesPtr=n(W,"HEAPU32")):(q._fontFeatureLen=0,q._fontFeatureNamesPtr=0,q._fontFeatureValuesPtr=
|
||||||
|
0);Array.isArray(q.fontVariations)&&q.fontVariations.length?(y=q.fontVariations,M=y.map(function(ma){return ma.axis}),W=y.map(function(ma){return ma.value}),q._fontVariationLen=y.length,q._fontVariationAxesPtr=t(M),q._fontVariationValuesPtr=n(W,"HEAPF32")):(q._fontVariationLen=0,q._fontVariationAxesPtr=0,q._fontVariationValuesPtr=0)}function C(q){g._free(q._fontFamiliesPtr);g._free(q._shadowColorsPtr);g._free(q._shadowOffsetsPtr);g._free(q._shadowBlurRadiiPtr);g._free(q._fontFeatureNamesPtr);g._free(q._fontFeatureValuesPtr);
|
||||||
|
g._free(q._fontVariationAxesPtr);g._free(q._fontVariationValuesPtr)}g.Paragraph.prototype.getRectsForRange=function(q,y,M,W){q=this._getRectsForRange(q,y,M,W);return h(q)};g.Paragraph.prototype.getRectsForPlaceholders=function(){var q=this._getRectsForPlaceholders();return h(q)};g.Paragraph.prototype.getGlyphInfoAt=function(q){return d(this._getGlyphInfoAt(q))};g.Paragraph.prototype.getClosestGlyphInfoAtCoordinate=function(q,y){return d(this._getClosestGlyphInfoAtCoordinate(q,y))};g.TypefaceFontProvider.prototype.registerFont=
|
||||||
|
function(q,y){q=g.Typeface.MakeTypefaceFromData(q);if(!q)return null;y=u(y);this._registerFont(q,y);q.delete()};g.ParagraphStyle=function(q){q.disableHinting=q.disableHinting||!1;if(q.ellipsis){var y=q.ellipsis;q._ellipsisPtr=u(y);q._ellipsisLen=qa(y)}else q._ellipsisPtr=0,q._ellipsisLen=0;null==q.heightMultiplier&&(q.heightMultiplier=-1);q.maxLines=q.maxLines||0;q.replaceTabCharacters=q.replaceTabCharacters||!1;y=(y=q.strutStyle)||{};y.strutEnabled=y.strutEnabled||!1;y.strutEnabled&&Array.isArray(y.fontFamilies)&&
|
||||||
|
y.fontFamilies.length?(y._fontFamiliesPtr=t(y.fontFamilies),y._fontFamiliesLen=y.fontFamilies.length):(y._fontFamiliesPtr=0,y._fontFamiliesLen=0);y.fontStyle=m(y.fontStyle);null==y.fontSize&&(y.fontSize=-1);null==y.heightMultiplier&&(y.heightMultiplier=-1);y.halfLeading=y.halfLeading||!1;y.leading=y.leading||0;y.forceStrutHeight=y.forceStrutHeight||!1;q.strutStyle=y;q.textAlign=q.textAlign||g.TextAlign.Start;q.textDirection=q.textDirection||g.TextDirection.LTR;q.textHeightBehavior=q.textHeightBehavior||
|
||||||
|
g.TextHeightBehavior.All;q.textStyle=g.TextStyle(q.textStyle);q.applyRoundingHack=!1!==q.applyRoundingHack;return q};g.TextStyle=function(q){q.color||(q.color=g.BLACK);q.decoration=q.decoration||0;q.decorationThickness=q.decorationThickness||0;q.decorationStyle=q.decorationStyle||g.DecorationStyle.Solid;q.textBaseline=q.textBaseline||g.TextBaseline.Alphabetic;null==q.fontSize&&(q.fontSize=-1);q.letterSpacing=q.letterSpacing||0;q.wordSpacing=q.wordSpacing||0;null==q.heightMultiplier&&(q.heightMultiplier=
|
||||||
|
-1);q.halfLeading=q.halfLeading||!1;q.fontStyle=m(q.fontStyle);return q};var G={},F=g._malloc(16),S=g._malloc(16),T=g._malloc(16);g.ParagraphBuilder.Make=function(q,y){x(q.textStyle);y=g.ParagraphBuilder._Make(q,y);C(q.textStyle);return y};g.ParagraphBuilder.MakeFromFontProvider=function(q,y){x(q.textStyle);y=g.ParagraphBuilder._MakeFromFontProvider(q,y);C(q.textStyle);return y};g.ParagraphBuilder.MakeFromFontCollection=function(q,y){x(q.textStyle);y=g.ParagraphBuilder._MakeFromFontCollection(q,y);
|
||||||
|
C(q.textStyle);return y};g.ParagraphBuilder.ShapeText=function(q,y,M){let W=0;for(const wa of y)W+=wa.length;if(W!==q.length)throw"Accumulated block lengths must equal text.length";return g.ParagraphBuilder._ShapeText(q,y,M)};g.ParagraphBuilder.prototype.pushStyle=function(q){x(q);this._pushStyle(q);C(q)};g.ParagraphBuilder.prototype.pushPaintStyle=function(q,y,M){x(q);this._pushPaintStyle(q,y,M);C(q)};g.ParagraphBuilder.prototype.addPlaceholder=function(q,y,M,W,wa){M=M||g.PlaceholderAlignment.Baseline;
|
||||||
|
W=W||g.TextBaseline.Alphabetic;this._addPlaceholder(q||0,y||0,M,W,wa||0)};g.ParagraphBuilder.prototype.setWordsUtf8=function(q){var y=n(q,"HEAPU32");this._setWordsUtf8(y,q&&q.length||0);k(y,q)};g.ParagraphBuilder.prototype.setWordsUtf16=function(q){var y=n(q,"HEAPU32");this._setWordsUtf16(y,q&&q.length||0);k(y,q)};g.ParagraphBuilder.prototype.setGraphemeBreaksUtf8=function(q){var y=n(q,"HEAPU32");this._setGraphemeBreaksUtf8(y,q&&q.length||0);k(y,q)};g.ParagraphBuilder.prototype.setGraphemeBreaksUtf16=
|
||||||
|
function(q){var y=n(q,"HEAPU32");this._setGraphemeBreaksUtf16(y,q&&q.length||0);k(y,q)};g.ParagraphBuilder.prototype.setLineBreaksUtf8=function(q){var y=n(q,"HEAPU32");this._setLineBreaksUtf8(y,q&&q.length||0);k(y,q)};g.ParagraphBuilder.prototype.setLineBreaksUtf16=function(q){var y=n(q,"HEAPU32");this._setLineBreaksUtf16(y,q&&q.length||0);k(y,q)}})})(r);a.ce=a.ce||[];a.ce.push(function(){a.Path.prototype.op=function(g,d){return this._op(g,d)?this:null};a.Path.prototype.simplify=function(){return this._simplify()?
|
||||||
|
this:null}});a.ce=a.ce||[];a.ce.push(function(){a.Canvas.prototype.drawText=function(g,d,h,m,t){var u=qa(g),x=a._malloc(u+1);ra(g,x,u+1);this._drawSimpleText(x,u,d,h,t,m);a._free(x)};a.Canvas.prototype.drawGlyphs=function(g,d,h,m,t,u){if(!(2*g.length<=d.length))throw"Not enough positions for the array of gyphs";a.$d(this.Zd);const x=n(g,"HEAPU16"),C=n(d,"HEAPF32");this._drawGlyphs(g.length,x,C,h,m,t,u);k(C,d);k(x,g)};a.Font.prototype.getGlyphBounds=function(g,d,h){var m=n(g,"HEAPU16"),t=a._malloc(16*
|
||||||
|
g.length);this._getGlyphWidthBounds(m,g.length,0,t,d||null);d=new Float32Array(a.HEAPU8.buffer,t,4*g.length);k(m,g);if(h)return h.set(d),a._free(t),h;g=Float32Array.from(d);a._free(t);return g};a.Font.prototype.getGlyphIDs=function(g,d,h){d||(d=g.length);var m=qa(g)+1,t=a._malloc(m);ra(g,t,m);g=a._malloc(2*d);d=this._getGlyphIDs(t,m-1,d,g);a._free(t);if(0>d)return a._free(g),null;t=new Uint16Array(a.HEAPU8.buffer,g,d);if(h)return h.set(t),a._free(g),h;h=Uint16Array.from(t);a._free(g);return h};a.Font.prototype.getGlyphIntercepts=
|
||||||
|
function(g,d,h,m){var t=n(g,"HEAPU16"),u=n(d,"HEAPF32");return this._getGlyphIntercepts(t,g.length,!(g&&g._ck),u,d.length,!(d&&d._ck),h,m)};a.Font.prototype.getGlyphWidths=function(g,d,h){var m=n(g,"HEAPU16"),t=a._malloc(4*g.length);this._getGlyphWidthBounds(m,g.length,t,0,d||null);d=new Float32Array(a.HEAPU8.buffer,t,g.length);k(m,g);if(h)return h.set(d),a._free(t),h;g=Float32Array.from(d);a._free(t);return g};a.FontMgr.FromData=function(){if(!arguments.length)return null;var g=arguments;1===g.length&&
|
||||||
|
Array.isArray(g[0])&&(g=arguments[0]);if(!g.length)return null;for(var d=[],h=[],m=0;m<g.length;m++){var t=new Uint8Array(g[m]),u=n(t,"HEAPU8");d.push(u);h.push(t.byteLength)}d=n(d,"HEAPU32");h=n(h,"HEAPU32");g=a.FontMgr._fromData(d,h,g.length);a._free(d);a._free(h);return g};a.Typeface.MakeTypefaceFromData=function(g){g=new Uint8Array(g);var d=n(g,"HEAPU8");return(g=a.Typeface._MakeTypefaceFromData(d,g.byteLength))?g:null};a.Typeface.MakeFreeTypeFaceFromData=a.Typeface.MakeTypefaceFromData;a.Typeface.prototype.getGlyphIDs=
|
||||||
|
function(g,d,h){d||(d=g.length);var m=qa(g)+1,t=a._malloc(m);ra(g,t,m);g=a._malloc(2*d);d=this._getGlyphIDs(t,m-1,d,g);a._free(t);if(0>d)return a._free(g),null;t=new Uint16Array(a.HEAPU8.buffer,g,d);if(h)return h.set(t),a._free(g),h;h=Uint16Array.from(t);a._free(g);return h};a.TextBlob.MakeOnPath=function(g,d,h,m){if(g&&g.length&&d&&d.countPoints()){if(1===d.countPoints())return this.MakeFromText(g,h);m||=0;var t=h.getGlyphIDs(g);t=h.getGlyphWidths(t);var u=[];d=new a.ContourMeasureIter(d,!1,1);for(var x=
|
||||||
|
d.next(),C=new Float32Array(4),G=0;G<g.length&&x;G++){var F=t[G];m+=F/2;if(m>x.length()){x.delete();x=d.next();if(!x){g=g.substring(0,G);break}m=F/2}x.getPosTan(m,C);var S=C[2],T=C[3];u.push(S,T,C[0]-F/2*S,C[1]-F/2*T);m+=F/2}g=this.MakeFromRSXform(g,u,h);x&&x.delete();d.delete();return g}};a.TextBlob.MakeFromRSXform=function(g,d,h){var m=qa(g)+1,t=a._malloc(m);ra(g,t,m);g=n(d,"HEAPF32");h=a.TextBlob._MakeFromRSXform(t,m-1,g,h);a._free(t);return h?h:null};a.TextBlob.MakeFromRSXformGlyphs=function(g,
|
||||||
|
d,h){var m=n(g,"HEAPU16");d=n(d,"HEAPF32");h=a.TextBlob._MakeFromRSXformGlyphs(m,2*g.length,d,h);k(m,g);return h?h:null};a.TextBlob.MakeFromGlyphs=function(g,d){var h=n(g,"HEAPU16");d=a.TextBlob._MakeFromGlyphs(h,2*g.length,d);k(h,g);return d?d:null};a.TextBlob.MakeFromText=function(g,d){var h=qa(g)+1,m=a._malloc(h);ra(g,m,h);g=a.TextBlob._MakeFromText(m,h-1,d);a._free(m);return g?g:null};a.MallocGlyphIDs=function(g){return a.Malloc(Uint16Array,g)}});a.ce=a.ce||[];a.ce.push(function(){a.MakePicture=
|
||||||
|
function(g){g=new Uint8Array(g);var d=a._malloc(g.byteLength);a.HEAPU8.set(g,d);return(g=a._MakePicture(d,g.byteLength))?g:null}});a.ce=a.ce||[];a.ce.push(function(){a.RuntimeEffect.Make=function(g,d){return a.RuntimeEffect._Make(g,{onError:d||function(h){console.log("RuntimeEffect error",h)}})};a.RuntimeEffect.MakeForBlender=function(g,d){return a.RuntimeEffect._MakeForBlender(g,{onError:d||function(h){console.log("RuntimeEffect error",h)}})};a.RuntimeEffect.prototype.makeShader=function(g,d){var h=
|
||||||
|
!g._ck,m=n(g,"HEAPF32");d=p(d);return this._makeShader(m,4*g.length,h,d)};a.RuntimeEffect.prototype.makeShaderWithChildren=function(g,d,h){var m=!g._ck,t=n(g,"HEAPF32");h=p(h);for(var u=[],x=0;x<d.length;x++)u.push(d[x].Yd.ae);d=n(u,"HEAPU32");return this._makeShaderWithChildren(t,4*g.length,m,d,u.length,h)};a.RuntimeEffect.prototype.makeBlender=function(g){var d=!g._ck,h=n(g,"HEAPF32");return this._makeBlender(h,4*g.length,d)}})})(r);var sa=Object.assign({},r),ta="",ua,va;
|
||||||
|
if(fa||ia)ia?ta=self.location.href:"undefined"!=typeof document&&document.currentScript&&(ta=document.currentScript.src),_scriptName&&(ta=_scriptName),ta.startsWith("blob:")?ta="":ta=ta.substr(0,ta.replace(/[?#].*/,"").lastIndexOf("/")+1),ia&&(va=a=>{var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)}),ua=a=>fetch(a,{credentials:"same-origin"}).then(b=>b.ok?b.arrayBuffer():Promise.reject(Error(b.status+" : "+b.url)));
|
||||||
|
var xa=console.log.bind(console),ya=console.error.bind(console);Object.assign(r,sa);sa=null;var za,Ba=!1,Ca,B,Da,Fa,E,H,J,Ga;function Ha(){var a=za.buffer;r.HEAP8=Ca=new Int8Array(a);r.HEAP16=Da=new Int16Array(a);r.HEAPU8=B=new Uint8Array(a);r.HEAPU16=Fa=new Uint16Array(a);r.HEAP32=E=new Int32Array(a);r.HEAPU32=H=new Uint32Array(a);r.HEAPF32=J=new Float32Array(a);r.HEAPF64=Ga=new Float64Array(a)}var Ia=[],Ja=[],Ka=[],La=0,Na=null,Oa=null;
|
||||||
|
function Pa(a){a="Aborted("+a+")";ya(a);Ba=!0;a=new WebAssembly.RuntimeError(a+". Build with -sASSERTIONS for more info.");ca(a);throw a;}var Qa=a=>a.startsWith("data:application/octet-stream;base64,"),Ra;function Sa(a){return ua(a).then(b=>new Uint8Array(b),()=>{if(va)var b=va(a);else throw"both async and sync fetching of the wasm failed";return b})}function Ta(a,b,c){return Sa(a).then(e=>WebAssembly.instantiate(e,b)).then(c,e=>{ya(`failed to asynchronously prepare wasm: ${e}`);Pa(e)})}
|
||||||
|
function Ua(a,b){var c=Ra;return"function"!=typeof WebAssembly.instantiateStreaming||Qa(c)||"function"!=typeof fetch?Ta(c,a,b):fetch(c,{credentials:"same-origin"}).then(e=>WebAssembly.instantiateStreaming(e,a).then(b,function(f){ya(`wasm streaming compile failed: ${f}`);ya("falling back to ArrayBuffer instantiation");return Ta(c,a,b)}))}function Va(a){this.name="ExitStatus";this.message=`Program terminated with exit(${a})`;this.status=a}var Wa=a=>{a.forEach(b=>b(r))},Xa=r.noExitRuntime||!0;
|
||||||
|
class Ya{constructor(a){this.ae=a-24}}
|
||||||
|
var Za=0,bb=0,cb="undefined"!=typeof TextDecoder?new TextDecoder:void 0,db=(a,b=0,c=NaN)=>{var e=b+c;for(c=b;a[c]&&!(c>=e);)++c;if(16<c-b&&a.buffer&&cb)return cb.decode(a.subarray(b,c));for(e="";b<c;){var f=a[b++];if(f&128){var k=a[b++]&63;if(192==(f&224))e+=String.fromCharCode((f&31)<<6|k);else{var n=a[b++]&63;f=224==(f&240)?(f&15)<<12|k<<6|n:(f&7)<<18|k<<12|n<<6|a[b++]&63;65536>f?e+=String.fromCharCode(f):(f-=65536,e+=String.fromCharCode(55296|f>>10,56320|f&1023))}}else e+=String.fromCharCode(f)}return e},
|
||||||
|
eb={},fb=a=>{for(;a.length;){var b=a.pop();a.pop()(b)}};function gb(a){return this.fromWireType(H[a>>2])}
|
||||||
|
var hb={},ib={},jb={},kb,mb=(a,b,c)=>{function e(l){l=c(l);if(l.length!==a.length)throw new kb("Mismatched type converter count");for(var p=0;p<a.length;++p)lb(a[p],l[p])}a.forEach(l=>jb[l]=b);var f=Array(b.length),k=[],n=0;b.forEach((l,p)=>{ib.hasOwnProperty(l)?f[p]=ib[l]:(k.push(l),hb.hasOwnProperty(l)||(hb[l]=[]),hb[l].push(()=>{f[p]=ib[l];++n;n===k.length&&e(f)}))});0===k.length&&e(f)},nb,K=a=>{for(var b="";B[a];)b+=nb[B[a++]];return b},L;
|
||||||
|
function ob(a,b,c={}){var e=b.name;if(!a)throw new L(`type "${e}" must have a positive integer typeid pointer`);if(ib.hasOwnProperty(a)){if(c.lf)return;throw new L(`Cannot register type '${e}' twice`);}ib[a]=b;delete jb[a];hb.hasOwnProperty(a)&&(b=hb[a],delete hb[a],b.forEach(f=>f()))}function lb(a,b,c={}){return ob(a,b,c)}
|
||||||
|
var pb=a=>{throw new L(a.Yd.de.be.name+" instance already deleted");},qb=!1,rb=()=>{},sb=(a,b,c)=>{if(b===c)return a;if(void 0===c.ge)return null;a=sb(a,b,c.ge);return null===a?null:c.cf(a)},yb={},zb={},Ab=(a,b)=>{if(void 0===b)throw new L("ptr should not be undefined");for(;a.ge;)b=a.ye(b),a=a.ge;return zb[b]},Cb=(a,b)=>{if(!b.de||!b.ae)throw new kb("makeClassHandle requires ptr and ptrType");if(!!b.ie!==!!b.ee)throw new kb("Both smartPtrType and smartPtr must be specified");b.count={value:1};return Bb(Object.create(a,
|
||||||
|
{Yd:{value:b,writable:!0}}))},Bb=a=>{if("undefined"===typeof FinalizationRegistry)return Bb=b=>b,a;qb=new FinalizationRegistry(b=>{b=b.Yd;--b.count.value;0===b.count.value&&(b.ee?b.ie.ne(b.ee):b.de.be.ne(b.ae))});Bb=b=>{var c=b.Yd;c.ee&&qb.register(b,{Yd:c},b);return b};rb=b=>{qb.unregister(b)};return Bb(a)},Db=[];function Eb(){}
|
||||||
|
var Fb=(a,b)=>Object.defineProperty(b,"name",{value:a}),Gb=(a,b,c)=>{if(void 0===a[b].fe){var e=a[b];a[b]=function(...f){if(!a[b].fe.hasOwnProperty(f.length))throw new L(`Function '${c}' called with an invalid number of arguments (${f.length}) - expects one of (${a[b].fe})!`);return a[b].fe[f.length].apply(this,f)};a[b].fe=[];a[b].fe[e.oe]=e}},Hb=(a,b,c)=>{if(r.hasOwnProperty(a)){if(void 0===c||void 0!==r[a].fe&&void 0!==r[a].fe[c])throw new L(`Cannot register public name '${a}' twice`);Gb(r,a,a);
|
||||||
|
if(r[a].fe.hasOwnProperty(c))throw new L(`Cannot register multiple overloads of a function with the same number of arguments (${c})!`);r[a].fe[c]=b}else r[a]=b,r[a].oe=c},Ib=a=>{a=a.replace(/[^a-zA-Z0-9_]/g,"$");var b=a.charCodeAt(0);return 48<=b&&57>=b?`_${a}`:a};function Jb(a,b,c,e,f,k,n,l){this.name=a;this.constructor=b;this.se=c;this.ne=e;this.ge=f;this.ff=k;this.ye=n;this.cf=l;this.pf=[]}
|
||||||
|
var Kb=(a,b,c)=>{for(;b!==c;){if(!b.ye)throw new L(`Expected null or instance of ${c.name}, got an instance of ${b.name}`);a=b.ye(a);b=b.ge}return a};function Lb(a,b){if(null===b){if(this.Ke)throw new L(`null is not a valid ${this.name}`);return 0}if(!b.Yd)throw new L(`Cannot pass "${Mb(b)}" as a ${this.name}`);if(!b.Yd.ae)throw new L(`Cannot pass deleted object as a pointer of type ${this.name}`);return Kb(b.Yd.ae,b.Yd.de.be,this.be)}
|
||||||
|
function Nb(a,b){if(null===b){if(this.Ke)throw new L(`null is not a valid ${this.name}`);if(this.De){var c=this.Le();null!==a&&a.push(this.ne,c);return c}return 0}if(!b||!b.Yd)throw new L(`Cannot pass "${Mb(b)}" as a ${this.name}`);if(!b.Yd.ae)throw new L(`Cannot pass deleted object as a pointer of type ${this.name}`);if(!this.Ce&&b.Yd.de.Ce)throw new L(`Cannot convert argument of type ${b.Yd.ie?b.Yd.ie.name:b.Yd.de.name} to parameter type ${this.name}`);c=Kb(b.Yd.ae,b.Yd.de.be,this.be);if(this.De){if(void 0===
|
||||||
|
b.Yd.ee)throw new L("Passing raw pointer to smart pointer is illegal");switch(this.uf){case 0:if(b.Yd.ie===this)c=b.Yd.ee;else throw new L(`Cannot convert argument of type ${b.Yd.ie?b.Yd.ie.name:b.Yd.de.name} to parameter type ${this.name}`);break;case 1:c=b.Yd.ee;break;case 2:if(b.Yd.ie===this)c=b.Yd.ee;else{var e=b.clone();c=this.qf(c,Ob(()=>e["delete"]()));null!==a&&a.push(this.ne,c)}break;default:throw new L("Unsupporting sharing policy");}}return c}
|
||||||
|
function Pb(a,b){if(null===b){if(this.Ke)throw new L(`null is not a valid ${this.name}`);return 0}if(!b.Yd)throw new L(`Cannot pass "${Mb(b)}" as a ${this.name}`);if(!b.Yd.ae)throw new L(`Cannot pass deleted object as a pointer of type ${this.name}`);if(b.Yd.de.Ce)throw new L(`Cannot convert argument of type ${b.Yd.de.name} to parameter type ${this.name}`);return Kb(b.Yd.ae,b.Yd.de.be,this.be)}
|
||||||
|
function Qb(a,b,c,e,f,k,n,l,p,v,w){this.name=a;this.be=b;this.Ke=c;this.Ce=e;this.De=f;this.nf=k;this.uf=n;this.Se=l;this.Le=p;this.qf=v;this.ne=w;f||void 0!==b.ge?this.toWireType=Nb:(this.toWireType=e?Lb:Pb,this.ke=null)}
|
||||||
|
var Rb=(a,b,c)=>{if(!r.hasOwnProperty(a))throw new kb("Replacing nonexistent public symbol");void 0!==r[a].fe&&void 0!==c?r[a].fe[c]=b:(r[a]=b,r[a].oe=c)},N,Sb=(a,b,c=[])=>{a.includes("j")?(a=a.replace(/p/g,"i"),b=(0,r["dynCall_"+a])(b,...c)):b=N.get(b)(...c);return b},Tb=(a,b)=>(...c)=>Sb(a,b,c),O=(a,b)=>{a=K(a);var c=a.includes("j")?Tb(a,b):N.get(b);if("function"!=typeof c)throw new L(`unknown function pointer with signature ${a}: ${b}`);return c},ac,dc=a=>{a=bc(a);var b=K(a);cc(a);return b},ec=
|
||||||
|
(a,b)=>{function c(k){f[k]||ib[k]||(jb[k]?jb[k].forEach(c):(e.push(k),f[k]=!0))}var e=[],f={};b.forEach(c);throw new ac(`${a}: `+e.map(dc).join([", "]));};function fc(a){for(var b=1;b<a.length;++b)if(null!==a[b]&&void 0===a[b].ke)return!0;return!1}
|
||||||
|
function gc(a,b,c,e,f){var k=b.length;if(2>k)throw new L("argTypes array size mismatch! Must at least get return value and 'this' types!");var n=null!==b[1]&&null!==c,l=fc(b),p="void"!==b[0].name,v=k-2,w=Array(v),A=[],D=[];return Fb(a,function(...I){D.length=0;A.length=n?2:1;A[0]=f;if(n){var Q=b[1].toWireType(D,this);A[1]=Q}for(var P=0;P<v;++P)w[P]=b[P+2].toWireType(D,I[P]),A.push(w[P]);I=e(...A);if(l)fb(D);else for(P=n?1:2;P<b.length;P++){var aa=1===P?Q:w[P-2];null!==b[P].ke&&b[P].ke(aa)}Q=p?b[0].fromWireType(I):
|
||||||
|
void 0;return Q})}
|
||||||
|
var hc=(a,b)=>{for(var c=[],e=0;e<a;e++)c.push(H[b+4*e>>2]);return c},ic=a=>{a=a.trim();const b=a.indexOf("(");return-1!==b?a.substr(0,b):a},jc=[],kc=[],lc=a=>{9<a&&0===--kc[a+1]&&(kc[a]=void 0,jc.push(a))},mc=a=>{if(!a)throw new L("Cannot use deleted val. handle = "+a);return kc[a]},Ob=a=>{switch(a){case void 0:return 2;case null:return 4;case !0:return 6;case !1:return 8;default:const b=jc.pop()||kc.length;kc[b]=a;kc[b+1]=1;return b}},nc={name:"emscripten::val",fromWireType:a=>{var b=mc(a);lc(a);
|
||||||
|
return b},toWireType:(a,b)=>Ob(b),je:8,readValueFromPointer:gb,ke:null},oc=(a,b,c)=>{switch(b){case 1:return c?function(e){return this.fromWireType(Ca[e])}:function(e){return this.fromWireType(B[e])};case 2:return c?function(e){return this.fromWireType(Da[e>>1])}:function(e){return this.fromWireType(Fa[e>>1])};case 4:return c?function(e){return this.fromWireType(E[e>>2])}:function(e){return this.fromWireType(H[e>>2])};default:throw new TypeError(`invalid integer width (${b}): ${a}`);}},pc=(a,b)=>
|
||||||
|
{var c=ib[a];if(void 0===c)throw a=`${b} has unknown type ${dc(a)}`,new L(a);return c},Mb=a=>{if(null===a)return"null";var b=typeof a;return"object"===b||"array"===b||"function"===b?a.toString():""+a},qc=(a,b)=>{switch(b){case 4:return function(c){return this.fromWireType(J[c>>2])};case 8:return function(c){return this.fromWireType(Ga[c>>3])};default:throw new TypeError(`invalid float width (${b}): ${a}`);}},rc=(a,b,c)=>{switch(b){case 1:return c?e=>Ca[e]:e=>B[e];case 2:return c?e=>Da[e>>1]:e=>Fa[e>>
|
||||||
|
1];case 4:return c?e=>E[e>>2]:e=>H[e>>2];default:throw new TypeError(`invalid integer width (${b}): ${a}`);}},ra=(a,b,c)=>{var e=B;if(!(0<c))return 0;var f=b;c=b+c-1;for(var k=0;k<a.length;++k){var n=a.charCodeAt(k);if(55296<=n&&57343>=n){var l=a.charCodeAt(++k);n=65536+((n&1023)<<10)|l&1023}if(127>=n){if(b>=c)break;e[b++]=n}else{if(2047>=n){if(b+1>=c)break;e[b++]=192|n>>6}else{if(65535>=n){if(b+2>=c)break;e[b++]=224|n>>12}else{if(b+3>=c)break;e[b++]=240|n>>18;e[b++]=128|n>>12&63}e[b++]=128|n>>6&
|
||||||
|
63}e[b++]=128|n&63}}e[b]=0;return b-f},qa=a=>{for(var b=0,c=0;c<a.length;++c){var e=a.charCodeAt(c);127>=e?b++:2047>=e?b+=2:55296<=e&&57343>=e?(b+=4,++c):b+=3}return b},sc="undefined"!=typeof TextDecoder?new TextDecoder("utf-16le"):void 0,tc=(a,b)=>{var c=a>>1;for(var e=c+b/2;!(c>=e)&&Fa[c];)++c;c<<=1;if(32<c-a&&sc)return sc.decode(B.subarray(a,c));c="";for(e=0;!(e>=b/2);++e){var f=Da[a+2*e>>1];if(0==f)break;c+=String.fromCharCode(f)}return c},uc=(a,b,c)=>{c??=2147483647;if(2>c)return 0;c-=2;var e=
|
||||||
|
b;c=c<2*a.length?c/2:a.length;for(var f=0;f<c;++f)Da[b>>1]=a.charCodeAt(f),b+=2;Da[b>>1]=0;return b-e},vc=a=>2*a.length,wc=(a,b)=>{for(var c=0,e="";!(c>=b/4);){var f=E[a+4*c>>2];if(0==f)break;++c;65536<=f?(f-=65536,e+=String.fromCharCode(55296|f>>10,56320|f&1023)):e+=String.fromCharCode(f)}return e},xc=(a,b,c)=>{c??=2147483647;if(4>c)return 0;var e=b;c=e+c-4;for(var f=0;f<a.length;++f){var k=a.charCodeAt(f);if(55296<=k&&57343>=k){var n=a.charCodeAt(++f);k=65536+((k&1023)<<10)|n&1023}E[b>>2]=k;b+=
|
||||||
|
4;if(b+4>c)break}E[b>>2]=0;return b-e},yc=a=>{for(var b=0,c=0;c<a.length;++c){var e=a.charCodeAt(c);55296<=e&&57343>=e&&++c;b+=4}return b},zc=(a,b,c)=>{var e=[];a=a.toWireType(e,c);e.length&&(H[b>>2]=Ob(e));return a},Ac=[],Bc={},Cc=a=>{var b=Bc[a];return void 0===b?K(a):b},Dc=()=>{function a(b){b.$$$embind_global$$$=b;var c="object"==typeof $$$embind_global$$$&&b.$$$embind_global$$$==b;c||delete b.$$$embind_global$$$;return c}if("object"==typeof globalThis)return globalThis;if("object"==typeof $$$embind_global$$$)return $$$embind_global$$$;
|
||||||
|
"object"==typeof global&&a(global)?$$$embind_global$$$=global:"object"==typeof self&&a(self)&&($$$embind_global$$$=self);if("object"==typeof $$$embind_global$$$)return $$$embind_global$$$;throw Error("unable to get global object.");},Ec=a=>{var b=Ac.length;Ac.push(a);return b},Fc=(a,b)=>{for(var c=Array(a),e=0;e<a;++e)c[e]=pc(H[b+4*e>>2],"parameter "+e);return c},Gc=Reflect.construct,R,Hc=a=>{var b=a.getExtension("ANGLE_instanced_arrays");b&&(a.vertexAttribDivisor=(c,e)=>b.vertexAttribDivisorANGLE(c,
|
||||||
|
e),a.drawArraysInstanced=(c,e,f,k)=>b.drawArraysInstancedANGLE(c,e,f,k),a.drawElementsInstanced=(c,e,f,k,n)=>b.drawElementsInstancedANGLE(c,e,f,k,n))},Ic=a=>{var b=a.getExtension("OES_vertex_array_object");b&&(a.createVertexArray=()=>b.createVertexArrayOES(),a.deleteVertexArray=c=>b.deleteVertexArrayOES(c),a.bindVertexArray=c=>b.bindVertexArrayOES(c),a.isVertexArray=c=>b.isVertexArrayOES(c))},Jc=a=>{var b=a.getExtension("WEBGL_draw_buffers");b&&(a.drawBuffers=(c,e)=>b.drawBuffersWEBGL(c,e))},Kc=a=>
|
||||||
|
{var b="ANGLE_instanced_arrays EXT_blend_minmax EXT_disjoint_timer_query EXT_frag_depth EXT_shader_texture_lod EXT_sRGB OES_element_index_uint OES_fbo_render_mipmap OES_standard_derivatives OES_texture_float OES_texture_half_float OES_texture_half_float_linear OES_vertex_array_object WEBGL_color_buffer_float WEBGL_depth_texture WEBGL_draw_buffers EXT_color_buffer_float EXT_conservative_depth EXT_disjoint_timer_query_webgl2 EXT_texture_norm16 NV_shader_noperspective_interpolation WEBGL_clip_cull_distance EXT_clip_control EXT_color_buffer_half_float EXT_depth_clamp EXT_float_blend EXT_polygon_offset_clamp EXT_texture_compression_bptc EXT_texture_compression_rgtc EXT_texture_filter_anisotropic KHR_parallel_shader_compile OES_texture_float_linear WEBGL_blend_func_extended WEBGL_compressed_texture_astc WEBGL_compressed_texture_etc WEBGL_compressed_texture_etc1 WEBGL_compressed_texture_s3tc WEBGL_compressed_texture_s3tc_srgb WEBGL_debug_renderer_info WEBGL_debug_shaders WEBGL_lose_context WEBGL_multi_draw WEBGL_polygon_mode".split(" ");
|
||||||
|
return(a.getSupportedExtensions()||[]).filter(c=>b.includes(c))},Lc=1,Mc=[],Nc=[],Oc=[],Pc=[],ka=[],Qc=[],Rc=[],pa=[],Sc=[],Tc=[],Uc=[],Wc={},Xc={},Yc=4,Zc=0,ja=a=>{for(var b=Lc++,c=a.length;c<b;c++)a[c]=null;return b},$c=(a,b,c,e)=>{for(var f=0;f<a;f++){var k=R[c](),n=k&&ja(e);k?(k.name=n,e[n]=k):U||=1282;E[b+4*f>>2]=n}},na=(a,b)=>{a.Ne||(a.Ne=a.getContext,a.getContext=function(e,f){f=a.Ne(e,f);return"webgl"==e==f instanceof WebGLRenderingContext?f:null});var c=1<b.majorVersion?a.getContext("webgl2",
|
||||||
|
b):a.getContext("webgl",b);return c?ad(c,b):0},ad=(a,b)=>{var c=ja(pa),e={handle:c,attributes:b,version:b.majorVersion,le:a};a.canvas&&(a.canvas.Ve=e);pa[c]=e;("undefined"==typeof b.df||b.df)&&bd(e);return c},oa=a=>{z=pa[a];r.vf=R=z?.le;return!(a&&!R)},bd=a=>{a||=z;if(!a.mf){a.mf=!0;var b=a.le;b.zf=b.getExtension("WEBGL_multi_draw");b.xf=b.getExtension("EXT_polygon_offset_clamp");b.wf=b.getExtension("EXT_clip_control");b.Bf=b.getExtension("WEBGL_polygon_mode");Hc(b);Ic(b);Jc(b);b.Pe=b.getExtension("WEBGL_draw_instanced_base_vertex_base_instance");
|
||||||
|
b.Re=b.getExtension("WEBGL_multi_draw_instanced_base_vertex_base_instance");2<=a.version&&(b.me=b.getExtension("EXT_disjoint_timer_query_webgl2"));if(2>a.version||!b.me)b.me=b.getExtension("EXT_disjoint_timer_query");Kc(b).forEach(c=>{c.includes("lose_context")||c.includes("debug")||b.getExtension(c)})}},z,U,cd=(a,b)=>{R.bindFramebuffer(a,Oc[b])},dd=a=>{R.bindVertexArray(Rc[a])},ed=a=>R.clear(a),fd=(a,b,c,e)=>R.clearColor(a,b,c,e),gd=a=>R.clearStencil(a),hd=(a,b)=>{for(var c=0;c<a;c++){var e=E[b+
|
||||||
|
4*c>>2];R.deleteVertexArray(Rc[e]);Rc[e]=null}},jd=[],kd=(a,b)=>{$c(a,b,"createVertexArray",Rc)};function ld(){var a=Kc(R);return a=a.concat(a.map(b=>"GL_"+b))}
|
||||||
|
var md=(a,b,c)=>{if(b){var e=void 0;switch(a){case 36346:e=1;break;case 36344:0!=c&&1!=c&&(U||=1280);return;case 34814:case 36345:e=0;break;case 34466:var f=R.getParameter(34467);e=f?f.length:0;break;case 33309:if(2>z.version){U||=1282;return}e=ld().length;break;case 33307:case 33308:if(2>z.version){U||=1280;return}e=33307==a?3:0}if(void 0===e)switch(f=R.getParameter(a),typeof f){case "number":e=f;break;case "boolean":e=f?1:0;break;case "string":U||=1280;return;case "object":if(null===f)switch(a){case 34964:case 35725:case 34965:case 36006:case 36007:case 32873:case 34229:case 36662:case 36663:case 35053:case 35055:case 36010:case 35097:case 35869:case 32874:case 36389:case 35983:case 35368:case 34068:e=
|
||||||
|
0;break;default:U||=1280;return}else{if(f instanceof Float32Array||f instanceof Uint32Array||f instanceof Int32Array||f instanceof Array){for(a=0;a<f.length;++a)switch(c){case 0:E[b+4*a>>2]=f[a];break;case 2:J[b+4*a>>2]=f[a];break;case 4:Ca[b+a]=f[a]?1:0}return}try{e=f.name|0}catch(k){U||=1280;ya(`GL_INVALID_ENUM in glGet${c}v: Unknown object returned from WebGL getParameter(${a})! (error: ${k})`);return}}break;default:U||=1280;ya(`GL_INVALID_ENUM in glGet${c}v: Native code calling glGet${c}v(${a}) and it returns ${f} of type ${typeof f}!`);
|
||||||
|
return}switch(c){case 1:c=e;H[b>>2]=c;H[b+4>>2]=(c-H[b>>2])/4294967296;break;case 0:E[b>>2]=e;break;case 2:J[b>>2]=e;break;case 4:Ca[b]=e?1:0}}else U||=1281},nd=(a,b)=>md(a,b,0),od=(a,b,c)=>{if(c){a=Sc[a];b=2>z.version?R.me.getQueryObjectEXT(a,b):R.getQueryParameter(a,b);var e;"boolean"==typeof b?e=b?1:0:e=b;H[c>>2]=e;H[c+4>>2]=(e-H[c>>2])/4294967296}else U||=1281},qd=a=>{var b=qa(a)+1,c=pd(b);c&&ra(a,c,b);return c},rd=a=>{var b=Wc[a];if(!b){switch(a){case 7939:b=qd(ld().join(" "));break;case 7936:case 7937:case 37445:case 37446:(b=
|
||||||
|
R.getParameter(a))||(U||=1280);b=b?qd(b):0;break;case 7938:b=R.getParameter(7938);var c=`OpenGL ES 2.0 (${b})`;2<=z.version&&(c=`OpenGL ES 3.0 (${b})`);b=qd(c);break;case 35724:b=R.getParameter(35724);c=b.match(/^WebGL GLSL ES ([0-9]\.[0-9][0-9]?)(?:$| .*)/);null!==c&&(3==c[1].length&&(c[1]+="0"),b=`OpenGL ES GLSL ES ${c[1]} (${b})`);b=qd(b);break;default:U||=1280}Wc[a]=b}return b},sd=(a,b)=>{if(2>z.version)return U||=1282,0;var c=Xc[a];if(c)return 0>b||b>=c.length?(U||=1281,0):c[b];switch(a){case 7939:return c=
|
||||||
|
ld().map(qd),c=Xc[a]=c,0>b||b>=c.length?(U||=1281,0):c[b];default:return U||=1280,0}},td=a=>"]"==a.slice(-1)&&a.lastIndexOf("["),ud=a=>{a-=5120;return 0==a?Ca:1==a?B:2==a?Da:4==a?E:6==a?J:5==a||28922==a||28520==a||30779==a||30782==a?H:Fa},vd=(a,b,c,e,f)=>{a=ud(a);b=e*((Zc||c)*({5:3,6:4,8:2,29502:3,29504:4,26917:2,26918:2,29846:3,29847:4}[b-6402]||1)*a.BYTES_PER_ELEMENT+Yc-1&-Yc);return a.subarray(f>>>31-Math.clz32(a.BYTES_PER_ELEMENT),f+b>>>31-Math.clz32(a.BYTES_PER_ELEMENT))},Y=a=>{var b=R.bf;if(b){var c=
|
||||||
|
b.xe[a];"number"==typeof c&&(b.xe[a]=c=R.getUniformLocation(b,b.Te[a]+(0<c?`[${c}]`:"")));return c}U||=1282},wd=[],xd=[],yd={},Ad=()=>{if(!zd){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:"./this.program"},b;for(b in yd)void 0===yd[b]?delete a[b]:a[b]=yd[b];var c=[];for(b in a)c.push(`${b}=${a[b]}`);zd=c}return zd},zd,Bd=[null,[],[]];
|
||||||
|
kb=r.InternalError=class extends Error{constructor(a){super(a);this.name="InternalError"}};for(var Cd=Array(256),Dd=0;256>Dd;++Dd)Cd[Dd]=String.fromCharCode(Dd);nb=Cd;L=r.BindingError=class extends Error{constructor(a){super(a);this.name="BindingError"}};
|
||||||
|
Object.assign(Eb.prototype,{isAliasOf:function(a){if(!(this instanceof Eb&&a instanceof Eb))return!1;var b=this.Yd.de.be,c=this.Yd.ae;a.Yd=a.Yd;var e=a.Yd.de.be;for(a=a.Yd.ae;b.ge;)c=b.ye(c),b=b.ge;for(;e.ge;)a=e.ye(a),e=e.ge;return b===e&&c===a},clone:function(){this.Yd.ae||pb(this);if(this.Yd.we)return this.Yd.count.value+=1,this;var a=Bb,b=Object,c=b.create,e=Object.getPrototypeOf(this),f=this.Yd;a=a(c.call(b,e,{Yd:{value:{count:f.count,ve:f.ve,we:f.we,ae:f.ae,de:f.de,ee:f.ee,ie:f.ie}}}));a.Yd.count.value+=
|
||||||
|
1;a.Yd.ve=!1;return a},["delete"](){this.Yd.ae||pb(this);if(this.Yd.ve&&!this.Yd.we)throw new L("Object already scheduled for deletion");rb(this);var a=this.Yd;--a.count.value;0===a.count.value&&(a.ee?a.ie.ne(a.ee):a.de.be.ne(a.ae));this.Yd.we||(this.Yd.ee=void 0,this.Yd.ae=void 0)},isDeleted:function(){return!this.Yd.ae},deleteLater:function(){this.Yd.ae||pb(this);if(this.Yd.ve&&!this.Yd.we)throw new L("Object already scheduled for deletion");Db.push(this);this.Yd.ve=!0;return this}});
|
||||||
|
Object.assign(Qb.prototype,{gf(a){this.Se&&(a=this.Se(a));return a},Oe(a){this.ne?.(a)},je:8,readValueFromPointer:gb,fromWireType:function(a){function b(){return this.De?Cb(this.be.se,{de:this.nf,ae:c,ie:this,ee:a}):Cb(this.be.se,{de:this,ae:a})}var c=this.gf(a);if(!c)return this.Oe(a),null;var e=Ab(this.be,c);if(void 0!==e){if(0===e.Yd.count.value)return e.Yd.ae=c,e.Yd.ee=a,e.clone();e=e.clone();this.Oe(a);return e}e=this.be.ff(c);e=yb[e];if(!e)return b.call(this);e=this.Ce?e.af:e.pointerType;var f=
|
||||||
|
sb(c,this.be,e.be);return null===f?b.call(this):this.De?Cb(e.be.se,{de:e,ae:f,ie:this,ee:a}):Cb(e.be.se,{de:e,ae:f})}});ac=r.UnboundTypeError=((a,b)=>{var c=Fb(b,function(e){this.name=b;this.message=e;e=Error(e).stack;void 0!==e&&(this.stack=this.toString()+"\n"+e.replace(/^Error(:[^\n]*)?\n/,""))});c.prototype=Object.create(a.prototype);c.prototype.constructor=c;c.prototype.toString=function(){return void 0===this.message?this.name:`${this.name}: ${this.message}`};return c})(Error,"UnboundTypeError");
|
||||||
|
kc.push(0,1,void 0,1,null,1,!0,1,!1,1);r.count_emval_handles=()=>kc.length/2-5-jc.length;for(var Ed=0;32>Ed;++Ed)jd.push(Array(Ed));var Fd=new Float32Array(288);for(Ed=0;288>=Ed;++Ed)wd[Ed]=Fd.subarray(0,Ed);var Gd=new Int32Array(288);for(Ed=0;288>=Ed;++Ed)xd[Ed]=Gd.subarray(0,Ed);
|
||||||
|
var Vd={F:(a,b,c)=>{var e=new Ya(a);H[e.ae+16>>2]=0;H[e.ae+4>>2]=b;H[e.ae+8>>2]=c;Za=a;bb++;throw Za;},V:function(){return 0},vd:()=>{},ud:function(){return 0},td:()=>{},sd:()=>{},U:function(){},rd:()=>{},nd:()=>{Pa("")},B:a=>{var b=eb[a];delete eb[a];var c=b.Le,e=b.ne,f=b.Qe,k=f.map(n=>n.kf).concat(f.map(n=>n.sf));mb([a],k,n=>{var l={};f.forEach((p,v)=>{var w=n[v],A=p.hf,D=p.jf,I=n[v+f.length],Q=p.rf,P=p.tf;l[p.ef]={read:aa=>w.fromWireType(A(D,aa)),write:(aa,la)=>{var X=[];Q(P,aa,I.toWireType(X,
|
||||||
|
la));fb(X)}}});return[{name:b.name,fromWireType:p=>{var v={},w;for(w in l)v[w]=l[w].read(p);e(p);return v},toWireType:(p,v)=>{for(var w in l)if(!(w in v))throw new TypeError(`Missing field: "${w}"`);var A=c();for(w in l)l[w].write(A,v[w]);null!==p&&p.push(e,A);return A},je:8,readValueFromPointer:gb,ke:e}]})},Y:()=>{},md:(a,b,c,e)=>{b=K(b);lb(a,{name:b,fromWireType:function(f){return!!f},toWireType:function(f,k){return k?c:e},je:8,readValueFromPointer:function(f){return this.fromWireType(B[f])},ke:null})},
|
||||||
|
k:(a,b,c,e,f,k,n,l,p,v,w,A,D)=>{w=K(w);k=O(f,k);l&&=O(n,l);v&&=O(p,v);D=O(A,D);var I=Ib(w);Hb(I,function(){ec(`Cannot construct ${w} due to unbound types`,[e])});mb([a,b,c],e?[e]:[],Q=>{Q=Q[0];if(e){var P=Q.be;var aa=P.se}else aa=Eb.prototype;Q=Fb(w,function(...Ea){if(Object.getPrototypeOf(this)!==la)throw new L("Use 'new' to construct "+w);if(void 0===X.pe)throw new L(w+" has no accessible constructor");var ea=X.pe[Ea.length];if(void 0===ea)throw new L(`Tried to invoke ctor of ${w} with invalid number of parameters (${Ea.length}) - expected (${Object.keys(X.pe).toString()}) parameters instead!`);
|
||||||
|
return ea.apply(this,Ea)});var la=Object.create(aa,{constructor:{value:Q}});Q.prototype=la;var X=new Jb(w,Q,la,D,P,k,l,v);if(X.ge){var ha;(ha=X.ge).ze??(ha.ze=[]);X.ge.ze.push(X)}P=new Qb(w,X,!0,!1,!1);ha=new Qb(w+"*",X,!1,!1,!1);aa=new Qb(w+" const*",X,!1,!0,!1);yb[a]={pointerType:ha,af:aa};Rb(I,Q);return[P,ha,aa]})},e:(a,b,c,e,f,k,n)=>{var l=hc(c,e);b=K(b);b=ic(b);k=O(f,k);mb([],[a],p=>{function v(){ec(`Cannot call ${w} due to unbound types`,l)}p=p[0];var w=`${p.name}.${b}`;b.startsWith("@@")&&
|
||||||
|
(b=Symbol[b.substring(2)]);var A=p.be.constructor;void 0===A[b]?(v.oe=c-1,A[b]=v):(Gb(A,b,w),A[b].fe[c-1]=v);mb([],l,D=>{D=[D[0],null].concat(D.slice(1));D=gc(w,D,null,k,n);void 0===A[b].fe?(D.oe=c-1,A[b]=D):A[b].fe[c-1]=D;if(p.be.ze)for(const I of p.be.ze)I.constructor.hasOwnProperty(b)||(I.constructor[b]=D);return[]});return[]})},z:(a,b,c,e,f,k)=>{var n=hc(b,c);f=O(e,f);mb([],[a],l=>{l=l[0];var p=`constructor ${l.name}`;void 0===l.be.pe&&(l.be.pe=[]);if(void 0!==l.be.pe[b-1])throw new L(`Cannot register multiple constructors with identical number of parameters (${b-
|
||||||
|
1}) for class '${l.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`);l.be.pe[b-1]=()=>{ec(`Cannot construct ${l.name} due to unbound types`,n)};mb([],n,v=>{v.splice(1,0,null);l.be.pe[b-1]=gc(p,v,null,f,k);return[]});return[]})},a:(a,b,c,e,f,k,n,l)=>{var p=hc(c,e);b=K(b);b=ic(b);k=O(f,k);mb([],[a],v=>{function w(){ec(`Cannot call ${A} due to unbound types`,p)}v=v[0];var A=`${v.name}.${b}`;b.startsWith("@@")&&(b=Symbol[b.substring(2)]);l&&v.be.pf.push(b);
|
||||||
|
var D=v.be.se,I=D[b];void 0===I||void 0===I.fe&&I.className!==v.name&&I.oe===c-2?(w.oe=c-2,w.className=v.name,D[b]=w):(Gb(D,b,A),D[b].fe[c-2]=w);mb([],p,Q=>{Q=gc(A,Q,v,k,n);void 0===D[b].fe?(Q.oe=c-2,D[b]=Q):D[b].fe[c-2]=Q;return[]});return[]})},r:(a,b,c)=>{a=K(a);mb([],[b],e=>{e=e[0];r[a]=e.fromWireType(c);return[]})},ld:a=>lb(a,nc),i:(a,b,c,e)=>{function f(){}b=K(b);f.values={};lb(a,{name:b,constructor:f,fromWireType:function(k){return this.constructor.values[k]},toWireType:(k,n)=>n.value,je:8,
|
||||||
|
readValueFromPointer:oc(b,c,e),ke:null});Hb(b,f)},b:(a,b,c)=>{var e=pc(a,"enum");b=K(b);a=e.constructor;e=Object.create(e.constructor.prototype,{value:{value:c},constructor:{value:Fb(`${e.name}_${b}`,function(){})}});a.values[c]=e;a[b]=e},S:(a,b,c)=>{b=K(b);lb(a,{name:b,fromWireType:e=>e,toWireType:(e,f)=>f,je:8,readValueFromPointer:qc(b,c),ke:null})},w:(a,b,c,e,f,k)=>{var n=hc(b,c);a=K(a);a=ic(a);f=O(e,f);Hb(a,function(){ec(`Cannot call ${a} due to unbound types`,n)},b-1);mb([],n,l=>{l=[l[0],null].concat(l.slice(1));
|
||||||
|
Rb(a,gc(a,l,null,f,k),b-1);return[]})},C:(a,b,c,e,f)=>{b=K(b);-1===f&&(f=4294967295);f=l=>l;if(0===e){var k=32-8*c;f=l=>l<<k>>>k}var n=b.includes("unsigned")?function(l,p){return p>>>0}:function(l,p){return p};lb(a,{name:b,fromWireType:f,toWireType:n,je:8,readValueFromPointer:rc(b,c,0!==e),ke:null})},q:(a,b,c)=>{function e(k){return new f(Ca.buffer,H[k+4>>2],H[k>>2])}var f=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array][b];c=K(c);lb(a,{name:c,fromWireType:e,
|
||||||
|
je:8,readValueFromPointer:e},{lf:!0})},o:(a,b,c,e,f,k,n,l,p,v,w,A)=>{c=K(c);k=O(f,k);l=O(n,l);v=O(p,v);A=O(w,A);mb([a],[b],D=>{D=D[0];return[new Qb(c,D.be,!1,!1,!0,D,e,k,l,v,A)]})},R:(a,b)=>{b=K(b);var c="std::string"===b;lb(a,{name:b,fromWireType:function(e){var f=H[e>>2],k=e+4;if(c)for(var n=k,l=0;l<=f;++l){var p=k+l;if(l==f||0==B[p]){n=n?db(B,n,p-n):"";if(void 0===v)var v=n;else v+=String.fromCharCode(0),v+=n;n=p+1}}else{v=Array(f);for(l=0;l<f;++l)v[l]=String.fromCharCode(B[k+l]);v=v.join("")}cc(e);
|
||||||
|
return v},toWireType:function(e,f){f instanceof ArrayBuffer&&(f=new Uint8Array(f));var k="string"==typeof f;if(!(k||f instanceof Uint8Array||f instanceof Uint8ClampedArray||f instanceof Int8Array))throw new L("Cannot pass non-string to std::string");var n=c&&k?qa(f):f.length;var l=pd(4+n+1),p=l+4;H[l>>2]=n;if(c&&k)ra(f,p,n+1);else if(k)for(k=0;k<n;++k){var v=f.charCodeAt(k);if(255<v)throw cc(p),new L("String has UTF-16 code units that do not fit in 8 bits");B[p+k]=v}else for(k=0;k<n;++k)B[p+k]=f[k];
|
||||||
|
null!==e&&e.push(cc,l);return l},je:8,readValueFromPointer:gb,ke(e){cc(e)}})},M:(a,b,c)=>{c=K(c);if(2===b){var e=tc;var f=uc;var k=vc;var n=l=>Fa[l>>1]}else 4===b&&(e=wc,f=xc,k=yc,n=l=>H[l>>2]);lb(a,{name:c,fromWireType:l=>{for(var p=H[l>>2],v,w=l+4,A=0;A<=p;++A){var D=l+4+A*b;if(A==p||0==n(D))w=e(w,D-w),void 0===v?v=w:(v+=String.fromCharCode(0),v+=w),w=D+b}cc(l);return v},toWireType:(l,p)=>{if("string"!=typeof p)throw new L(`Cannot pass non-string to C++ string type ${c}`);var v=k(p),w=pd(4+v+b);
|
||||||
|
H[w>>2]=v/b;f(p,w+4,v+b);null!==l&&l.push(cc,w);return w},je:8,readValueFromPointer:gb,ke(l){cc(l)}})},A:(a,b,c,e,f,k)=>{eb[a]={name:K(b),Le:O(c,e),ne:O(f,k),Qe:[]}},d:(a,b,c,e,f,k,n,l,p,v)=>{eb[a].Qe.push({ef:K(b),kf:c,hf:O(e,f),jf:k,sf:n,rf:O(l,p),tf:v})},kd:(a,b)=>{b=K(b);lb(a,{yf:!0,name:b,je:0,fromWireType:()=>{},toWireType:()=>{}})},jd:()=>1,id:()=>{throw Infinity;},E:(a,b,c)=>{a=mc(a);b=pc(b,"emval::as");return zc(b,c,a)},L:(a,b,c,e)=>{a=Ac[a];b=mc(b);return a(null,b,c,e)},t:(a,b,c,e,f)=>{a=
|
||||||
|
Ac[a];b=mc(b);c=Cc(c);return a(b,b[c],e,f)},c:lc,K:a=>{if(0===a)return Ob(Dc());a=Cc(a);return Ob(Dc()[a])},n:(a,b,c)=>{var e=Fc(a,b),f=e.shift();a--;var k=Array(a);b=`methodCaller<(${e.map(n=>n.name).join(", ")}) => ${f.name}>`;return Ec(Fb(b,(n,l,p,v)=>{for(var w=0,A=0;A<a;++A)k[A]=e[A].readValueFromPointer(v+w),w+=e[A].je;n=1===c?Gc(l,k):l.apply(n,k);return zc(f,p,n)}))},y:(a,b)=>{a=mc(a);b=mc(b);return Ob(a[b])},H:a=>{9<a&&(kc[a+1]+=1)},G:()=>Ob([]),f:a=>Ob(Cc(a)),D:()=>Ob({}),hd:a=>{a=mc(a);
|
||||||
|
return!a},l:a=>{var b=mc(a);fb(b);lc(a)},h:(a,b,c)=>{a=mc(a);b=mc(b);c=mc(c);a[b]=c},g:(a,b)=>{a=pc(a,"_emval_take_value");a=a.readValueFromPointer(b);return Ob(a)},X:function(){return-52},W:function(){},gd:(a,b,c,e)=>{var f=(new Date).getFullYear(),k=(new Date(f,0,1)).getTimezoneOffset();f=(new Date(f,6,1)).getTimezoneOffset();H[a>>2]=60*Math.max(k,f);E[b>>2]=Number(k!=f);b=n=>{var l=Math.abs(n);return`UTC${0<=n?"-":"+"}${String(Math.floor(l/60)).padStart(2,"0")}${String(l%60).padStart(2,"0")}`};
|
||||||
|
a=b(k);b=b(f);f<k?(ra(a,c,17),ra(b,e,17)):(ra(a,e,17),ra(b,c,17))},fd:()=>performance.now(),ed:a=>R.activeTexture(a),dd:(a,b)=>{R.attachShader(Nc[a],Qc[b])},cd:(a,b)=>{R.beginQuery(a,Sc[b])},bd:(a,b)=>{R.me.beginQueryEXT(a,Sc[b])},ad:(a,b,c)=>{R.bindAttribLocation(Nc[a],b,c?db(B,c):"")},$c:(a,b)=>{35051==a?R.Ie=b:35052==a&&(R.re=b);R.bindBuffer(a,Mc[b])},_c:cd,Zc:(a,b)=>{R.bindRenderbuffer(a,Pc[b])},Yc:(a,b)=>{R.bindSampler(a,Tc[b])},Xc:(a,b)=>{R.bindTexture(a,ka[b])},Wc:dd,Vc:dd,Uc:(a,b,c,e)=>R.blendColor(a,
|
||||||
|
b,c,e),Tc:a=>R.blendEquation(a),Sc:(a,b)=>R.blendFunc(a,b),Rc:(a,b,c,e,f,k,n,l,p,v)=>R.blitFramebuffer(a,b,c,e,f,k,n,l,p,v),Qc:(a,b,c,e)=>{2<=z.version?c&&b?R.bufferData(a,B,e,c,b):R.bufferData(a,b,e):R.bufferData(a,c?B.subarray(c,c+b):b,e)},Pc:(a,b,c,e)=>{2<=z.version?c&&R.bufferSubData(a,b,B,e,c):R.bufferSubData(a,b,B.subarray(e,e+c))},Oc:a=>R.checkFramebufferStatus(a),Nc:ed,Mc:fd,Lc:gd,Kc:(a,b,c,e)=>R.clientWaitSync(Uc[a],b,(c>>>0)+4294967296*e),Jc:(a,b,c,e)=>{R.colorMask(!!a,!!b,!!c,!!e)},Ic:a=>
|
||||||
|
{R.compileShader(Qc[a])},Hc:(a,b,c,e,f,k,n,l)=>{2<=z.version?R.re||!n?R.compressedTexImage2D(a,b,c,e,f,k,n,l):R.compressedTexImage2D(a,b,c,e,f,k,B,l,n):R.compressedTexImage2D(a,b,c,e,f,k,B.subarray(l,l+n))},Gc:(a,b,c,e,f,k,n,l,p)=>{2<=z.version?R.re||!l?R.compressedTexSubImage2D(a,b,c,e,f,k,n,l,p):R.compressedTexSubImage2D(a,b,c,e,f,k,n,B,p,l):R.compressedTexSubImage2D(a,b,c,e,f,k,n,B.subarray(p,p+l))},Fc:(a,b,c,e,f)=>R.copyBufferSubData(a,b,c,e,f),Ec:(a,b,c,e,f,k,n,l)=>R.copyTexSubImage2D(a,b,c,
|
||||||
|
e,f,k,n,l),Dc:()=>{var a=ja(Nc),b=R.createProgram();b.name=a;b.Ge=b.Ee=b.Fe=0;b.Me=1;Nc[a]=b;return a},Cc:a=>{var b=ja(Qc);Qc[b]=R.createShader(a);return b},Bc:a=>R.cullFace(a),Ac:(a,b)=>{for(var c=0;c<a;c++){var e=E[b+4*c>>2],f=Mc[e];f&&(R.deleteBuffer(f),f.name=0,Mc[e]=null,e==R.Ie&&(R.Ie=0),e==R.re&&(R.re=0))}},zc:(a,b)=>{for(var c=0;c<a;++c){var e=E[b+4*c>>2],f=Oc[e];f&&(R.deleteFramebuffer(f),f.name=0,Oc[e]=null)}},yc:a=>{if(a){var b=Nc[a];b?(R.deleteProgram(b),b.name=0,Nc[a]=null):U||=1281}},
|
||||||
|
xc:(a,b)=>{for(var c=0;c<a;c++){var e=E[b+4*c>>2],f=Sc[e];f&&(R.deleteQuery(f),Sc[e]=null)}},wc:(a,b)=>{for(var c=0;c<a;c++){var e=E[b+4*c>>2],f=Sc[e];f&&(R.me.deleteQueryEXT(f),Sc[e]=null)}},vc:(a,b)=>{for(var c=0;c<a;c++){var e=E[b+4*c>>2],f=Pc[e];f&&(R.deleteRenderbuffer(f),f.name=0,Pc[e]=null)}},uc:(a,b)=>{for(var c=0;c<a;c++){var e=E[b+4*c>>2],f=Tc[e];f&&(R.deleteSampler(f),f.name=0,Tc[e]=null)}},tc:a=>{if(a){var b=Qc[a];b?(R.deleteShader(b),Qc[a]=null):U||=1281}},sc:a=>{if(a){var b=Uc[a];b?
|
||||||
|
(R.deleteSync(b),b.name=0,Uc[a]=null):U||=1281}},rc:(a,b)=>{for(var c=0;c<a;c++){var e=E[b+4*c>>2],f=ka[e];f&&(R.deleteTexture(f),f.name=0,ka[e]=null)}},qc:hd,pc:hd,oc:a=>{R.depthMask(!!a)},nc:a=>R.disable(a),mc:a=>{R.disableVertexAttribArray(a)},lc:(a,b,c)=>{R.drawArrays(a,b,c)},kc:(a,b,c,e)=>{R.drawArraysInstanced(a,b,c,e)},jc:(a,b,c,e,f)=>{R.Pe.drawArraysInstancedBaseInstanceWEBGL(a,b,c,e,f)},ic:(a,b)=>{for(var c=jd[a],e=0;e<a;e++)c[e]=E[b+4*e>>2];R.drawBuffers(c)},hc:(a,b,c,e)=>{R.drawElements(a,
|
||||||
|
b,c,e)},gc:(a,b,c,e,f)=>{R.drawElementsInstanced(a,b,c,e,f)},fc:(a,b,c,e,f,k,n)=>{R.Pe.drawElementsInstancedBaseVertexBaseInstanceWEBGL(a,b,c,e,f,k,n)},ec:(a,b,c,e,f,k)=>{R.drawElements(a,e,f,k)},dc:a=>R.enable(a),cc:a=>{R.enableVertexAttribArray(a)},bc:a=>R.endQuery(a),ac:a=>{R.me.endQueryEXT(a)},$b:(a,b)=>(a=R.fenceSync(a,b))?(b=ja(Uc),a.name=b,Uc[b]=a,b):0,_b:()=>R.finish(),Zb:()=>R.flush(),Yb:(a,b,c,e)=>{R.framebufferRenderbuffer(a,b,c,Pc[e])},Xb:(a,b,c,e,f)=>{R.framebufferTexture2D(a,b,c,ka[e],
|
||||||
|
f)},Wb:a=>R.frontFace(a),Vb:(a,b)=>{$c(a,b,"createBuffer",Mc)},Ub:(a,b)=>{$c(a,b,"createFramebuffer",Oc)},Tb:(a,b)=>{$c(a,b,"createQuery",Sc)},Sb:(a,b)=>{for(var c=0;c<a;c++){var e=R.me.createQueryEXT();if(!e){for(U||=1282;c<a;)E[b+4*c++>>2]=0;break}var f=ja(Sc);e.name=f;Sc[f]=e;E[b+4*c>>2]=f}},Rb:(a,b)=>{$c(a,b,"createRenderbuffer",Pc)},Qb:(a,b)=>{$c(a,b,"createSampler",Tc)},Pb:(a,b)=>{$c(a,b,"createTexture",ka)},Ob:kd,Nb:kd,Mb:a=>R.generateMipmap(a),Lb:(a,b,c)=>{c?E[c>>2]=R.getBufferParameter(a,
|
||||||
|
b):U||=1281},Kb:()=>{var a=R.getError()||U;U=0;return a},Jb:(a,b)=>md(a,b,2),Ib:(a,b,c,e)=>{a=R.getFramebufferAttachmentParameter(a,b,c);if(a instanceof WebGLRenderbuffer||a instanceof WebGLTexture)a=a.name|0;E[e>>2]=a},Hb:nd,Gb:(a,b,c,e)=>{a=R.getProgramInfoLog(Nc[a]);null===a&&(a="(unknown error)");b=0<b&&e?ra(a,e,b):0;c&&(E[c>>2]=b)},Fb:(a,b,c)=>{if(c)if(a>=Lc)U||=1281;else if(a=Nc[a],35716==b)a=R.getProgramInfoLog(a),null===a&&(a="(unknown error)"),E[c>>2]=a.length+1;else if(35719==b){if(!a.Ge){var e=
|
||||||
|
R.getProgramParameter(a,35718);for(b=0;b<e;++b)a.Ge=Math.max(a.Ge,R.getActiveUniform(a,b).name.length+1)}E[c>>2]=a.Ge}else if(35722==b){if(!a.Ee)for(e=R.getProgramParameter(a,35721),b=0;b<e;++b)a.Ee=Math.max(a.Ee,R.getActiveAttrib(a,b).name.length+1);E[c>>2]=a.Ee}else if(35381==b){if(!a.Fe)for(e=R.getProgramParameter(a,35382),b=0;b<e;++b)a.Fe=Math.max(a.Fe,R.getActiveUniformBlockName(a,b).length+1);E[c>>2]=a.Fe}else E[c>>2]=R.getProgramParameter(a,b);else U||=1281},Eb:od,Db:od,Cb:(a,b,c)=>{if(c){a=
|
||||||
|
R.getQueryParameter(Sc[a],b);var e;"boolean"==typeof a?e=a?1:0:e=a;E[c>>2]=e}else U||=1281},Bb:(a,b,c)=>{if(c){a=R.me.getQueryObjectEXT(Sc[a],b);var e;"boolean"==typeof a?e=a?1:0:e=a;E[c>>2]=e}else U||=1281},Ab:(a,b,c)=>{c?E[c>>2]=R.getQuery(a,b):U||=1281},zb:(a,b,c)=>{c?E[c>>2]=R.me.getQueryEXT(a,b):U||=1281},yb:(a,b,c)=>{c?E[c>>2]=R.getRenderbufferParameter(a,b):U||=1281},xb:(a,b,c,e)=>{a=R.getShaderInfoLog(Qc[a]);null===a&&(a="(unknown error)");b=0<b&&e?ra(a,e,b):0;c&&(E[c>>2]=b)},wb:(a,b,c,e)=>
|
||||||
|
{a=R.getShaderPrecisionFormat(a,b);E[c>>2]=a.rangeMin;E[c+4>>2]=a.rangeMax;E[e>>2]=a.precision},vb:(a,b,c)=>{c?35716==b?(a=R.getShaderInfoLog(Qc[a]),null===a&&(a="(unknown error)"),E[c>>2]=a?a.length+1:0):35720==b?(a=R.getShaderSource(Qc[a]),E[c>>2]=a?a.length+1:0):E[c>>2]=R.getShaderParameter(Qc[a],b):U||=1281},ub:rd,tb:sd,sb:(a,b)=>{b=b?db(B,b):"";if(a=Nc[a]){var c=a,e=c.xe,f=c.Ue,k;if(!e){c.xe=e={};c.Te={};var n=R.getProgramParameter(c,35718);for(k=0;k<n;++k){var l=R.getActiveUniform(c,k);var p=
|
||||||
|
l.name;l=l.size;var v=td(p);v=0<v?p.slice(0,v):p;var w=c.Me;c.Me+=l;f[v]=[l,w];for(p=0;p<l;++p)e[w]=p,c.Te[w++]=v}}c=a.xe;e=0;f=b;k=td(b);0<k&&(e=parseInt(b.slice(k+1))>>>0,f=b.slice(0,k));if((f=a.Ue[f])&&e<f[0]&&(e+=f[1],c[e]=c[e]||R.getUniformLocation(a,b)))return e}else U||=1281;return-1},rb:(a,b,c)=>{for(var e=jd[b],f=0;f<b;f++)e[f]=E[c+4*f>>2];R.invalidateFramebuffer(a,e)},qb:(a,b,c,e,f,k,n)=>{for(var l=jd[b],p=0;p<b;p++)l[p]=E[c+4*p>>2];R.invalidateSubFramebuffer(a,l,e,f,k,n)},pb:a=>R.isSync(Uc[a]),
|
||||||
|
ob:a=>(a=ka[a])?R.isTexture(a):0,nb:a=>R.lineWidth(a),mb:a=>{a=Nc[a];R.linkProgram(a);a.xe=0;a.Ue={}},lb:(a,b,c,e,f,k)=>{R.Re.multiDrawArraysInstancedBaseInstanceWEBGL(a,E,b>>2,E,c>>2,E,e>>2,H,f>>2,k)},kb:(a,b,c,e,f,k,n,l)=>{R.Re.multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL(a,E,b>>2,c,E,e>>2,E,f>>2,E,k>>2,H,n>>2,l)},jb:(a,b)=>{3317==a?Yc=b:3314==a&&(Zc=b);R.pixelStorei(a,b)},ib:(a,b)=>{R.me.queryCounterEXT(Sc[a],b)},hb:a=>R.readBuffer(a),gb:(a,b,c,e,f,k,n)=>{if(2<=z.version)if(R.Ie)R.readPixels(a,
|
||||||
|
b,c,e,f,k,n);else{var l=ud(k);n>>>=31-Math.clz32(l.BYTES_PER_ELEMENT);R.readPixels(a,b,c,e,f,k,l,n)}else(l=vd(k,f,c,e,n))?R.readPixels(a,b,c,e,f,k,l):U||=1280},fb:(a,b,c,e)=>R.renderbufferStorage(a,b,c,e),eb:(a,b,c,e,f)=>R.renderbufferStorageMultisample(a,b,c,e,f),db:(a,b,c)=>{R.samplerParameterf(Tc[a],b,c)},cb:(a,b,c)=>{R.samplerParameteri(Tc[a],b,c)},bb:(a,b,c)=>{R.samplerParameteri(Tc[a],b,E[c>>2])},ab:(a,b,c,e)=>R.scissor(a,b,c,e),$a:(a,b,c,e)=>{for(var f="",k=0;k<b;++k){var n=(n=H[c+4*k>>2])?
|
||||||
|
db(B,n,e?H[e+4*k>>2]:void 0):"";f+=n}R.shaderSource(Qc[a],f)},_a:(a,b,c)=>R.stencilFunc(a,b,c),Za:(a,b,c,e)=>R.stencilFuncSeparate(a,b,c,e),Ya:a=>R.stencilMask(a),Xa:(a,b)=>R.stencilMaskSeparate(a,b),Wa:(a,b,c)=>R.stencilOp(a,b,c),Va:(a,b,c,e)=>R.stencilOpSeparate(a,b,c,e),Ua:(a,b,c,e,f,k,n,l,p)=>{if(2<=z.version){if(R.re){R.texImage2D(a,b,c,e,f,k,n,l,p);return}if(p){var v=ud(l);p>>>=31-Math.clz32(v.BYTES_PER_ELEMENT);R.texImage2D(a,b,c,e,f,k,n,l,v,p);return}}v=p?vd(l,n,e,f,p):null;R.texImage2D(a,
|
||||||
|
b,c,e,f,k,n,l,v)},Ta:(a,b,c)=>R.texParameterf(a,b,c),Sa:(a,b,c)=>{R.texParameterf(a,b,J[c>>2])},Ra:(a,b,c)=>R.texParameteri(a,b,c),Qa:(a,b,c)=>{R.texParameteri(a,b,E[c>>2])},Pa:(a,b,c,e,f)=>R.texStorage2D(a,b,c,e,f),Oa:(a,b,c,e,f,k,n,l,p)=>{if(2<=z.version){if(R.re){R.texSubImage2D(a,b,c,e,f,k,n,l,p);return}if(p){var v=ud(l);R.texSubImage2D(a,b,c,e,f,k,n,l,v,p>>>31-Math.clz32(v.BYTES_PER_ELEMENT));return}}p=p?vd(l,n,f,k,p):null;R.texSubImage2D(a,b,c,e,f,k,n,l,p)},Na:(a,b)=>{R.uniform1f(Y(a),b)},Ma:(a,
|
||||||
|
b,c)=>{if(2<=z.version)b&&R.uniform1fv(Y(a),J,c>>2,b);else{if(288>=b)for(var e=wd[b],f=0;f<b;++f)e[f]=J[c+4*f>>2];else e=J.subarray(c>>2,c+4*b>>2);R.uniform1fv(Y(a),e)}},La:(a,b)=>{R.uniform1i(Y(a),b)},Ka:(a,b,c)=>{if(2<=z.version)b&&R.uniform1iv(Y(a),E,c>>2,b);else{if(288>=b)for(var e=xd[b],f=0;f<b;++f)e[f]=E[c+4*f>>2];else e=E.subarray(c>>2,c+4*b>>2);R.uniform1iv(Y(a),e)}},Ja:(a,b,c)=>{R.uniform2f(Y(a),b,c)},Ia:(a,b,c)=>{if(2<=z.version)b&&R.uniform2fv(Y(a),J,c>>2,2*b);else{if(144>=b){b*=2;for(var e=
|
||||||
|
wd[b],f=0;f<b;f+=2)e[f]=J[c+4*f>>2],e[f+1]=J[c+(4*f+4)>>2]}else e=J.subarray(c>>2,c+8*b>>2);R.uniform2fv(Y(a),e)}},Ha:(a,b,c)=>{R.uniform2i(Y(a),b,c)},Ga:(a,b,c)=>{if(2<=z.version)b&&R.uniform2iv(Y(a),E,c>>2,2*b);else{if(144>=b){b*=2;for(var e=xd[b],f=0;f<b;f+=2)e[f]=E[c+4*f>>2],e[f+1]=E[c+(4*f+4)>>2]}else e=E.subarray(c>>2,c+8*b>>2);R.uniform2iv(Y(a),e)}},Fa:(a,b,c,e)=>{R.uniform3f(Y(a),b,c,e)},Ea:(a,b,c)=>{if(2<=z.version)b&&R.uniform3fv(Y(a),J,c>>2,3*b);else{if(96>=b){b*=3;for(var e=wd[b],f=0;f<
|
||||||
|
b;f+=3)e[f]=J[c+4*f>>2],e[f+1]=J[c+(4*f+4)>>2],e[f+2]=J[c+(4*f+8)>>2]}else e=J.subarray(c>>2,c+12*b>>2);R.uniform3fv(Y(a),e)}},Da:(a,b,c,e)=>{R.uniform3i(Y(a),b,c,e)},Ca:(a,b,c)=>{if(2<=z.version)b&&R.uniform3iv(Y(a),E,c>>2,3*b);else{if(96>=b){b*=3;for(var e=xd[b],f=0;f<b;f+=3)e[f]=E[c+4*f>>2],e[f+1]=E[c+(4*f+4)>>2],e[f+2]=E[c+(4*f+8)>>2]}else e=E.subarray(c>>2,c+12*b>>2);R.uniform3iv(Y(a),e)}},Ba:(a,b,c,e,f)=>{R.uniform4f(Y(a),b,c,e,f)},Aa:(a,b,c)=>{if(2<=z.version)b&&R.uniform4fv(Y(a),J,c>>2,4*
|
||||||
|
b);else{if(72>=b){var e=wd[4*b],f=J;c>>=2;b*=4;for(var k=0;k<b;k+=4){var n=c+k;e[k]=f[n];e[k+1]=f[n+1];e[k+2]=f[n+2];e[k+3]=f[n+3]}}else e=J.subarray(c>>2,c+16*b>>2);R.uniform4fv(Y(a),e)}},za:(a,b,c,e,f)=>{R.uniform4i(Y(a),b,c,e,f)},ya:(a,b,c)=>{if(2<=z.version)b&&R.uniform4iv(Y(a),E,c>>2,4*b);else{if(72>=b){b*=4;for(var e=xd[b],f=0;f<b;f+=4)e[f]=E[c+4*f>>2],e[f+1]=E[c+(4*f+4)>>2],e[f+2]=E[c+(4*f+8)>>2],e[f+3]=E[c+(4*f+12)>>2]}else e=E.subarray(c>>2,c+16*b>>2);R.uniform4iv(Y(a),e)}},xa:(a,b,c,e)=>
|
||||||
|
{if(2<=z.version)b&&R.uniformMatrix2fv(Y(a),!!c,J,e>>2,4*b);else{if(72>=b){b*=4;for(var f=wd[b],k=0;k<b;k+=4)f[k]=J[e+4*k>>2],f[k+1]=J[e+(4*k+4)>>2],f[k+2]=J[e+(4*k+8)>>2],f[k+3]=J[e+(4*k+12)>>2]}else f=J.subarray(e>>2,e+16*b>>2);R.uniformMatrix2fv(Y(a),!!c,f)}},wa:(a,b,c,e)=>{if(2<=z.version)b&&R.uniformMatrix3fv(Y(a),!!c,J,e>>2,9*b);else{if(32>=b){b*=9;for(var f=wd[b],k=0;k<b;k+=9)f[k]=J[e+4*k>>2],f[k+1]=J[e+(4*k+4)>>2],f[k+2]=J[e+(4*k+8)>>2],f[k+3]=J[e+(4*k+12)>>2],f[k+4]=J[e+(4*k+16)>>2],f[k+
|
||||||
|
5]=J[e+(4*k+20)>>2],f[k+6]=J[e+(4*k+24)>>2],f[k+7]=J[e+(4*k+28)>>2],f[k+8]=J[e+(4*k+32)>>2]}else f=J.subarray(e>>2,e+36*b>>2);R.uniformMatrix3fv(Y(a),!!c,f)}},va:(a,b,c,e)=>{if(2<=z.version)b&&R.uniformMatrix4fv(Y(a),!!c,J,e>>2,16*b);else{if(18>=b){var f=wd[16*b],k=J;e>>=2;b*=16;for(var n=0;n<b;n+=16){var l=e+n;f[n]=k[l];f[n+1]=k[l+1];f[n+2]=k[l+2];f[n+3]=k[l+3];f[n+4]=k[l+4];f[n+5]=k[l+5];f[n+6]=k[l+6];f[n+7]=k[l+7];f[n+8]=k[l+8];f[n+9]=k[l+9];f[n+10]=k[l+10];f[n+11]=k[l+11];f[n+12]=k[l+12];f[n+
|
||||||
|
13]=k[l+13];f[n+14]=k[l+14];f[n+15]=k[l+15]}}else f=J.subarray(e>>2,e+64*b>>2);R.uniformMatrix4fv(Y(a),!!c,f)}},ua:a=>{a=Nc[a];R.useProgram(a);R.bf=a},ta:(a,b)=>R.vertexAttrib1f(a,b),sa:(a,b)=>{R.vertexAttrib2f(a,J[b>>2],J[b+4>>2])},ra:(a,b)=>{R.vertexAttrib3f(a,J[b>>2],J[b+4>>2],J[b+8>>2])},qa:(a,b)=>{R.vertexAttrib4f(a,J[b>>2],J[b+4>>2],J[b+8>>2],J[b+12>>2])},pa:(a,b)=>{R.vertexAttribDivisor(a,b)},oa:(a,b,c,e,f)=>{R.vertexAttribIPointer(a,b,c,e,f)},na:(a,b,c,e,f,k)=>{R.vertexAttribPointer(a,b,c,
|
||||||
|
!!e,f,k)},ma:(a,b,c,e)=>R.viewport(a,b,c,e),la:(a,b,c,e)=>{R.waitSync(Uc[a],b,(c>>>0)+4294967296*e)},ka:a=>{var b=B.length;a>>>=0;if(2147483648<a)return!1;for(var c=1;4>=c;c*=2){var e=b*(1+1/c);e=Math.min(e,a+100663296);a:{e=(Math.min(2147483648,65536*Math.ceil(Math.max(a,e)/65536))-za.buffer.byteLength+65535)/65536|0;try{za.grow(e);Ha();var f=1;break a}catch(k){}f=void 0}if(f)return!0}return!1},ja:()=>z?z.handle:0,qd:(a,b)=>{var c=0;Ad().forEach((e,f)=>{var k=b+c;f=H[a+4*f>>2]=k;for(k=0;k<e.length;++k)Ca[f++]=
|
||||||
|
e.charCodeAt(k);Ca[f]=0;c+=e.length+1});return 0},pd:(a,b)=>{var c=Ad();H[a>>2]=c.length;var e=0;c.forEach(f=>e+=f.length+1);H[b>>2]=e;return 0},ia:a=>{Xa||(Ba=!0);throw new Va(a);},N:()=>52,_:function(){return 52},od:()=>52,Z:function(){return 70},T:(a,b,c,e)=>{for(var f=0,k=0;k<c;k++){var n=H[b>>2],l=H[b+4>>2];b+=8;for(var p=0;p<l;p++){var v=B[n+p],w=Bd[a];0===v||10===v?((1===a?xa:ya)(db(w)),w.length=0):w.push(v)}f+=l}H[e>>2]=f;return 0},ha:cd,ga:ed,fa:fd,ea:gd,J:nd,Q:rd,da:sd,j:Hd,v:Id,m:Jd,I:Kd,
|
||||||
|
ca:Ld,P:Md,O:Nd,s:Od,x:Pd,p:Qd,u:Rd,ba:Sd,aa:Td,$:Ud},Z=function(){function a(c){Z=c.exports;za=Z.wd;Ha();N=Z.zd;Ja.unshift(Z.xd);La--;0==La&&(null!==Na&&(clearInterval(Na),Na=null),Oa&&(c=Oa,Oa=null,c()));return Z}var b={a:Vd};La++;if(r.instantiateWasm)try{return r.instantiateWasm(b,a)}catch(c){ya(`Module.instantiateWasm callback failed with error: ${c}`),ca(c)}Ra??=r.locateFile?Qa("canvaskit.wasm")?"canvaskit.wasm":ta+"canvaskit.wasm":(new URL("canvaskit.wasm",import.meta.url)).href;
|
||||||
|
Ua(b,function(c){a(c.instance)}).catch(ca);return{}}(),bc=a=>(bc=Z.yd)(a),pd=r._malloc=a=>(pd=r._malloc=Z.Ad)(a),cc=r._free=a=>(cc=r._free=Z.Bd)(a),Wd=(a,b)=>(Wd=Z.Cd)(a,b),Xd=a=>(Xd=Z.Dd)(a),Yd=()=>(Yd=Z.Ed)();r.dynCall_viji=(a,b,c,e,f)=>(r.dynCall_viji=Z.Fd)(a,b,c,e,f);r.dynCall_vijiii=(a,b,c,e,f,k,n)=>(r.dynCall_vijiii=Z.Gd)(a,b,c,e,f,k,n);r.dynCall_viiiiij=(a,b,c,e,f,k,n,l)=>(r.dynCall_viiiiij=Z.Hd)(a,b,c,e,f,k,n,l);r.dynCall_vij=(a,b,c,e)=>(r.dynCall_vij=Z.Id)(a,b,c,e);
|
||||||
|
r.dynCall_iiiji=(a,b,c,e,f,k)=>(r.dynCall_iiiji=Z.Jd)(a,b,c,e,f,k);r.dynCall_jii=(a,b,c)=>(r.dynCall_jii=Z.Kd)(a,b,c);r.dynCall_jiiiiii=(a,b,c,e,f,k,n)=>(r.dynCall_jiiiiii=Z.Ld)(a,b,c,e,f,k,n);r.dynCall_jiiiiji=(a,b,c,e,f,k,n,l)=>(r.dynCall_jiiiiji=Z.Md)(a,b,c,e,f,k,n,l);r.dynCall_ji=(a,b)=>(r.dynCall_ji=Z.Nd)(a,b);r.dynCall_iijj=(a,b,c,e,f,k)=>(r.dynCall_iijj=Z.Od)(a,b,c,e,f,k);r.dynCall_iiji=(a,b,c,e,f)=>(r.dynCall_iiji=Z.Pd)(a,b,c,e,f);
|
||||||
|
r.dynCall_iijjiii=(a,b,c,e,f,k,n,l,p)=>(r.dynCall_iijjiii=Z.Qd)(a,b,c,e,f,k,n,l,p);r.dynCall_iij=(a,b,c,e)=>(r.dynCall_iij=Z.Rd)(a,b,c,e);r.dynCall_vijjjii=(a,b,c,e,f,k,n,l,p,v)=>(r.dynCall_vijjjii=Z.Sd)(a,b,c,e,f,k,n,l,p,v);r.dynCall_jiji=(a,b,c,e,f)=>(r.dynCall_jiji=Z.Td)(a,b,c,e,f);r.dynCall_viijii=(a,b,c,e,f,k,n)=>(r.dynCall_viijii=Z.Ud)(a,b,c,e,f,k,n);r.dynCall_iiiiij=(a,b,c,e,f,k,n)=>(r.dynCall_iiiiij=Z.Vd)(a,b,c,e,f,k,n);
|
||||||
|
r.dynCall_iiiiijj=(a,b,c,e,f,k,n,l,p)=>(r.dynCall_iiiiijj=Z.Wd)(a,b,c,e,f,k,n,l,p);r.dynCall_iiiiiijj=(a,b,c,e,f,k,n,l,p,v)=>(r.dynCall_iiiiiijj=Z.Xd)(a,b,c,e,f,k,n,l,p,v);function Rd(a,b,c,e,f){var k=Yd();try{N.get(a)(b,c,e,f)}catch(n){Xd(k);if(n!==n+0)throw n;Wd(1,0)}}function Id(a,b,c){var e=Yd();try{return N.get(a)(b,c)}catch(f){Xd(e);if(f!==f+0)throw f;Wd(1,0)}}function Pd(a,b,c){var e=Yd();try{N.get(a)(b,c)}catch(f){Xd(e);if(f!==f+0)throw f;Wd(1,0)}}
|
||||||
|
function Hd(a,b){var c=Yd();try{return N.get(a)(b)}catch(e){Xd(c);if(e!==e+0)throw e;Wd(1,0)}}function Od(a,b){var c=Yd();try{N.get(a)(b)}catch(e){Xd(c);if(e!==e+0)throw e;Wd(1,0)}}function Jd(a,b,c,e){var f=Yd();try{return N.get(a)(b,c,e)}catch(k){Xd(f);if(k!==k+0)throw k;Wd(1,0)}}function Ud(a,b,c,e,f,k,n,l,p,v){var w=Yd();try{N.get(a)(b,c,e,f,k,n,l,p,v)}catch(A){Xd(w);if(A!==A+0)throw A;Wd(1,0)}}function Qd(a,b,c,e){var f=Yd();try{N.get(a)(b,c,e)}catch(k){Xd(f);if(k!==k+0)throw k;Wd(1,0)}}
|
||||||
|
function Td(a,b,c,e,f,k,n){var l=Yd();try{N.get(a)(b,c,e,f,k,n)}catch(p){Xd(l);if(p!==p+0)throw p;Wd(1,0)}}function Md(a,b,c,e,f,k,n,l){var p=Yd();try{return N.get(a)(b,c,e,f,k,n,l)}catch(v){Xd(p);if(v!==v+0)throw v;Wd(1,0)}}function Sd(a,b,c,e,f,k){var n=Yd();try{N.get(a)(b,c,e,f,k)}catch(l){Xd(n);if(l!==l+0)throw l;Wd(1,0)}}function Kd(a,b,c,e,f){var k=Yd();try{return N.get(a)(b,c,e,f)}catch(n){Xd(k);if(n!==n+0)throw n;Wd(1,0)}}
|
||||||
|
function Nd(a,b,c,e,f,k,n,l,p,v){var w=Yd();try{return N.get(a)(b,c,e,f,k,n,l,p,v)}catch(A){Xd(w);if(A!==A+0)throw A;Wd(1,0)}}function Ld(a,b,c,e,f,k,n){var l=Yd();try{return N.get(a)(b,c,e,f,k,n)}catch(p){Xd(l);if(p!==p+0)throw p;Wd(1,0)}}var Zd,$d;Oa=function ae(){Zd||be();Zd||(Oa=ae)};function be(){if(!(0<La)){if(!$d&&($d=1,Wa(Ia),0<La))return;Zd||(Zd=1,r.calledRun=1,Ba||(Wa(Ja),ba(r),r.onRuntimeInitialized?.(),Wa(Ka)))}}be();moduleRtn=da;
|
||||||
|
|
||||||
|
|
||||||
|
return moduleRtn;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
export default CanvasKitInit;
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
|
||||||
|
var CanvasKitInit = (() => {
|
||||||
|
var _scriptName = import.meta.url;
|
||||||
|
|
||||||
|
return (
|
||||||
|
function(moduleArg = {}) {
|
||||||
|
var moduleRtn;
|
||||||
|
|
||||||
|
var r=moduleArg,ba,ca,da=new Promise((a,b)=>{ba=a;ca=b}),fa="object"==typeof window,ia="function"==typeof importScripts;
|
||||||
|
(function(a){a.Xd=a.Xd||[];a.Xd.push(function(){a.MakeSWCanvasSurface=function(b){var c=b,e="undefined"!==typeof OffscreenCanvas&&c instanceof OffscreenCanvas;if(!("undefined"!==typeof HTMLCanvasElement&&c instanceof HTMLCanvasElement||e||(c=document.getElementById(b),c)))throw"Canvas with id "+b+" was not found";if(b=a.MakeSurface(c.width,c.height))b.ue=c;return b};a.MakeCanvasSurface||(a.MakeCanvasSurface=a.MakeSWCanvasSurface);a.MakeSurface=function(b,c){var e={width:b,height:c,colorType:a.ColorType.RGBA_8888,
|
||||||
|
alphaType:a.AlphaType.Unpremul,colorSpace:a.ColorSpace.SRGB},f=b*c*4,k=a._malloc(f);if(e=a.Surface._makeRasterDirect(e,k,4*b))e.ue=null,e.Ue=b,e.Re=c,e.Se=f,e.Be=k,e.getCanvas().clear(a.TRANSPARENT);return e};a.MakeRasterDirectSurface=function(b,c,e){return a.Surface._makeRasterDirect(b,c.byteOffset,e)};a.Surface.prototype.flush=function(b){a.Ud(this.Td);this._flush();if(this.ue){var c=new Uint8ClampedArray(a.HEAPU8.buffer,this.Be,this.Se);c=new ImageData(c,this.Ue,this.Re);b?this.ue.getContext("2d").putImageData(c,
|
||||||
|
0,0,b[0],b[1],b[2]-b[0],b[3]-b[1]):this.ue.getContext("2d").putImageData(c,0,0)}};a.Surface.prototype.dispose=function(){this.Be&&a._free(this.Be);this.delete()};a.Ud=a.Ud||function(){};a.ve=a.ve||function(){return null}})})(r);
|
||||||
|
(function(a){a.Xd=a.Xd||[];a.Xd.push(function(){function b(l,q,v){return l&&l.hasOwnProperty(q)?l[q]:v}function c(l){var q=ja(ka);ka[q]=l;return q}function e(l){return l.naturalHeight||l.videoHeight||l.displayHeight||l.height}function f(l){return l.naturalWidth||l.videoWidth||l.displayWidth||l.width}function k(l,q,v,w){l.bindTexture(l.TEXTURE_2D,q);w||v.alphaType!==a.AlphaType.Premul||l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!0);return q}function n(l,q,v){v||q.alphaType!==a.AlphaType.Premul||
|
||||||
|
l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1);l.bindTexture(l.TEXTURE_2D,null)}a.GetWebGLContext=function(l,q){if(!l)throw"null canvas passed into makeWebGLContext";var v={alpha:b(q,"alpha",1),depth:b(q,"depth",1),stencil:b(q,"stencil",8),antialias:b(q,"antialias",0),premultipliedAlpha:b(q,"premultipliedAlpha",1),preserveDrawingBuffer:b(q,"preserveDrawingBuffer",0),preferLowPowerToHighPerformance:b(q,"preferLowPowerToHighPerformance",0),failIfMajorPerformanceCaveat:b(q,"failIfMajorPerformanceCaveat",
|
||||||
|
0),enableExtensionsByDefault:b(q,"enableExtensionsByDefault",1),explicitSwapControl:b(q,"explicitSwapControl",0),renderViaOffscreenBackBuffer:b(q,"renderViaOffscreenBackBuffer",0)};v.majorVersion=q&&q.majorVersion?q.majorVersion:"undefined"!==typeof WebGL2RenderingContext?2:1;if(v.explicitSwapControl)throw"explicitSwapControl is not supported";l=na(l,v);if(!l)return 0;oa(l);z.fe.getExtension("WEBGL_debug_renderer_info");return l};a.deleteContext=function(l){z===pa[l]&&(z=null);"object"==typeof JSEvents&&
|
||||||
|
JSEvents.uf(pa[l].fe.canvas);pa[l]&&pa[l].fe.canvas&&(pa[l].fe.canvas.Pe=void 0);pa[l]=null};a._setTextureCleanup({deleteTexture:function(l,q){var v=ka[q];v&&pa[l].fe.deleteTexture(v);ka[q]=null}});a.MakeWebGLContext=function(l){if(!this.Ud(l))return null;var q=this._MakeGrContext();if(!q)return null;q.Td=l;var v=q.delete.bind(q);q["delete"]=function(){a.Ud(this.Td);v()}.bind(q);return z.De=q};a.MakeGrContext=a.MakeWebGLContext;a.GrDirectContext.prototype.getResourceCacheLimitBytes=function(){a.Ud(this.Td);
|
||||||
|
this._getResourceCacheLimitBytes()};a.GrDirectContext.prototype.getResourceCacheUsageBytes=function(){a.Ud(this.Td);this._getResourceCacheUsageBytes()};a.GrDirectContext.prototype.releaseResourcesAndAbandonContext=function(){a.Ud(this.Td);this._releaseResourcesAndAbandonContext()};a.GrDirectContext.prototype.setResourceCacheLimitBytes=function(l){a.Ud(this.Td);this._setResourceCacheLimitBytes(l)};a.MakeOnScreenGLSurface=function(l,q,v,w,A,D){if(!this.Ud(l.Td))return null;q=void 0===A||void 0===D?
|
||||||
|
this._MakeOnScreenGLSurface(l,q,v,w):this._MakeOnScreenGLSurface(l,q,v,w,A,D);if(!q)return null;q.Td=l.Td;return q};a.MakeRenderTarget=function(){var l=arguments[0];if(!this.Ud(l.Td))return null;if(3===arguments.length){var q=this._MakeRenderTargetWH(l,arguments[1],arguments[2]);if(!q)return null}else if(2===arguments.length){if(q=this._MakeRenderTargetII(l,arguments[1]),!q)return null}else return null;q.Td=l.Td;return q};a.MakeWebGLCanvasSurface=function(l,q,v){q=q||null;var w=l,A="undefined"!==
|
||||||
|
typeof OffscreenCanvas&&w instanceof OffscreenCanvas;if(!("undefined"!==typeof HTMLCanvasElement&&w instanceof HTMLCanvasElement||A||(w=document.getElementById(l),w)))throw"Canvas with id "+l+" was not found";l=this.GetWebGLContext(w,v);if(!l||0>l)throw"failed to create webgl context: err "+l;l=this.MakeWebGLContext(l);q=this.MakeOnScreenGLSurface(l,w.width,w.height,q);return q?q:(q=w.cloneNode(!0),w.parentNode.replaceChild(q,w),q.classList.add("ck-replaced"),a.MakeSWCanvasSurface(q))};a.MakeCanvasSurface=
|
||||||
|
a.MakeWebGLCanvasSurface;a.Surface.prototype.makeImageFromTexture=function(l,q){a.Ud(this.Td);l=c(l);if(q=this._makeImageFromTexture(this.Td,l,q))q.oe=l;return q};a.Surface.prototype.makeImageFromTextureSource=function(l,q,v){q||={height:e(l),width:f(l),colorType:a.ColorType.RGBA_8888,alphaType:v?a.AlphaType.Premul:a.AlphaType.Unpremul};q.colorSpace||(q.colorSpace=a.ColorSpace.SRGB);a.Ud(this.Td);var w=z.fe;v=k(w,w.createTexture(),q,v);2===z.version?w.texImage2D(w.TEXTURE_2D,0,w.RGBA,q.width,q.height,
|
||||||
|
0,w.RGBA,w.UNSIGNED_BYTE,l):w.texImage2D(w.TEXTURE_2D,0,w.RGBA,w.RGBA,w.UNSIGNED_BYTE,l);n(w,q);this._resetContext();return this.makeImageFromTexture(v,q)};a.Surface.prototype.updateTextureFromSource=function(l,q,v){if(l.oe){a.Ud(this.Td);var w=l.getImageInfo(),A=z.fe,D=k(A,ka[l.oe],w,v);2===z.version?A.texImage2D(A.TEXTURE_2D,0,A.RGBA,f(q),e(q),0,A.RGBA,A.UNSIGNED_BYTE,q):A.texImage2D(A.TEXTURE_2D,0,A.RGBA,A.RGBA,A.UNSIGNED_BYTE,q);n(A,w,v);this._resetContext();ka[l.oe]=null;l.oe=c(D);w.colorSpace=
|
||||||
|
l.getColorSpace();q=this._makeImageFromTexture(this.Td,l.oe,w);v=l.Sd.Vd;A=l.Sd.Zd;l.Sd.Vd=q.Sd.Vd;l.Sd.Zd=q.Sd.Zd;q.Sd.Vd=v;q.Sd.Zd=A;q.delete();w.colorSpace.delete()}};a.MakeLazyImageFromTextureSource=function(l,q,v){q||={height:e(l),width:f(l),colorType:a.ColorType.RGBA_8888,alphaType:v?a.AlphaType.Premul:a.AlphaType.Unpremul};q.colorSpace||(q.colorSpace=a.ColorSpace.SRGB);var w={makeTexture:function(){var A=z,D=A.fe,I=k(D,D.createTexture(),q,v);2===A.version?D.texImage2D(D.TEXTURE_2D,0,D.RGBA,
|
||||||
|
q.width,q.height,0,D.RGBA,D.UNSIGNED_BYTE,l):D.texImage2D(D.TEXTURE_2D,0,D.RGBA,D.RGBA,D.UNSIGNED_BYTE,l);n(D,q,v);return c(I)},freeSrc:function(){}};"VideoFrame"===l.constructor.name&&(w.freeSrc=function(){l.close()});return a.Image._makeFromGenerator(q,w)};a.Ud=function(l){return l?oa(l):!1};a.ve=function(){return z&&z.De&&!z.De.isDeleted()?z.De:null}})})(r);
|
||||||
|
(function(a){function b(g){return(f(255*g[3])<<24|f(255*g[0])<<16|f(255*g[1])<<8|f(255*g[2])<<0)>>>0}function c(g){if(g&&g._ck)return g;if(g instanceof Float32Array){for(var d=Math.floor(g.length/4),h=new Uint32Array(d),m=0;m<d;m++)h[m]=b(g.slice(4*m,4*(m+1)));return h}if(g instanceof Uint32Array)return g;if(g instanceof Array&&g[0]instanceof Float32Array)return g.map(b)}function e(g){if(void 0===g)return 1;var d=parseFloat(g);return g&&-1!==g.indexOf("%")?d/100:d}function f(g){return Math.round(Math.max(0,
|
||||||
|
Math.min(g||0,255)))}function k(g,d){d&&d._ck||a._free(g)}function n(g,d,h){if(!g||!g.length)return 0;if(g&&g._ck)return g.byteOffset;var m=a[d].BYTES_PER_ELEMENT;h||=a._malloc(g.length*m);a[d].set(g,h/m);return h}function l(g){var d={be:0,count:g.length,colorType:a.ColorType.RGBA_F32};if(g instanceof Float32Array)d.be=n(g,"HEAPF32"),d.count=g.length/4;else if(g instanceof Uint32Array)d.be=n(g,"HEAPU32"),d.colorType=a.ColorType.RGBA_8888;else if(g instanceof Array){if(g&&g.length){for(var h=a._malloc(16*
|
||||||
|
g.length),m=0,t=h/4,u=0;u<g.length;u++)for(var x=0;4>x;x++)a.HEAPF32[t+m]=g[u][x],m++;g=h}else g=0;d.be=g}else throw"Invalid argument to copyFlexibleColorArray, Not a color array "+typeof g;return d}function q(g){if(!g)return 0;var d=aa.toTypedArray();if(g.length){if(6===g.length||9===g.length)return n(g,"HEAPF32",O),6===g.length&&a.HEAPF32.set(Vc,6+O/4),O;if(16===g.length)return d[0]=g[0],d[1]=g[1],d[2]=g[3],d[3]=g[4],d[4]=g[5],d[5]=g[7],d[6]=g[12],d[7]=g[13],d[8]=g[15],O;throw"invalid matrix size";
|
||||||
|
}if(void 0===g.m11)throw"invalid matrix argument";d[0]=g.m11;d[1]=g.m21;d[2]=g.m41;d[3]=g.m12;d[4]=g.m22;d[5]=g.m42;d[6]=g.m14;d[7]=g.m24;d[8]=g.m44;return O}function v(g){if(!g)return 0;var d=X.toTypedArray();if(g.length){if(16!==g.length&&6!==g.length&&9!==g.length)throw"invalid matrix size";if(16===g.length)return n(g,"HEAPF32",la);d.fill(0);d[0]=g[0];d[1]=g[1];d[3]=g[2];d[4]=g[3];d[5]=g[4];d[7]=g[5];d[10]=1;d[12]=g[6];d[13]=g[7];d[15]=g[8];6===g.length&&(d[12]=0,d[13]=0,d[15]=1);return la}if(void 0===
|
||||||
|
g.m11)throw"invalid matrix argument";d[0]=g.m11;d[1]=g.m21;d[2]=g.m31;d[3]=g.m41;d[4]=g.m12;d[5]=g.m22;d[6]=g.m32;d[7]=g.m42;d[8]=g.m13;d[9]=g.m23;d[10]=g.m33;d[11]=g.m43;d[12]=g.m14;d[13]=g.m24;d[14]=g.m34;d[15]=g.m44;return la}function w(g,d){return n(g,"HEAPF32",d||ha)}function A(g,d,h,m){var t=Ea.toTypedArray();t[0]=g;t[1]=d;t[2]=h;t[3]=m;return ha}function D(g){for(var d=new Float32Array(4),h=0;4>h;h++)d[h]=a.HEAPF32[g/4+h];return d}function I(g,d){return n(g,"HEAPF32",d||V)}function P(g,d){return n(g,
|
||||||
|
"HEAPF32",d||tb)}a.Color=function(g,d,h,m){void 0===m&&(m=1);return a.Color4f(f(g)/255,f(d)/255,f(h)/255,m)};a.ColorAsInt=function(g,d,h,m){void 0===m&&(m=255);return(f(m)<<24|f(g)<<16|f(d)<<8|f(h)<<0&268435455)>>>0};a.Color4f=function(g,d,h,m){void 0===m&&(m=1);return Float32Array.of(g,d,h,m)};Object.defineProperty(a,"TRANSPARENT",{get:function(){return a.Color4f(0,0,0,0)}});Object.defineProperty(a,"BLACK",{get:function(){return a.Color4f(0,0,0,1)}});Object.defineProperty(a,"WHITE",{get:function(){return a.Color4f(1,
|
||||||
|
1,1,1)}});Object.defineProperty(a,"RED",{get:function(){return a.Color4f(1,0,0,1)}});Object.defineProperty(a,"GREEN",{get:function(){return a.Color4f(0,1,0,1)}});Object.defineProperty(a,"BLUE",{get:function(){return a.Color4f(0,0,1,1)}});Object.defineProperty(a,"YELLOW",{get:function(){return a.Color4f(1,1,0,1)}});Object.defineProperty(a,"CYAN",{get:function(){return a.Color4f(0,1,1,1)}});Object.defineProperty(a,"MAGENTA",{get:function(){return a.Color4f(1,0,1,1)}});a.getColorComponents=function(g){return[Math.floor(255*
|
||||||
|
g[0]),Math.floor(255*g[1]),Math.floor(255*g[2]),g[3]]};a.parseColorString=function(g,d){g=g.toLowerCase();if(g.startsWith("#")){d=255;switch(g.length){case 9:d=parseInt(g.slice(7,9),16);case 7:var h=parseInt(g.slice(1,3),16);var m=parseInt(g.slice(3,5),16);var t=parseInt(g.slice(5,7),16);break;case 5:d=17*parseInt(g.slice(4,5),16);case 4:h=17*parseInt(g.slice(1,2),16),m=17*parseInt(g.slice(2,3),16),t=17*parseInt(g.slice(3,4),16)}return a.Color(h,m,t,d/255)}return g.startsWith("rgba")?(g=g.slice(5,
|
||||||
|
-1),g=g.split(","),a.Color(+g[0],+g[1],+g[2],e(g[3]))):g.startsWith("rgb")?(g=g.slice(4,-1),g=g.split(","),a.Color(+g[0],+g[1],+g[2],e(g[3]))):g.startsWith("gray(")||g.startsWith("hsl")||!d||(g=d[g],void 0===g)?a.BLACK:g};a.multiplyByAlpha=function(g,d){g=g.slice();g[3]=Math.max(0,Math.min(g[3]*d,1));return g};a.Malloc=function(g,d){var h=a._malloc(d*g.BYTES_PER_ELEMENT);return{_ck:!0,length:d,byteOffset:h,ke:null,subarray:function(m,t){m=this.toTypedArray().subarray(m,t);m._ck=!0;return m},toTypedArray:function(){if(this.ke&&
|
||||||
|
this.ke.length)return this.ke;this.ke=new g(a.HEAPU8.buffer,h,d);this.ke._ck=!0;return this.ke}}};a.Free=function(g){a._free(g.byteOffset);g.byteOffset=0;g.toTypedArray=null;g.ke=null};var O=0,aa,la=0,X,ha=0,Ea,ea,V=0,Ub,Aa=0,Vb,ub=0,Wb,vb=0,$a,Ma=0,Xb,tb=0,Yb,Zb=0,Vc=Float32Array.of(0,0,1);a.onRuntimeInitialized=function(){function g(d,h,m,t,u,x,C){x||(x=4*t.width,t.colorType===a.ColorType.RGBA_F16?x*=2:t.colorType===a.ColorType.RGBA_F32&&(x*=4));var G=x*t.height;var F=u?u.byteOffset:a._malloc(G);
|
||||||
|
if(C?!d._readPixels(t,F,x,h,m,C):!d._readPixels(t,F,x,h,m))return u||a._free(F),null;if(u)return u.toTypedArray();switch(t.colorType){case a.ColorType.RGBA_8888:case a.ColorType.RGBA_F16:d=(new Uint8Array(a.HEAPU8.buffer,F,G)).slice();break;case a.ColorType.RGBA_F32:d=(new Float32Array(a.HEAPU8.buffer,F,G)).slice();break;default:return null}a._free(F);return d}Ea=a.Malloc(Float32Array,4);ha=Ea.byteOffset;X=a.Malloc(Float32Array,16);la=X.byteOffset;aa=a.Malloc(Float32Array,9);O=aa.byteOffset;Xb=a.Malloc(Float32Array,
|
||||||
|
12);tb=Xb.byteOffset;Yb=a.Malloc(Float32Array,12);Zb=Yb.byteOffset;ea=a.Malloc(Float32Array,4);V=ea.byteOffset;Ub=a.Malloc(Float32Array,4);Aa=Ub.byteOffset;Vb=a.Malloc(Float32Array,3);ub=Vb.byteOffset;Wb=a.Malloc(Float32Array,3);vb=Wb.byteOffset;$a=a.Malloc(Int32Array,4);Ma=$a.byteOffset;a.ColorSpace.SRGB=a.ColorSpace._MakeSRGB();a.ColorSpace.DISPLAY_P3=a.ColorSpace._MakeDisplayP3();a.ColorSpace.ADOBE_RGB=a.ColorSpace._MakeAdobeRGB();a.GlyphRunFlags={IsWhiteSpace:a._GlyphRunFlags_isWhiteSpace};a.Path.MakeFromCmds=
|
||||||
|
function(d){var h=n(d,"HEAPF32"),m=a.Path._MakeFromCmds(h,d.length);k(h,d);return m};a.Path.MakeFromVerbsPointsWeights=function(d,h,m){var t=n(d,"HEAPU8"),u=n(h,"HEAPF32"),x=n(m,"HEAPF32"),C=a.Path._MakeFromVerbsPointsWeights(t,d.length,u,h.length,x,m&&m.length||0);k(t,d);k(u,h);k(x,m);return C};a.Path.prototype.addArc=function(d,h,m){d=I(d);this._addArc(d,h,m);return this};a.Path.prototype.addCircle=function(d,h,m,t){this._addCircle(d,h,m,!!t);return this};a.Path.prototype.addOval=function(d,h,m){void 0===
|
||||||
|
m&&(m=1);d=I(d);this._addOval(d,!!h,m);return this};a.Path.prototype.addPath=function(){var d=Array.prototype.slice.call(arguments),h=d[0],m=!1;"boolean"===typeof d[d.length-1]&&(m=d.pop());if(1===d.length)this._addPath(h,1,0,0,0,1,0,0,0,1,m);else if(2===d.length)d=d[1],this._addPath(h,d[0],d[1],d[2],d[3],d[4],d[5],d[6]||0,d[7]||0,d[8]||1,m);else if(7===d.length||10===d.length)this._addPath(h,d[1],d[2],d[3],d[4],d[5],d[6],d[7]||0,d[8]||0,d[9]||1,m);else return null;return this};a.Path.prototype.addPoly=
|
||||||
|
function(d,h){var m=n(d,"HEAPF32");this._addPoly(m,d.length/2,h);k(m,d);return this};a.Path.prototype.addRect=function(d,h){d=I(d);this._addRect(d,!!h);return this};a.Path.prototype.addRRect=function(d,h){d=P(d);this._addRRect(d,!!h);return this};a.Path.prototype.addVerbsPointsWeights=function(d,h,m){var t=n(d,"HEAPU8"),u=n(h,"HEAPF32"),x=n(m,"HEAPF32");this._addVerbsPointsWeights(t,d.length,u,h.length,x,m&&m.length||0);k(t,d);k(u,h);k(x,m)};a.Path.prototype.arc=function(d,h,m,t,u,x){d=a.LTRBRect(d-
|
||||||
|
m,h-m,d+m,h+m);u=(u-t)/Math.PI*180-360*!!x;x=new a.Path;x.addArc(d,t/Math.PI*180,u);this.addPath(x,!0);x.delete();return this};a.Path.prototype.arcToOval=function(d,h,m,t){d=I(d);this._arcToOval(d,h,m,t);return this};a.Path.prototype.arcToRotated=function(d,h,m,t,u,x,C){this._arcToRotated(d,h,m,!!t,!!u,x,C);return this};a.Path.prototype.arcToTangent=function(d,h,m,t,u){this._arcToTangent(d,h,m,t,u);return this};a.Path.prototype.close=function(){this._close();return this};a.Path.prototype.conicTo=
|
||||||
|
function(d,h,m,t,u){this._conicTo(d,h,m,t,u);return this};a.Path.prototype.computeTightBounds=function(d){this._computeTightBounds(V);var h=ea.toTypedArray();return d?(d.set(h),d):h.slice()};a.Path.prototype.cubicTo=function(d,h,m,t,u,x){this._cubicTo(d,h,m,t,u,x);return this};a.Path.prototype.dash=function(d,h,m){return this._dash(d,h,m)?this:null};a.Path.prototype.getBounds=function(d){this._getBounds(V);var h=ea.toTypedArray();return d?(d.set(h),d):h.slice()};a.Path.prototype.lineTo=function(d,
|
||||||
|
h){this._lineTo(d,h);return this};a.Path.prototype.moveTo=function(d,h){this._moveTo(d,h);return this};a.Path.prototype.offset=function(d,h){this._transform(1,0,d,0,1,h,0,0,1);return this};a.Path.prototype.quadTo=function(d,h,m,t){this._quadTo(d,h,m,t);return this};a.Path.prototype.rArcTo=function(d,h,m,t,u,x,C){this._rArcTo(d,h,m,t,u,x,C);return this};a.Path.prototype.rConicTo=function(d,h,m,t,u){this._rConicTo(d,h,m,t,u);return this};a.Path.prototype.rCubicTo=function(d,h,m,t,u,x){this._rCubicTo(d,
|
||||||
|
h,m,t,u,x);return this};a.Path.prototype.rLineTo=function(d,h){this._rLineTo(d,h);return this};a.Path.prototype.rMoveTo=function(d,h){this._rMoveTo(d,h);return this};a.Path.prototype.rQuadTo=function(d,h,m,t){this._rQuadTo(d,h,m,t);return this};a.Path.prototype.stroke=function(d){d=d||{};d.width=d.width||1;d.miter_limit=d.miter_limit||4;d.cap=d.cap||a.StrokeCap.Butt;d.join=d.join||a.StrokeJoin.Miter;d.precision=d.precision||1;return this._stroke(d)?this:null};a.Path.prototype.transform=function(){if(1===
|
||||||
|
arguments.length){var d=arguments[0];this._transform(d[0],d[1],d[2],d[3],d[4],d[5],d[6]||0,d[7]||0,d[8]||1)}else if(6===arguments.length||9===arguments.length)d=arguments,this._transform(d[0],d[1],d[2],d[3],d[4],d[5],d[6]||0,d[7]||0,d[8]||1);else throw"transform expected to take 1 or 9 arguments. Got "+arguments.length;return this};a.Path.prototype.trim=function(d,h,m){return this._trim(d,h,!!m)?this:null};a.Image.prototype.encodeToBytes=function(d,h){var m=a.ve();d=d||a.ImageFormat.PNG;h=h||100;
|
||||||
|
return m?this._encodeToBytes(d,h,m):this._encodeToBytes(d,h)};a.Image.prototype.makeShaderCubic=function(d,h,m,t,u){u=q(u);return this._makeShaderCubic(d,h,m,t,u)};a.Image.prototype.makeShaderOptions=function(d,h,m,t,u){u=q(u);return this._makeShaderOptions(d,h,m,t,u)};a.Image.prototype.readPixels=function(d,h,m,t,u){var x=a.ve();return g(this,d,h,m,t,u,x)};a.Canvas.prototype.clear=function(d){a.Ud(this.Td);d=w(d);this._clear(d)};a.Canvas.prototype.clipRRect=function(d,h,m){a.Ud(this.Td);d=P(d);this._clipRRect(d,
|
||||||
|
h,m)};a.Canvas.prototype.clipRect=function(d,h,m){a.Ud(this.Td);d=I(d);this._clipRect(d,h,m)};a.Canvas.prototype.concat=function(d){a.Ud(this.Td);d=v(d);this._concat(d)};a.Canvas.prototype.drawArc=function(d,h,m,t,u){a.Ud(this.Td);d=I(d);this._drawArc(d,h,m,t,u)};a.Canvas.prototype.drawAtlas=function(d,h,m,t,u,x,C){if(d&&t&&h&&m&&h.length===m.length){a.Ud(this.Td);u||(u=a.BlendMode.SrcOver);var G=n(h,"HEAPF32"),F=n(m,"HEAPF32"),S=m.length/4,T=n(c(x),"HEAPU32");if(C&&"B"in C&&"C"in C)this._drawAtlasCubic(d,
|
||||||
|
F,G,T,S,u,C.B,C.C,t);else{let p=a.FilterMode.Linear,y=a.MipmapMode.None;C&&(p=C.filter,"mipmap"in C&&(y=C.mipmap));this._drawAtlasOptions(d,F,G,T,S,u,p,y,t)}k(G,h);k(F,m);k(T,x)}};a.Canvas.prototype.drawCircle=function(d,h,m,t){a.Ud(this.Td);this._drawCircle(d,h,m,t)};a.Canvas.prototype.drawColor=function(d,h){a.Ud(this.Td);d=w(d);void 0!==h?this._drawColor(d,h):this._drawColor(d)};a.Canvas.prototype.drawColorInt=function(d,h){a.Ud(this.Td);this._drawColorInt(d,h||a.BlendMode.SrcOver)};a.Canvas.prototype.drawColorComponents=
|
||||||
|
function(d,h,m,t,u){a.Ud(this.Td);d=A(d,h,m,t);void 0!==u?this._drawColor(d,u):this._drawColor(d)};a.Canvas.prototype.drawDRRect=function(d,h,m){a.Ud(this.Td);d=P(d,tb);h=P(h,Zb);this._drawDRRect(d,h,m)};a.Canvas.prototype.drawImage=function(d,h,m,t){a.Ud(this.Td);this._drawImage(d,h,m,t||null)};a.Canvas.prototype.drawImageCubic=function(d,h,m,t,u,x){a.Ud(this.Td);this._drawImageCubic(d,h,m,t,u,x||null)};a.Canvas.prototype.drawImageOptions=function(d,h,m,t,u,x){a.Ud(this.Td);this._drawImageOptions(d,
|
||||||
|
h,m,t,u,x||null)};a.Canvas.prototype.drawImageNine=function(d,h,m,t,u){a.Ud(this.Td);h=n(h,"HEAP32",Ma);m=I(m);this._drawImageNine(d,h,m,t,u||null)};a.Canvas.prototype.drawImageRect=function(d,h,m,t,u){a.Ud(this.Td);I(h,V);I(m,Aa);this._drawImageRect(d,V,Aa,t,!!u)};a.Canvas.prototype.drawImageRectCubic=function(d,h,m,t,u,x){a.Ud(this.Td);I(h,V);I(m,Aa);this._drawImageRectCubic(d,V,Aa,t,u,x||null)};a.Canvas.prototype.drawImageRectOptions=function(d,h,m,t,u,x){a.Ud(this.Td);I(h,V);I(m,Aa);this._drawImageRectOptions(d,
|
||||||
|
V,Aa,t,u,x||null)};a.Canvas.prototype.drawLine=function(d,h,m,t,u){a.Ud(this.Td);this._drawLine(d,h,m,t,u)};a.Canvas.prototype.drawOval=function(d,h){a.Ud(this.Td);d=I(d);this._drawOval(d,h)};a.Canvas.prototype.drawPaint=function(d){a.Ud(this.Td);this._drawPaint(d)};a.Canvas.prototype.drawParagraph=function(d,h,m){a.Ud(this.Td);this._drawParagraph(d,h,m)};a.Canvas.prototype.drawPatch=function(d,h,m,t,u){if(24>d.length)throw"Need 12 cubic points";if(h&&4>h.length)throw"Need 4 colors";if(m&&8>m.length)throw"Need 4 shader coordinates";
|
||||||
|
a.Ud(this.Td);const x=n(d,"HEAPF32"),C=h?n(c(h),"HEAPU32"):0,G=m?n(m,"HEAPF32"):0;t||(t=a.BlendMode.Modulate);this._drawPatch(x,C,G,t,u);k(G,m);k(C,h);k(x,d)};a.Canvas.prototype.drawPath=function(d,h){a.Ud(this.Td);this._drawPath(d,h)};a.Canvas.prototype.drawPicture=function(d){a.Ud(this.Td);this._drawPicture(d)};a.Canvas.prototype.drawPoints=function(d,h,m){a.Ud(this.Td);var t=n(h,"HEAPF32");this._drawPoints(d,t,h.length/2,m);k(t,h)};a.Canvas.prototype.drawRRect=function(d,h){a.Ud(this.Td);d=P(d);
|
||||||
|
this._drawRRect(d,h)};a.Canvas.prototype.drawRect=function(d,h){a.Ud(this.Td);d=I(d);this._drawRect(d,h)};a.Canvas.prototype.drawRect4f=function(d,h,m,t,u){a.Ud(this.Td);this._drawRect4f(d,h,m,t,u)};a.Canvas.prototype.drawShadow=function(d,h,m,t,u,x,C){a.Ud(this.Td);var G=n(u,"HEAPF32"),F=n(x,"HEAPF32");h=n(h,"HEAPF32",ub);m=n(m,"HEAPF32",vb);this._drawShadow(d,h,m,t,G,F,C);k(G,u);k(F,x)};a.getShadowLocalBounds=function(d,h,m,t,u,x,C){d=q(d);m=n(m,"HEAPF32",ub);t=n(t,"HEAPF32",vb);if(!this._getShadowLocalBounds(d,
|
||||||
|
h,m,t,u,x,V))return null;h=ea.toTypedArray();return C?(C.set(h),C):h.slice()};a.Canvas.prototype.drawTextBlob=function(d,h,m,t){a.Ud(this.Td);this._drawTextBlob(d,h,m,t)};a.Canvas.prototype.drawVertices=function(d,h,m){a.Ud(this.Td);this._drawVertices(d,h,m)};a.Canvas.prototype.getDeviceClipBounds=function(d){this._getDeviceClipBounds(Ma);var h=$a.toTypedArray();d?d.set(h):d=h.slice();return d};a.Canvas.prototype.quickReject=function(d){d=I(d);return this._quickReject(d)};a.Canvas.prototype.getLocalToDevice=
|
||||||
|
function(){this._getLocalToDevice(la);for(var d=la,h=Array(16),m=0;16>m;m++)h[m]=a.HEAPF32[d/4+m];return h};a.Canvas.prototype.getTotalMatrix=function(){this._getTotalMatrix(O);for(var d=Array(9),h=0;9>h;h++)d[h]=a.HEAPF32[O/4+h];return d};a.Canvas.prototype.makeSurface=function(d){d=this._makeSurface(d);d.Td=this.Td;return d};a.Canvas.prototype.readPixels=function(d,h,m,t,u){a.Ud(this.Td);return g(this,d,h,m,t,u)};a.Canvas.prototype.saveLayer=function(d,h,m,t,u){h=I(h);return this._saveLayer(d||
|
||||||
|
null,h,m||null,t||0,u||a.TileMode.Clamp)};a.Canvas.prototype.writePixels=function(d,h,m,t,u,x,C,G){if(d.byteLength%(h*m))throw"pixels length must be a multiple of the srcWidth * srcHeight";a.Ud(this.Td);var F=d.byteLength/(h*m);x=x||a.AlphaType.Unpremul;C=C||a.ColorType.RGBA_8888;G=G||a.ColorSpace.SRGB;var S=F*h;F=n(d,"HEAPU8");h=this._writePixels({width:h,height:m,colorType:C,alphaType:x,colorSpace:G},F,S,t,u);k(F,d);return h};a.ColorFilter.MakeBlend=function(d,h,m){d=w(d);m=m||a.ColorSpace.SRGB;
|
||||||
|
return a.ColorFilter._MakeBlend(d,h,m)};a.ColorFilter.MakeMatrix=function(d){if(!d||20!==d.length)throw"invalid color matrix";var h=n(d,"HEAPF32"),m=a.ColorFilter._makeMatrix(h);k(h,d);return m};a.ContourMeasure.prototype.getPosTan=function(d,h){this._getPosTan(d,V);d=ea.toTypedArray();return h?(h.set(d),h):d.slice()};a.ImageFilter.prototype.getOutputBounds=function(d,h,m){d=I(d,V);h=q(h);this._getOutputBounds(d,h,Ma);h=$a.toTypedArray();return m?(m.set(h),m):h.slice()};a.ImageFilter.MakeDropShadow=
|
||||||
|
function(d,h,m,t,u,x){u=w(u,ha);return a.ImageFilter._MakeDropShadow(d,h,m,t,u,x)};a.ImageFilter.MakeDropShadowOnly=function(d,h,m,t,u,x){u=w(u,ha);return a.ImageFilter._MakeDropShadowOnly(d,h,m,t,u,x)};a.ImageFilter.MakeImage=function(d,h,m,t){m=I(m,V);t=I(t,Aa);if("B"in h&&"C"in h)return a.ImageFilter._MakeImageCubic(d,h.B,h.C,m,t);const u=h.filter;let x=a.MipmapMode.None;"mipmap"in h&&(x=h.mipmap);return a.ImageFilter._MakeImageOptions(d,u,x,m,t)};a.ImageFilter.MakeMatrixTransform=function(d,h,
|
||||||
|
m){d=q(d);if("B"in h&&"C"in h)return a.ImageFilter._MakeMatrixTransformCubic(d,h.B,h.C,m);const t=h.filter;let u=a.MipmapMode.None;"mipmap"in h&&(u=h.mipmap);return a.ImageFilter._MakeMatrixTransformOptions(d,t,u,m)};a.Paint.prototype.getColor=function(){this._getColor(ha);return D(ha)};a.Paint.prototype.setColor=function(d,h){h=h||null;d=w(d);this._setColor(d,h)};a.Paint.prototype.setColorComponents=function(d,h,m,t,u){u=u||null;d=A(d,h,m,t);this._setColor(d,u)};a.Path.prototype.getPoint=function(d,
|
||||||
|
h){this._getPoint(d,V);d=ea.toTypedArray();return h?(h[0]=d[0],h[1]=d[1],h):d.slice(0,2)};a.Picture.prototype.makeShader=function(d,h,m,t,u){t=q(t);u=I(u);return this._makeShader(d,h,m,t,u)};a.Picture.prototype.cullRect=function(d){this._cullRect(V);var h=ea.toTypedArray();return d?(d.set(h),d):h.slice()};a.PictureRecorder.prototype.beginRecording=function(d,h){d=I(d);return this._beginRecording(d,!!h)};a.Surface.prototype.getCanvas=function(){var d=this._getCanvas();d.Td=this.Td;return d};a.Surface.prototype.makeImageSnapshot=
|
||||||
|
function(d){a.Ud(this.Td);d=n(d,"HEAP32",Ma);return this._makeImageSnapshot(d)};a.Surface.prototype.makeSurface=function(d){a.Ud(this.Td);d=this._makeSurface(d);d.Td=this.Td;return d};a.Surface.prototype.Te=function(d,h){this.ne||(this.ne=this.getCanvas());return requestAnimationFrame(function(){a.Ud(this.Td);d(this.ne);this.flush(h)}.bind(this))};a.Surface.prototype.requestAnimationFrame||(a.Surface.prototype.requestAnimationFrame=a.Surface.prototype.Te);a.Surface.prototype.Qe=function(d,h){this.ne||
|
||||||
|
(this.ne=this.getCanvas());requestAnimationFrame(function(){a.Ud(this.Td);d(this.ne);this.flush(h);this.dispose()}.bind(this))};a.Surface.prototype.drawOnce||(a.Surface.prototype.drawOnce=a.Surface.prototype.Qe);a.PathEffect.MakeDash=function(d,h){h||=0;if(!d.length||1===d.length%2)throw"Intervals array must have even length";var m=n(d,"HEAPF32");h=a.PathEffect._MakeDash(m,d.length,h);k(m,d);return h};a.PathEffect.MakeLine2D=function(d,h){h=q(h);return a.PathEffect._MakeLine2D(d,h)};a.PathEffect.MakePath2D=
|
||||||
|
function(d,h){d=q(d);return a.PathEffect._MakePath2D(d,h)};a.Shader.MakeColor=function(d,h){h=h||null;d=w(d);return a.Shader._MakeColor(d,h)};a.Shader.Blend=a.Shader.MakeBlend;a.Shader.Color=a.Shader.MakeColor;a.Shader.MakeLinearGradient=function(d,h,m,t,u,x,C,G){G=G||null;var F=l(m),S=n(t,"HEAPF32");C=C||0;x=q(x);var T=ea.toTypedArray();T.set(d);T.set(h,2);d=a.Shader._MakeLinearGradient(V,F.be,F.colorType,S,F.count,u,C,x,G);k(F.be,m);t&&k(S,t);return d};a.Shader.MakeRadialGradient=function(d,h,m,
|
||||||
|
t,u,x,C,G){G=G||null;var F=l(m),S=n(t,"HEAPF32");C=C||0;x=q(x);d=a.Shader._MakeRadialGradient(d[0],d[1],h,F.be,F.colorType,S,F.count,u,C,x,G);k(F.be,m);t&&k(S,t);return d};a.Shader.MakeSweepGradient=function(d,h,m,t,u,x,C,G,F,S){S=S||null;var T=l(m),p=n(t,"HEAPF32");C=C||0;G=G||0;F=F||360;x=q(x);d=a.Shader._MakeSweepGradient(d,h,T.be,T.colorType,p,T.count,u,G,F,C,x,S);k(T.be,m);t&&k(p,t);return d};a.Shader.MakeTwoPointConicalGradient=function(d,h,m,t,u,x,C,G,F,S){S=S||null;var T=l(u),p=n(x,"HEAPF32");
|
||||||
|
F=F||0;G=q(G);var y=ea.toTypedArray();y.set(d);y.set(m,2);d=a.Shader._MakeTwoPointConicalGradient(V,h,t,T.be,T.colorType,p,T.count,C,F,G,S);k(T.be,u);x&&k(p,x);return d};a.Vertices.prototype.bounds=function(d){this._bounds(V);var h=ea.toTypedArray();return d?(d.set(h),d):h.slice()};a.Xd&&a.Xd.forEach(function(d){d()})};a.computeTonalColors=function(g){var d=n(g.ambient,"HEAPF32"),h=n(g.spot,"HEAPF32");this._computeTonalColors(d,h);var m={ambient:D(d),spot:D(h)};k(d,g.ambient);k(h,g.spot);return m};
|
||||||
|
a.LTRBRect=function(g,d,h,m){return Float32Array.of(g,d,h,m)};a.XYWHRect=function(g,d,h,m){return Float32Array.of(g,d,g+h,d+m)};a.LTRBiRect=function(g,d,h,m){return Int32Array.of(g,d,h,m)};a.XYWHiRect=function(g,d,h,m){return Int32Array.of(g,d,g+h,d+m)};a.RRectXY=function(g,d,h){return Float32Array.of(g[0],g[1],g[2],g[3],d,h,d,h,d,h,d,h)};a.MakeAnimatedImageFromEncoded=function(g){g=new Uint8Array(g);var d=a._malloc(g.byteLength);a.HEAPU8.set(g,d);return(g=a._decodeAnimatedImage(d,g.byteLength))?
|
||||||
|
g:null};a.MakeImageFromEncoded=function(g){g=new Uint8Array(g);var d=a._malloc(g.byteLength);a.HEAPU8.set(g,d);return(g=a._decodeImage(d,g.byteLength))?g:null};var ab=null;a.MakeImageFromCanvasImageSource=function(g){var d=g.width,h=g.height;ab||=document.createElement("canvas");ab.width=d;ab.height=h;var m=ab.getContext("2d",{willReadFrequently:!0});m.drawImage(g,0,0);g=m.getImageData(0,0,d,h);return a.MakeImage({width:d,height:h,alphaType:a.AlphaType.Unpremul,colorType:a.ColorType.RGBA_8888,colorSpace:a.ColorSpace.SRGB},
|
||||||
|
g.data,4*d)};a.MakeImage=function(g,d,h){var m=a._malloc(d.length);a.HEAPU8.set(d,m);return a._MakeImage(g,m,d.length,h)};a.MakeVertices=function(g,d,h,m,t,u){var x=t&&t.length||0,C=0;h&&h.length&&(C|=1);m&&m.length&&(C|=2);void 0===u||u||(C|=4);g=new a._VerticesBuilder(g,d.length/2,x,C);n(d,"HEAPF32",g.positions());g.texCoords()&&n(h,"HEAPF32",g.texCoords());g.colors()&&n(c(m),"HEAPU32",g.colors());g.indices()&&n(t,"HEAPU16",g.indices());return g.detach()};(function(g){g.Xd=g.Xd||[];g.Xd.push(function(){function d(p){p&&
|
||||||
|
(p.dir=0===p.dir?g.TextDirection.RTL:g.TextDirection.LTR);return p}function h(p){if(!p||!p.length)return[];for(var y=[],M=0;M<p.length;M+=5){var W=g.LTRBRect(p[M],p[M+1],p[M+2],p[M+3]),wa=g.TextDirection.LTR;0===p[M+4]&&(wa=g.TextDirection.RTL);y.push({rect:W,dir:wa})}g._free(p.byteOffset);return y}function m(p){p=p||{};void 0===p.weight&&(p.weight=g.FontWeight.Normal);p.width=p.width||g.FontWidth.Normal;p.slant=p.slant||g.FontSlant.Upright;return p}function t(p){if(!p||!p.length)return 0;for(var y=
|
||||||
|
[],M=0;M<p.length;M++){var W=u(p[M]);y.push(W)}return n(y,"HEAPU32")}function u(p){if(G[p])return G[p];var y=qa(p)+1,M=g._malloc(y);ra(p,M,y);return G[p]=M}function x(p){p._colorPtr=w(p.color);p._foregroundColorPtr=0;p._backgroundColorPtr=0;p._decorationColorPtr=0;p.foregroundColor&&(p._foregroundColorPtr=w(p.foregroundColor,F));p.backgroundColor&&(p._backgroundColorPtr=w(p.backgroundColor,S));p.decorationColor&&(p._decorationColorPtr=w(p.decorationColor,T));Array.isArray(p.fontFamilies)&&p.fontFamilies.length?
|
||||||
|
(p._fontFamiliesPtr=t(p.fontFamilies),p._fontFamiliesLen=p.fontFamilies.length):(p._fontFamiliesPtr=0,p._fontFamiliesLen=0);if(p.locale){var y=p.locale;p._localePtr=u(y);p._localeLen=qa(y)}else p._localePtr=0,p._localeLen=0;if(Array.isArray(p.shadows)&&p.shadows.length){y=p.shadows;var M=y.map(function(ma){return ma.color||g.BLACK}),W=y.map(function(ma){return ma.blurRadius||0});p._shadowLen=y.length;for(var wa=g._malloc(8*y.length),wb=wa/4,xb=0;xb<y.length;xb++){var $b=y[xb].offset||[0,0];g.HEAPF32[wb]=
|
||||||
|
$b[0];g.HEAPF32[wb+1]=$b[1];wb+=2}p._shadowColorsPtr=l(M).be;p._shadowOffsetsPtr=wa;p._shadowBlurRadiiPtr=n(W,"HEAPF32")}else p._shadowLen=0,p._shadowColorsPtr=0,p._shadowOffsetsPtr=0,p._shadowBlurRadiiPtr=0;Array.isArray(p.fontFeatures)&&p.fontFeatures.length?(y=p.fontFeatures,M=y.map(function(ma){return ma.name}),W=y.map(function(ma){return ma.value}),p._fontFeatureLen=y.length,p._fontFeatureNamesPtr=t(M),p._fontFeatureValuesPtr=n(W,"HEAPU32")):(p._fontFeatureLen=0,p._fontFeatureNamesPtr=0,p._fontFeatureValuesPtr=
|
||||||
|
0);Array.isArray(p.fontVariations)&&p.fontVariations.length?(y=p.fontVariations,M=y.map(function(ma){return ma.axis}),W=y.map(function(ma){return ma.value}),p._fontVariationLen=y.length,p._fontVariationAxesPtr=t(M),p._fontVariationValuesPtr=n(W,"HEAPF32")):(p._fontVariationLen=0,p._fontVariationAxesPtr=0,p._fontVariationValuesPtr=0)}function C(p){g._free(p._fontFamiliesPtr);g._free(p._shadowColorsPtr);g._free(p._shadowOffsetsPtr);g._free(p._shadowBlurRadiiPtr);g._free(p._fontFeatureNamesPtr);g._free(p._fontFeatureValuesPtr);
|
||||||
|
g._free(p._fontVariationAxesPtr);g._free(p._fontVariationValuesPtr)}g.Paragraph.prototype.getRectsForRange=function(p,y,M,W){p=this._getRectsForRange(p,y,M,W);return h(p)};g.Paragraph.prototype.getRectsForPlaceholders=function(){var p=this._getRectsForPlaceholders();return h(p)};g.Paragraph.prototype.getGlyphInfoAt=function(p){return d(this._getGlyphInfoAt(p))};g.Paragraph.prototype.getClosestGlyphInfoAtCoordinate=function(p,y){return d(this._getClosestGlyphInfoAtCoordinate(p,y))};g.TypefaceFontProvider.prototype.registerFont=
|
||||||
|
function(p,y){p=g.Typeface.MakeTypefaceFromData(p);if(!p)return null;y=u(y);this._registerFont(p,y);p.delete()};g.ParagraphStyle=function(p){p.disableHinting=p.disableHinting||!1;if(p.ellipsis){var y=p.ellipsis;p._ellipsisPtr=u(y);p._ellipsisLen=qa(y)}else p._ellipsisPtr=0,p._ellipsisLen=0;null==p.heightMultiplier&&(p.heightMultiplier=-1);p.maxLines=p.maxLines||0;p.replaceTabCharacters=p.replaceTabCharacters||!1;y=(y=p.strutStyle)||{};y.strutEnabled=y.strutEnabled||!1;y.strutEnabled&&Array.isArray(y.fontFamilies)&&
|
||||||
|
y.fontFamilies.length?(y._fontFamiliesPtr=t(y.fontFamilies),y._fontFamiliesLen=y.fontFamilies.length):(y._fontFamiliesPtr=0,y._fontFamiliesLen=0);y.fontStyle=m(y.fontStyle);null==y.fontSize&&(y.fontSize=-1);null==y.heightMultiplier&&(y.heightMultiplier=-1);y.halfLeading=y.halfLeading||!1;y.leading=y.leading||0;y.forceStrutHeight=y.forceStrutHeight||!1;p.strutStyle=y;p.textAlign=p.textAlign||g.TextAlign.Start;p.textDirection=p.textDirection||g.TextDirection.LTR;p.textHeightBehavior=p.textHeightBehavior||
|
||||||
|
g.TextHeightBehavior.All;p.textStyle=g.TextStyle(p.textStyle);p.applyRoundingHack=!1!==p.applyRoundingHack;return p};g.TextStyle=function(p){p.color||(p.color=g.BLACK);p.decoration=p.decoration||0;p.decorationThickness=p.decorationThickness||0;p.decorationStyle=p.decorationStyle||g.DecorationStyle.Solid;p.textBaseline=p.textBaseline||g.TextBaseline.Alphabetic;null==p.fontSize&&(p.fontSize=-1);p.letterSpacing=p.letterSpacing||0;p.wordSpacing=p.wordSpacing||0;null==p.heightMultiplier&&(p.heightMultiplier=
|
||||||
|
-1);p.halfLeading=p.halfLeading||!1;p.fontStyle=m(p.fontStyle);return p};var G={},F=g._malloc(16),S=g._malloc(16),T=g._malloc(16);g.ParagraphBuilder.Make=function(p,y){x(p.textStyle);y=g.ParagraphBuilder._Make(p,y);C(p.textStyle);return y};g.ParagraphBuilder.MakeFromFontProvider=function(p,y){x(p.textStyle);y=g.ParagraphBuilder._MakeFromFontProvider(p,y);C(p.textStyle);return y};g.ParagraphBuilder.MakeFromFontCollection=function(p,y){x(p.textStyle);y=g.ParagraphBuilder._MakeFromFontCollection(p,y);
|
||||||
|
C(p.textStyle);return y};g.ParagraphBuilder.ShapeText=function(p,y,M){let W=0;for(const wa of y)W+=wa.length;if(W!==p.length)throw"Accumulated block lengths must equal text.length";return g.ParagraphBuilder._ShapeText(p,y,M)};g.ParagraphBuilder.prototype.pushStyle=function(p){x(p);this._pushStyle(p);C(p)};g.ParagraphBuilder.prototype.pushPaintStyle=function(p,y,M){x(p);this._pushPaintStyle(p,y,M);C(p)};g.ParagraphBuilder.prototype.addPlaceholder=function(p,y,M,W,wa){M=M||g.PlaceholderAlignment.Baseline;
|
||||||
|
W=W||g.TextBaseline.Alphabetic;this._addPlaceholder(p||0,y||0,M,W,wa||0)};g.ParagraphBuilder.prototype.setWordsUtf8=function(p){var y=n(p,"HEAPU32");this._setWordsUtf8(y,p&&p.length||0);k(y,p)};g.ParagraphBuilder.prototype.setWordsUtf16=function(p){var y=n(p,"HEAPU32");this._setWordsUtf16(y,p&&p.length||0);k(y,p)};g.ParagraphBuilder.prototype.setGraphemeBreaksUtf8=function(p){var y=n(p,"HEAPU32");this._setGraphemeBreaksUtf8(y,p&&p.length||0);k(y,p)};g.ParagraphBuilder.prototype.setGraphemeBreaksUtf16=
|
||||||
|
function(p){var y=n(p,"HEAPU32");this._setGraphemeBreaksUtf16(y,p&&p.length||0);k(y,p)};g.ParagraphBuilder.prototype.setLineBreaksUtf8=function(p){var y=n(p,"HEAPU32");this._setLineBreaksUtf8(y,p&&p.length||0);k(y,p)};g.ParagraphBuilder.prototype.setLineBreaksUtf16=function(p){var y=n(p,"HEAPU32");this._setLineBreaksUtf16(y,p&&p.length||0);k(y,p)}})})(r);a.Xd=a.Xd||[];a.Xd.push(function(){a.Path.prototype.op=function(g,d){return this._op(g,d)?this:null};a.Path.prototype.simplify=function(){return this._simplify()?
|
||||||
|
this:null}});a.Xd=a.Xd||[];a.Xd.push(function(){a.Canvas.prototype.drawText=function(g,d,h,m,t){var u=qa(g),x=a._malloc(u+1);ra(g,x,u+1);this._drawSimpleText(x,u,d,h,t,m);a._free(x)};a.Canvas.prototype.drawGlyphs=function(g,d,h,m,t,u){if(!(2*g.length<=d.length))throw"Not enough positions for the array of gyphs";a.Ud(this.Td);const x=n(g,"HEAPU16"),C=n(d,"HEAPF32");this._drawGlyphs(g.length,x,C,h,m,t,u);k(C,d);k(x,g)};a.Font.prototype.getGlyphBounds=function(g,d,h){var m=n(g,"HEAPU16"),t=a._malloc(16*
|
||||||
|
g.length);this._getGlyphWidthBounds(m,g.length,0,t,d||null);d=new Float32Array(a.HEAPU8.buffer,t,4*g.length);k(m,g);if(h)return h.set(d),a._free(t),h;g=Float32Array.from(d);a._free(t);return g};a.Font.prototype.getGlyphIDs=function(g,d,h){d||(d=g.length);var m=qa(g)+1,t=a._malloc(m);ra(g,t,m);g=a._malloc(2*d);d=this._getGlyphIDs(t,m-1,d,g);a._free(t);if(0>d)return a._free(g),null;t=new Uint16Array(a.HEAPU8.buffer,g,d);if(h)return h.set(t),a._free(g),h;h=Uint16Array.from(t);a._free(g);return h};a.Font.prototype.getGlyphIntercepts=
|
||||||
|
function(g,d,h,m){var t=n(g,"HEAPU16"),u=n(d,"HEAPF32");return this._getGlyphIntercepts(t,g.length,!(g&&g._ck),u,d.length,!(d&&d._ck),h,m)};a.Font.prototype.getGlyphWidths=function(g,d,h){var m=n(g,"HEAPU16"),t=a._malloc(4*g.length);this._getGlyphWidthBounds(m,g.length,t,0,d||null);d=new Float32Array(a.HEAPU8.buffer,t,g.length);k(m,g);if(h)return h.set(d),a._free(t),h;g=Float32Array.from(d);a._free(t);return g};a.FontMgr.FromData=function(){if(!arguments.length)return null;var g=arguments;1===g.length&&
|
||||||
|
Array.isArray(g[0])&&(g=arguments[0]);if(!g.length)return null;for(var d=[],h=[],m=0;m<g.length;m++){var t=new Uint8Array(g[m]),u=n(t,"HEAPU8");d.push(u);h.push(t.byteLength)}d=n(d,"HEAPU32");h=n(h,"HEAPU32");g=a.FontMgr._fromData(d,h,g.length);a._free(d);a._free(h);return g};a.Typeface.MakeTypefaceFromData=function(g){g=new Uint8Array(g);var d=n(g,"HEAPU8");return(g=a.Typeface._MakeTypefaceFromData(d,g.byteLength))?g:null};a.Typeface.MakeFreeTypeFaceFromData=a.Typeface.MakeTypefaceFromData;a.Typeface.prototype.getGlyphIDs=
|
||||||
|
function(g,d,h){d||(d=g.length);var m=qa(g)+1,t=a._malloc(m);ra(g,t,m);g=a._malloc(2*d);d=this._getGlyphIDs(t,m-1,d,g);a._free(t);if(0>d)return a._free(g),null;t=new Uint16Array(a.HEAPU8.buffer,g,d);if(h)return h.set(t),a._free(g),h;h=Uint16Array.from(t);a._free(g);return h};a.TextBlob.MakeOnPath=function(g,d,h,m){if(g&&g.length&&d&&d.countPoints()){if(1===d.countPoints())return this.MakeFromText(g,h);m||=0;var t=h.getGlyphIDs(g);t=h.getGlyphWidths(t);var u=[];d=new a.ContourMeasureIter(d,!1,1);for(var x=
|
||||||
|
d.next(),C=new Float32Array(4),G=0;G<g.length&&x;G++){var F=t[G];m+=F/2;if(m>x.length()){x.delete();x=d.next();if(!x){g=g.substring(0,G);break}m=F/2}x.getPosTan(m,C);var S=C[2],T=C[3];u.push(S,T,C[0]-F/2*S,C[1]-F/2*T);m+=F/2}g=this.MakeFromRSXform(g,u,h);x&&x.delete();d.delete();return g}};a.TextBlob.MakeFromRSXform=function(g,d,h){var m=qa(g)+1,t=a._malloc(m);ra(g,t,m);g=n(d,"HEAPF32");h=a.TextBlob._MakeFromRSXform(t,m-1,g,h);a._free(t);return h?h:null};a.TextBlob.MakeFromRSXformGlyphs=function(g,
|
||||||
|
d,h){var m=n(g,"HEAPU16");d=n(d,"HEAPF32");h=a.TextBlob._MakeFromRSXformGlyphs(m,2*g.length,d,h);k(m,g);return h?h:null};a.TextBlob.MakeFromGlyphs=function(g,d){var h=n(g,"HEAPU16");d=a.TextBlob._MakeFromGlyphs(h,2*g.length,d);k(h,g);return d?d:null};a.TextBlob.MakeFromText=function(g,d){var h=qa(g)+1,m=a._malloc(h);ra(g,m,h);g=a.TextBlob._MakeFromText(m,h-1,d);a._free(m);return g?g:null};a.MallocGlyphIDs=function(g){return a.Malloc(Uint16Array,g)}});a.Xd=a.Xd||[];a.Xd.push(function(){a.MakePicture=
|
||||||
|
function(g){g=new Uint8Array(g);var d=a._malloc(g.byteLength);a.HEAPU8.set(g,d);return(g=a._MakePicture(d,g.byteLength))?g:null}});a.Xd=a.Xd||[];a.Xd.push(function(){a.RuntimeEffect.Make=function(g,d){return a.RuntimeEffect._Make(g,{onError:d||function(h){console.log("RuntimeEffect error",h)}})};a.RuntimeEffect.MakeForBlender=function(g,d){return a.RuntimeEffect._MakeForBlender(g,{onError:d||function(h){console.log("RuntimeEffect error",h)}})};a.RuntimeEffect.prototype.makeShader=function(g,d){var h=
|
||||||
|
!g._ck,m=n(g,"HEAPF32");d=q(d);return this._makeShader(m,4*g.length,h,d)};a.RuntimeEffect.prototype.makeShaderWithChildren=function(g,d,h){var m=!g._ck,t=n(g,"HEAPF32");h=q(h);for(var u=[],x=0;x<d.length;x++)u.push(d[x].Sd.Vd);d=n(u,"HEAPU32");return this._makeShaderWithChildren(t,4*g.length,m,d,u.length,h)};a.RuntimeEffect.prototype.makeBlender=function(g){var d=!g._ck,h=n(g,"HEAPF32");return this._makeBlender(h,4*g.length,d)}})})(r);var sa=Object.assign({},r),ta="",ua,va;
|
||||||
|
if(fa||ia)ia?ta=self.location.href:"undefined"!=typeof document&&document.currentScript&&(ta=document.currentScript.src),_scriptName&&(ta=_scriptName),ta.startsWith("blob:")?ta="":ta=ta.substr(0,ta.replace(/[?#].*/,"").lastIndexOf("/")+1),ia&&(va=a=>{var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)}),ua=a=>fetch(a,{credentials:"same-origin"}).then(b=>b.ok?b.arrayBuffer():Promise.reject(Error(b.status+" : "+b.url)));
|
||||||
|
var xa=console.log.bind(console),ya=console.error.bind(console);Object.assign(r,sa);sa=null;var za,Ba=!1,Ca,B,Da,Fa,E,H,J,Ga;function Ha(){var a=za.buffer;r.HEAP8=Ca=new Int8Array(a);r.HEAP16=Da=new Int16Array(a);r.HEAPU8=B=new Uint8Array(a);r.HEAPU16=Fa=new Uint16Array(a);r.HEAP32=E=new Int32Array(a);r.HEAPU32=H=new Uint32Array(a);r.HEAPF32=J=new Float32Array(a);r.HEAPF64=Ga=new Float64Array(a)}var Ia=[],Ja=[],Ka=[],La=0,Na=null,Oa=null;
|
||||||
|
function Pa(a){a="Aborted("+a+")";ya(a);Ba=!0;a=new WebAssembly.RuntimeError(a+". Build with -sASSERTIONS for more info.");ca(a);throw a;}var Qa=a=>a.startsWith("data:application/octet-stream;base64,"),Ra;function Sa(a){return ua(a).then(b=>new Uint8Array(b),()=>{if(va)var b=va(a);else throw"both async and sync fetching of the wasm failed";return b})}function Ta(a,b,c){return Sa(a).then(e=>WebAssembly.instantiate(e,b)).then(c,e=>{ya(`failed to asynchronously prepare wasm: ${e}`);Pa(e)})}
|
||||||
|
function Ua(a,b){var c=Ra;return"function"!=typeof WebAssembly.instantiateStreaming||Qa(c)||"function"!=typeof fetch?Ta(c,a,b):fetch(c,{credentials:"same-origin"}).then(e=>WebAssembly.instantiateStreaming(e,a).then(b,function(f){ya(`wasm streaming compile failed: ${f}`);ya("falling back to ArrayBuffer instantiation");return Ta(c,a,b)}))}function Va(a){this.name="ExitStatus";this.message=`Program terminated with exit(${a})`;this.status=a}var Wa=a=>{a.forEach(b=>b(r))},Xa=r.noExitRuntime||!0;
|
||||||
|
class Ya{constructor(a){this.Vd=a-24}}
|
||||||
|
var Za=0,bb=0,cb="undefined"!=typeof TextDecoder?new TextDecoder:void 0,db=(a,b=0,c=NaN)=>{var e=b+c;for(c=b;a[c]&&!(c>=e);)++c;if(16<c-b&&a.buffer&&cb)return cb.decode(a.subarray(b,c));for(e="";b<c;){var f=a[b++];if(f&128){var k=a[b++]&63;if(192==(f&224))e+=String.fromCharCode((f&31)<<6|k);else{var n=a[b++]&63;f=224==(f&240)?(f&15)<<12|k<<6|n:(f&7)<<18|k<<12|n<<6|a[b++]&63;65536>f?e+=String.fromCharCode(f):(f-=65536,e+=String.fromCharCode(55296|f>>10,56320|f&1023))}}else e+=String.fromCharCode(f)}return e},
|
||||||
|
eb={},fb=a=>{for(;a.length;){var b=a.pop();a.pop()(b)}};function gb(a){return this.fromWireType(H[a>>2])}
|
||||||
|
var hb={},ib={},jb={},kb,mb=(a,b,c)=>{function e(l){l=c(l);if(l.length!==a.length)throw new kb("Mismatched type converter count");for(var q=0;q<a.length;++q)lb(a[q],l[q])}a.forEach(l=>jb[l]=b);var f=Array(b.length),k=[],n=0;b.forEach((l,q)=>{ib.hasOwnProperty(l)?f[q]=ib[l]:(k.push(l),hb.hasOwnProperty(l)||(hb[l]=[]),hb[l].push(()=>{f[q]=ib[l];++n;n===k.length&&e(f)}))});0===k.length&&e(f)},nb,K=a=>{for(var b="";B[a];)b+=nb[B[a++]];return b},L;
|
||||||
|
function ob(a,b,c={}){var e=b.name;if(!a)throw new L(`type "${e}" must have a positive integer typeid pointer`);if(ib.hasOwnProperty(a)){if(c.ef)return;throw new L(`Cannot register type '${e}' twice`);}ib[a]=b;delete jb[a];hb.hasOwnProperty(a)&&(b=hb[a],delete hb[a],b.forEach(f=>f()))}function lb(a,b,c={}){return ob(a,b,c)}
|
||||||
|
var pb=a=>{throw new L(a.Sd.Yd.Wd.name+" instance already deleted");},qb=!1,rb=()=>{},sb=(a,b,c)=>{if(b===c)return a;if(void 0===c.ae)return null;a=sb(a,b,c.ae);return null===a?null:c.Xe(a)},yb={},zb={},Ab=(a,b)=>{if(void 0===b)throw new L("ptr should not be undefined");for(;a.ae;)b=a.se(b),a=a.ae;return zb[b]},Cb=(a,b)=>{if(!b.Yd||!b.Vd)throw new kb("makeClassHandle requires ptr and ptrType");if(!!b.ce!==!!b.Zd)throw new kb("Both smartPtrType and smartPtr must be specified");b.count={value:1};return Bb(Object.create(a,
|
||||||
|
{Sd:{value:b,writable:!0}}))},Bb=a=>{if("undefined"===typeof FinalizationRegistry)return Bb=b=>b,a;qb=new FinalizationRegistry(b=>{b=b.Sd;--b.count.value;0===b.count.value&&(b.Zd?b.ce.he(b.Zd):b.Yd.Wd.he(b.Vd))});Bb=b=>{var c=b.Sd;c.Zd&&qb.register(b,{Sd:c},b);return b};rb=b=>{qb.unregister(b)};return Bb(a)},Db=[];function Eb(){}
|
||||||
|
var Fb=(a,b)=>Object.defineProperty(b,"name",{value:a}),Gb=(a,b,c)=>{if(void 0===a[b].$d){var e=a[b];a[b]=function(...f){if(!a[b].$d.hasOwnProperty(f.length))throw new L(`Function '${c}' called with an invalid number of arguments (${f.length}) - expects one of (${a[b].$d})!`);return a[b].$d[f.length].apply(this,f)};a[b].$d=[];a[b].$d[e.ie]=e}},Hb=(a,b,c)=>{if(r.hasOwnProperty(a)){if(void 0===c||void 0!==r[a].$d&&void 0!==r[a].$d[c])throw new L(`Cannot register public name '${a}' twice`);Gb(r,a,a);
|
||||||
|
if(r[a].$d.hasOwnProperty(c))throw new L(`Cannot register multiple overloads of a function with the same number of arguments (${c})!`);r[a].$d[c]=b}else r[a]=b,r[a].ie=c},Ib=a=>{a=a.replace(/[^a-zA-Z0-9_]/g,"$");var b=a.charCodeAt(0);return 48<=b&&57>=b?`_${a}`:a};function Jb(a,b,c,e,f,k,n,l){this.name=a;this.constructor=b;this.me=c;this.he=e;this.ae=f;this.$e=k;this.se=n;this.Xe=l;this.hf=[]}
|
||||||
|
var Kb=(a,b,c)=>{for(;b!==c;){if(!b.se)throw new L(`Expected null or instance of ${c.name}, got an instance of ${b.name}`);a=b.se(a);b=b.ae}return a};function Lb(a,b){if(null===b){if(this.Ee)throw new L(`null is not a valid ${this.name}`);return 0}if(!b.Sd)throw new L(`Cannot pass "${Mb(b)}" as a ${this.name}`);if(!b.Sd.Vd)throw new L(`Cannot pass deleted object as a pointer of type ${this.name}`);return Kb(b.Sd.Vd,b.Sd.Yd.Wd,this.Wd)}
|
||||||
|
function Nb(a,b){if(null===b){if(this.Ee)throw new L(`null is not a valid ${this.name}`);if(this.xe){var c=this.Fe();null!==a&&a.push(this.he,c);return c}return 0}if(!b||!b.Sd)throw new L(`Cannot pass "${Mb(b)}" as a ${this.name}`);if(!b.Sd.Vd)throw new L(`Cannot pass deleted object as a pointer of type ${this.name}`);if(!this.we&&b.Sd.Yd.we)throw new L(`Cannot convert argument of type ${b.Sd.ce?b.Sd.ce.name:b.Sd.Yd.name} to parameter type ${this.name}`);c=Kb(b.Sd.Vd,b.Sd.Yd.Wd,this.Wd);if(this.xe){if(void 0===
|
||||||
|
b.Sd.Zd)throw new L("Passing raw pointer to smart pointer is illegal");switch(this.nf){case 0:if(b.Sd.ce===this)c=b.Sd.Zd;else throw new L(`Cannot convert argument of type ${b.Sd.ce?b.Sd.ce.name:b.Sd.Yd.name} to parameter type ${this.name}`);break;case 1:c=b.Sd.Zd;break;case 2:if(b.Sd.ce===this)c=b.Sd.Zd;else{var e=b.clone();c=this.jf(c,Ob(()=>e["delete"]()));null!==a&&a.push(this.he,c)}break;default:throw new L("Unsupporting sharing policy");}}return c}
|
||||||
|
function Pb(a,b){if(null===b){if(this.Ee)throw new L(`null is not a valid ${this.name}`);return 0}if(!b.Sd)throw new L(`Cannot pass "${Mb(b)}" as a ${this.name}`);if(!b.Sd.Vd)throw new L(`Cannot pass deleted object as a pointer of type ${this.name}`);if(b.Sd.Yd.we)throw new L(`Cannot convert argument of type ${b.Sd.Yd.name} to parameter type ${this.name}`);return Kb(b.Sd.Vd,b.Sd.Yd.Wd,this.Wd)}
|
||||||
|
function Qb(a,b,c,e,f,k,n,l,q,v,w){this.name=a;this.Wd=b;this.Ee=c;this.we=e;this.xe=f;this.gf=k;this.nf=n;this.Me=l;this.Fe=q;this.jf=v;this.he=w;f||void 0!==b.ae?this.toWireType=Nb:(this.toWireType=e?Lb:Pb,this.ee=null)}
|
||||||
|
var Rb=(a,b,c)=>{if(!r.hasOwnProperty(a))throw new kb("Replacing nonexistent public symbol");void 0!==r[a].$d&&void 0!==c?r[a].$d[c]=b:(r[a]=b,r[a].ie=c)},N,Sb=(a,b,c=[])=>{a.includes("j")?(a=a.replace(/p/g,"i"),b=(0,r["dynCall_"+a])(b,...c)):b=N.get(b)(...c);return b},Tb=(a,b)=>(...c)=>Sb(a,b,c),Q=(a,b)=>{a=K(a);var c=a.includes("j")?Tb(a,b):N.get(b);if("function"!=typeof c)throw new L(`unknown function pointer with signature ${a}: ${b}`);return c},ac,dc=a=>{a=bc(a);var b=K(a);cc(a);return b},ec=
|
||||||
|
(a,b)=>{function c(k){f[k]||ib[k]||(jb[k]?jb[k].forEach(c):(e.push(k),f[k]=!0))}var e=[],f={};b.forEach(c);throw new ac(`${a}: `+e.map(dc).join([", "]));};function fc(a){for(var b=1;b<a.length;++b)if(null!==a[b]&&void 0===a[b].ee)return!0;return!1}
|
||||||
|
function gc(a,b,c,e,f){var k=b.length;if(2>k)throw new L("argTypes array size mismatch! Must at least get return value and 'this' types!");var n=null!==b[1]&&null!==c,l=fc(b),q="void"!==b[0].name,v=k-2,w=Array(v),A=[],D=[];return Fb(a,function(...I){D.length=0;A.length=n?2:1;A[0]=f;if(n){var P=b[1].toWireType(D,this);A[1]=P}for(var O=0;O<v;++O)w[O]=b[O+2].toWireType(D,I[O]),A.push(w[O]);I=e(...A);if(l)fb(D);else for(O=n?1:2;O<b.length;O++){var aa=1===O?P:w[O-2];null!==b[O].ee&&b[O].ee(aa)}P=q?b[0].fromWireType(I):
|
||||||
|
void 0;return P})}
|
||||||
|
var hc=(a,b)=>{for(var c=[],e=0;e<a;e++)c.push(H[b+4*e>>2]);return c},ic=a=>{a=a.trim();const b=a.indexOf("(");return-1!==b?a.substr(0,b):a},jc=[],kc=[],lc=a=>{9<a&&0===--kc[a+1]&&(kc[a]=void 0,jc.push(a))},mc=a=>{if(!a)throw new L("Cannot use deleted val. handle = "+a);return kc[a]},Ob=a=>{switch(a){case void 0:return 2;case null:return 4;case !0:return 6;case !1:return 8;default:const b=jc.pop()||kc.length;kc[b]=a;kc[b+1]=1;return b}},nc={name:"emscripten::val",fromWireType:a=>{var b=mc(a);lc(a);
|
||||||
|
return b},toWireType:(a,b)=>Ob(b),de:8,readValueFromPointer:gb,ee:null},oc=(a,b,c)=>{switch(b){case 1:return c?function(e){return this.fromWireType(Ca[e])}:function(e){return this.fromWireType(B[e])};case 2:return c?function(e){return this.fromWireType(Da[e>>1])}:function(e){return this.fromWireType(Fa[e>>1])};case 4:return c?function(e){return this.fromWireType(E[e>>2])}:function(e){return this.fromWireType(H[e>>2])};default:throw new TypeError(`invalid integer width (${b}): ${a}`);}},pc=(a,b)=>
|
||||||
|
{var c=ib[a];if(void 0===c)throw a=`${b} has unknown type ${dc(a)}`,new L(a);return c},Mb=a=>{if(null===a)return"null";var b=typeof a;return"object"===b||"array"===b||"function"===b?a.toString():""+a},qc=(a,b)=>{switch(b){case 4:return function(c){return this.fromWireType(J[c>>2])};case 8:return function(c){return this.fromWireType(Ga[c>>3])};default:throw new TypeError(`invalid float width (${b}): ${a}`);}},rc=(a,b,c)=>{switch(b){case 1:return c?e=>Ca[e]:e=>B[e];case 2:return c?e=>Da[e>>1]:e=>Fa[e>>
|
||||||
|
1];case 4:return c?e=>E[e>>2]:e=>H[e>>2];default:throw new TypeError(`invalid integer width (${b}): ${a}`);}},ra=(a,b,c)=>{var e=B;if(!(0<c))return 0;var f=b;c=b+c-1;for(var k=0;k<a.length;++k){var n=a.charCodeAt(k);if(55296<=n&&57343>=n){var l=a.charCodeAt(++k);n=65536+((n&1023)<<10)|l&1023}if(127>=n){if(b>=c)break;e[b++]=n}else{if(2047>=n){if(b+1>=c)break;e[b++]=192|n>>6}else{if(65535>=n){if(b+2>=c)break;e[b++]=224|n>>12}else{if(b+3>=c)break;e[b++]=240|n>>18;e[b++]=128|n>>12&63}e[b++]=128|n>>6&
|
||||||
|
63}e[b++]=128|n&63}}e[b]=0;return b-f},qa=a=>{for(var b=0,c=0;c<a.length;++c){var e=a.charCodeAt(c);127>=e?b++:2047>=e?b+=2:55296<=e&&57343>=e?(b+=4,++c):b+=3}return b},sc="undefined"!=typeof TextDecoder?new TextDecoder("utf-16le"):void 0,tc=(a,b)=>{var c=a>>1;for(var e=c+b/2;!(c>=e)&&Fa[c];)++c;c<<=1;if(32<c-a&&sc)return sc.decode(B.subarray(a,c));c="";for(e=0;!(e>=b/2);++e){var f=Da[a+2*e>>1];if(0==f)break;c+=String.fromCharCode(f)}return c},uc=(a,b,c)=>{c??=2147483647;if(2>c)return 0;c-=2;var e=
|
||||||
|
b;c=c<2*a.length?c/2:a.length;for(var f=0;f<c;++f)Da[b>>1]=a.charCodeAt(f),b+=2;Da[b>>1]=0;return b-e},vc=a=>2*a.length,wc=(a,b)=>{for(var c=0,e="";!(c>=b/4);){var f=E[a+4*c>>2];if(0==f)break;++c;65536<=f?(f-=65536,e+=String.fromCharCode(55296|f>>10,56320|f&1023)):e+=String.fromCharCode(f)}return e},xc=(a,b,c)=>{c??=2147483647;if(4>c)return 0;var e=b;c=e+c-4;for(var f=0;f<a.length;++f){var k=a.charCodeAt(f);if(55296<=k&&57343>=k){var n=a.charCodeAt(++f);k=65536+((k&1023)<<10)|n&1023}E[b>>2]=k;b+=
|
||||||
|
4;if(b+4>c)break}E[b>>2]=0;return b-e},yc=a=>{for(var b=0,c=0;c<a.length;++c){var e=a.charCodeAt(c);55296<=e&&57343>=e&&++c;b+=4}return b},zc=(a,b,c)=>{var e=[];a=a.toWireType(e,c);e.length&&(H[b>>2]=Ob(e));return a},Ac=[],Bc={},Cc=a=>{var b=Bc[a];return void 0===b?K(a):b},Dc=()=>{function a(b){b.$$$embind_global$$$=b;var c="object"==typeof $$$embind_global$$$&&b.$$$embind_global$$$==b;c||delete b.$$$embind_global$$$;return c}if("object"==typeof globalThis)return globalThis;if("object"==typeof $$$embind_global$$$)return $$$embind_global$$$;
|
||||||
|
"object"==typeof global&&a(global)?$$$embind_global$$$=global:"object"==typeof self&&a(self)&&($$$embind_global$$$=self);if("object"==typeof $$$embind_global$$$)return $$$embind_global$$$;throw Error("unable to get global object.");},Ec=a=>{var b=Ac.length;Ac.push(a);return b},Fc=(a,b)=>{for(var c=Array(a),e=0;e<a;++e)c[e]=pc(H[b+4*e>>2],"parameter "+e);return c},Gc=Reflect.construct,R,Hc=a=>{var b=a.getExtension("ANGLE_instanced_arrays");b&&(a.vertexAttribDivisor=(c,e)=>b.vertexAttribDivisorANGLE(c,
|
||||||
|
e),a.drawArraysInstanced=(c,e,f,k)=>b.drawArraysInstancedANGLE(c,e,f,k),a.drawElementsInstanced=(c,e,f,k,n)=>b.drawElementsInstancedANGLE(c,e,f,k,n))},Ic=a=>{var b=a.getExtension("OES_vertex_array_object");b&&(a.createVertexArray=()=>b.createVertexArrayOES(),a.deleteVertexArray=c=>b.deleteVertexArrayOES(c),a.bindVertexArray=c=>b.bindVertexArrayOES(c),a.isVertexArray=c=>b.isVertexArrayOES(c))},Jc=a=>{var b=a.getExtension("WEBGL_draw_buffers");b&&(a.drawBuffers=(c,e)=>b.drawBuffersWEBGL(c,e))},Kc=a=>
|
||||||
|
{var b="ANGLE_instanced_arrays EXT_blend_minmax EXT_disjoint_timer_query EXT_frag_depth EXT_shader_texture_lod EXT_sRGB OES_element_index_uint OES_fbo_render_mipmap OES_standard_derivatives OES_texture_float OES_texture_half_float OES_texture_half_float_linear OES_vertex_array_object WEBGL_color_buffer_float WEBGL_depth_texture WEBGL_draw_buffers EXT_color_buffer_float EXT_conservative_depth EXT_disjoint_timer_query_webgl2 EXT_texture_norm16 NV_shader_noperspective_interpolation WEBGL_clip_cull_distance EXT_clip_control EXT_color_buffer_half_float EXT_depth_clamp EXT_float_blend EXT_polygon_offset_clamp EXT_texture_compression_bptc EXT_texture_compression_rgtc EXT_texture_filter_anisotropic KHR_parallel_shader_compile OES_texture_float_linear WEBGL_blend_func_extended WEBGL_compressed_texture_astc WEBGL_compressed_texture_etc WEBGL_compressed_texture_etc1 WEBGL_compressed_texture_s3tc WEBGL_compressed_texture_s3tc_srgb WEBGL_debug_renderer_info WEBGL_debug_shaders WEBGL_lose_context WEBGL_multi_draw WEBGL_polygon_mode".split(" ");
|
||||||
|
return(a.getSupportedExtensions()||[]).filter(c=>b.includes(c))},Lc=1,Mc=[],Nc=[],Oc=[],Pc=[],ka=[],Qc=[],Rc=[],pa=[],Sc=[],Tc=[],Uc=[],Wc={},Xc={},Yc=4,Zc=0,ja=a=>{for(var b=Lc++,c=a.length;c<b;c++)a[c]=null;return b},$c=(a,b,c,e)=>{for(var f=0;f<a;f++){var k=R[c](),n=k&&ja(e);k?(k.name=n,e[n]=k):U||=1282;E[b+4*f>>2]=n}},na=(a,b)=>{a.He||(a.He=a.getContext,a.getContext=function(e,f){f=a.He(e,f);return"webgl"==e==f instanceof WebGLRenderingContext?f:null});var c=1<b.majorVersion?a.getContext("webgl2",
|
||||||
|
b):a.getContext("webgl",b);return c?ad(c,b):0},ad=(a,b)=>{var c=ja(pa),e={handle:c,attributes:b,version:b.majorVersion,fe:a};a.canvas&&(a.canvas.Pe=e);pa[c]=e;("undefined"==typeof b.Ye||b.Ye)&&bd(e);return c},oa=a=>{z=pa[a];r.pf=R=z?.fe;return!(a&&!R)},bd=a=>{a||=z;if(!a.ff){a.ff=!0;var b=a.fe;b.tf=b.getExtension("WEBGL_multi_draw");b.rf=b.getExtension("EXT_polygon_offset_clamp");b.qf=b.getExtension("EXT_clip_control");b.vf=b.getExtension("WEBGL_polygon_mode");Hc(b);Ic(b);Jc(b);b.Je=b.getExtension("WEBGL_draw_instanced_base_vertex_base_instance");
|
||||||
|
b.Le=b.getExtension("WEBGL_multi_draw_instanced_base_vertex_base_instance");2<=a.version&&(b.ge=b.getExtension("EXT_disjoint_timer_query_webgl2"));if(2>a.version||!b.ge)b.ge=b.getExtension("EXT_disjoint_timer_query");Kc(b).forEach(c=>{c.includes("lose_context")||c.includes("debug")||b.getExtension(c)})}},z,U,cd=(a,b)=>{R.bindFramebuffer(a,Oc[b])},dd=a=>{R.bindVertexArray(Rc[a])},ed=a=>R.clear(a),fd=(a,b,c,e)=>R.clearColor(a,b,c,e),gd=a=>R.clearStencil(a),hd=(a,b)=>{for(var c=0;c<a;c++){var e=E[b+
|
||||||
|
4*c>>2];R.deleteVertexArray(Rc[e]);Rc[e]=null}},jd=[],kd=(a,b)=>{$c(a,b,"createVertexArray",Rc)};function ld(){var a=Kc(R);return a=a.concat(a.map(b=>"GL_"+b))}
|
||||||
|
var md=(a,b,c)=>{if(b){var e=void 0;switch(a){case 36346:e=1;break;case 36344:0!=c&&1!=c&&(U||=1280);return;case 34814:case 36345:e=0;break;case 34466:var f=R.getParameter(34467);e=f?f.length:0;break;case 33309:if(2>z.version){U||=1282;return}e=ld().length;break;case 33307:case 33308:if(2>z.version){U||=1280;return}e=33307==a?3:0}if(void 0===e)switch(f=R.getParameter(a),typeof f){case "number":e=f;break;case "boolean":e=f?1:0;break;case "string":U||=1280;return;case "object":if(null===f)switch(a){case 34964:case 35725:case 34965:case 36006:case 36007:case 32873:case 34229:case 36662:case 36663:case 35053:case 35055:case 36010:case 35097:case 35869:case 32874:case 36389:case 35983:case 35368:case 34068:e=
|
||||||
|
0;break;default:U||=1280;return}else{if(f instanceof Float32Array||f instanceof Uint32Array||f instanceof Int32Array||f instanceof Array){for(a=0;a<f.length;++a)switch(c){case 0:E[b+4*a>>2]=f[a];break;case 2:J[b+4*a>>2]=f[a];break;case 4:Ca[b+a]=f[a]?1:0}return}try{e=f.name|0}catch(k){U||=1280;ya(`GL_INVALID_ENUM in glGet${c}v: Unknown object returned from WebGL getParameter(${a})! (error: ${k})`);return}}break;default:U||=1280;ya(`GL_INVALID_ENUM in glGet${c}v: Native code calling glGet${c}v(${a}) and it returns ${f} of type ${typeof f}!`);
|
||||||
|
return}switch(c){case 1:c=e;H[b>>2]=c;H[b+4>>2]=(c-H[b>>2])/4294967296;break;case 0:E[b>>2]=e;break;case 2:J[b>>2]=e;break;case 4:Ca[b]=e?1:0}}else U||=1281},nd=(a,b)=>md(a,b,0),od=(a,b,c)=>{if(c){a=Sc[a];b=2>z.version?R.ge.getQueryObjectEXT(a,b):R.getQueryParameter(a,b);var e;"boolean"==typeof b?e=b?1:0:e=b;H[c>>2]=e;H[c+4>>2]=(e-H[c>>2])/4294967296}else U||=1281},qd=a=>{var b=qa(a)+1,c=pd(b);c&&ra(a,c,b);return c},rd=a=>{var b=Wc[a];if(!b){switch(a){case 7939:b=qd(ld().join(" "));break;case 7936:case 7937:case 37445:case 37446:(b=
|
||||||
|
R.getParameter(a))||(U||=1280);b=b?qd(b):0;break;case 7938:b=R.getParameter(7938);var c=`OpenGL ES 2.0 (${b})`;2<=z.version&&(c=`OpenGL ES 3.0 (${b})`);b=qd(c);break;case 35724:b=R.getParameter(35724);c=b.match(/^WebGL GLSL ES ([0-9]\.[0-9][0-9]?)(?:$| .*)/);null!==c&&(3==c[1].length&&(c[1]+="0"),b=`OpenGL ES GLSL ES ${c[1]} (${b})`);b=qd(b);break;default:U||=1280}Wc[a]=b}return b},sd=(a,b)=>{if(2>z.version)return U||=1282,0;var c=Xc[a];if(c)return 0>b||b>=c.length?(U||=1281,0):c[b];switch(a){case 7939:return c=
|
||||||
|
ld().map(qd),c=Xc[a]=c,0>b||b>=c.length?(U||=1281,0):c[b];default:return U||=1280,0}},td=a=>"]"==a.slice(-1)&&a.lastIndexOf("["),ud=a=>{a-=5120;return 0==a?Ca:1==a?B:2==a?Da:4==a?E:6==a?J:5==a||28922==a||28520==a||30779==a||30782==a?H:Fa},vd=(a,b,c,e,f)=>{a=ud(a);b=e*((Zc||c)*({5:3,6:4,8:2,29502:3,29504:4,26917:2,26918:2,29846:3,29847:4}[b-6402]||1)*a.BYTES_PER_ELEMENT+Yc-1&-Yc);return a.subarray(f>>>31-Math.clz32(a.BYTES_PER_ELEMENT),f+b>>>31-Math.clz32(a.BYTES_PER_ELEMENT))},Y=a=>{var b=R.We;if(b){var c=
|
||||||
|
b.re[a];"number"==typeof c&&(b.re[a]=c=R.getUniformLocation(b,b.Ne[a]+(0<c?`[${c}]`:"")));return c}U||=1282},wd=[],xd=[],yd={},Ad=()=>{if(!zd){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:"./this.program"},b;for(b in yd)void 0===yd[b]?delete a[b]:a[b]=yd[b];var c=[];for(b in a)c.push(`${b}=${a[b]}`);zd=c}return zd},zd,Bd=[null,[],[]];
|
||||||
|
kb=r.InternalError=class extends Error{constructor(a){super(a);this.name="InternalError"}};for(var Cd=Array(256),Dd=0;256>Dd;++Dd)Cd[Dd]=String.fromCharCode(Dd);nb=Cd;L=r.BindingError=class extends Error{constructor(a){super(a);this.name="BindingError"}};
|
||||||
|
Object.assign(Eb.prototype,{isAliasOf:function(a){if(!(this instanceof Eb&&a instanceof Eb))return!1;var b=this.Sd.Yd.Wd,c=this.Sd.Vd;a.Sd=a.Sd;var e=a.Sd.Yd.Wd;for(a=a.Sd.Vd;b.ae;)c=b.se(c),b=b.ae;for(;e.ae;)a=e.se(a),e=e.ae;return b===e&&c===a},clone:function(){this.Sd.Vd||pb(this);if(this.Sd.qe)return this.Sd.count.value+=1,this;var a=Bb,b=Object,c=b.create,e=Object.getPrototypeOf(this),f=this.Sd;a=a(c.call(b,e,{Sd:{value:{count:f.count,pe:f.pe,qe:f.qe,Vd:f.Vd,Yd:f.Yd,Zd:f.Zd,ce:f.ce}}}));a.Sd.count.value+=
|
||||||
|
1;a.Sd.pe=!1;return a},["delete"](){this.Sd.Vd||pb(this);if(this.Sd.pe&&!this.Sd.qe)throw new L("Object already scheduled for deletion");rb(this);var a=this.Sd;--a.count.value;0===a.count.value&&(a.Zd?a.ce.he(a.Zd):a.Yd.Wd.he(a.Vd));this.Sd.qe||(this.Sd.Zd=void 0,this.Sd.Vd=void 0)},isDeleted:function(){return!this.Sd.Vd},deleteLater:function(){this.Sd.Vd||pb(this);if(this.Sd.pe&&!this.Sd.qe)throw new L("Object already scheduled for deletion");Db.push(this);this.Sd.pe=!0;return this}});
|
||||||
|
Object.assign(Qb.prototype,{af(a){this.Me&&(a=this.Me(a));return a},Ie(a){this.he?.(a)},de:8,readValueFromPointer:gb,fromWireType:function(a){function b(){return this.xe?Cb(this.Wd.me,{Yd:this.gf,Vd:c,ce:this,Zd:a}):Cb(this.Wd.me,{Yd:this,Vd:a})}var c=this.af(a);if(!c)return this.Ie(a),null;var e=Ab(this.Wd,c);if(void 0!==e){if(0===e.Sd.count.value)return e.Sd.Vd=c,e.Sd.Zd=a,e.clone();e=e.clone();this.Ie(a);return e}e=this.Wd.$e(c);e=yb[e];if(!e)return b.call(this);e=this.we?e.Ve:e.pointerType;var f=
|
||||||
|
sb(c,this.Wd,e.Wd);return null===f?b.call(this):this.xe?Cb(e.Wd.me,{Yd:e,Vd:f,ce:this,Zd:a}):Cb(e.Wd.me,{Yd:e,Vd:f})}});ac=r.UnboundTypeError=((a,b)=>{var c=Fb(b,function(e){this.name=b;this.message=e;e=Error(e).stack;void 0!==e&&(this.stack=this.toString()+"\n"+e.replace(/^Error(:[^\n]*)?\n/,""))});c.prototype=Object.create(a.prototype);c.prototype.constructor=c;c.prototype.toString=function(){return void 0===this.message?this.name:`${this.name}: ${this.message}`};return c})(Error,"UnboundTypeError");
|
||||||
|
kc.push(0,1,void 0,1,null,1,!0,1,!1,1);r.count_emval_handles=()=>kc.length/2-5-jc.length;for(var Ed=0;32>Ed;++Ed)jd.push(Array(Ed));var Fd=new Float32Array(288);for(Ed=0;288>=Ed;++Ed)wd[Ed]=Fd.subarray(0,Ed);var Gd=new Int32Array(288);for(Ed=0;288>=Ed;++Ed)xd[Ed]=Gd.subarray(0,Ed);
|
||||||
|
var Vd={F:(a,b,c)=>{var e=new Ya(a);H[e.Vd+16>>2]=0;H[e.Vd+4>>2]=b;H[e.Vd+8>>2]=c;Za=a;bb++;throw Za;},U:function(){return 0},ud:()=>{},td:function(){return 0},sd:()=>{},rd:function(){},qd:()=>{},md:()=>{Pa("")},B:a=>{var b=eb[a];delete eb[a];var c=b.Fe,e=b.he,f=b.Ke,k=f.map(n=>n.df).concat(f.map(n=>n.lf));mb([a],k,n=>{var l={};f.forEach((q,v)=>{var w=n[v],A=q.bf,D=q.cf,I=n[v+f.length],P=q.kf,O=q.mf;l[q.Ze]={read:aa=>w.fromWireType(A(D,aa)),write:(aa,la)=>{var X=[];P(O,aa,I.toWireType(X,la));fb(X)}}});
|
||||||
|
return[{name:b.name,fromWireType:q=>{var v={},w;for(w in l)v[w]=l[w].read(q);e(q);return v},toWireType:(q,v)=>{for(var w in l)if(!(w in v))throw new TypeError(`Missing field: "${w}"`);var A=c();for(w in l)l[w].write(A,v[w]);null!==q&&q.push(e,A);return A},de:8,readValueFromPointer:gb,ee:e}]})},X:()=>{},ld:(a,b,c,e)=>{b=K(b);lb(a,{name:b,fromWireType:function(f){return!!f},toWireType:function(f,k){return k?c:e},de:8,readValueFromPointer:function(f){return this.fromWireType(B[f])},ee:null})},k:(a,b,
|
||||||
|
c,e,f,k,n,l,q,v,w,A,D)=>{w=K(w);k=Q(f,k);l&&=Q(n,l);v&&=Q(q,v);D=Q(A,D);var I=Ib(w);Hb(I,function(){ec(`Cannot construct ${w} due to unbound types`,[e])});mb([a,b,c],e?[e]:[],P=>{P=P[0];if(e){var O=P.Wd;var aa=O.me}else aa=Eb.prototype;P=Fb(w,function(...Ea){if(Object.getPrototypeOf(this)!==la)throw new L("Use 'new' to construct "+w);if(void 0===X.je)throw new L(w+" has no accessible constructor");var ea=X.je[Ea.length];if(void 0===ea)throw new L(`Tried to invoke ctor of ${w} with invalid number of parameters (${Ea.length}) - expected (${Object.keys(X.je).toString()}) parameters instead!`);
|
||||||
|
return ea.apply(this,Ea)});var la=Object.create(aa,{constructor:{value:P}});P.prototype=la;var X=new Jb(w,P,la,D,O,k,l,v);if(X.ae){var ha;(ha=X.ae).te??(ha.te=[]);X.ae.te.push(X)}O=new Qb(w,X,!0,!1,!1);ha=new Qb(w+"*",X,!1,!1,!1);aa=new Qb(w+" const*",X,!1,!0,!1);yb[a]={pointerType:ha,Ve:aa};Rb(I,P);return[O,ha,aa]})},e:(a,b,c,e,f,k,n)=>{var l=hc(c,e);b=K(b);b=ic(b);k=Q(f,k);mb([],[a],q=>{function v(){ec(`Cannot call ${w} due to unbound types`,l)}q=q[0];var w=`${q.name}.${b}`;b.startsWith("@@")&&
|
||||||
|
(b=Symbol[b.substring(2)]);var A=q.Wd.constructor;void 0===A[b]?(v.ie=c-1,A[b]=v):(Gb(A,b,w),A[b].$d[c-1]=v);mb([],l,D=>{D=[D[0],null].concat(D.slice(1));D=gc(w,D,null,k,n);void 0===A[b].$d?(D.ie=c-1,A[b]=D):A[b].$d[c-1]=D;if(q.Wd.te)for(const I of q.Wd.te)I.constructor.hasOwnProperty(b)||(I.constructor[b]=D);return[]});return[]})},z:(a,b,c,e,f,k)=>{var n=hc(b,c);f=Q(e,f);mb([],[a],l=>{l=l[0];var q=`constructor ${l.name}`;void 0===l.Wd.je&&(l.Wd.je=[]);if(void 0!==l.Wd.je[b-1])throw new L(`Cannot register multiple constructors with identical number of parameters (${b-
|
||||||
|
1}) for class '${l.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`);l.Wd.je[b-1]=()=>{ec(`Cannot construct ${l.name} due to unbound types`,n)};mb([],n,v=>{v.splice(1,0,null);l.Wd.je[b-1]=gc(q,v,null,f,k);return[]});return[]})},a:(a,b,c,e,f,k,n,l)=>{var q=hc(c,e);b=K(b);b=ic(b);k=Q(f,k);mb([],[a],v=>{function w(){ec(`Cannot call ${A} due to unbound types`,q)}v=v[0];var A=`${v.name}.${b}`;b.startsWith("@@")&&(b=Symbol[b.substring(2)]);l&&v.Wd.hf.push(b);
|
||||||
|
var D=v.Wd.me,I=D[b];void 0===I||void 0===I.$d&&I.className!==v.name&&I.ie===c-2?(w.ie=c-2,w.className=v.name,D[b]=w):(Gb(D,b,A),D[b].$d[c-2]=w);mb([],q,P=>{P=gc(A,P,v,k,n);void 0===D[b].$d?(P.ie=c-2,D[b]=P):D[b].$d[c-2]=P;return[]});return[]})},r:(a,b,c)=>{a=K(a);mb([],[b],e=>{e=e[0];r[a]=e.fromWireType(c);return[]})},kd:a=>lb(a,nc),i:(a,b,c,e)=>{function f(){}b=K(b);f.values={};lb(a,{name:b,constructor:f,fromWireType:function(k){return this.constructor.values[k]},toWireType:(k,n)=>n.value,de:8,
|
||||||
|
readValueFromPointer:oc(b,c,e),ee:null});Hb(b,f)},b:(a,b,c)=>{var e=pc(a,"enum");b=K(b);a=e.constructor;e=Object.create(e.constructor.prototype,{value:{value:c},constructor:{value:Fb(`${e.name}_${b}`,function(){})}});a.values[c]=e;a[b]=e},R:(a,b,c)=>{b=K(b);lb(a,{name:b,fromWireType:e=>e,toWireType:(e,f)=>f,de:8,readValueFromPointer:qc(b,c),ee:null})},w:(a,b,c,e,f,k)=>{var n=hc(b,c);a=K(a);a=ic(a);f=Q(e,f);Hb(a,function(){ec(`Cannot call ${a} due to unbound types`,n)},b-1);mb([],n,l=>{l=[l[0],null].concat(l.slice(1));
|
||||||
|
Rb(a,gc(a,l,null,f,k),b-1);return[]})},C:(a,b,c,e,f)=>{b=K(b);-1===f&&(f=4294967295);f=l=>l;if(0===e){var k=32-8*c;f=l=>l<<k>>>k}var n=b.includes("unsigned")?function(l,q){return q>>>0}:function(l,q){return q};lb(a,{name:b,fromWireType:f,toWireType:n,de:8,readValueFromPointer:rc(b,c,0!==e),ee:null})},q:(a,b,c)=>{function e(k){return new f(Ca.buffer,H[k+4>>2],H[k>>2])}var f=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array][b];c=K(c);lb(a,{name:c,fromWireType:e,
|
||||||
|
de:8,readValueFromPointer:e},{ef:!0})},o:(a,b,c,e,f,k,n,l,q,v,w,A)=>{c=K(c);k=Q(f,k);l=Q(n,l);v=Q(q,v);A=Q(w,A);mb([a],[b],D=>{D=D[0];return[new Qb(c,D.Wd,!1,!1,!0,D,e,k,l,v,A)]})},Q:(a,b)=>{b=K(b);var c="std::string"===b;lb(a,{name:b,fromWireType:function(e){var f=H[e>>2],k=e+4;if(c)for(var n=k,l=0;l<=f;++l){var q=k+l;if(l==f||0==B[q]){n=n?db(B,n,q-n):"";if(void 0===v)var v=n;else v+=String.fromCharCode(0),v+=n;n=q+1}}else{v=Array(f);for(l=0;l<f;++l)v[l]=String.fromCharCode(B[k+l]);v=v.join("")}cc(e);
|
||||||
|
return v},toWireType:function(e,f){f instanceof ArrayBuffer&&(f=new Uint8Array(f));var k="string"==typeof f;if(!(k||f instanceof Uint8Array||f instanceof Uint8ClampedArray||f instanceof Int8Array))throw new L("Cannot pass non-string to std::string");var n=c&&k?qa(f):f.length;var l=pd(4+n+1),q=l+4;H[l>>2]=n;if(c&&k)ra(f,q,n+1);else if(k)for(k=0;k<n;++k){var v=f.charCodeAt(k);if(255<v)throw cc(q),new L("String has UTF-16 code units that do not fit in 8 bits");B[q+k]=v}else for(k=0;k<n;++k)B[q+k]=f[k];
|
||||||
|
null!==e&&e.push(cc,l);return l},de:8,readValueFromPointer:gb,ee(e){cc(e)}})},M:(a,b,c)=>{c=K(c);if(2===b){var e=tc;var f=uc;var k=vc;var n=l=>Fa[l>>1]}else 4===b&&(e=wc,f=xc,k=yc,n=l=>H[l>>2]);lb(a,{name:c,fromWireType:l=>{for(var q=H[l>>2],v,w=l+4,A=0;A<=q;++A){var D=l+4+A*b;if(A==q||0==n(D))w=e(w,D-w),void 0===v?v=w:(v+=String.fromCharCode(0),v+=w),w=D+b}cc(l);return v},toWireType:(l,q)=>{if("string"!=typeof q)throw new L(`Cannot pass non-string to C++ string type ${c}`);var v=k(q),w=pd(4+v+b);
|
||||||
|
H[w>>2]=v/b;f(q,w+4,v+b);null!==l&&l.push(cc,w);return w},de:8,readValueFromPointer:gb,ee(l){cc(l)}})},A:(a,b,c,e,f,k)=>{eb[a]={name:K(b),Fe:Q(c,e),he:Q(f,k),Ke:[]}},d:(a,b,c,e,f,k,n,l,q,v)=>{eb[a].Ke.push({Ze:K(b),df:c,bf:Q(e,f),cf:k,lf:n,kf:Q(l,q),mf:v})},jd:(a,b)=>{b=K(b);lb(a,{sf:!0,name:b,de:0,fromWireType:()=>{},toWireType:()=>{}})},id:()=>1,hd:()=>{throw Infinity;},E:(a,b,c)=>{a=mc(a);b=pc(b,"emval::as");return zc(b,c,a)},L:(a,b,c,e)=>{a=Ac[a];b=mc(b);return a(null,b,c,e)},t:(a,b,c,e,f)=>{a=
|
||||||
|
Ac[a];b=mc(b);c=Cc(c);return a(b,b[c],e,f)},c:lc,K:a=>{if(0===a)return Ob(Dc());a=Cc(a);return Ob(Dc()[a])},n:(a,b,c)=>{var e=Fc(a,b),f=e.shift();a--;var k=Array(a);b=`methodCaller<(${e.map(n=>n.name).join(", ")}) => ${f.name}>`;return Ec(Fb(b,(n,l,q,v)=>{for(var w=0,A=0;A<a;++A)k[A]=e[A].readValueFromPointer(v+w),w+=e[A].de;n=1===c?Gc(l,k):l.apply(n,k);return zc(f,q,n)}))},y:(a,b)=>{a=mc(a);b=mc(b);return Ob(a[b])},H:a=>{9<a&&(kc[a+1]+=1)},G:()=>Ob([]),f:a=>Ob(Cc(a)),D:()=>Ob({}),gd:a=>{a=mc(a);
|
||||||
|
return!a},l:a=>{var b=mc(a);fb(b);lc(a)},h:(a,b,c)=>{a=mc(a);b=mc(b);c=mc(c);a[b]=c},g:(a,b)=>{a=pc(a,"_emval_take_value");a=a.readValueFromPointer(b);return Ob(a)},W:function(){return-52},V:function(){},fd:(a,b,c,e)=>{var f=(new Date).getFullYear(),k=(new Date(f,0,1)).getTimezoneOffset();f=(new Date(f,6,1)).getTimezoneOffset();H[a>>2]=60*Math.max(k,f);E[b>>2]=Number(k!=f);b=n=>{var l=Math.abs(n);return`UTC${0<=n?"-":"+"}${String(Math.floor(l/60)).padStart(2,"0")}${String(l%60).padStart(2,"0")}`};
|
||||||
|
a=b(k);b=b(f);f<k?(ra(a,c,17),ra(b,e,17)):(ra(a,e,17),ra(b,c,17))},ed:()=>performance.now(),dd:a=>R.activeTexture(a),cd:(a,b)=>{R.attachShader(Nc[a],Qc[b])},bd:(a,b)=>{R.beginQuery(a,Sc[b])},ad:(a,b)=>{R.ge.beginQueryEXT(a,Sc[b])},$c:(a,b,c)=>{R.bindAttribLocation(Nc[a],b,c?db(B,c):"")},_c:(a,b)=>{35051==a?R.Ce=b:35052==a&&(R.le=b);R.bindBuffer(a,Mc[b])},Zc:cd,Yc:(a,b)=>{R.bindRenderbuffer(a,Pc[b])},Xc:(a,b)=>{R.bindSampler(a,Tc[b])},Wc:(a,b)=>{R.bindTexture(a,ka[b])},Vc:dd,Uc:dd,Tc:(a,b,c,e)=>R.blendColor(a,
|
||||||
|
b,c,e),Sc:a=>R.blendEquation(a),Rc:(a,b)=>R.blendFunc(a,b),Qc:(a,b,c,e,f,k,n,l,q,v)=>R.blitFramebuffer(a,b,c,e,f,k,n,l,q,v),Pc:(a,b,c,e)=>{2<=z.version?c&&b?R.bufferData(a,B,e,c,b):R.bufferData(a,b,e):R.bufferData(a,c?B.subarray(c,c+b):b,e)},Oc:(a,b,c,e)=>{2<=z.version?c&&R.bufferSubData(a,b,B,e,c):R.bufferSubData(a,b,B.subarray(e,e+c))},Nc:a=>R.checkFramebufferStatus(a),Mc:ed,Lc:fd,Kc:gd,Jc:(a,b,c,e)=>R.clientWaitSync(Uc[a],b,(c>>>0)+4294967296*e),Ic:(a,b,c,e)=>{R.colorMask(!!a,!!b,!!c,!!e)},Hc:a=>
|
||||||
|
{R.compileShader(Qc[a])},Gc:(a,b,c,e,f,k,n,l)=>{2<=z.version?R.le||!n?R.compressedTexImage2D(a,b,c,e,f,k,n,l):R.compressedTexImage2D(a,b,c,e,f,k,B,l,n):R.compressedTexImage2D(a,b,c,e,f,k,B.subarray(l,l+n))},Fc:(a,b,c,e,f,k,n,l,q)=>{2<=z.version?R.le||!l?R.compressedTexSubImage2D(a,b,c,e,f,k,n,l,q):R.compressedTexSubImage2D(a,b,c,e,f,k,n,B,q,l):R.compressedTexSubImage2D(a,b,c,e,f,k,n,B.subarray(q,q+l))},Ec:(a,b,c,e,f)=>R.copyBufferSubData(a,b,c,e,f),Dc:(a,b,c,e,f,k,n,l)=>R.copyTexSubImage2D(a,b,c,
|
||||||
|
e,f,k,n,l),Cc:()=>{var a=ja(Nc),b=R.createProgram();b.name=a;b.Ae=b.ye=b.ze=0;b.Ge=1;Nc[a]=b;return a},Bc:a=>{var b=ja(Qc);Qc[b]=R.createShader(a);return b},Ac:a=>R.cullFace(a),zc:(a,b)=>{for(var c=0;c<a;c++){var e=E[b+4*c>>2],f=Mc[e];f&&(R.deleteBuffer(f),f.name=0,Mc[e]=null,e==R.Ce&&(R.Ce=0),e==R.le&&(R.le=0))}},yc:(a,b)=>{for(var c=0;c<a;++c){var e=E[b+4*c>>2],f=Oc[e];f&&(R.deleteFramebuffer(f),f.name=0,Oc[e]=null)}},xc:a=>{if(a){var b=Nc[a];b?(R.deleteProgram(b),b.name=0,Nc[a]=null):U||=1281}},
|
||||||
|
wc:(a,b)=>{for(var c=0;c<a;c++){var e=E[b+4*c>>2],f=Sc[e];f&&(R.deleteQuery(f),Sc[e]=null)}},vc:(a,b)=>{for(var c=0;c<a;c++){var e=E[b+4*c>>2],f=Sc[e];f&&(R.ge.deleteQueryEXT(f),Sc[e]=null)}},uc:(a,b)=>{for(var c=0;c<a;c++){var e=E[b+4*c>>2],f=Pc[e];f&&(R.deleteRenderbuffer(f),f.name=0,Pc[e]=null)}},tc:(a,b)=>{for(var c=0;c<a;c++){var e=E[b+4*c>>2],f=Tc[e];f&&(R.deleteSampler(f),f.name=0,Tc[e]=null)}},sc:a=>{if(a){var b=Qc[a];b?(R.deleteShader(b),Qc[a]=null):U||=1281}},rc:a=>{if(a){var b=Uc[a];b?
|
||||||
|
(R.deleteSync(b),b.name=0,Uc[a]=null):U||=1281}},qc:(a,b)=>{for(var c=0;c<a;c++){var e=E[b+4*c>>2],f=ka[e];f&&(R.deleteTexture(f),f.name=0,ka[e]=null)}},pc:hd,oc:hd,nc:a=>{R.depthMask(!!a)},mc:a=>R.disable(a),lc:a=>{R.disableVertexAttribArray(a)},kc:(a,b,c)=>{R.drawArrays(a,b,c)},jc:(a,b,c,e)=>{R.drawArraysInstanced(a,b,c,e)},ic:(a,b,c,e,f)=>{R.Je.drawArraysInstancedBaseInstanceWEBGL(a,b,c,e,f)},hc:(a,b)=>{for(var c=jd[a],e=0;e<a;e++)c[e]=E[b+4*e>>2];R.drawBuffers(c)},gc:(a,b,c,e)=>{R.drawElements(a,
|
||||||
|
b,c,e)},fc:(a,b,c,e,f)=>{R.drawElementsInstanced(a,b,c,e,f)},ec:(a,b,c,e,f,k,n)=>{R.Je.drawElementsInstancedBaseVertexBaseInstanceWEBGL(a,b,c,e,f,k,n)},dc:(a,b,c,e,f,k)=>{R.drawElements(a,e,f,k)},cc:a=>R.enable(a),bc:a=>{R.enableVertexAttribArray(a)},ac:a=>R.endQuery(a),$b:a=>{R.ge.endQueryEXT(a)},_b:(a,b)=>(a=R.fenceSync(a,b))?(b=ja(Uc),a.name=b,Uc[b]=a,b):0,Zb:()=>R.finish(),Yb:()=>R.flush(),Xb:(a,b,c,e)=>{R.framebufferRenderbuffer(a,b,c,Pc[e])},Wb:(a,b,c,e,f)=>{R.framebufferTexture2D(a,b,c,ka[e],
|
||||||
|
f)},Vb:a=>R.frontFace(a),Ub:(a,b)=>{$c(a,b,"createBuffer",Mc)},Tb:(a,b)=>{$c(a,b,"createFramebuffer",Oc)},Sb:(a,b)=>{$c(a,b,"createQuery",Sc)},Rb:(a,b)=>{for(var c=0;c<a;c++){var e=R.ge.createQueryEXT();if(!e){for(U||=1282;c<a;)E[b+4*c++>>2]=0;break}var f=ja(Sc);e.name=f;Sc[f]=e;E[b+4*c>>2]=f}},Qb:(a,b)=>{$c(a,b,"createRenderbuffer",Pc)},Pb:(a,b)=>{$c(a,b,"createSampler",Tc)},Ob:(a,b)=>{$c(a,b,"createTexture",ka)},Nb:kd,Mb:kd,Lb:a=>R.generateMipmap(a),Kb:(a,b,c)=>{c?E[c>>2]=R.getBufferParameter(a,
|
||||||
|
b):U||=1281},Jb:()=>{var a=R.getError()||U;U=0;return a},Ib:(a,b)=>md(a,b,2),Hb:(a,b,c,e)=>{a=R.getFramebufferAttachmentParameter(a,b,c);if(a instanceof WebGLRenderbuffer||a instanceof WebGLTexture)a=a.name|0;E[e>>2]=a},Gb:nd,Fb:(a,b,c,e)=>{a=R.getProgramInfoLog(Nc[a]);null===a&&(a="(unknown error)");b=0<b&&e?ra(a,e,b):0;c&&(E[c>>2]=b)},Eb:(a,b,c)=>{if(c)if(a>=Lc)U||=1281;else if(a=Nc[a],35716==b)a=R.getProgramInfoLog(a),null===a&&(a="(unknown error)"),E[c>>2]=a.length+1;else if(35719==b){if(!a.Ae){var e=
|
||||||
|
R.getProgramParameter(a,35718);for(b=0;b<e;++b)a.Ae=Math.max(a.Ae,R.getActiveUniform(a,b).name.length+1)}E[c>>2]=a.Ae}else if(35722==b){if(!a.ye)for(e=R.getProgramParameter(a,35721),b=0;b<e;++b)a.ye=Math.max(a.ye,R.getActiveAttrib(a,b).name.length+1);E[c>>2]=a.ye}else if(35381==b){if(!a.ze)for(e=R.getProgramParameter(a,35382),b=0;b<e;++b)a.ze=Math.max(a.ze,R.getActiveUniformBlockName(a,b).length+1);E[c>>2]=a.ze}else E[c>>2]=R.getProgramParameter(a,b);else U||=1281},Db:od,Cb:od,Bb:(a,b,c)=>{if(c){a=
|
||||||
|
R.getQueryParameter(Sc[a],b);var e;"boolean"==typeof a?e=a?1:0:e=a;E[c>>2]=e}else U||=1281},Ab:(a,b,c)=>{if(c){a=R.ge.getQueryObjectEXT(Sc[a],b);var e;"boolean"==typeof a?e=a?1:0:e=a;E[c>>2]=e}else U||=1281},zb:(a,b,c)=>{c?E[c>>2]=R.getQuery(a,b):U||=1281},yb:(a,b,c)=>{c?E[c>>2]=R.ge.getQueryEXT(a,b):U||=1281},xb:(a,b,c)=>{c?E[c>>2]=R.getRenderbufferParameter(a,b):U||=1281},wb:(a,b,c,e)=>{a=R.getShaderInfoLog(Qc[a]);null===a&&(a="(unknown error)");b=0<b&&e?ra(a,e,b):0;c&&(E[c>>2]=b)},vb:(a,b,c,e)=>
|
||||||
|
{a=R.getShaderPrecisionFormat(a,b);E[c>>2]=a.rangeMin;E[c+4>>2]=a.rangeMax;E[e>>2]=a.precision},ub:(a,b,c)=>{c?35716==b?(a=R.getShaderInfoLog(Qc[a]),null===a&&(a="(unknown error)"),E[c>>2]=a?a.length+1:0):35720==b?(a=R.getShaderSource(Qc[a]),E[c>>2]=a?a.length+1:0):E[c>>2]=R.getShaderParameter(Qc[a],b):U||=1281},tb:rd,sb:sd,rb:(a,b)=>{b=b?db(B,b):"";if(a=Nc[a]){var c=a,e=c.re,f=c.Oe,k;if(!e){c.re=e={};c.Ne={};var n=R.getProgramParameter(c,35718);for(k=0;k<n;++k){var l=R.getActiveUniform(c,k);var q=
|
||||||
|
l.name;l=l.size;var v=td(q);v=0<v?q.slice(0,v):q;var w=c.Ge;c.Ge+=l;f[v]=[l,w];for(q=0;q<l;++q)e[w]=q,c.Ne[w++]=v}}c=a.re;e=0;f=b;k=td(b);0<k&&(e=parseInt(b.slice(k+1))>>>0,f=b.slice(0,k));if((f=a.Oe[f])&&e<f[0]&&(e+=f[1],c[e]=c[e]||R.getUniformLocation(a,b)))return e}else U||=1281;return-1},qb:(a,b,c)=>{for(var e=jd[b],f=0;f<b;f++)e[f]=E[c+4*f>>2];R.invalidateFramebuffer(a,e)},pb:(a,b,c,e,f,k,n)=>{for(var l=jd[b],q=0;q<b;q++)l[q]=E[c+4*q>>2];R.invalidateSubFramebuffer(a,l,e,f,k,n)},ob:a=>R.isSync(Uc[a]),
|
||||||
|
nb:a=>(a=ka[a])?R.isTexture(a):0,mb:a=>R.lineWidth(a),lb:a=>{a=Nc[a];R.linkProgram(a);a.re=0;a.Oe={}},kb:(a,b,c,e,f,k)=>{R.Le.multiDrawArraysInstancedBaseInstanceWEBGL(a,E,b>>2,E,c>>2,E,e>>2,H,f>>2,k)},jb:(a,b,c,e,f,k,n,l)=>{R.Le.multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL(a,E,b>>2,c,E,e>>2,E,f>>2,E,k>>2,H,n>>2,l)},ib:(a,b)=>{3317==a?Yc=b:3314==a&&(Zc=b);R.pixelStorei(a,b)},hb:(a,b)=>{R.ge.queryCounterEXT(Sc[a],b)},gb:a=>R.readBuffer(a),fb:(a,b,c,e,f,k,n)=>{if(2<=z.version)if(R.Ce)R.readPixels(a,
|
||||||
|
b,c,e,f,k,n);else{var l=ud(k);n>>>=31-Math.clz32(l.BYTES_PER_ELEMENT);R.readPixels(a,b,c,e,f,k,l,n)}else(l=vd(k,f,c,e,n))?R.readPixels(a,b,c,e,f,k,l):U||=1280},eb:(a,b,c,e)=>R.renderbufferStorage(a,b,c,e),db:(a,b,c,e,f)=>R.renderbufferStorageMultisample(a,b,c,e,f),cb:(a,b,c)=>{R.samplerParameterf(Tc[a],b,c)},bb:(a,b,c)=>{R.samplerParameteri(Tc[a],b,c)},ab:(a,b,c)=>{R.samplerParameteri(Tc[a],b,E[c>>2])},$a:(a,b,c,e)=>R.scissor(a,b,c,e),_a:(a,b,c,e)=>{for(var f="",k=0;k<b;++k){var n=(n=H[c+4*k>>2])?
|
||||||
|
db(B,n,e?H[e+4*k>>2]:void 0):"";f+=n}R.shaderSource(Qc[a],f)},Za:(a,b,c)=>R.stencilFunc(a,b,c),Ya:(a,b,c,e)=>R.stencilFuncSeparate(a,b,c,e),Xa:a=>R.stencilMask(a),Wa:(a,b)=>R.stencilMaskSeparate(a,b),Va:(a,b,c)=>R.stencilOp(a,b,c),Ua:(a,b,c,e)=>R.stencilOpSeparate(a,b,c,e),Ta:(a,b,c,e,f,k,n,l,q)=>{if(2<=z.version){if(R.le){R.texImage2D(a,b,c,e,f,k,n,l,q);return}if(q){var v=ud(l);q>>>=31-Math.clz32(v.BYTES_PER_ELEMENT);R.texImage2D(a,b,c,e,f,k,n,l,v,q);return}}v=q?vd(l,n,e,f,q):null;R.texImage2D(a,
|
||||||
|
b,c,e,f,k,n,l,v)},Sa:(a,b,c)=>R.texParameterf(a,b,c),Ra:(a,b,c)=>{R.texParameterf(a,b,J[c>>2])},Qa:(a,b,c)=>R.texParameteri(a,b,c),Pa:(a,b,c)=>{R.texParameteri(a,b,E[c>>2])},Oa:(a,b,c,e,f)=>R.texStorage2D(a,b,c,e,f),Na:(a,b,c,e,f,k,n,l,q)=>{if(2<=z.version){if(R.le){R.texSubImage2D(a,b,c,e,f,k,n,l,q);return}if(q){var v=ud(l);R.texSubImage2D(a,b,c,e,f,k,n,l,v,q>>>31-Math.clz32(v.BYTES_PER_ELEMENT));return}}q=q?vd(l,n,f,k,q):null;R.texSubImage2D(a,b,c,e,f,k,n,l,q)},Ma:(a,b)=>{R.uniform1f(Y(a),b)},La:(a,
|
||||||
|
b,c)=>{if(2<=z.version)b&&R.uniform1fv(Y(a),J,c>>2,b);else{if(288>=b)for(var e=wd[b],f=0;f<b;++f)e[f]=J[c+4*f>>2];else e=J.subarray(c>>2,c+4*b>>2);R.uniform1fv(Y(a),e)}},Ka:(a,b)=>{R.uniform1i(Y(a),b)},Ja:(a,b,c)=>{if(2<=z.version)b&&R.uniform1iv(Y(a),E,c>>2,b);else{if(288>=b)for(var e=xd[b],f=0;f<b;++f)e[f]=E[c+4*f>>2];else e=E.subarray(c>>2,c+4*b>>2);R.uniform1iv(Y(a),e)}},Ia:(a,b,c)=>{R.uniform2f(Y(a),b,c)},Ha:(a,b,c)=>{if(2<=z.version)b&&R.uniform2fv(Y(a),J,c>>2,2*b);else{if(144>=b){b*=2;for(var e=
|
||||||
|
wd[b],f=0;f<b;f+=2)e[f]=J[c+4*f>>2],e[f+1]=J[c+(4*f+4)>>2]}else e=J.subarray(c>>2,c+8*b>>2);R.uniform2fv(Y(a),e)}},Ga:(a,b,c)=>{R.uniform2i(Y(a),b,c)},Fa:(a,b,c)=>{if(2<=z.version)b&&R.uniform2iv(Y(a),E,c>>2,2*b);else{if(144>=b){b*=2;for(var e=xd[b],f=0;f<b;f+=2)e[f]=E[c+4*f>>2],e[f+1]=E[c+(4*f+4)>>2]}else e=E.subarray(c>>2,c+8*b>>2);R.uniform2iv(Y(a),e)}},Ea:(a,b,c,e)=>{R.uniform3f(Y(a),b,c,e)},Da:(a,b,c)=>{if(2<=z.version)b&&R.uniform3fv(Y(a),J,c>>2,3*b);else{if(96>=b){b*=3;for(var e=wd[b],f=0;f<
|
||||||
|
b;f+=3)e[f]=J[c+4*f>>2],e[f+1]=J[c+(4*f+4)>>2],e[f+2]=J[c+(4*f+8)>>2]}else e=J.subarray(c>>2,c+12*b>>2);R.uniform3fv(Y(a),e)}},Ca:(a,b,c,e)=>{R.uniform3i(Y(a),b,c,e)},Ba:(a,b,c)=>{if(2<=z.version)b&&R.uniform3iv(Y(a),E,c>>2,3*b);else{if(96>=b){b*=3;for(var e=xd[b],f=0;f<b;f+=3)e[f]=E[c+4*f>>2],e[f+1]=E[c+(4*f+4)>>2],e[f+2]=E[c+(4*f+8)>>2]}else e=E.subarray(c>>2,c+12*b>>2);R.uniform3iv(Y(a),e)}},Aa:(a,b,c,e,f)=>{R.uniform4f(Y(a),b,c,e,f)},za:(a,b,c)=>{if(2<=z.version)b&&R.uniform4fv(Y(a),J,c>>2,4*
|
||||||
|
b);else{if(72>=b){var e=wd[4*b],f=J;c>>=2;b*=4;for(var k=0;k<b;k+=4){var n=c+k;e[k]=f[n];e[k+1]=f[n+1];e[k+2]=f[n+2];e[k+3]=f[n+3]}}else e=J.subarray(c>>2,c+16*b>>2);R.uniform4fv(Y(a),e)}},ya:(a,b,c,e,f)=>{R.uniform4i(Y(a),b,c,e,f)},xa:(a,b,c)=>{if(2<=z.version)b&&R.uniform4iv(Y(a),E,c>>2,4*b);else{if(72>=b){b*=4;for(var e=xd[b],f=0;f<b;f+=4)e[f]=E[c+4*f>>2],e[f+1]=E[c+(4*f+4)>>2],e[f+2]=E[c+(4*f+8)>>2],e[f+3]=E[c+(4*f+12)>>2]}else e=E.subarray(c>>2,c+16*b>>2);R.uniform4iv(Y(a),e)}},wa:(a,b,c,e)=>
|
||||||
|
{if(2<=z.version)b&&R.uniformMatrix2fv(Y(a),!!c,J,e>>2,4*b);else{if(72>=b){b*=4;for(var f=wd[b],k=0;k<b;k+=4)f[k]=J[e+4*k>>2],f[k+1]=J[e+(4*k+4)>>2],f[k+2]=J[e+(4*k+8)>>2],f[k+3]=J[e+(4*k+12)>>2]}else f=J.subarray(e>>2,e+16*b>>2);R.uniformMatrix2fv(Y(a),!!c,f)}},va:(a,b,c,e)=>{if(2<=z.version)b&&R.uniformMatrix3fv(Y(a),!!c,J,e>>2,9*b);else{if(32>=b){b*=9;for(var f=wd[b],k=0;k<b;k+=9)f[k]=J[e+4*k>>2],f[k+1]=J[e+(4*k+4)>>2],f[k+2]=J[e+(4*k+8)>>2],f[k+3]=J[e+(4*k+12)>>2],f[k+4]=J[e+(4*k+16)>>2],f[k+
|
||||||
|
5]=J[e+(4*k+20)>>2],f[k+6]=J[e+(4*k+24)>>2],f[k+7]=J[e+(4*k+28)>>2],f[k+8]=J[e+(4*k+32)>>2]}else f=J.subarray(e>>2,e+36*b>>2);R.uniformMatrix3fv(Y(a),!!c,f)}},ua:(a,b,c,e)=>{if(2<=z.version)b&&R.uniformMatrix4fv(Y(a),!!c,J,e>>2,16*b);else{if(18>=b){var f=wd[16*b],k=J;e>>=2;b*=16;for(var n=0;n<b;n+=16){var l=e+n;f[n]=k[l];f[n+1]=k[l+1];f[n+2]=k[l+2];f[n+3]=k[l+3];f[n+4]=k[l+4];f[n+5]=k[l+5];f[n+6]=k[l+6];f[n+7]=k[l+7];f[n+8]=k[l+8];f[n+9]=k[l+9];f[n+10]=k[l+10];f[n+11]=k[l+11];f[n+12]=k[l+12];f[n+
|
||||||
|
13]=k[l+13];f[n+14]=k[l+14];f[n+15]=k[l+15]}}else f=J.subarray(e>>2,e+64*b>>2);R.uniformMatrix4fv(Y(a),!!c,f)}},ta:a=>{a=Nc[a];R.useProgram(a);R.We=a},sa:(a,b)=>R.vertexAttrib1f(a,b),ra:(a,b)=>{R.vertexAttrib2f(a,J[b>>2],J[b+4>>2])},qa:(a,b)=>{R.vertexAttrib3f(a,J[b>>2],J[b+4>>2],J[b+8>>2])},pa:(a,b)=>{R.vertexAttrib4f(a,J[b>>2],J[b+4>>2],J[b+8>>2],J[b+12>>2])},oa:(a,b)=>{R.vertexAttribDivisor(a,b)},na:(a,b,c,e,f)=>{R.vertexAttribIPointer(a,b,c,e,f)},ma:(a,b,c,e,f,k)=>{R.vertexAttribPointer(a,b,c,
|
||||||
|
!!e,f,k)},la:(a,b,c,e)=>R.viewport(a,b,c,e),ka:(a,b,c,e)=>{R.waitSync(Uc[a],b,(c>>>0)+4294967296*e)},ja:a=>{var b=B.length;a>>>=0;if(2147483648<a)return!1;for(var c=1;4>=c;c*=2){var e=b*(1+1/c);e=Math.min(e,a+100663296);a:{e=(Math.min(2147483648,65536*Math.ceil(Math.max(a,e)/65536))-za.buffer.byteLength+65535)/65536|0;try{za.grow(e);Ha();var f=1;break a}catch(k){}f=void 0}if(f)return!0}return!1},ia:()=>z?z.handle:0,pd:(a,b)=>{var c=0;Ad().forEach((e,f)=>{var k=b+c;f=H[a+4*f>>2]=k;for(k=0;k<e.length;++k)Ca[f++]=
|
||||||
|
e.charCodeAt(k);Ca[f]=0;c+=e.length+1});return 0},od:(a,b)=>{var c=Ad();H[a>>2]=c.length;var e=0;c.forEach(f=>e+=f.length+1);H[b>>2]=e;return 0},ha:a=>{Xa||(Ba=!0);throw new Va(a);},T:()=>52,Z:function(){return 52},nd:()=>52,Y:function(){return 70},S:(a,b,c,e)=>{for(var f=0,k=0;k<c;k++){var n=H[b>>2],l=H[b+4>>2];b+=8;for(var q=0;q<l;q++){var v=B[n+q],w=Bd[a];0===v||10===v?((1===a?xa:ya)(db(w)),w.length=0):w.push(v)}f+=l}H[e>>2]=f;return 0},ga:cd,fa:ed,ea:fd,da:gd,J:nd,P:rd,ca:sd,j:Hd,v:Id,m:Jd,I:Kd,
|
||||||
|
ba:Ld,O:Md,N:Nd,s:Od,x:Pd,p:Qd,u:Rd,aa:Sd,$:Td,_:Ud},Z=function(){function a(c){Z=c.exports;za=Z.vd;Ha();N=Z.yd;Ja.unshift(Z.wd);La--;0==La&&(null!==Na&&(clearInterval(Na),Na=null),Oa&&(c=Oa,Oa=null,c()));return Z}var b={a:Vd};La++;if(r.instantiateWasm)try{return r.instantiateWasm(b,a)}catch(c){ya(`Module.instantiateWasm callback failed with error: ${c}`),ca(c)}Ra??=r.locateFile?Qa("canvaskit.wasm")?"canvaskit.wasm":ta+"canvaskit.wasm":(new URL("canvaskit.wasm",import.meta.url)).href;Ua(b,
|
||||||
|
function(c){a(c.instance)}).catch(ca);return{}}(),bc=a=>(bc=Z.xd)(a),pd=r._malloc=a=>(pd=r._malloc=Z.zd)(a),cc=r._free=a=>(cc=r._free=Z.Ad)(a),Wd=(a,b)=>(Wd=Z.Bd)(a,b),Xd=a=>(Xd=Z.Cd)(a),Yd=()=>(Yd=Z.Dd)();r.dynCall_viji=(a,b,c,e,f)=>(r.dynCall_viji=Z.Ed)(a,b,c,e,f);r.dynCall_vijiii=(a,b,c,e,f,k,n)=>(r.dynCall_vijiii=Z.Fd)(a,b,c,e,f,k,n);r.dynCall_viiiiij=(a,b,c,e,f,k,n,l)=>(r.dynCall_viiiiij=Z.Gd)(a,b,c,e,f,k,n,l);r.dynCall_vij=(a,b,c,e)=>(r.dynCall_vij=Z.Hd)(a,b,c,e);
|
||||||
|
r.dynCall_jii=(a,b,c)=>(r.dynCall_jii=Z.Id)(a,b,c);r.dynCall_jiiiiii=(a,b,c,e,f,k,n)=>(r.dynCall_jiiiiii=Z.Jd)(a,b,c,e,f,k,n);r.dynCall_jiiiiji=(a,b,c,e,f,k,n,l)=>(r.dynCall_jiiiiji=Z.Kd)(a,b,c,e,f,k,n,l);r.dynCall_ji=(a,b)=>(r.dynCall_ji=Z.Ld)(a,b);r.dynCall_iijj=(a,b,c,e,f,k)=>(r.dynCall_iijj=Z.Md)(a,b,c,e,f,k);r.dynCall_jiji=(a,b,c,e,f)=>(r.dynCall_jiji=Z.Nd)(a,b,c,e,f);r.dynCall_viijii=(a,b,c,e,f,k,n)=>(r.dynCall_viijii=Z.Od)(a,b,c,e,f,k,n);
|
||||||
|
r.dynCall_iiiiij=(a,b,c,e,f,k,n)=>(r.dynCall_iiiiij=Z.Pd)(a,b,c,e,f,k,n);r.dynCall_iiiiijj=(a,b,c,e,f,k,n,l,q)=>(r.dynCall_iiiiijj=Z.Qd)(a,b,c,e,f,k,n,l,q);r.dynCall_iiiiiijj=(a,b,c,e,f,k,n,l,q,v)=>(r.dynCall_iiiiiijj=Z.Rd)(a,b,c,e,f,k,n,l,q,v);function Rd(a,b,c,e,f){var k=Yd();try{N.get(a)(b,c,e,f)}catch(n){Xd(k);if(n!==n+0)throw n;Wd(1,0)}}function Id(a,b,c){var e=Yd();try{return N.get(a)(b,c)}catch(f){Xd(e);if(f!==f+0)throw f;Wd(1,0)}}
|
||||||
|
function Pd(a,b,c){var e=Yd();try{N.get(a)(b,c)}catch(f){Xd(e);if(f!==f+0)throw f;Wd(1,0)}}function Hd(a,b){var c=Yd();try{return N.get(a)(b)}catch(e){Xd(c);if(e!==e+0)throw e;Wd(1,0)}}function Od(a,b){var c=Yd();try{N.get(a)(b)}catch(e){Xd(c);if(e!==e+0)throw e;Wd(1,0)}}function Jd(a,b,c,e){var f=Yd();try{return N.get(a)(b,c,e)}catch(k){Xd(f);if(k!==k+0)throw k;Wd(1,0)}}function Ud(a,b,c,e,f,k,n,l,q,v){var w=Yd();try{N.get(a)(b,c,e,f,k,n,l,q,v)}catch(A){Xd(w);if(A!==A+0)throw A;Wd(1,0)}}
|
||||||
|
function Qd(a,b,c,e){var f=Yd();try{N.get(a)(b,c,e)}catch(k){Xd(f);if(k!==k+0)throw k;Wd(1,0)}}function Td(a,b,c,e,f,k,n){var l=Yd();try{N.get(a)(b,c,e,f,k,n)}catch(q){Xd(l);if(q!==q+0)throw q;Wd(1,0)}}function Md(a,b,c,e,f,k,n,l){var q=Yd();try{return N.get(a)(b,c,e,f,k,n,l)}catch(v){Xd(q);if(v!==v+0)throw v;Wd(1,0)}}function Sd(a,b,c,e,f,k){var n=Yd();try{N.get(a)(b,c,e,f,k)}catch(l){Xd(n);if(l!==l+0)throw l;Wd(1,0)}}
|
||||||
|
function Kd(a,b,c,e,f){var k=Yd();try{return N.get(a)(b,c,e,f)}catch(n){Xd(k);if(n!==n+0)throw n;Wd(1,0)}}function Nd(a,b,c,e,f,k,n,l,q,v){var w=Yd();try{return N.get(a)(b,c,e,f,k,n,l,q,v)}catch(A){Xd(w);if(A!==A+0)throw A;Wd(1,0)}}function Ld(a,b,c,e,f,k,n){var l=Yd();try{return N.get(a)(b,c,e,f,k,n)}catch(q){Xd(l);if(q!==q+0)throw q;Wd(1,0)}}var Zd,$d;Oa=function ae(){Zd||be();Zd||(Oa=ae)};
|
||||||
|
function be(){if(!(0<La)){if(!$d&&($d=1,Wa(Ia),0<La))return;Zd||(Zd=1,r.calledRun=1,Ba||(Wa(Ja),ba(r),r.onRuntimeInitialized?.(),Wa(Ka)))}}be();moduleRtn=da;
|
||||||
|
|
||||||
|
|
||||||
|
return moduleRtn;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
export default CanvasKitInit;
|
||||||