feat: integrate web support
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
import threading
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from ..messages.message_pusher import MessagePusher
|
||||
@@ -10,11 +11,15 @@ from .platform_handlers import get_platform_info
|
||||
from .stream_manager import LiveStreamRecorder
|
||||
|
||||
|
||||
class GlobalRecordingState:
|
||||
recordings = []
|
||||
lock = threading.Lock()
|
||||
|
||||
|
||||
class RecordingManager:
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.settings = app.settings
|
||||
self.recordings = []
|
||||
self.periodic_task_started = False
|
||||
self.loop_time_seconds = None
|
||||
self.app.language_manager.add_observer(self)
|
||||
@@ -23,6 +28,14 @@ class RecordingManager:
|
||||
self.load()
|
||||
self.initialize_dynamic_state()
|
||||
|
||||
@property
|
||||
def recordings(self):
|
||||
return GlobalRecordingState.recordings
|
||||
|
||||
@recordings.setter
|
||||
def recordings(self, value):
|
||||
raise AttributeError("Please use add_recording/update_recording methods to modify data")
|
||||
|
||||
def load(self):
|
||||
language = self.app.language_manager.language
|
||||
for key in ("recording_manager", "video_quality"):
|
||||
@@ -31,7 +44,8 @@ class RecordingManager:
|
||||
def load_recordings(self):
|
||||
"""Load recordings from a JSON file into objects."""
|
||||
recordings_data = self.app.config_manager.load_recordings_config()
|
||||
self.recordings = [Recording.from_dict(rec) for rec in recordings_data]
|
||||
if not GlobalRecordingState.recordings:
|
||||
GlobalRecordingState.recordings = [Recording.from_dict(rec) for rec in recordings_data]
|
||||
logger.info(f"Live Recordings: Loaded {len(self.recordings)} items")
|
||||
|
||||
def initialize_dynamic_state(self):
|
||||
@@ -42,11 +56,31 @@ class RecordingManager:
|
||||
recording.loop_time_seconds = self.loop_time_seconds
|
||||
recording.update_title(self._[recording.quality])
|
||||
|
||||
async def update_recording(self, recording: Recording, updated_info: dict):
|
||||
async def add_recording(self, recording):
|
||||
with GlobalRecordingState.lock:
|
||||
GlobalRecordingState.recordings.append(recording)
|
||||
await self.persist_recordings()
|
||||
|
||||
async def remove_recording(self, recording: Recording):
|
||||
with GlobalRecordingState.lock:
|
||||
GlobalRecordingState.recordings.remove(recording)
|
||||
await self.persist_recordings()
|
||||
|
||||
async def clear_all_recordings(self):
|
||||
with GlobalRecordingState.lock:
|
||||
GlobalRecordingState.recordings.clear()
|
||||
await self.persist_recordings()
|
||||
|
||||
async def persist_recordings(self):
|
||||
"""Persist recordings to a JSON file."""
|
||||
data_to_save = [rec.to_dict() for rec in self.recordings]
|
||||
await self.app.config_manager.save_recordings_config(data_to_save)
|
||||
|
||||
async def update_recording_card(self, recording: Recording, updated_info: dict):
|
||||
"""Update an existing recording object and persist changes to a JSON file."""
|
||||
if recording:
|
||||
recording.update(updated_info)
|
||||
self.app.page.run_task(self.save_to_json)
|
||||
self.app.page.run_task(self.persist_recordings)
|
||||
|
||||
@staticmethod
|
||||
async def _update_recording(
|
||||
@@ -74,9 +108,10 @@ class RecordingManager:
|
||||
selected=False,
|
||||
)
|
||||
self.app.page.run_task(self.check_if_live, recording)
|
||||
self.app.page.run_task(self.app.record_card_manager.update_cards, recording)
|
||||
self.app.page.run_task(self.app.record_card_manager.update_card, recording)
|
||||
self.app.page.pubsub.send_others_on_topic("update", recording)
|
||||
if auto_save:
|
||||
self.app.page.run_task(self.save_to_json)
|
||||
self.app.page.run_task(self.persist_recordings)
|
||||
|
||||
async def stop_monitor_recording(self, recording: Recording, auto_save: bool = True):
|
||||
"""
|
||||
@@ -91,9 +126,10 @@ class RecordingManager:
|
||||
selected=False,
|
||||
)
|
||||
self.stop_recording(recording)
|
||||
self.app.page.run_task(self.app.record_card_manager.update_cards, recording)
|
||||
self.app.page.run_task(self.app.record_card_manager.update_card, recording)
|
||||
self.app.page.pubsub.send_others_on_topic("update", recording)
|
||||
if auto_save:
|
||||
self.app.page.run_task(self.save_to_json)
|
||||
self.app.page.run_task(self.persist_recordings)
|
||||
|
||||
async def start_monitor_recordings(self):
|
||||
"""
|
||||
@@ -105,7 +141,7 @@ class RecordingManager:
|
||||
for recording in pre_start_monitor_recordings:
|
||||
if cards_obj[recording.rec_id]["card"].visible:
|
||||
self.app.page.run_task(self.start_monitor_recording, recording, auto_save=False)
|
||||
self.app.page.run_task(self.save_to_json)
|
||||
self.app.page.run_task(self.persist_recordings)
|
||||
logger.info(f"Batch Start Monitor Recordings: {[i.rec_id for i in pre_start_monitor_recordings]}")
|
||||
|
||||
async def stop_monitor_recordings(self, selected_recordings: list[Recording | None] | None = None):
|
||||
@@ -119,19 +155,18 @@ class RecordingManager:
|
||||
for recording in pre_stop_monitor_recordings:
|
||||
if cards_obj[recording.rec_id]["card"].visible:
|
||||
self.app.page.run_task(self.stop_monitor_recording, recording, auto_save=False)
|
||||
self.app.page.run_task(self.save_to_json)
|
||||
self.app.page.run_task(self.persist_recordings)
|
||||
logger.info(f"Batch Stop Monitor Recordings: {[i.rec_id for i in pre_stop_monitor_recordings]}")
|
||||
|
||||
async def get_selected_recordings(self):
|
||||
return [recording for recording in self.recordings if recording.selected]
|
||||
|
||||
def remove_recordings(self, recordings: list[Recording]):
|
||||
async def remove_recordings(self, recordings: list[Recording]):
|
||||
"""Remove a recording from the list and update the JSON file."""
|
||||
for recording in recordings:
|
||||
if recording in self.recordings:
|
||||
self.recordings.remove(recording)
|
||||
await self.remove_recording(recording)
|
||||
logger.info(f"Delete Items: {recording.rec_id}-{recording.streamer_name}")
|
||||
self.app.page.run_task(self.save_to_json)
|
||||
|
||||
def find_recording_by_id(self, rec_id: str):
|
||||
"""Find a recording by its ID (hash of dict representation)."""
|
||||
@@ -140,11 +175,6 @@ class RecordingManager:
|
||||
return rec
|
||||
return None
|
||||
|
||||
async def save_to_json(self):
|
||||
"""Persist recordings to a JSON file."""
|
||||
recordings_data = [rec.to_dict() for rec in self.recordings]
|
||||
await self.app.config_manager.save_recordings_config(recordings_data)
|
||||
|
||||
async def check_all_live_status(self):
|
||||
"""Check the live status of all recordings and update their display titles."""
|
||||
for recording in self.recordings:
|
||||
@@ -264,7 +294,8 @@ class RecordingManager:
|
||||
self.start_update(recording)
|
||||
self.app.page.run_task(recorder.start_recording, stream_info)
|
||||
|
||||
self.app.page.run_task(self.app.record_card_manager.update_cards, recording)
|
||||
self.app.page.run_task(self.app.record_card_manager.update_card, recording)
|
||||
self.app.page.pubsub.send_others_on_topic("update", recording)
|
||||
|
||||
else:
|
||||
recording.status_info = RecordingStatus.MONITORING
|
||||
@@ -278,8 +309,9 @@ class RecordingManager:
|
||||
"display_title": title,
|
||||
}
|
||||
)
|
||||
self.app.page.run_task(self.app.record_card_manager.update_cards, recording)
|
||||
self.app.page.run_task(self.save_to_json)
|
||||
self.app.page.run_task(self.app.record_card_manager.update_card, recording)
|
||||
self.app.page.pubsub.send_others_on_topic("update", recording)
|
||||
self.app.page.run_task(self.persist_recordings)
|
||||
|
||||
@staticmethod
|
||||
def start_update(recording: Recording):
|
||||
@@ -323,8 +355,9 @@ class RecordingManager:
|
||||
return str(total_duration).split(".")[0]
|
||||
|
||||
async def delete_recording_cards(self, recordings: list[Recording]):
|
||||
self.remove_recordings(recordings)
|
||||
self.app.page.run_task(self.app.record_card_manager.remove_recording_card, recordings)
|
||||
self.app.page.pubsub.send_others_on_topic('delete', recordings)
|
||||
await self.remove_recordings(recordings)
|
||||
|
||||
async def check_free_space(self, output_dir: str | None = None):
|
||||
disk_space_limit = float(self.settings.user_config.get("recording_space_threshold"))
|
||||
@@ -342,4 +375,4 @@ class RecordingManager:
|
||||
)
|
||||
|
||||
else:
|
||||
self.app.recording_enabled = True
|
||||
self.app.recording_enabled = True
|
||||
|
||||
@@ -93,7 +93,7 @@ class LiveStreamRecorder:
|
||||
output_dir = os.path.join(output_dir, f"{now[:10]}_{live_title}")
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
self.recording.recording_dir = output_dir
|
||||
self.app.page.run_task(self.app.record_manager.save_to_json)
|
||||
self.app.page.run_task(self.app.record_manager.persist_recordings)
|
||||
return output_dir
|
||||
|
||||
def _get_save_path(self, filename: str) -> str:
|
||||
@@ -225,18 +225,19 @@ class LiveStreamRecorder:
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
return_code = process.returncode
|
||||
safe_return_code = [0, 255]
|
||||
stdout, stderr = await process.communicate()
|
||||
if stderr:
|
||||
if return_code not in safe_return_code and stderr:
|
||||
logger.error(f"FFmpeg Stderr Output: {str(stderr.decode()).splitlines()[0]}")
|
||||
self.recording.status_info = RecordingStatus.RECORDING_ERROR
|
||||
self.app.record_manager.stop_recording(self.recording)
|
||||
await self.app.record_card_manager.update_cards(self.recording)
|
||||
await self.app.record_card_manager.update_card(self.recording)
|
||||
self.app.page.pubsub.send_others_on_topic("update", self.recording)
|
||||
await self.app.snack_bar.show_snack_bar(
|
||||
record_name + " " + self._["record_stream_error"], duration=2000
|
||||
)
|
||||
|
||||
return_code = process.returncode
|
||||
safe_return_code = [0, 255]
|
||||
if return_code in safe_return_code:
|
||||
if self.recording.monitor_status:
|
||||
self.recording.status_info = RecordingStatus.MONITORING
|
||||
@@ -253,7 +254,8 @@ class LiveStreamRecorder:
|
||||
logger.success(f"Live recording completed: {record_name}")
|
||||
|
||||
self.recording.update({"display_title": display_title})
|
||||
self.app.page.run_task(self.app.record_card_manager.update_cards, self.recording)
|
||||
await self.app.record_card_manager.update_card(self.recording)
|
||||
self.app.page.pubsub.send_others_on_topic("update", self.recording)
|
||||
if self.app.recording_enabled and process in self.app.process_manager.ffmpeg_processes:
|
||||
self.app.page.run_task(self.app.record_manager.check_if_live, self.recording)
|
||||
else:
|
||||
@@ -400,6 +402,6 @@ class LiveStreamRecorder:
|
||||
"qiandurebo": "referer:https://qiandurebo.com",
|
||||
"17live": "referer:https://17.live/en/live/6302408",
|
||||
"lang": "referer:https://www.lang.live",
|
||||
"shopee": f"origin:{live_domain}",
|
||||
"shopee": "origin:" + live_domain,
|
||||
}
|
||||
return record_headers.get(platform_key)
|
||||
return record_headers.get(platform_key)
|
||||
|
||||
@@ -20,12 +20,17 @@ class RecordingCardManager:
|
||||
self.app.language_manager.add_observer(self)
|
||||
self._ = {}
|
||||
self.load()
|
||||
self.pubsub_subscribe()
|
||||
|
||||
def load(self):
|
||||
language = self.app.language_manager.language
|
||||
for key in ("recording_card", "recording_manager", "base", "home_page", "video_quality"):
|
||||
self._.update(language.get(key, {}))
|
||||
|
||||
def pubsub_subscribe(self):
|
||||
self.app.page.pubsub.subscribe_topic("update", self.subscribe_update_card)
|
||||
self.app.page.pubsub.subscribe_topic("delete", self.subscribe_remove_cards)
|
||||
|
||||
async def create_card(self, recording: Recording):
|
||||
"""Create a card for a given recording."""
|
||||
rec_id = recording.rec_id
|
||||
@@ -124,7 +129,7 @@ class RecordingCardManager:
|
||||
"monitor_button": monitor_button,
|
||||
}
|
||||
|
||||
async def update_cards(self, recording):
|
||||
async def update_card(self, recording):
|
||||
"""Update only the recordings cards in the scrollable content area."""
|
||||
if recording.rec_id in self.cards_obj:
|
||||
recording_card = self.cards_obj[recording.rec_id]
|
||||
@@ -161,8 +166,10 @@ class RecordingCardManager:
|
||||
)
|
||||
self.app.page.run_task(self.app.record_manager.check_if_live, recording)
|
||||
self.app.page.run_task(self.app.snack_bar.show_snack_bar, self._["start_monitor_tip"], ft.Colors.GREEN)
|
||||
await self.update_cards(recording)
|
||||
self.app.page.run_task(self.app.record_manager.save_to_json)
|
||||
|
||||
await self.update_card(recording)
|
||||
self.app.page.pubsub.send_others_on_topic("update", recording)
|
||||
self.app.page.run_task(self.app.record_manager.persist_recordings)
|
||||
|
||||
async def show_recording_info_dialog(self, recording: Recording):
|
||||
"""Display a dialog with detailed information about the recording."""
|
||||
@@ -175,10 +182,11 @@ class RecordingCardManager:
|
||||
recording = recording_list[0]
|
||||
rec_id = recording["rec_id"]
|
||||
recording_obj = self.app.record_manager.find_recording_by_id(rec_id)
|
||||
await self.app.record_manager.update_recording(recording_obj, updated_info=recording)
|
||||
await self.app.record_manager.update_recording_card(recording_obj, updated_info=recording)
|
||||
if not recording["monitor_status"]:
|
||||
recording_obj.display_title = f"[{self._['monitor_stopped']}] " + recording_obj.title
|
||||
await self.update_cards(recording_obj)
|
||||
await self.update_card(recording_obj)
|
||||
self.app.page.pubsub.send_others_on_topic("update", recording)
|
||||
|
||||
async def on_toggle_recording(self, recording: Recording):
|
||||
"""Toggle the recording state for a specific recording."""
|
||||
@@ -197,7 +205,8 @@ class RecordingCardManager:
|
||||
else:
|
||||
await self.app.snack_bar.show_snack_bar(self._["please_start_monitor_tip"])
|
||||
|
||||
await self.update_cards(recording)
|
||||
await self.update_card(recording)
|
||||
self.app.page.pubsub.send_others_on_topic("update", recording)
|
||||
|
||||
async def on_delete_recording(self, recording: Recording):
|
||||
"""Delete a recording from the list and update UI."""
|
||||
@@ -212,11 +221,27 @@ class RecordingCardManager:
|
||||
|
||||
async def remove_recording_card(self, recordings: list[Recording]):
|
||||
home_page = self.app.current_page
|
||||
for recording in recordings:
|
||||
if recording.rec_id in self.cards_obj:
|
||||
card = self.cards_obj[recording.rec_id]["card"]
|
||||
home_page.recording_card_area.controls.remove(card)
|
||||
del self.cards_obj[recording.rec_id]
|
||||
|
||||
existing_ids = {rec.rec_id for rec in self.app.record_manager.recordings}
|
||||
remove_ids = {rec.rec_id for rec in recordings}
|
||||
keep_ids = existing_ids - remove_ids
|
||||
|
||||
cards_to_remove = [
|
||||
card_data["card"]
|
||||
for rec_id, card_data in self.cards_obj.items()
|
||||
if rec_id not in keep_ids
|
||||
]
|
||||
|
||||
home_page.recording_card_area.controls = [
|
||||
control
|
||||
for control in home_page.recording_card_area.controls
|
||||
if control not in cards_to_remove
|
||||
]
|
||||
|
||||
self.cards_obj = {
|
||||
k: v for k, v in self.cards_obj.items()
|
||||
if k in keep_ids
|
||||
}
|
||||
home_page.recording_card_area.update()
|
||||
|
||||
@staticmethod
|
||||
@@ -318,3 +343,9 @@ class RecordingCardManager:
|
||||
|
||||
async def recording_card_on_click(self, _, recording: Recording):
|
||||
await self.on_card_click(recording)
|
||||
|
||||
async def subscribe_update_card(self, _, recording: Recording):
|
||||
await self.update_card(recording)
|
||||
|
||||
async def subscribe_remove_cards(self, _, recordings: list[Recording]):
|
||||
await self.remove_recording_card(recordings)
|
||||
|
||||
@@ -28,6 +28,7 @@ class HomePage(PageBase):
|
||||
def init(self):
|
||||
self.recording_card_area = ft.Column(controls=[], spacing=10, expand=True)
|
||||
self.add_recording_dialog = RecordingDialog(self.app, self.add_recording)
|
||||
self.pubsub_subscribe()
|
||||
|
||||
async def load(self):
|
||||
"""Load the home page content."""
|
||||
@@ -37,6 +38,10 @@ class HomePage(PageBase):
|
||||
self.page.run_task(self.show_all_cards)
|
||||
self.page.on_keyboard_event = self.on_keyboard
|
||||
|
||||
def pubsub_subscribe(self):
|
||||
self.app.page.pubsub.subscribe_topic('add', self.subscribe_add_cards)
|
||||
self.app.page.pubsub.subscribe_topic('delete_all', self.subscribe_del_all_cards)
|
||||
|
||||
def create_home_title_area(self):
|
||||
return ft.Row(
|
||||
[
|
||||
@@ -161,10 +166,9 @@ class HomePage(PageBase):
|
||||
|
||||
recording.loop_time_seconds = int(user_config.get("loop_time_seconds", 300))
|
||||
recording.update_title(self._[recording.quality])
|
||||
self.app.record_manager.recordings.append(recording)
|
||||
await self.app.record_manager.add_recording(recording)
|
||||
self.page.run_task(self.add_record_card, recording, True)
|
||||
|
||||
self.page.run_task(self.app.record_manager.save_to_json)
|
||||
self.app.page.pubsub.send_others_on_topic("add", recording)
|
||||
await self.app.snack_bar.show_snack_bar(self._["add_recording_success_tip"], bgcolor=ft.Colors.GREEN)
|
||||
|
||||
async def search_on_click(self, _e):
|
||||
@@ -203,15 +207,15 @@ class HomePage(PageBase):
|
||||
tips = self._["batch_delete_confirm_tip"] if selected_recordings else self._["clear_all_confirm_tip"]
|
||||
|
||||
async def confirm_dlg(_):
|
||||
|
||||
if selected_recordings:
|
||||
await self.app.record_manager.stop_monitor_recordings(selected_recordings)
|
||||
await self.app.record_manager.delete_recording_cards(selected_recordings)
|
||||
else:
|
||||
await self.app.record_manager.stop_monitor_recordings(self.app.record_manager.recordings)
|
||||
self.app.record_manager.recordings = []
|
||||
self.recording_card_area.controls.clear()
|
||||
self.app.record_card_manager.cards_obj = {}
|
||||
self.page.run_task(self.app.record_manager.save_to_json)
|
||||
await self.app.record_manager.clear_all_recordings()
|
||||
await self.delete_all_recording_cards()
|
||||
self.app.page.pubsub.send_others_on_topic("delete_all", None)
|
||||
|
||||
self.recording_card_area.update()
|
||||
await self.app.snack_bar.show_snack_bar(
|
||||
@@ -238,6 +242,17 @@ class HomePage(PageBase):
|
||||
self.app.dialog_area.content = batch_delete_alert_dialog
|
||||
self.page.update()
|
||||
|
||||
async def delete_all_recording_cards(self):
|
||||
self.recording_card_area.controls.clear()
|
||||
self.recording_card_area.update()
|
||||
self.app.record_card_manager.cards_obj = {}
|
||||
|
||||
async def subscribe_del_all_cards(self, *_):
|
||||
await self.delete_all_recording_cards()
|
||||
|
||||
async def subscribe_add_cards(self, _, recording):
|
||||
await self.add_record_card(recording, True)
|
||||
|
||||
async def on_keyboard(self, e: ft.KeyboardEvent):
|
||||
if e.alt and e.key == "H":
|
||||
self.app.dialog_area.content = HelpDialog(self.app)
|
||||
|
||||
@@ -135,12 +135,19 @@ def jsonp_to_json(jsonp_str: str) -> OptionalDict:
|
||||
|
||||
|
||||
def open_folder(directory_path):
|
||||
if sys.platform == "win32":
|
||||
os.startfile(directory_path)
|
||||
elif sys.platform == "darwin":
|
||||
subprocess.run(["open", directory_path])
|
||||
else:
|
||||
subprocess.run(["xdg-open", directory_path])
|
||||
try:
|
||||
if sys.platform == "win32":
|
||||
os.startfile(directory_path)
|
||||
elif sys.platform == "darwin":
|
||||
subprocess.run(["open", directory_path], check=True)
|
||||
else:
|
||||
subprocess.run(["xdg-open", directory_path], check=True)
|
||||
except FileNotFoundError:
|
||||
logger.error(f"Unable to open folder. The command may not be available on this system.")
|
||||
except subprocess.CalledProcessError:
|
||||
logger.error(f"Failed to open folder '{directory_path}'. Please ensure the path is valid and accessible.")
|
||||
except Exception as e:
|
||||
logger.error(f"An unexpected error occurred: {e}")
|
||||
|
||||
|
||||
def add_hours_to_time(time_str, hours_to_add):
|
||||
|
||||
BIN
assets/favicon.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
assets/icons/Appicon.icns
Normal file
BIN
assets/icons/app_icon.png
Normal file
|
After Width: | Height: | Size: 537 KiB |
BIN
assets/icons/icon.iconset/icon_128x128.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
assets/icons/icon.iconset/icon_128x128@2x.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/icons/icon.iconset/icon_16x16.png
Normal file
|
After Width: | Height: | Size: 750 B |
BIN
assets/icons/icon.iconset/icon_16x16@2x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/icons/icon.iconset/icon_256x256.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/icons/icon.iconset/icon_256x256@2x.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
assets/icons/icon.iconset/icon_32x32.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/icons/icon.iconset/icon_32x32@2x.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
assets/icons/icon.iconset/icon_512x512.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
assets/icons/icon.iconset/icon_512x512@2x.png
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
assets/icons/loading-animation.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
33
main.py
@@ -1,4 +1,4 @@
|
||||
|
||||
import argparse
|
||||
import multiprocessing
|
||||
import os
|
||||
|
||||
@@ -39,6 +39,9 @@ def main(page: ft.Page):
|
||||
else:
|
||||
page.go("/")
|
||||
|
||||
def disconnect(_):
|
||||
page.pubsub.unsubscribe_all()
|
||||
|
||||
async def on_window_event(e):
|
||||
if e.data == "close":
|
||||
progress_overlay.show()
|
||||
@@ -48,11 +51,37 @@ def main(page: ft.Page):
|
||||
|
||||
page.window.prevent_close = True
|
||||
page.window.on_event = on_window_event
|
||||
|
||||
page.on_route_change = route_change
|
||||
page.window.to_front()
|
||||
page.skip_task_bar = True
|
||||
page.always_on_top = True
|
||||
page.focused = True
|
||||
if os.getenv('PLATFORM') == "web":
|
||||
page.on_disconnect = disconnect
|
||||
|
||||
page.update()
|
||||
route_change(ft.RouteChangeEvent(route=page.route))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser(description="Run the Flet app with optional web mode.")
|
||||
parser.add_argument("--web", action="store_true", help="Run the app in web mode")
|
||||
parser.add_argument("--host", type=str, default="127.0.0.1",
|
||||
help="Host address for the web server (default: 127.0.0.1)")
|
||||
parser.add_argument("--port", type=int, default=6006, help="Port number for the web server (default: 6006)")
|
||||
args = parser.parse_args()
|
||||
|
||||
multiprocessing.freeze_support()
|
||||
ft.app(target=main, assets_dir="assets")
|
||||
if args.web or os.getenv('PLATFORM') == "web":
|
||||
platform = "web"
|
||||
ft.app(
|
||||
target=main,
|
||||
view=ft.AppView.WEB_BROWSER,
|
||||
host=args.host,
|
||||
port=args.port,
|
||||
assets_dir="assets"
|
||||
)
|
||||
else:
|
||||
ft.app(target=main, assets_dir="assets")
|
||||
|
||||