Commit 6b980613 authored by DatHV's avatar DatHV
Browse files

update project structure

parent bfff9e47
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/base/app_loading.dart'; import 'package:mypoint_flutter_app/shared/widgets/loading/app_loading.dart';
class BaseViewModel extends GetxController with WidgetsBindingObserver { class BaseViewModel extends GetxController with WidgetsBindingObserver {
var isShowLoading = false; var isShowLoading = false;
RxBool isShowKey = false.obs; RxBool isShowKey = false.obs;
FToast fToast = FToast(); FToast fToast = FToast();
final RxBool isLoading = false.obs;
int _trackedLoadingCount = 0;
@override @override
void onInit() { void onInit() {
...@@ -42,4 +44,21 @@ class BaseViewModel extends GetxController with WidgetsBindingObserver { ...@@ -42,4 +44,21 @@ class BaseViewModel extends GetxController with WidgetsBindingObserver {
void hideLoading() { void hideLoading() {
AppLoading().hide(); AppLoading().hide();
} }
@protected
void beginTrackedLoading() {
_trackedLoadingCount++;
if (_trackedLoadingCount >= 1) {
isLoading.value = true;
}
}
@protected
void endTrackedLoading() {
// if (_trackedLoadingCount == 0) return;
_trackedLoadingCount--;
if (_trackedLoadingCount <= 0) {
isLoading.value = false;
}
}
} }
...@@ -32,8 +32,7 @@ mixin BasicState<Screen extends BaseScreen> on BaseState<Screen> { ...@@ -32,8 +32,7 @@ mixin BasicState<Screen extends BaseScreen> on BaseState<Screen> {
); );
} }
if (!isScaffold) { if (!isScaffold) {
return PopScope( return PopScope(child: createBody());
child: createBody());
} }
return WillPopScope( return WillPopScope(
child: Scaffold( child: Scaffold(
......
// bottom_sheet_helper.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
...@@ -20,7 +19,6 @@ class BottomSheetHelper { ...@@ -20,7 +19,6 @@ class BottomSheetHelper {
borderRadius: BorderRadius.vertical(top: Radius.circular(16)), borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
), ),
builder: (context) { builder: (context) {
final bottom = MediaQuery.of(context).padding.bottom;
return Padding( return Padding(
padding: MediaQuery.of(context).viewInsets.add( padding: MediaQuery.of(context).viewInsets.add(
const EdgeInsets.only(bottom: 0), // 👈 Safe area bottom const EdgeInsets.only(bottom: 0), // 👈 Safe area bottom
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'back_button.dart'; import 'back_button.dart';
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class EmptyWidget extends StatelessWidget { class EmptyWidget extends StatelessWidget {
final String imageAsset; final String? imageAsset;
final String content; final String? content;
final Size size; final Size size;
final bool isLoading;
const EmptyWidget({ const EmptyWidget({
super.key, super.key,
this.imageAsset = 'assets/images/ic_pipi_06.png', this.imageAsset,
this.content = 'Không có dữ liệu hiển thị', this.content,
this.size = const Size(200, 200), this.size = const Size(200, 200),
this.isLoading = false,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final displayText = content ?? (isLoading ? 'Đang tải dữ liệu...' : 'Không có dữ liệu hiển thị');
final assetPath = (imageAsset != null && imageAsset!.isNotEmpty)
? imageAsset!
: (isLoading ? 'assets/images/ic_pipi_07.png' : 'assets/images/ic_pipi_03.png');
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Image.asset( Image.asset(
imageAsset, assetPath,
width: size.width, width: size.width,
height: size.height, height: size.height,
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
content, displayText,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
......
import 'package:auto_size_text/auto_size_text.dart'; import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../screen/home/header_home_viewmodel.dart'; import '../../features/home/header_home_viewmodel.dart';
import 'back_button.dart'; import 'back_button.dart';
import 'image_loader.dart'; import 'image_loader.dart';
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import '../../core/utils/extensions/num_extension.dart';
import '../extensions/num_extension.dart'; import '../../features/voucher/models/cash_type.dart';
import '../screen/voucher/models/cash_type.dart';
class CustomPointText extends StatelessWidget { class CustomPointText extends StatelessWidget {
final int point; final int point;
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../extensions/num_extension.dart'; import '../../core/utils/extensions/num_extension.dart';
import '../resources/base_color.dart'; import '../../core/theme/base_color.dart';
import '../screen/voucher/models/cash_type.dart'; import '../../features/voucher/models/cash_type.dart';
class PriceTagWidget extends StatelessWidget { class PriceTagWidget extends StatelessWidget {
final int point; final int point;
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../screen/home/header_home_viewmodel.dart'; import '../../features/home/header_home_viewmodel.dart';
import 'back_button.dart'; import 'back_button.dart';
import 'image_loader.dart'; import 'image_loader.dart';
......
...@@ -7,12 +7,12 @@ class DashedLine extends StatelessWidget { ...@@ -7,12 +7,12 @@ class DashedLine extends StatelessWidget {
final Color color; final Color color;
const DashedLine({ const DashedLine({
Key? key, super.key,
this.height = 1, this.height = 1,
this.dashWidth = 4, this.dashWidth = 4,
this.dashSpacing = 4, this.dashSpacing = 4,
this.color = Colors.grey, this.color = Colors.grey,
}) : super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
......
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../configs/constants.dart'; import '../../../app/config/constants.dart';
class AppLoading { class AppLoading {
// Singleton ẩn // Singleton ẩn
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../resources/base_color.dart'; import '../../core/theme/base_color.dart';
import '../screen/support/support_screen.dart'; import '../../features/support/support_screen.dart';
class SupportButton extends StatelessWidget { class SupportButton extends StatelessWidget {
const SupportButton({super.key}); const SupportButton({super.key});
......
// import 'package:flutter/material.dart';
// import 'package:intl/intl.dart';
//
// class DatePickerField extends StatefulWidget {
// final String label;
// final DateTime? initialDate;
// final Function(DateTime) onDateSelected;
// final bool enabled;
//
// const DatePickerField({
// super.key,
// required this.label,
// this.initialDate,
// required this.onDateSelected,
// this.enabled = true,
// });
//
// @override
// State<DatePickerField> createState() => _DatePickerFieldState();
// }
//
// class _DatePickerFieldState extends State<DatePickerField> {
// DateTime? _selectedDate;
//
// @override
// void initState() {
// super.initState();
// _selectedDate = widget.initialDate;
// }
//
// Future<void> _pickDate() async {
// if (!widget.enabled) return;
//
// final now = DateTime.now();
// final picked = await showDatePicker(
// context: context,
// initialDate: _selectedDate ?? now,
// firstDate: DateTime(1900),
// lastDate: DateTime(2100),
// );
//
// if (picked != null) {
// setState(() {
// _selectedDate = picked;
// });
// widget.onDateSelected(picked);
// }
// }
//
// @override
// Widget build(BuildContext context) {
// final displayText = _selectedDate != null
// ? DateFormat('dd/MM/yyyy').format(_selectedDate!)
// : widget.label;
//
// return GestureDetector(
// onTap: _pickDate,
// child: AbsorbPointer(
// child: Container(
// padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
// decoration: BoxDecoration(
// border: Border.all(color: Colors.grey.shade400),
// borderRadius: BorderRadius.circular(8),
// color: widget.enabled ? Colors.white : Colors.grey.shade100,
// ),
// child: Row(
// children: [
// const Icon(Icons.calendar_today, color: Colors.blueGrey, size: 20),
// const SizedBox(width: 12),
// Expanded(
// child: Text(
// displayText,
// style: TextStyle(
// fontSize: 16,
// color: _selectedDate != null
// ? Colors.black87
// : Colors.grey.shade500,
// ),
// ),
// ),
// if (widget.enabled)
// const Icon(Icons.expand_more, color: Colors.grey),
// ],
// ),
// ),
// ),
// );
// }
// }
#!/usr/bin/env bash
# Script để tự động di chuyển files theo cấu trúc mới
# Chạy từng phase một, test kỹ sau mỗi phase
set -euo pipefail
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="${PROJECT_ROOT}/lib"
BACKUP_DIR="${PROJECT_ROOT}/.migration_backup_$(date +%Y%m%d_%H%M%S)"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
create_backup() {
log_info "Creating backup at ${BACKUP_DIR}..."
mkdir -p "${BACKUP_DIR}"
cp -r "${LIB_DIR}" "${BACKUP_DIR}/lib"
log_info "Backup created successfully!"
}
create_directories() {
local phase="$1"
log_info "Creating directories for Phase ${phase}..."
if [ "$phase" = "1" ]; then
# Phase 1: Core reorganization
mkdir -p "${LIB_DIR}/core/config"
mkdir -p "${LIB_DIR}/core/initialization"
mkdir -p "${LIB_DIR}/core/utils/extensions"
mkdir -p "${LIB_DIR}/core/navigation"
mkdir -p "${LIB_DIR}/core/theme"
log_info "Phase 1 directories created"
elif [ "$phase" = "2" ]; then
# Phase 2: Network & Services
mkdir -p "${LIB_DIR}/core/network/api"
mkdir -p "${LIB_DIR}/core/network/interceptor"
mkdir -p "${LIB_DIR}/core/storage/point"
mkdir -p "${LIB_DIR}/core/services/auth"
mkdir -p "${LIB_DIR}/core/platform/web"
mkdir -p "${LIB_DIR}/core/platform/permission"
mkdir -p "${LIB_DIR}/core/platform/firebase"
log_info "Phase 2 directories created"
elif [ "$phase" = "3" ]; then
# Phase 3: Feature-based (sẽ tạo khi cần)
log_warn "Phase 3 directories will be created per feature"
fi
}
phase1_core_reorganization() {
log_info "=== Phase 1: Core Reorganization ==="
# 1. Move configs
log_info "Moving configs to core/config/..."
if [ -d "${LIB_DIR}/configs" ]; then
mv "${LIB_DIR}/configs"/* "${LIB_DIR}/core/config/" 2>/dev/null || true
rmdir "${LIB_DIR}/configs" 2>/dev/null || true
fi
if [ -f "${LIB_DIR}/env_loader.dart" ]; then
mv "${LIB_DIR}/env_loader.dart" "${LIB_DIR}/core/config/"
fi
# 2. Move initialization
log_info "Moving initialization to core/initialization/..."
if [ -d "${LIB_DIR}/core" ]; then
if [ -f "${LIB_DIR}/core/app_initializer.dart" ]; then
mv "${LIB_DIR}/core/app_initializer.dart" "${LIB_DIR}/core/initialization/"
fi
if [ -f "${LIB_DIR}/core/web_app_initializer.dart" ]; then
mv "${LIB_DIR}/core/web_app_initializer.dart" "${LIB_DIR}/core/initialization/"
fi
if [ -f "${LIB_DIR}/core/deep_link_service.dart" ]; then
mv "${LIB_DIR}/core/deep_link_service.dart" "${LIB_DIR}/core/initialization/"
fi
fi
# 3. Move utils and extensions
log_info "Moving utils and extensions to core/utils/..."
if [ -d "${LIB_DIR}/utils" ]; then
# Move non-extension files
for file in "${LIB_DIR}/utils"/*.dart; do
if [ -f "$file" ]; then
mv "$file" "${LIB_DIR}/core/utils/"
fi
done
rmdir "${LIB_DIR}/utils" 2>/dev/null || true
fi
if [ -d "${LIB_DIR}/extensions" ]; then
mv "${LIB_DIR}/extensions"/* "${LIB_DIR}/core/utils/extensions/" 2>/dev/null || true
rmdir "${LIB_DIR}/extensions" 2>/dev/null || true
fi
# Move shared files to utils
if [ -d "${LIB_DIR}/shared" ]; then
for file in "${LIB_DIR}/shared"/*.dart; do
if [ -f "$file" ]; then
mv "$file" "${LIB_DIR}/core/utils/"
fi
done
rmdir "${LIB_DIR}/shared" 2>/dev/null || true
fi
# 4. Move navigation
log_info "Moving navigation to core/navigation/..."
if [ -f "${LIB_DIR}/base/app_navigator.dart" ]; then
mkdir -p "${LIB_DIR}/core/navigation"
mv "${LIB_DIR}/base/app_navigator.dart" "${LIB_DIR}/core/navigation/"
fi
if [ -d "${LIB_DIR}/deferred" ]; then
mv "${LIB_DIR}/deferred"/* "${LIB_DIR}/core/navigation/" 2>/dev/null || true
rmdir "${LIB_DIR}/deferred" 2>/dev/null || true
fi
if [ -d "${LIB_DIR}/directional" ]; then
mv "${LIB_DIR}/directional"/* "${LIB_DIR}/core/navigation/" 2>/dev/null || true
rmdir "${LIB_DIR}/directional" 2>/dev/null || true
fi
# 5. Move resources to theme
log_info "Moving resources to core/theme/..."
if [ -d "${LIB_DIR}/resources" ]; then
mkdir -p "${LIB_DIR}/core/theme"
# Rename files
if [ -f "${LIB_DIR}/resources/base_color.dart" ]; then
mv "${LIB_DIR}/resources/base_color.dart" "${LIB_DIR}/core/theme/colors.dart"
fi
if [ -f "${LIB_DIR}/resources/text_style.dart" ]; then
mv "${LIB_DIR}/resources/text_style.dart" "${LIB_DIR}/core/theme/text_styles.dart"
fi
if [ -f "${LIB_DIR}/resources/button_style.dart" ]; then
mv "${LIB_DIR}/resources/button_style.dart" "${LIB_DIR}/core/theme/button_styles.dart"
fi
if [ -f "${LIB_DIR}/resources/define_image.dart" ]; then
mv "${LIB_DIR}/resources/define_image.dart" "${LIB_DIR}/core/theme/images.dart"
fi
rmdir "${LIB_DIR}/resources" 2>/dev/null || true
fi
log_info "Phase 1 completed!"
log_warn "Please update imports and test the app before proceeding to Phase 2"
}
phase2_network_services() {
log_info "=== Phase 2: Network & Services Reorganization ==="
# 1. Move networking
log_info "Moving networking to core/network/..."
if [ -d "${LIB_DIR}/networking" ]; then
# Move API clients
if [ -d "${LIB_DIR}/networking/api" ]; then
mv "${LIB_DIR}/networking/api"/* "${LIB_DIR}/core/network/api/" 2>/dev/null || true
fi
# Move interceptors
if [ -d "${LIB_DIR}/networking/interceptor" ]; then
mv "${LIB_DIR}/networking/interceptor"/* "${LIB_DIR}/core/network/interceptor/" 2>/dev/null || true
fi
# Move other networking files
for file in "${LIB_DIR}/networking"/*.dart; do
if [ -f "$file" ]; then
mv "$file" "${LIB_DIR}/core/network/"
fi
done
rmdir "${LIB_DIR}/networking/api" 2>/dev/null || true
rmdir "${LIB_DIR}/networking/interceptor" 2>/dev/null || true
rmdir "${LIB_DIR}/networking" 2>/dev/null || true
fi
# 2. Move services
log_info "Moving services to core/services/..."
if [ -d "${LIB_DIR}/services" ]; then
mkdir -p "${LIB_DIR}/core/services/auth"
for file in "${LIB_DIR}/services"/*.dart; do
if [ -f "$file" ]; then
mv "$file" "${LIB_DIR}/core/services/auth/"
fi
done
rmdir "${LIB_DIR}/services" 2>/dev/null || true
fi
# 3. Move preference to storage
log_info "Moving preference to core/storage/..."
if [ -d "${LIB_DIR}/preference" ]; then
# Move point folder
if [ -d "${LIB_DIR}/preference/point" ]; then
mv "${LIB_DIR}/preference/point" "${LIB_DIR}/core/storage/"
fi
# Move other files
for file in "${LIB_DIR}/preference"/*.dart; do
if [ -f "$file" ]; then
mv "$file" "${LIB_DIR}/core/storage/"
fi
done
rmdir "${LIB_DIR}/preference" 2>/dev/null || true
fi
# 4. Move platform-specific
log_info "Moving platform-specific to core/platform/..."
# Web
if [ -d "${LIB_DIR}/web" ]; then
mv "${LIB_DIR}/web"/* "${LIB_DIR}/core/platform/web/" 2>/dev/null || true
rmdir "${LIB_DIR}/web" 2>/dev/null || true
fi
# Permission
if [ -d "${LIB_DIR}/permission" ]; then
mv "${LIB_DIR}/permission"/* "${LIB_DIR}/core/platform/permission/" 2>/dev/null || true
rmdir "${LIB_DIR}/permission" 2>/dev/null || true
fi
# Firebase
if [ -d "${LIB_DIR}/firebase" ]; then
mv "${LIB_DIR}/firebase"/* "${LIB_DIR}/core/platform/firebase/" 2>/dev/null || true
rmdir "${LIB_DIR}/firebase" 2>/dev/null || true
fi
log_info "Phase 2 completed!"
log_warn "Please update imports and test the app before proceeding to Phase 3"
}
phase3_features() {
log_info "=== Phase 3: Feature-based Organization ==="
log_warn "Phase 3 requires manual work and careful testing"
log_info "This phase will create feature structure but won't move files automatically"
# Create feature directories structure
mkdir -p "${LIB_DIR}/features"
# List of features to create
local features=(
"auth:login,otp,create_pass"
"home"
"voucher"
"transaction"
"campaign:campaign7day,quiz_campaign,register_campaign,invite_friend_campaign"
"payment:electric_payment,mobile_card,topup,traffic_service"
"point:history_point,history_point_cashback,daily_checkin"
"profile:personal,setting,change_pass,delete_account,biometric"
"notification"
"membership"
"affiliate:affiliate,affiliate_brand_detail"
"game:game,vplay_game_center"
"support:support,faqs,news"
"common:splash,onboarding,webview,qr_code,popup_manager,main_tab_screen"
)
for feature_info in "${features[@]}"; do
IFS=':' read -r feature_name subfeatures <<< "$feature_info"
log_info "Creating structure for feature: ${feature_name}"
mkdir -p "${LIB_DIR}/features/${feature_name}/data/models"
mkdir -p "${LIB_DIR}/features/${feature_name}/data/repositories"
mkdir -p "${LIB_DIR}/features/${feature_name}/data/datasources"
mkdir -p "${LIB_DIR}/features/${feature_name}/domain/entities"
mkdir -p "${LIB_DIR}/features/${feature_name}/domain/repositories"
mkdir -p "${LIB_DIR}/features/${feature_name}/domain/usecases"
mkdir -p "${LIB_DIR}/features/${feature_name}/presentation/screens"
mkdir -p "${LIB_DIR}/features/${feature_name}/presentation/viewmodels"
mkdir -p "${LIB_DIR}/features/${feature_name}/presentation/widgets"
# Create placeholder README
cat > "${LIB_DIR}/features/${feature_name}/README.md" <<EOF
# ${feature_name^} Feature
This feature contains:
- \`data/\`: Data models, repositories, and data sources
- \`domain/\`: Business entities and use cases
- \`presentation/\`: UI screens, viewmodels, and widgets
## Migration Notes
Move related screens from \`lib/screen/\` to this feature's \`presentation/screens/\` directory.
EOF
done
log_info "Feature structures created!"
log_warn "Now you need to manually move screens from lib/screen/ to appropriate features"
log_warn "Example: lib/screen/login/ -> lib/features/auth/presentation/screens/login/"
}
update_imports() {
log_info "=== Updating Imports ==="
log_warn "This is a placeholder. You need to update imports manually or use find/replace"
log_info "Common import changes:"
echo " - configs/ -> core/config/"
echo " - utils/ -> core/utils/"
echo " - shared/ -> core/utils/"
echo " - resources/ -> core/theme/"
echo " - networking/ -> core/network/"
echo " - services/ -> core/services/"
echo " - preference/ -> core/storage/"
echo " - web/ -> core/platform/web/"
echo " - firebase/ -> core/platform/firebase/"
}
main() {
local phase="${1:-}"
if [ -z "$phase" ]; then
echo "Usage: $0 [phase1|phase2|phase3|all]"
echo ""
echo "Phases:"
echo " phase1 - Core reorganization (low risk)"
echo " phase2 - Network & Services (medium risk)"
echo " phase3 - Feature-based structure (high risk, manual)"
echo " all - Run all phases (NOT RECOMMENDED)"
exit 1
fi
log_info "Starting migration to new structure..."
log_warn "Make sure you have committed your changes before running this script!"
read -p "Continue? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log_info "Migration cancelled"
exit 0
fi
create_backup
log_info "Backup created at: ${BACKUP_DIR}"
case "$phase" in
phase1)
create_directories 1
phase1_core_reorganization
;;
phase2)
create_directories 2
phase2_network_services
;;
phase3)
create_directories 3
phase3_features
;;
all)
log_error "Running all phases at once is NOT RECOMMENDED!"
log_error "Please run each phase separately and test after each one."
exit 1
;;
*)
log_error "Invalid phase: $phase"
exit 1
;;
esac
log_info "Migration completed!"
log_info "Backup location: ${BACKUP_DIR}"
log_warn "Next steps:"
log_warn "1. Update imports in your code"
log_warn "2. Run 'flutter pub get'"
log_warn "3. Test the app thoroughly"
log_warn "4. If something breaks, restore from backup"
}
main "$@"
...@@ -120,7 +120,8 @@ class CORSRequestHandler(http.server.SimpleHTTPRequestHandler): ...@@ -120,7 +120,8 @@ class CORSRequestHandler(http.server.SimpleHTTPRequestHandler):
self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept, Origin') self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept, Origin')
# Add cache headers for static assets to speed up localhost loading # Add cache headers for static assets to speed up localhost loading
if self.path.endswith(('.js', '.wasm', '.css', '.woff2', '.png', '.jpg', '.svg')): cacheable_exts = ('.js', '.wasm', '.css', '.woff2', '.png', '.jpg', '.jpeg', '.svg', '.webp')
if self.path.endswith(cacheable_exts):
self.send_header('Cache-Control', 'public, max-age=3600') self.send_header('Cache-Control', 'public, max-age=3600')
super().end_headers() super().end_headers()
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment