From 8c06ea1ec10cc7fb7730484cc43cb37043e268ed Mon Sep 17 00:00:00 2001 From: SethBurkart123 Date: Tue, 9 Apr 2024 19:34:28 +1000 Subject: [PATCH] add theme installer page --- package.json | 2 + src/background.ts | 12 +- src/interface/components/ThemeCover.tsx | 25 +++- src/interface/components/backgroundBeams.tsx | 141 +++++++++++++++++++ src/interface/main.tsx | 2 + src/interface/pages/Theme.tsx | 114 +++++++++++++++ src/interface/types/pocketbase-types.ts | 88 ++++++++++++ tailwind.config.js | 20 ++- 8 files changed, 394 insertions(+), 10 deletions(-) create mode 100644 src/interface/components/backgroundBeams.tsx create mode 100644 src/interface/pages/Theme.tsx create mode 100644 src/interface/types/pocketbase-types.ts diff --git a/package.json b/package.json index 40365e1e..b077a18d 100644 --- a/package.json +++ b/package.json @@ -67,9 +67,11 @@ "preact": "^10.20.0", "react": "^18.2.0", "react-best-gradient-color-picker": "3.0.5", + "react-confetti-explosion": "^2.1.2", "react-dom": "^18.2.0", "react-router-dom": "^6.22.0", "react-shadow": "^20.4.0", + "react-toastify": "^10.0.5", "rimraf": "^5.0.5", "sortablejs": "^1.15.2", "swiper": "^11.1.0", diff --git a/src/background.ts b/src/background.ts index 72bc9d2b..e458bf53 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,5 +1,7 @@ import * as Sentry from "@sentry/browser"; import browser from 'webextension-polyfill' +import { onError } from './seqta/utils/onError'; +import { SettingsState } from "./types/storage"; browser.storage.local.get([ "telemetry" ]).then((telemetry) => { if (telemetry.telemetry === true) { @@ -14,8 +16,6 @@ browser.storage.local.get([ "telemetry" ]).then((telemetry) => { } }) -import { onError } from './seqta/utils/onError'; -import { SettingsState } from "./types/storage"; export const openDB = () => { return new Promise((resolve, reject) => { const request = indexedDB.open('MyDatabase', 1); @@ -84,7 +84,13 @@ function reloadSeqtaPages() { result.then(open, onError) } -// Helper function to handle setting permissions +browser.tabs.onUpdated.addListener((tabId, _, tab) => { + if (tab.url?.includes('share.betterseqta')) { + const id = new URL(tab.url).searchParams.get('id'); + const justCreated = new URL(tab.url).searchParams.get('justCreated'); + browser.tabs.update(tabId, { url: `${browser.runtime.getURL('src/interface/index.html')}?id=${id}&justCreated=${justCreated}#theme` }); + } +}); // Main message listener browser.runtime.onMessage.addListener((request: any, _sender: any, sendResponse: any) => { diff --git a/src/interface/components/ThemeCover.tsx b/src/interface/components/ThemeCover.tsx index 39642244..33d20177 100644 --- a/src/interface/components/ThemeCover.tsx +++ b/src/interface/components/ThemeCover.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import React, { useState } from 'react'; import { CustomTheme } from '../types/CustomThemes'; import browser from 'webextension-polyfill'; -import { ArrowUpOnSquareIcon, PencilIcon, ShareIcon } from '@heroicons/react/24/outline'; +import { ArrowUpOnSquareIcon, PencilIcon } from '@heroicons/react/24/outline'; type ThemeCoverProps = { theme: Omit; @@ -18,6 +18,7 @@ export const ThemeCover: React.FC = ({ onThemeSelect, onThemeDelete, }) => { + const [uploading, setUploading] = useState(false); const handleThemeClick = () => { if (isEditMode) return; onThemeSelect(theme.id); @@ -28,6 +29,15 @@ export const ThemeCover: React.FC = ({ onThemeDelete(theme.id); }; + const handleShareClick = (event: React.MouseEvent) => { + event?.preventDefault(); + setUploading(true); + browser.runtime.sendMessage({ type: 'currentTab', info: 'ShareTheme', body: { themeID: theme.id } }).then((response) => { + setUploading(false); + browser.tabs.create({ url: `https://share.betterseqta/theme?id=${response.id}&justCreated=true` }); + }); + }; + return ( ); -}; \ No newline at end of file +}; + +const LoadingSpinner = ({ size }: { size: number }) => { + return
; +}; + diff --git a/src/interface/components/backgroundBeams.tsx b/src/interface/components/backgroundBeams.tsx new file mode 100644 index 00000000..6edbeac3 --- /dev/null +++ b/src/interface/components/backgroundBeams.tsx @@ -0,0 +1,141 @@ +"use client"; +import React from "react"; +import { motion } from "framer-motion"; +import { cn } from "../utils/cn"; + +export const BackgroundBeams = React.memo( + ({ className }: { className?: string }) => { + const paths = [ + "M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875", + "M-373 -197C-373 -197 -305 208 159 335C623 462 691 867 691 867", + "M-366 -205C-366 -205 -298 200 166 327C630 454 698 859 698 859", + "M-359 -213C-359 -213 -291 192 173 319C637 446 705 851 705 851", + "M-352 -221C-352 -221 -284 184 180 311C644 438 712 843 712 843", + "M-345 -229C-345 -229 -277 176 187 303C651 430 719 835 719 835", + "M-338 -237C-338 -237 -270 168 194 295C658 422 726 827 726 827", + "M-331 -245C-331 -245 -263 160 201 287C665 414 733 819 733 819", + "M-324 -253C-324 -253 -256 152 208 279C672 406 740 811 740 811", + "M-317 -261C-317 -261 -249 144 215 271C679 398 747 803 747 803", + "M-310 -269C-310 -269 -242 136 222 263C686 390 754 795 754 795", + "M-303 -277C-303 -277 -235 128 229 255C693 382 761 787 761 787", + "M-296 -285C-296 -285 -228 120 236 247C700 374 768 779 768 779", + "M-289 -293C-289 -293 -221 112 243 239C707 366 775 771 775 771", + "M-282 -301C-282 -301 -214 104 250 231C714 358 782 763 782 763", + "M-275 -309C-275 -309 -207 96 257 223C721 350 789 755 789 755", + "M-268 -317C-268 -317 -200 88 264 215C728 342 796 747 796 747", + "M-261 -325C-261 -325 -193 80 271 207C735 334 803 739 803 739", + "M-254 -333C-254 -333 -186 72 278 199C742 326 810 731 810 731", + "M-247 -341C-247 -341 -179 64 285 191C749 318 817 723 817 723", + "M-240 -349C-240 -349 -172 56 292 183C756 310 824 715 824 715", + "M-233 -357C-233 -357 -165 48 299 175C763 302 831 707 831 707", + "M-226 -365C-226 -365 -158 40 306 167C770 294 838 699 838 699", + "M-219 -373C-219 -373 -151 32 313 159C777 286 845 691 845 691", + "M-212 -381C-212 -381 -144 24 320 151C784 278 852 683 852 683", + "M-205 -389C-205 -389 -137 16 327 143C791 270 859 675 859 675", + "M-198 -397C-198 -397 -130 8 334 135C798 262 866 667 866 667", + "M-191 -405C-191 -405 -123 0 341 127C805 254 873 659 873 659", + "M-184 -413C-184 -413 -116 -8 348 119C812 246 880 651 880 651", + "M-177 -421C-177 -421 -109 -16 355 111C819 238 887 643 887 643", + "M-170 -429C-170 -429 -102 -24 362 103C826 230 894 635 894 635", + "M-163 -437C-163 -437 -95 -32 369 95C833 222 901 627 901 627", + "M-156 -445C-156 -445 -88 -40 376 87C840 214 908 619 908 619", + "M-149 -453C-149 -453 -81 -48 383 79C847 206 915 611 915 611", + "M-142 -461C-142 -461 -74 -56 390 71C854 198 922 603 922 603", + "M-135 -469C-135 -469 -67 -64 397 63C861 190 929 595 929 595", + "M-128 -477C-128 -477 -60 -72 404 55C868 182 936 587 936 587", + "M-121 -485C-121 -485 -53 -80 411 47C875 174 943 579 943 579", + "M-114 -493C-114 -493 -46 -88 418 39C882 166 950 571 950 571", + "M-107 -501C-107 -501 -39 -96 425 31C889 158 957 563 957 563", + "M-100 -509C-100 -509 -32 -104 432 23C896 150 964 555 964 555", + "M-93 -517C-93 -517 -25 -112 439 15C903 142 971 547 971 547", + "M-86 -525C-86 -525 -18 -120 446 7C910 134 978 539 978 539", + "M-79 -533C-79 -533 -11 -128 453 -1C917 126 985 531 985 531", + "M-72 -541C-72 -541 -4 -136 460 -9C924 118 992 523 992 523", + "M-65 -549C-65 -549 3 -144 467 -17C931 110 999 515 999 515", + "M-58 -557C-58 -557 10 -152 474 -25C938 102 1006 507 1006 507", + "M-51 -565C-51 -565 17 -160 481 -33C945 94 1013 499 1013 499", + "M-44 -573C-44 -573 24 -168 488 -41C952 86 1020 491 1020 491", + "M-37 -581C-37 -581 31 -176 495 -49C959 78 1027 483 1027 483", + ]; + return ( +
+ + + + {paths.map((path, index) => ( + + ))} + + {paths.map((_, index) => ( + + + + + + + ))} + + + + + + + + +
+ ); + } +); + +BackgroundBeams.displayName = "BackgroundBeams"; diff --git a/src/interface/main.tsx b/src/interface/main.tsx index 23ae293e..a7414c91 100644 --- a/src/interface/main.tsx +++ b/src/interface/main.tsx @@ -9,6 +9,7 @@ import font from '../resources/fonts/IconFamily.woff' import * as Sentry from "@sentry/react"; import ThemeCreator from './pages/ThemeCreator'; import Store from './pages/Store'; +import Theme from './pages/Theme'; browser.storage.local.get().then(({ telemetry, DarkMode }) => { if (DarkMode) document.documentElement.classList.add('dark'); @@ -45,6 +46,7 @@ root.render( } /> } /> } /> + } /> , diff --git a/src/interface/pages/Theme.tsx b/src/interface/pages/Theme.tsx new file mode 100644 index 00000000..f4fbac40 --- /dev/null +++ b/src/interface/pages/Theme.tsx @@ -0,0 +1,114 @@ +import { useEffect, useState } from "react"; +import PocketBase from 'pocketbase'; +import { ThemesRecord } from "../types/pocketbase-types"; +import ConfettiExplosion from 'react-confetti-explosion'; +import { BackgroundBeams } from "../components/backgroundBeams"; +import { LinkIcon } from "@heroicons/react/24/outline"; +import { ToastContainer, toast } from 'react-toastify'; +import browser from 'webextension-polyfill'; +import 'react-toastify/dist/ReactToastify.css'; + +const pb = new PocketBase('https://betterseqta.pockethost.io'); + +const Theme = () => { + const [isLoading, setIsLoading] = useState(true); + const [theme, setTheme] = useState(null); + const [themeID, setThemeID] = useState(''); + const [justCreated, setJustCreated] = useState(false); + const [displayConfetti, setDisplayConfetti] = useState(true); + + useEffect(() => { + const timer = setTimeout(() => { + setDisplayConfetti(false); + }, 5000); + return () => clearTimeout(timer); + }, []); + + useEffect(() => { + const urlParams = new URLSearchParams(window.location.search); + const themeID = urlParams.get('id'); + const justCreated = urlParams.get('justCreated'); + if (themeID) { + setThemeID(themeID); + } + + const getTheme = async (themeID: string) => { + const theme = await pb.collection('themes').getOne(themeID); + console.log(theme); + setIsLoading(false); + setTheme(theme); + } + + if (themeID && themeID !== 'null') { + getTheme(themeID); + setJustCreated(justCreated === 'true'); + } + }, []) + + return ( + <> + {isLoading && ( +
+
+
+ )} + {theme && ( +
+ { justCreated ? ( + <> + { + displayConfetti && ( + + ) + } +
+

Theme Created Successfully!

+

Share the install link below with your friends to install the theme!

+
+ + {`https://share.betterseqta/theme?id=${themeID}`} + + + +
+
+ + ) : ( +
+

{theme.name}

+

{theme.description}

+ +
+ )} + +
+ )} + + + ); +}; + +export default Theme; + diff --git a/src/interface/types/pocketbase-types.ts b/src/interface/types/pocketbase-types.ts new file mode 100644 index 00000000..f044c91f --- /dev/null +++ b/src/interface/types/pocketbase-types.ts @@ -0,0 +1,88 @@ +/** +* This file was @generated using pocketbase-typegen +*/ + +import type PocketBase from 'pocketbase' +import type { RecordService } from 'pocketbase' + +export enum Collections { + PublishedThemes = "publishedThemes", + Themes = "themes", + Users = "users", +} + +// Alias types for improved usability +export type IsoDateString = string +export type RecordIdString = string +export type HTMLString = string + +// System fields +export type BaseSystemFields = { + id: RecordIdString + created: IsoDateString + updated: IsoDateString + collectionId: string + collectionName: Collections + expand?: T +} + +export type AuthSystemFields = { + email: string + emailVisibility: boolean + username: string + verified: boolean +} & BaseSystemFields + +// Record types for each collection + +export type PublishedThemesRecord = { + coverImage?: string + description?: string + downloads?: string + marqueeImage?: string + name: string + themeURL?: string +} + +export type ThemesRecord = { + coverImage?: string + description?: string + downloads?: string + images?: string[] + name: string + submitted?: boolean + theme?: null | Ttheme +} + +export type UsersRecord = { + avatar?: string + name?: string +} + +// Response types include system fields and match responses from the PocketBase API +export type PublishedThemesResponse = Required & BaseSystemFields +export type ThemesResponse = Required> & BaseSystemFields +export type UsersResponse = Required & AuthSystemFields + +// Types containing all Records and Responses, useful for creating typing helper functions + +export type CollectionRecords = { + publishedThemes: PublishedThemesRecord + themes: ThemesRecord + users: UsersRecord +} + +export type CollectionResponses = { + publishedThemes: PublishedThemesResponse + themes: ThemesResponse + users: UsersResponse +} + +// Type for usage with type asserted PocketBase instance +// https://github.com/pocketbase/js-sdk#specify-typescript-definitions + +export type TypedPocketBase = PocketBase & { + collection(idOrName: 'publishedThemes'): RecordService + collection(idOrName: 'themes'): RecordService + collection(idOrName: 'users'): RecordService +} diff --git a/tailwind.config.js b/tailwind.config.js index e4b8c52a..a0b2ecc4 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,3 +1,8 @@ +const colors = require("tailwindcss/colors"); +const { + default: flattenColorPalette, +} = require("tailwindcss/lib/util/flattenColorPalette"); + /** @type {import('tailwindcss').Config} */ export default { content: [ @@ -35,5 +40,16 @@ export default { } } }, - plugins: [], -}; \ No newline at end of file + plugins: [addVariablesForColors], +}; + +function addVariablesForColors({ addBase, theme }) { + let allColors = flattenColorPalette(theme("colors")); + let newVars = Object.fromEntries( + Object.entries(allColors).map(([key, val]) => [`--${key}`, val]) + ); + + addBase({ + ":root": newVars, + }); +} \ No newline at end of file