From 3d4c06a4450c7f28547a4478f10c5c0907aff013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20M=C3=A9doc?= Date: Sun, 4 Aug 2024 21:45:39 +0200 Subject: [PATCH] =?UTF-8?q?=20feat:=20=E2=9C=A8=20Allow=20editing=20of=20a?= =?UTF-8?q?ll=20onboarding=20pages=20(#471)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 🐝 Only pass value to Custom page * refactor: đŸ“Ļ Move onboarding interface under api folder * feat: 🎉 Add backend for fixed onboarding page * feat: ✨ Add variable functionality to markdown editor * feat: 🎊 Update onboarding store to handle fixed onboarding pages * feat: 🚀 Replace static onboarding page with markdown ones * refactor: đŸ“Ļ Remove unused download buttons * fix: 🐛 Error inte onboarding api for fixed pages * fix: 🐝 Variable replacement in help pages * fix: 🚑 Don't make Request help page editable * feat: 🎉 Create migration file for fixed onboarding pages * feat: 🎊 Make it possible to reorder all onboarding pages * fix: 🩹 Fix onboarding migration * fix: 🐛 Correct onboarding model * refactor: 🔧 Simplify onboarding page creation logic * Revert "refactor: đŸ“Ļ Remove unused download buttons" This reverts commit 5bb405911b047c63b82982d7a3c859fcfdfd05f1. * feat: 🚀 Add property editable to onboarding page feat: 🚀 Make it possible to show/hide onboarding pages * refactor: đŸ“Ļ Remove unused download buttons * feat: 🎉 Add Download component to onboarding pages * fix: 🚑 Fix migration for onboarding pages * feat: ✨ Make "open server"-button a link * fix: 🩹 Error when adding request service --- .../wizarr_backend/api/routes/requests_api.py | 6 + .../wizarr_backend/api/routes/settings_api.py | 23 +++- .../migrations/2024-08-02_22-27-11.py | 114 +++++++++++++++++ .../app/models/database/onboarding.py | 6 +- .../wizarr_backend/helpers/__init__.py | 1 + .../wizarr_backend/helpers/onboarding.py | 45 +++++++ .../DownloadButtons/DownloadAndroid.vue | 28 ----- .../DownloadButtons/DownloadAppStore.vue | 31 ----- .../Buttons/DownloadButtons/DownloadLinux.vue | 60 --------- .../src/modules/help/components/Custom.vue | 18 +-- .../src/modules/help/components/Download.vue | 53 ++++++-- .../modules/help/components/Emby/Download.vue | 118 ------------------ .../modules/help/components/Emby/Welcome.vue | 27 ---- .../help/components/Jellyfin/Download.vue | 118 ------------------ .../help/components/Jellyfin/Welcome.vue | 27 ---- .../modules/help/components/Plex/Download.vue | 43 ------- .../modules/help/components/Plex/Welcome.vue | 25 ---- .../src/modules/help/components/Welcome.vue | 39 ------ .../src/modules/help/router/index.ts | 11 +- .../src/modules/help/views/Help.vue | 78 ++++++------ .../settings/components/Forms/MediaForm.vue | 2 +- .../components/MDToolbars/Variables.vue | 64 ++++++++++ .../components/Modals/EditOnboarding.vue | 25 +++- .../Onboarding/OnboardingSection.vue | 60 +++++++-- .../src/modules/settings/pages/Onboarding.vue | 73 ++++++----- .../src/router/middleware/openServer.ts | 13 -- apps/wizarr-frontend/src/stores/onboarding.ts | 36 ++++-- .../onboarding/onboardingPage.ts} | 2 + 28 files changed, 492 insertions(+), 654 deletions(-) create mode 100644 apps/wizarr-backend/wizarr_backend/app/migrator/migrations/2024-08-02_22-27-11.py create mode 100644 apps/wizarr-backend/wizarr_backend/helpers/onboarding.py delete mode 100644 apps/wizarr-frontend/src/components/Buttons/DownloadButtons/DownloadAndroid.vue delete mode 100644 apps/wizarr-frontend/src/components/Buttons/DownloadButtons/DownloadAppStore.vue delete mode 100644 apps/wizarr-frontend/src/components/Buttons/DownloadButtons/DownloadLinux.vue delete mode 100644 apps/wizarr-frontend/src/modules/help/components/Emby/Download.vue delete mode 100644 apps/wizarr-frontend/src/modules/help/components/Emby/Welcome.vue delete mode 100644 apps/wizarr-frontend/src/modules/help/components/Jellyfin/Download.vue delete mode 100644 apps/wizarr-frontend/src/modules/help/components/Jellyfin/Welcome.vue delete mode 100644 apps/wizarr-frontend/src/modules/help/components/Plex/Download.vue delete mode 100644 apps/wizarr-frontend/src/modules/help/components/Plex/Welcome.vue delete mode 100644 apps/wizarr-frontend/src/modules/help/components/Welcome.vue create mode 100644 apps/wizarr-frontend/src/modules/settings/components/MDToolbars/Variables.vue delete mode 100644 apps/wizarr-frontend/src/router/middleware/openServer.ts rename apps/wizarr-frontend/src/types/{OnboardingPage.ts => api/onboarding/onboardingPage.ts} (90%) diff --git a/apps/wizarr-backend/wizarr_backend/api/routes/requests_api.py b/apps/wizarr-backend/wizarr_backend/api/routes/requests_api.py index 14a1dad6f..68b3f64c8 100644 --- a/apps/wizarr-backend/wizarr_backend/api/routes/requests_api.py +++ b/apps/wizarr-backend/wizarr_backend/api/routes/requests_api.py @@ -7,6 +7,7 @@ from datetime import datetime from app.models.database.requests import Requests +from helpers.onboarding import showRequest api = Namespace("Requests", description="Requests related operations", path="/requests") @@ -27,6 +28,7 @@ def post(self) -> tuple[dict[str, str], int]: # Create the request request_db = Requests.create(**request.form) request_db.created = datetime.utcnow() + showRequest(True) # Return the request return loads(dumps(model_to_dict(request_db), indent=4, sort_keys=True, default=str)), 200 @@ -51,6 +53,10 @@ def delete(self, requests_id: str) -> tuple[dict[str, str], int]: # Delete the request request.delete_instance() + # Check if there are no more requests in the database + if Requests.select().count() == 0: + showRequest(False) + # Responnse response = { "message": f"Request { requests_id } has been deleted" } diff --git a/apps/wizarr-backend/wizarr_backend/api/routes/settings_api.py b/apps/wizarr-backend/wizarr_backend/api/routes/settings_api.py index 21808937b..a90cd9ed9 100644 --- a/apps/wizarr-backend/wizarr_backend/api/routes/settings_api.py +++ b/apps/wizarr-backend/wizarr_backend/api/routes/settings_api.py @@ -11,6 +11,7 @@ from app.models.database.users import Users from app.models.database.invitations import Invitations from app.models.database.requests import Requests +from helpers.onboarding import populateForServerType, showDiscord api = Namespace("Settings", description="Settings related operations", path="/settings") @@ -53,6 +54,14 @@ def post(self): for key, value in settings.items(): Settings.update(key=key, value=value) + # Get the value of the 'setup' query parameter + initial_setup = request.args.get('setup') == "true" + if initial_setup and "server_type" in settings: + populateForServerType(settings["server_type"]) + + if "server_discord_id" in settings: + showDiscord(bool(settings["server_discord_id"])) + response = { key: value for key, value in settings.items() } return response, 200 @@ -71,11 +80,19 @@ def put(self): data = SettingsModel(**form) # Extract the data from the model to a dictionary - response = data.model_dump() + settings = data.model_dump() + + # Get the value of the 'setup' query parameter + initial_setup = request.args.get('setup') == "true" + if initial_setup and "server_type" in settings: + populateForServerType(settings["server_type"]) + + if "server_discord_id" in settings: + showDiscord(settings["server_discord_id"] != "") # FIXME: This will send many queries to the database # Insert the settings into the database - for key, value in response.items(): + for key, value in settings.items(): setting = Settings.get_or_none(Settings.key == key) if not setting: Settings.create(key=key, value=value) @@ -92,7 +109,7 @@ def put(self): elif value == "jellyfin" or value == "emby": Requests.delete().where(Requests.service == "overseerr").execute() - return response, 200 + return settings, 200 @api.route('/') diff --git a/apps/wizarr-backend/wizarr_backend/app/migrator/migrations/2024-08-02_22-27-11.py b/apps/wizarr-backend/wizarr_backend/app/migrator/migrations/2024-08-02_22-27-11.py new file mode 100644 index 000000000..1383aaea4 --- /dev/null +++ b/apps/wizarr-backend/wizarr_backend/app/migrator/migrations/2024-08-02_22-27-11.py @@ -0,0 +1,114 @@ +# +# CREATED ON VERSION: V4.1.1 +# MIGRATION: 2024-08-02_22-27-11 +# CREATED: Fri Aug 02 2024 +# + +from peewee import * +from playhouse.migrate import * + +from app import db + +# Do not change the name of this file, +# migrations are run in order of their filenames date and time + +def run(): + # Use migrator to perform actions on the database + migrator = SqliteMigrator(db) + + # update onboarding table with columns (needs to be recreated) + with db.transaction(): + # Step 1: Create a new table with the desired structure + db.execute_sql(""" + CREATE TABLE "onboarding_temp" ( + "id" INTEGER NOT NULL UNIQUE, + "value" TEXT, + "order" INTEGER NOT NULL, + "enabled" INTEGER NOT NULL DEFAULT 1, + "template" INTEGER, + "editable" INTEGER NOT NULL DEFAULT 1, + PRIMARY KEY("id") + ) + """) + + # Step 2: Copy data from the existing table to the new table + db.execute_sql(""" + INSERT INTO onboarding_temp ("value", "order", "enabled") + SELECT "value", "order", "enabled" FROM onboarding; + """) + + # Step 3: Drop the old table + db.execute_sql("DROP TABLE onboarding;") + + # Step 4: Rename the new table to match the old table's name + db.execute_sql("ALTER TABLE onboarding_temp RENAME TO onboarding;") + + + # populate onboarding with default values + with db.transaction(): + + add_requests = bool(db.execute_sql("SELECT EXISTS(SELECT 1 FROM requests)").fetchone()[0]) + add_discord = bool(db.execute_sql("SELECT key FROM settings WHERE key = 'server_discord_id' AND value IS NOT NULL").fetchone()) + + # Increment the order column by 2 for each onboarding row + db.execute_sql(f"UPDATE onboarding SET 'order' = 'order' + {2 + int(add_requests) + int(add_discord)}") + # Check if server_type is set in the settings table + if db.execute_sql("SELECT key FROM settings WHERE key = 'server_type'").fetchone(): + # Get the server_type + server_type = db.execute_sql("SELECT value FROM settings WHERE key = 'server_type'").fetchone()[0] + if(server_type == "plex"): + db.execute_sql(""" + INSERT INTO onboarding ("order", "template", "value") VALUES + (0, NULL, '## ℹī¸ Eh, So, What is Plex exactly? + +Great question! Plex is a software that allows individuals to share their media collections with others. If you''ve received this invitation, it means someone wants to share their library with you. + +With Plex, you''ll have access to all of the movies, TV shows, music, and photos that are stored on their server! + +So let''s see how to get started!'), + (1, 3, '## Join & Download Plex + +So you now have access to our server''s media collection. Let''s make sure you know how to use it with Plex. + +Planning on watching movies on this device?') + """) + if(server_type == "jellyfin"): + db.execute_sql(""" + INSERT INTO onboarding ("order", "template", "value") VALUES + (0, NULL, '## ℹī¸ Eh, So, What is Jellyfin exactly? + +Jellyfin is a platform that lets you stream all your favorite movies, TV shows, and music in one place. It''s like having your own personal movie theater right at your fingertips! Think of it as a digital library of your favorite content that you can access from anywhere, on any device - your phone, tablet, laptop, smart TV, you name it.? + +## đŸŋ Right, so how do I watch stuff? + +It couldn''t be simpler! Jellyfin is available on a wide variety of devices including laptops, tablets, smartphones, and TVs. All you need to do is download the Jellyfin app on your device, sign in with your account, and you''re ready to start streaming your media. It''s that easy!'), + (1, 3, '## Join & Download Jellyfin + +So you now have access to our server''s media collection. Let''s make sure you know how to use it with Jellyfin. + +Planning on watching movies on this device?') + """) + if(server_type == "emby"): + db.execute_sql(""" + INSERT INTO onboarding ("order", "template", "value") VALUES + (0, NULL, '## ℹī¸ Eh, So, What is Emby exactly? + +Emby is a platform that lets you stream all your favorite movies, TV shows, and music in one place. It''s like having your own personal movie theater right at your fingertips! Think of it as a digital library of your favorite content that you can access from anywhere, on any device - your phone, tablet, laptop, smart TV, you name it. + +## đŸŋ Right, so how do I watch stuff? + +It couldn''t be simpler! Emby is available on a wide variety of devices including laptops, tablets, smartphones, and TVs. All you need to do is download the Emby app on your device, sign in with your account, and you''re ready to start streaming your media. It''s that easy!'), + (1, 3, '## Join & Download Emby + +Great news! You now have access to our server''s media collection. Let''s make sure you know how to use it with Emby. + +Planning on watching movies on this device?') + """) + + if add_discord: + db.execute_sql("""INSERT INTO onboarding ("order", "template", "editable") VALUES (2, 1, 0)""") + if add_requests: + db.execute_sql("""INSERT INTO onboarding ("order", "template", "editable") VALUES (3, 2, 0)""") + + elif add_requests: + db.execute_sql("""INSERT INTO onboarding ("order", "template", "editable") VALUES (2, 2, 0)""") diff --git a/apps/wizarr-backend/wizarr_backend/app/models/database/onboarding.py b/apps/wizarr-backend/wizarr_backend/app/models/database/onboarding.py index fcb78a8a0..c2982b39a 100644 --- a/apps/wizarr-backend/wizarr_backend/app/models/database/onboarding.py +++ b/apps/wizarr-backend/wizarr_backend/app/models/database/onboarding.py @@ -3,6 +3,8 @@ class Onboarding(BaseModel): id = IntegerField(primary_key=True, unique=True) - value = CharField(null=False) + value = CharField() order = IntegerField(null=False, unique=True) - enabled = BooleanField(default=False) + enabled = BooleanField(default=True) + template = IntegerField() + editable = BooleanField(default=True) diff --git a/apps/wizarr-backend/wizarr_backend/helpers/__init__.py b/apps/wizarr-backend/wizarr_backend/helpers/__init__.py index 366e98313..43030d612 100644 --- a/apps/wizarr-backend/wizarr_backend/helpers/__init__.py +++ b/apps/wizarr-backend/wizarr_backend/helpers/__init__.py @@ -4,3 +4,4 @@ from .settings import * from .accounts import * from .universal import * +from .onboarding import * diff --git a/apps/wizarr-backend/wizarr_backend/helpers/onboarding.py b/apps/wizarr-backend/wizarr_backend/helpers/onboarding.py new file mode 100644 index 000000000..ae30b6ad8 --- /dev/null +++ b/apps/wizarr-backend/wizarr_backend/helpers/onboarding.py @@ -0,0 +1,45 @@ +from enum import Enum +from peewee import fn +from app.models.database.settings import Settings +from app.models.database.onboarding import Onboarding + +class TemplateType(Enum): + Discord = 1 + Request = 2 + Download = 3 + +def getNextOrder(): + return (Onboarding.select(fn.MAX(Onboarding.order)).scalar() or -1) + 1 + +def populateForServerType(server_type: str): + next_order = getNextOrder() + if(server_type == "plex"): + Onboarding.create(id=1, order=next_order, value="## ℹī¸ Eh, So, What is Plex exactly?\n\nGreat question! Plex is a software that allows individuals to share their media collections with others. If you've received this invitation, it means someone wants to share their library with you.\n\nWith Plex, you'll have access to all of the movies, TV shows, music, and photos that are stored on their server!\n\nSo let's see how to get started!") + Onboarding.create(id=2, order=next_order + 1, template=TemplateType.Download.value, value="## Join & Download Plex\n\nSo you now have access to our server's media collection. Let's make sure you know how to use it with Plex.\n\nPlanning on watching movies on this device?") + elif(server_type == "jellyfin"): + Onboarding.create(id=3, order=next_order, value="## ℹī¸ Eh, So, What is Jellyfin exactly?\n\nJellyfin is a platform that lets you stream all your favorite movies, TV shows, and music in one place. It's like having your own personal movie theater right at your fingertips! Think of it as a digital library of your favorite content that you can access from anywhere, on any device - your phone, tablet, laptop, smart TV, you name it.?\n\n## đŸŋ Right, so how do I watch stuff??\n\nIt couldn't be simpler! Jellyfin is available on a wide variety of devices including laptops, tablets, smartphones, and TVs. All you need to do is download the Jellyfin app on your device, sign in with your account, and you're ready to start streaming your media. It's that easy!") + Onboarding.create(id=4, order=next_order + 1, template=TemplateType.Download.value, value="## Join & Download Jellyfin\n\nSo you now have access to our server's media collection. Let's make sure you know how to use it with Jellyfin.\n\nPlanning on watching movies on this device?") + elif(server_type == "emby"): + Onboarding.create(id=5, order=next_order, value="## ℹī¸ Eh, So, What is Emby exactly?\n\nEmby is a platform that lets you stream all your favorite movies, TV shows, and music in one place. It's like having your own personal movie theater right at your fingertips! Think of it as a digital library of your favorite content that you can access from anywhere, on any device - your phone, tablet, laptop, smart TV, you name it.\n\n## đŸŋ Right, so how do I watch stuff?\n\nIt couldn't be simpler! Emby is available on a wide variety of devices including laptops, tablets, smartphones, and TVs. All you need to do is download the Emby app on your device, sign in with your account, and you're ready to start streaming your media. It's that easy!") + Onboarding.create(id=6, order=next_order + 1, template=TemplateType.Download.value, value="## Join & Download Emby\n\nGreat news! You now have access to our server's media collection. Let's make sure you know how to use it with Emby.\n\nPlanning on watching movies on this device?") + +def showStatic(template: int, show: bool): + static_row = Onboarding.get_or_none(template=template) + if show: + if not static_row: + Onboarding.create(order=getNextOrder(), template=template, editable=False) + elif static_row.enabled == False: + static_row.enabled = True + static_row.save() + else: + if static_row and static_row.enabled == True: + static_row.enabled = False + static_row.save() + + +def showRequest(show: bool): + showStatic(TemplateType.Request.value, show) + +def showDiscord(show: bool): + showStatic(TemplateType.Discord.value, show) + diff --git a/apps/wizarr-frontend/src/components/Buttons/DownloadButtons/DownloadAndroid.vue b/apps/wizarr-frontend/src/components/Buttons/DownloadButtons/DownloadAndroid.vue deleted file mode 100644 index c1acc0ea8..000000000 --- a/apps/wizarr-frontend/src/components/Buttons/DownloadButtons/DownloadAndroid.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - diff --git a/apps/wizarr-frontend/src/components/Buttons/DownloadButtons/DownloadAppStore.vue b/apps/wizarr-frontend/src/components/Buttons/DownloadButtons/DownloadAppStore.vue deleted file mode 100644 index c712dbc29..000000000 --- a/apps/wizarr-frontend/src/components/Buttons/DownloadButtons/DownloadAppStore.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - diff --git a/apps/wizarr-frontend/src/components/Buttons/DownloadButtons/DownloadLinux.vue b/apps/wizarr-frontend/src/components/Buttons/DownloadButtons/DownloadLinux.vue deleted file mode 100644 index 39e886b28..000000000 --- a/apps/wizarr-frontend/src/components/Buttons/DownloadButtons/DownloadLinux.vue +++ /dev/null @@ -1,60 +0,0 @@ - - - diff --git a/apps/wizarr-frontend/src/modules/help/components/Custom.vue b/apps/wizarr-frontend/src/modules/help/components/Custom.vue index 678e4a484..f27e69853 100644 --- a/apps/wizarr-frontend/src/modules/help/components/Custom.vue +++ b/apps/wizarr-frontend/src/modules/help/components/Custom.vue @@ -1,15 +1,14 @@ diff --git a/apps/wizarr-frontend/src/modules/help/components/Emby/Download.vue b/apps/wizarr-frontend/src/modules/help/components/Emby/Download.vue deleted file mode 100644 index 87f1d6a12..000000000 --- a/apps/wizarr-frontend/src/modules/help/components/Emby/Download.vue +++ /dev/null @@ -1,118 +0,0 @@ - - - diff --git a/apps/wizarr-frontend/src/modules/help/components/Emby/Welcome.vue b/apps/wizarr-frontend/src/modules/help/components/Emby/Welcome.vue deleted file mode 100644 index f91de4bfb..000000000 --- a/apps/wizarr-frontend/src/modules/help/components/Emby/Welcome.vue +++ /dev/null @@ -1,27 +0,0 @@ - - - diff --git a/apps/wizarr-frontend/src/modules/help/components/Jellyfin/Download.vue b/apps/wizarr-frontend/src/modules/help/components/Jellyfin/Download.vue deleted file mode 100644 index e7630ba61..000000000 --- a/apps/wizarr-frontend/src/modules/help/components/Jellyfin/Download.vue +++ /dev/null @@ -1,118 +0,0 @@ - - - diff --git a/apps/wizarr-frontend/src/modules/help/components/Jellyfin/Welcome.vue b/apps/wizarr-frontend/src/modules/help/components/Jellyfin/Welcome.vue deleted file mode 100644 index 7ab4cdc11..000000000 --- a/apps/wizarr-frontend/src/modules/help/components/Jellyfin/Welcome.vue +++ /dev/null @@ -1,27 +0,0 @@ - - - diff --git a/apps/wizarr-frontend/src/modules/help/components/Plex/Download.vue b/apps/wizarr-frontend/src/modules/help/components/Plex/Download.vue deleted file mode 100644 index d4411d915..000000000 --- a/apps/wizarr-frontend/src/modules/help/components/Plex/Download.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - diff --git a/apps/wizarr-frontend/src/modules/help/components/Plex/Welcome.vue b/apps/wizarr-frontend/src/modules/help/components/Plex/Welcome.vue deleted file mode 100644 index 285c0ee3c..000000000 --- a/apps/wizarr-frontend/src/modules/help/components/Plex/Welcome.vue +++ /dev/null @@ -1,25 +0,0 @@ - - - diff --git a/apps/wizarr-frontend/src/modules/help/components/Welcome.vue b/apps/wizarr-frontend/src/modules/help/components/Welcome.vue deleted file mode 100644 index 656e5700f..000000000 --- a/apps/wizarr-frontend/src/modules/help/components/Welcome.vue +++ /dev/null @@ -1,39 +0,0 @@ - - - diff --git a/apps/wizarr-frontend/src/modules/help/router/index.ts b/apps/wizarr-frontend/src/modules/help/router/index.ts index 3348fdf91..d08121a40 100644 --- a/apps/wizarr-frontend/src/modules/help/router/index.ts +++ b/apps/wizarr-frontend/src/modules/help/router/index.ts @@ -1,4 +1,3 @@ -import openServer from '@/router/middleware/openServer'; import type { RouteRecordRaw } from 'vue-router'; const routes: Readonly = [ @@ -6,15 +5,7 @@ const routes: Readonly = [ path: '/help', name: 'help', component: () => import('../views/Help.vue'), - }, - { - path: '/open', - name: 'open', - component: () => '', - meta: { - middleware: [openServer], - }, - }, + } ]; export default routes; diff --git a/apps/wizarr-frontend/src/modules/help/views/Help.vue b/apps/wizarr-frontend/src/modules/help/views/Help.vue index 8c7f416ff..4c4feef89 100644 --- a/apps/wizarr-frontend/src/modules/help/views/Help.vue +++ b/apps/wizarr-frontend/src/modules/help/views/Help.vue @@ -34,7 +34,7 @@ diff --git a/apps/wizarr-frontend/src/modules/settings/components/Modals/EditOnboarding.vue b/apps/wizarr-frontend/src/modules/settings/components/Modals/EditOnboarding.vue index 015e637f7..734366593 100644 --- a/apps/wizarr-frontend/src/modules/settings/components/Modals/EditOnboarding.vue +++ b/apps/wizarr-frontend/src/modules/settings/components/Modals/EditOnboarding.vue @@ -1,29 +1,44 @@