From 500a77761fcf7236e8eaad967cb34ead11ab1b87 Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Sun, 21 Dec 2025 19:38:40 +0100 Subject: [PATCH] allow download raw project as json --- hipsterfy/hipsterfy.py | 10 +++++++++- hipsterfy/panel_page.py | 43 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/hipsterfy/hipsterfy.py b/hipsterfy/hipsterfy.py index 11b919a..8787939 100644 --- a/hipsterfy/hipsterfy.py +++ b/hipsterfy/hipsterfy.py @@ -391,7 +391,10 @@ class HipsterfyPlaylistItem(object): class HipsterfyPlaylist(object): - def __init__(self, playlist_uri, hipsterfy:Hipsterfy): + def __init__(self, playlist_uri: str, hipsterfy:Hipsterfy, serialized_raw_data:List[Dict[str, Any]] = None ): + if serialized_raw_data: + self._tracks_data = [HipsterfyPlaylistItem(item) for item in serialized_raw_data] + return self._playlist_uri = playlist_uri self._hipsterfy = hipsterfy self._tracks_data = [] @@ -416,3 +419,8 @@ class HipsterfyPlaylist(object): """Returns the list of HipsterfyPlaylistItem objects representing the tracks in the playlist. """ return self._tracks_data + def get_raw_playlist_data(self) -> List[Dict[str, Any]]: + """Returns the raw playlist data as a list of dictionaries. + """ + return [item._raw_data for item in self._tracks_data] + diff --git a/hipsterfy/panel_page.py b/hipsterfy/panel_page.py index c30a39e..eed13ed 100644 --- a/hipsterfy/panel_page.py +++ b/hipsterfy/panel_page.py @@ -4,6 +4,7 @@ from io import BytesIO from hipsterfy.hipsterfy import Hipsterfy, HipsterfyPlaylist, HipsterfyPlaylistItem, CardStyle import qrcode +pn.extension("filedropper") pn.extension("filedownload", "notifications", "location") @@ -12,13 +13,25 @@ pn.extension("filedownload", "notifications", "location") def create_panel_page(hipsterfy: Hipsterfy, playlist_uri: str=None, root_url = None) -> pn.Template: # create widgets + mode = pn.widgets.RadioButtonGroup(name='Input Mode', options=['Playlist URI', 'Upload JSON'], button_type='success', value='Playlist URI', sizing_mode='stretch_width') playlist_uri = pn.widgets.TextInput(name='Playlist URI', value=playlist_uri or '', placeholder='Enter Spotify Playlist URI') + json_upload = pn.widgets.FileDropper(name='Upload Playlist JSON', accepted_filetypes=['application/json'], sizing_mode='stretch_width') primary_item = pn.widgets.Select(name='Primary Item', options=HipsterfyPlaylistItem.get_available_properties(), value="release_year") secondary_item = pn.widgets.Select(name='Secondary Item', options=HipsterfyPlaylistItem.get_available_properties(), value="title") additional_items = pn.widgets.MultiChoice(name='Additional Items', options=HipsterfyPlaylistItem.get_available_properties(), value=["artists", "album", "popularity"]) card_style = pn.widgets.Select(name='Card Style', options=[style.value for style in CardStyle], value=CardStyle.FULL_ALBUM_COVER.value) cards_per_row_widget = pn.widgets.IntSlider(name="Cards per Row in print", start=1, end=8, value=4, step=1) + def on_mode_change(event): + if mode.value == 'Playlist URI': + playlist_uri.visible = True + json_upload.visible = False + else: + playlist_uri.visible = False + json_upload.visible = True + mode.param.watch(on_mode_change, 'value') + on_mode_change(None) # Initial call to set visibility + pn.state.location.sync(playlist_uri, {"value": "playlist_uri"}) pn.state.location.sync(primary_item, {"value": "primary_item"}) pn.state.location.sync(secondary_item, {"value": "secondary_item"}) @@ -39,6 +52,10 @@ def create_panel_page(hipsterfy: Hipsterfy, playlist_uri: str=None, root_url = N label="Download Back Cards (HTML)", button_type="success", visible=False, filename="hipsterfy_back_cards.html", sizing_mode='stretch_width' ) + download_json_data_button = pn.widgets.FileDownload( + label="Download Playlist Data (JSON)", button_type="success", visible=False, filename="hipsterfy_raw_playlist_data.json", sizing_mode='stretch_width' + ) + playlist_instructions = pn.pane.HTML( """
@@ -110,6 +127,16 @@ def create_panel_page(hipsterfy: Hipsterfy, playlist_uri: str=None, root_url = N download_back_html_button.callback = lambda: BytesIO(generate_html(cards_back, row_reverse=True)) download_back_html_button.visible = True + # Raw Playlist Data as JSON + def generate_playlist_json(): + import json + uri = playlist_uri.value.strip() + hipsterfy_playlist = HipsterfyPlaylist(uri, hipsterfy) + data = hipsterfy_playlist.get_raw_playlist_data() + return json.dumps(data, indent=4).encode() # FileDownload erwartet Bytes + download_json_data_button.callback = lambda: BytesIO(generate_playlist_json()) + download_json_data_button.visible = True + print_instructions.visible = True # Show print instructions after generating preview pn.state.notifications.success("Download buttons updated successfully!. Download and print them from the sidebar") @@ -135,7 +162,15 @@ def create_panel_page(hipsterfy: Hipsterfy, playlist_uri: str=None, root_url = N return # create HipsterfyPlaylist instance - hipsterfy_playlist = HipsterfyPlaylist(uri, hipsterfy) + if mode.value == 'Upload JSON': + if len(json_upload.value) == 0: + pn.state.notifications.error("Please upload a valid JSON file containing playlist data.") + return + import json + raw_data = json.loads(list(json_upload.value.values())[0].decode()) + hipsterfy_playlist = HipsterfyPlaylist(uri, hipsterfy, serialized_raw_data=raw_data) + else: + hipsterfy_playlist = HipsterfyPlaylist(uri, hipsterfy) new_back_cards = [] new_front_cards = [] @@ -168,6 +203,8 @@ def create_panel_page(hipsterfy: Hipsterfy, playlist_uri: str=None, root_url = N create_preview_button.on_click(create_preview) + + # Dynamischer QR-Code für die Player-App def generate_qr_code_base64(url): qr = qrcode.QRCode(box_size=4, border=2) @@ -199,9 +236,9 @@ def create_panel_page(hipsterfy: Hipsterfy, playlist_uri: str=None, root_url = N title='Hipsterfy', sidebar=[ player_qr_pane, # <-- Dynamischer QR-Code ganz oben in der Sidebar - playlist_instructions, playlist_uri, primary_item, secondary_item, additional_items, card_style, + playlist_instructions, mode, json_upload, playlist_uri, primary_item, secondary_item, additional_items, card_style, cards_per_row_widget, - create_preview_button, download_front_html_button, download_back_html_button, print_instructions + create_preview_button, download_front_html_button, download_back_html_button, download_json_data_button, print_instructions ], main=[pn.Row(front_cards_column, back_cards_column, sizing_mode='stretch_width')], accent_base_color='indigo',