@@ -68,6 +68,7 @@
|
||||
"next": "15.1.6",
|
||||
"posthog-js": "^1.205.0",
|
||||
"react": "19.0.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "19.0.0",
|
||||
"react-i18next": "^15.2.0",
|
||||
"react-icons": "^5.4.0",
|
||||
@@ -81,6 +82,7 @@
|
||||
"@types/cssbeautify": "^0.3.5",
|
||||
"@types/node": "^22.10.1",
|
||||
"@types/react": "18.3.12",
|
||||
"@types/react-color": "^3.0.13",
|
||||
"@types/react-dom": "18.3.1",
|
||||
"@types/tinycolor2": "^1.4.6",
|
||||
"autoprefixer": "^10.4.20",
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"Hyphenation": "الواصلة",
|
||||
"Identifier:": "المعرف:",
|
||||
"Import Books": "استيراد الكتب",
|
||||
"Invert Colors in Dark Mode": "عكس الألوان في الوضع المظلم",
|
||||
"Language:": "اللغة:",
|
||||
"Layout": "التخطيط",
|
||||
"Light Mode": "الوضع الفاتح",
|
||||
@@ -235,5 +234,14 @@
|
||||
"Continuous Scroll": "التمرير المستمر",
|
||||
"Fullscreen": "ملء الشاشة",
|
||||
"No supported files found. Supported formats: {{formats}}": "لم يتم العثور على ملفات مدعومة. الصيغ المدعومة: {{formats}}",
|
||||
"Drop to Import Books": "اسحب لاستيراد الكتب"
|
||||
"Drop to Import Books": "اسحب لاستيراد الكتب",
|
||||
"Custom": "مخصص",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "العالم مسرح،\nوالناس فيه ممثلون؛\nلهم مخارج ومداخل،\nوالإنسان في حياته يلعب دورًا كثيرًا،\nوأفعاله تمر بسبع مراحل.\n\n— ويليام شكسبير",
|
||||
"Custom Theme": "سمة مخصصة",
|
||||
"Theme Name": "اسم السمة",
|
||||
"Text Color": "لون النص",
|
||||
"Background Color": "لون الخلفية",
|
||||
"Preview": "معاينة",
|
||||
"Contrast": "التباين",
|
||||
"Sunset": "غروب الشمس"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"Hyphenation": "Silbentrennung",
|
||||
"Identifier:": "Kennung:",
|
||||
"Import Books": "Bücher importieren",
|
||||
"Invert Colors in Dark Mode": "Farben im Dunkelmodus invertieren",
|
||||
"Language:": "Sprache:",
|
||||
"Layout": "Layout",
|
||||
"Light Mode": "Hellmodus",
|
||||
@@ -235,5 +234,14 @@
|
||||
"Continuous Scroll": "Kontinuierliches Scrollen",
|
||||
"Fullscreen": "Vollbild",
|
||||
"No supported files found. Supported formats: {{formats}}": "Keine unterstützten Dateien gefunden. Unterstützte Formate: {{formats}}",
|
||||
"Drop to Import Books": "Zum Importieren von Büchern ablegen"
|
||||
"Drop to Import Books": "Zum Importieren von Büchern ablegen",
|
||||
"Custom": "Benutzerdefiniert",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Die ganze Welt ist eine Bühne,\nUnd alle Männer und Frauen sind bloß Spieler;\nSie haben ihre Abgänge und ihre Auftritte,\nUnd ein Mann spielt in seinem Leben viele Rollen,\nSeine Taten sind sieben Zeitalter.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Benutzerdefiniertes Thema",
|
||||
"Theme Name": "Themenname",
|
||||
"Text Color": "Textfarbe",
|
||||
"Background Color": "Hintergrundfarbe",
|
||||
"Preview": "Vorschau",
|
||||
"Contrast": "Kontrast",
|
||||
"Sunset": "Sonnenuntergang"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"Hyphenation": "Συλλαβισμός",
|
||||
"Identifier:": "Αναγνωριστικό:",
|
||||
"Import Books": "Εισαγωγή βιβλίων",
|
||||
"Invert Colors in Dark Mode": "Αντιστροφή χρωμάτων στη σκοτεινή λειτουργία",
|
||||
"Language:": "Γλώσσα:",
|
||||
"Layout": "Διάταξη",
|
||||
"Light Mode": "Φωτεινή λειτουργία",
|
||||
@@ -235,5 +234,14 @@
|
||||
"Continuous Scroll": "Συνεχής κύλιση",
|
||||
"Fullscreen": "Πλήρης οθόνη",
|
||||
"No supported files found. Supported formats: {{formats}}": "Δεν βρέθηκαν υποστηριζόμενα αρχεία. Υποστηριζόμενες μορφές: {{formats}}",
|
||||
"Drop to Import Books": "Ρίξτε για εισαγωγή βιβλίων"
|
||||
"Drop to Import Books": "Ρίξτε για εισαγωγή βιβλίων",
|
||||
"Custom": "Προσαρμοσμένο",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Όλος ο κόσμος είναι σκηνή,\nΚαι όλοι οι άνδρες και οι γυναίκες απλώς ηθοποιοί·\nΈχουν τις εξόδους και τις εισόδους τους,\nΚαι ένας άνθρωπος στον χρόνο του παίζει πολλούς ρόλους,\nΟι πράξεις του υπάρχουν σε επτά ηλικίες.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Προσαρμοσμένο θέμα",
|
||||
"Theme Name": "Όνομα θέματος",
|
||||
"Text Color": "Χρώμα κειμένου",
|
||||
"Background Color": "Χρώμα φόντου",
|
||||
"Preview": "Προεπισκόπηση",
|
||||
"Contrast": "Αντίθεση",
|
||||
"Sunset": "Ηλιοβασίλεμα"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"Hyphenation": "Guionización",
|
||||
"Identifier:": "Identificador:",
|
||||
"Import Books": "Importar libros",
|
||||
"Invert Colors in Dark Mode": "Invertir colores en modo oscuro",
|
||||
"Language:": "Idioma:",
|
||||
"Layout": "Diseño",
|
||||
"Light Mode": "Modo claro",
|
||||
@@ -235,5 +234,14 @@
|
||||
"Continuous Scroll": "Desplazamiento continuo",
|
||||
"Fullscreen": "Pantalla completa",
|
||||
"No supported files found. Supported formats: {{formats}}": "No se encontraron archivos compatibles. Formatos compatibles: {{formats}}",
|
||||
"Drop to Import Books": "Soltar para importar libros"
|
||||
"Drop to Import Books": "Soltar para importar libros",
|
||||
"Custom": "Personalizado",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Todo el mundo es un escenario,\nY todos los hombres y mujeres son meros actores;\nTienen sus salidas y sus entradas,\nY un hombre en su tiempo interpreta muchos papeles,\nSus actos son siete edades.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Tema personalizado",
|
||||
"Theme Name": "Nombre del tema",
|
||||
"Text Color": "Color del texto",
|
||||
"Background Color": "Color de fondo",
|
||||
"Preview": "Vista previa",
|
||||
"Contrast": "Contraste",
|
||||
"Sunset": "Atardecer"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"Hyphenation": "Césure",
|
||||
"Identifier:": "Identifiant :",
|
||||
"Import Books": "Importer des livres",
|
||||
"Invert Colors in Dark Mode": "Inverser les couleurs en mode sombre",
|
||||
"Language:": "Langue :",
|
||||
"Layout": "Mise en page",
|
||||
"Light Mode": "Mode clair",
|
||||
@@ -235,5 +234,14 @@
|
||||
"Continuous Scroll": "Défilement continu",
|
||||
"Fullscreen": "Plein écran",
|
||||
"No supported files found. Supported formats: {{formats}}": "Aucun fichier pris en charge trouvé. Formats pris en charge : {{formats}}",
|
||||
"Drop to Import Books": "Déposez pour importer des livres"
|
||||
"Drop to Import Books": "Déposez pour importer des livres",
|
||||
"Custom": "Personnalisé",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Le monde entier est un théâtre,\nEt tous les hommes et les femmes ne sont que des acteurs ;\nIls ont leurs sorties et leurs entrées,\nEt un homme, dans sa vie, joue de nombreux rôles,\nSes actes étant sept âges.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Thème personnalisé",
|
||||
"Theme Name": "Nom du thème",
|
||||
"Text Color": "Couleur du texte",
|
||||
"Background Color": "Couleur de fond",
|
||||
"Preview": "Aperçu",
|
||||
"Contrast": "Contraste",
|
||||
"Sunset": "Coucher de soleil"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"Hyphenation": "हाइफ़नेशन",
|
||||
"Identifier:": "पहचानकर्ता:",
|
||||
"Import Books": "पुस्तकें आयात करें",
|
||||
"Invert Colors in Dark Mode": "डार्क मोड में रंग उलटें",
|
||||
"Language:": "भाषा:",
|
||||
"Layout": "लेआउट",
|
||||
"Light Mode": "लाइट मोड",
|
||||
@@ -235,5 +234,14 @@
|
||||
"Continuous Scroll": "निरंतर स्क्रॉल",
|
||||
"Fullscreen": "पूर्णस्क्रीन",
|
||||
"No supported files found. Supported formats: {{formats}}": "कोई समर्थित फ़ाइलें नहीं मिलीं। समर्थित प्रारूप: {{formats}}",
|
||||
"Drop to Import Books": "पुस्तकें आयात करने के लिए छोड़ें"
|
||||
"Drop to Import Books": "पुस्तकें आयात करने के लिए छोड़ें",
|
||||
"Custom": "कस्टम",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "पूरी दुनिया एक स्टेज है,\nऔर सभी आदमी और महिलाएँ केवल अभिनेता हैं;\nउनके निकास और प्रवेश होते हैं,\nऔर एक आदमी अपने समय में कई भूमिकाएँ निभाता है,\nउसके कार्य सात युग होते हैं।\n\n— विलियम शेक्सपियर",
|
||||
"Custom Theme": "कस्टम थीम",
|
||||
"Theme Name": "थीम का नाम",
|
||||
"Text Color": "टेक्स्ट रंग",
|
||||
"Background Color": "पृष्ठभूमि रंग",
|
||||
"Preview": "पूर्वावलोकन",
|
||||
"Contrast": "विरोधाभास",
|
||||
"Sunset": "सनसेट"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"Hyphenation": "Pemenggalan Kata",
|
||||
"Identifier:": "Pengenal:",
|
||||
"Import Books": "Impor Buku",
|
||||
"Invert Colors in Dark Mode": "Balik Warna di Mode Gelap",
|
||||
"Language:": "Bahasa:",
|
||||
"Layout": "Tata Letak",
|
||||
"Light Mode": "Mode Terang",
|
||||
@@ -235,5 +234,14 @@
|
||||
"Continuous Scroll": "Gulir Terus",
|
||||
"Fullscreen": "Layar Penuh",
|
||||
"No supported files found. Supported formats: {{formats}}": "Tidak ada file yang didukung ditemukan. Format yang didukung: {{formats}}",
|
||||
"Drop to Import Books": "Jatuhkan untuk Mengimpor Buku"
|
||||
"Drop to Import Books": "Jatuhkan untuk Mengimpor Buku",
|
||||
"Custom": "Kustom",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Seluruh dunia adalah panggung,\nDan semua pria dan wanita hanyalah pemain;\nMereka memiliki keluar dan masuk mereka,\nDan seorang pria dalam hidupnya memainkan banyak peran,\nTindakannya ada tujuh zaman.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Tema Kustom",
|
||||
"Theme Name": "Nama Tema",
|
||||
"Text Color": "Warna Teks",
|
||||
"Background Color": "Warna Latar Belakang",
|
||||
"Preview": "Pratinjau",
|
||||
"Contrast": "Kontras",
|
||||
"Sunset": "Senja"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"Hyphenation": "Sillabazione",
|
||||
"Identifier:": "Identificatore:",
|
||||
"Import Books": "Importa libri",
|
||||
"Invert Colors in Dark Mode": "Inverti colori in modalità scura",
|
||||
"Language:": "Lingua:",
|
||||
"Layout": "Layout",
|
||||
"Light Mode": "Modalità chiara",
|
||||
@@ -235,5 +234,14 @@
|
||||
"Continuous Scroll": "Scorrimento continuo",
|
||||
"Fullscreen": "Schermo intero",
|
||||
"No supported files found. Supported formats: {{formats}}": "Nessun file supportato trovato. Formati supportati: {{formats}}",
|
||||
"Drop to Import Books": "Rilascia per importare libri"
|
||||
"Drop to Import Books": "Rilascia per importare libri",
|
||||
"Custom": "Personalizzato",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Tutto il mondo è un palcoscenico,\nE tutti gli uomini e le donne sono solo attori;\nHanno le loro uscite e le loro entrate,\nE un uomo nella sua vita interpreta molte parti,\nI suoi atti sono sette età.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Tema personalizzato",
|
||||
"Theme Name": "Nome tema",
|
||||
"Text Color": "Colore testo",
|
||||
"Background Color": "Colore sfondo",
|
||||
"Preview": "Anteprima",
|
||||
"Contrast": "Contrasto",
|
||||
"Sunset": "Tramonto"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"Hyphenation": "ハイフネーション",
|
||||
"Identifier:": "識別子:",
|
||||
"Import Books": "書籍をインポート",
|
||||
"Invert Colors in Dark Mode": "ダークモードで色を反転",
|
||||
"Language:": "言語:",
|
||||
"Layout": "レイアウト",
|
||||
"Light Mode": "ライトモード",
|
||||
@@ -235,5 +234,14 @@
|
||||
"Continuous Scroll": "連続スクロール",
|
||||
"Fullscreen": "全画面",
|
||||
"No supported files found. Supported formats: {{formats}}": "サポートされているファイルが見つかりません。サポートされている形式:{{formats}}",
|
||||
"Drop to Import Books": "書籍をインポートするにはドロップ"
|
||||
"Drop to Import Books": "書籍をインポートするにはドロップ",
|
||||
"Custom": "カスタム",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "全世界は舞台であり、\nそしてすべての男性と女性は単なる役者です。\n彼らは出入り口を持っており、\nそして一人の男性は時に多くの役を演じます。\n彼の行為は七つの時代です。\n\n— ウィリアム・シェイクスピア",
|
||||
"Custom Theme": "カスタムテーマ",
|
||||
"Theme Name": "テーマ名",
|
||||
"Text Color": "テキストカラー",
|
||||
"Background Color": "背景色",
|
||||
"Preview": "プレビュー",
|
||||
"Contrast": "コントラスト",
|
||||
"Sunset": "サンセット"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"Hyphenation": "하이픈 넣기",
|
||||
"Identifier:": "식별자:",
|
||||
"Import Books": "책 가져오기",
|
||||
"Invert Colors in Dark Mode": "다크 모드에서 색상 반전",
|
||||
"Language:": "언어:",
|
||||
"Layout": "레이아웃",
|
||||
"Light Mode": "라이트 모드",
|
||||
@@ -235,5 +234,14 @@
|
||||
"Continuous Scroll": "연속 스크롤",
|
||||
"Fullscreen": "전체 화면",
|
||||
"No supported files found. Supported formats: {{formats}}": "지원되는 파일이 없습니다. 지원되는 형식: {{formats}}",
|
||||
"Drop to Import Books": "책 가져오기 위해 드롭"
|
||||
"Drop to Import Books": "책 가져오기 위해 드롭",
|
||||
"Custom": "사용자 정의",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "세상은 무대이며,\n그리고 모든 남자와 여자는 단지 배우일 뿐입니다;\n그들은 나가는 곳과 들어오는 곳이 있으며,\n그리고 한 남자는 그의 시간에 많은 역할을 합니다,\n그의 행동은 일곱 시대입니다.\n\n— 윌리엄 셰익스피어",
|
||||
"Custom Theme": "사용자 정의 테마",
|
||||
"Theme Name": "테마 이름",
|
||||
"Text Color": "텍스트 색상",
|
||||
"Background Color": "배경 색상",
|
||||
"Preview": "미리보기",
|
||||
"Contrast": "대비",
|
||||
"Sunset": "일몰"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"Hyphenation": "Dzielenie wyrazów",
|
||||
"Identifier:": "Identyfikator:",
|
||||
"Import Books": "Importuj książki",
|
||||
"Invert Colors in Dark Mode": "Odwróć kolory w trybie ciemnym",
|
||||
"Language:": "Język:",
|
||||
"Layout": "Układ",
|
||||
"Light Mode": "Tryb jasny",
|
||||
@@ -235,5 +234,14 @@
|
||||
"Continuous Scroll": "Przewijanie ciągłe",
|
||||
"Fullscreen": "Pełny ekran",
|
||||
"No supported files found. Supported formats: {{formats}}": "Nie znaleziono obsługiwanych plików. Obsługiwane formaty: {{formats}}",
|
||||
"Drop to Import Books": "Upuść, aby zaimportować książki"
|
||||
"Drop to Import Books": "Upuść, aby zaimportować książki",
|
||||
"Custom": "Niestandardowy",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Cały świat jest sceną,\nA wszyscy mężczyźni i kobiety są tylko aktorami;\nMają swoje wyjścia i wejścia,\nI jeden człowiek w swoim czasie gra wiele ról,\nJego czyny to siedem wieków.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Niestandardowy motyw",
|
||||
"Theme Name": "Nazwa motywu",
|
||||
"Text Color": "Kolor tekstu",
|
||||
"Background Color": "Kolor tła",
|
||||
"Preview": "Podgląd",
|
||||
"Contrast": "Kontrast",
|
||||
"Sunset": "Zachód słońca"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"Hyphenation": "Hifenização",
|
||||
"Identifier:": "Identificador:",
|
||||
"Import Books": "Importar Livros",
|
||||
"Invert Colors in Dark Mode": "Inverter Cores no Modo Escuro",
|
||||
"Language:": "Idioma:",
|
||||
"Layout": "Layout",
|
||||
"Light Mode": "Modo Claro",
|
||||
@@ -235,5 +234,14 @@
|
||||
"Continuous Scroll": "Rolagem Contínua",
|
||||
"Fullscreen": "Tela Cheia",
|
||||
"No supported files found. Supported formats: {{formats}}": "Nenhum arquivo suportado encontrado. Formatos suportados: {{formats}}",
|
||||
"Drop to Import Books": "Arraste para Importar Livros"
|
||||
"Drop to Import Books": "Arraste para Importar Livros",
|
||||
"Custom": "Personalizado",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Todo o mundo é um palco,\nE todos os homens e mulheres são meros atores;\nEles têm suas saídas e suas entradas,\nE um homem em seu tempo desempenha muitos papéis,\nSeus atos sendo sete idades.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Tema Personalizado",
|
||||
"Theme Name": "Nome do Tema",
|
||||
"Text Color": "Cor do Texto",
|
||||
"Background Color": "Cor de Fundo",
|
||||
"Preview": "Visualizar",
|
||||
"Contrast": "Contraste",
|
||||
"Sunset": "Pôr do Sol"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"Hyphenation": "Перенос слов",
|
||||
"Identifier:": "Идентификатор:",
|
||||
"Import Books": "Импорт книг",
|
||||
"Invert Colors in Dark Mode": "Инвертировать цвета в тёмной теме",
|
||||
"Language:": "Язык:",
|
||||
"Layout": "Макет",
|
||||
"Light Mode": "Светлая тема",
|
||||
@@ -235,5 +234,14 @@
|
||||
"Continuous Scroll": "Непрерывная прокрутка",
|
||||
"Fullscreen": "Полноэкранный режим",
|
||||
"No supported files found. Supported formats: {{formats}}": "Поддерживаемые файлы не найдены. Поддерживаемые форматы: {{formats}}",
|
||||
"Drop to Import Books": "Перетащите сюда книги для импорта"
|
||||
"Drop to Import Books": "Перетащите сюда книги для импорта",
|
||||
"Custom": "Пользовательский",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Весь мир — театр,\nИ все мужчины и женщины — просто актёры;\nУ них есть свои выходы и входы,\nИ один человек в своё время играет много ролей,\nЕго поступки — это семь веков.\n\n— Уильям Шекспир",
|
||||
"Custom Theme": "Пользовательская тема",
|
||||
"Theme Name": "Название темы",
|
||||
"Text Color": "Цвет текста",
|
||||
"Background Color": "Цвет фона",
|
||||
"Preview": "Предпросмотр",
|
||||
"Contrast": "Контраст",
|
||||
"Sunset": "Закат"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"Hyphenation": "Heceleme",
|
||||
"Identifier:": "Tanımlayıcı:",
|
||||
"Import Books": "Kitapları İçe Aktar",
|
||||
"Invert Colors in Dark Mode": "Karanlık Modda Renkleri Ters Çevir",
|
||||
"Language:": "Dil:",
|
||||
"Layout": "Düzen",
|
||||
"Light Mode": "Aydınlık Mod",
|
||||
@@ -235,5 +234,14 @@
|
||||
"Continuous Scroll": "Sürekli Kaydırma",
|
||||
"Fullscreen": "Tam Ekran",
|
||||
"No supported files found. Supported formats: {{formats}}": "Desteklenen dosya bulunamadı. Desteklenen formatlar: {{formats}}",
|
||||
"Drop to Import Books": "Kitapları İçe Aktarmak İçin Bırak"
|
||||
"Drop to Import Books": "Kitapları İçe Aktarmak İçin Bırak",
|
||||
"Custom": "Özel",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Tüm dünya bir sahnedir,\nVe tüm erkekler ve kadınlar sadece oyunculardır;\nOnların çıkışları ve girişleri vardır,\nVe bir adam zamanında birçok rol oynar,\nOnun eylemleri yedi yaşındadır.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Özel Tema",
|
||||
"Theme Name": "Tema Adı",
|
||||
"Text Color": "Metin Rengi",
|
||||
"Background Color": "Arka Plan Rengi",
|
||||
"Preview": "Önizleme",
|
||||
"Contrast": "Kontrast",
|
||||
"Sunset": "Gün Batımı"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"Hyphenation": "Перенесення слів",
|
||||
"Identifier:": "Ідентифікатор:",
|
||||
"Import Books": "Імпортувати книги",
|
||||
"Invert Colors in Dark Mode": "Інвертувати кольори в темному режимі",
|
||||
"Language:": "Мова:",
|
||||
"Layout": "Макет",
|
||||
"Light Mode": "Світлий режим",
|
||||
@@ -235,5 +234,14 @@
|
||||
"Continuous Scroll": "Постійна прокрутка",
|
||||
"Fullscreen": "Повноекранний",
|
||||
"No supported files found. Supported formats: {{formats}}": "Не знайдено підтримуваних файлів. Підтримувані формати: {{formats}}",
|
||||
"Drop to Import Books": "Перетягніть для імпорту книг"
|
||||
"Drop to Import Books": "Перетягніть для імпорту книг",
|
||||
"Custom": "Користувацький",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Весь світ — театр,\nІ всі чоловіки та жінки — просто актори;\nУ них є свої виходи та входи,\nІ один чоловік у свій час грає багато ролей,\nЙого вчинки — це сім віков.\n\n— Вільям Шекспір",
|
||||
"Custom Theme": "Користувацька тема",
|
||||
"Theme Name": "Назва теми",
|
||||
"Text Color": "Колір тексту",
|
||||
"Background Color": "Колір фону",
|
||||
"Preview": "Попередній перегляд",
|
||||
"Contrast": "Контраст",
|
||||
"Sunset": "Захід сонця"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"Hyphenation": "Gạch nối từ",
|
||||
"Identifier:": "Định danh:",
|
||||
"Import Books": "Nhập sách",
|
||||
"Invert Colors in Dark Mode": "Đảo màu trong chế độ tối",
|
||||
"Language:": "Ngôn ngữ:",
|
||||
"Layout": "Bố cục",
|
||||
"Light Mode": "Chế độ sáng",
|
||||
@@ -235,5 +234,14 @@
|
||||
"Continuous Scroll": "Cuộn liên tục",
|
||||
"Fullscreen": "Toàn màn hình",
|
||||
"No supported files found. Supported formats: {{formats}}": "Không tìm thấy tệp được hỗ trợ. Định dạng được hỗ trợ: {{formats}}",
|
||||
"Drop to Import Books": "Kéo và thả để nhập sách"
|
||||
"Drop to Import Books": "Kéo và thả để nhập sách",
|
||||
"Custom": "Tùy chỉnh",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Cả thế giới này là một sân khấu,\nVà tất cả đàn ông và phụ nữ chỉ là những diễn viên;\nHọ có cửa ra và cửa vào của mình,\nVà một người trong cuộc đời mình đóng nhiều vai,\nHành động của mình là bảy tuổi.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Chủ đề tùy chỉnh",
|
||||
"Theme Name": "Tên chủ đề",
|
||||
"Text Color": "Màu văn bản",
|
||||
"Background Color": "Màu nền",
|
||||
"Preview": "Xem trước",
|
||||
"Contrast": "Tương phản",
|
||||
"Sunset": "Hoàng hôn"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"Hyphenation": "断字",
|
||||
"Identifier:": "识别码",
|
||||
"Import Books": "导入书籍",
|
||||
"Invert Colors in Dark Mode": "深色主题下反色",
|
||||
"Language:": "语言",
|
||||
"Layout": "布局",
|
||||
"Light Mode": "浅色主题",
|
||||
@@ -235,5 +234,14 @@
|
||||
"Continuous Scroll": "连续滚动",
|
||||
"Fullscreen": "全屏",
|
||||
"No supported files found. Supported formats: {{formats}}": "未找到支持的文件。支持的格式:{{formats}}",
|
||||
"Drop to Import Books": "拖放导入书籍"
|
||||
"Drop to Import Books": "拖放导入书籍",
|
||||
"Custom": "自定义",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "世界是座舞台, \n所有男女都只是演员; \n各有其出场和入场; \n每个人皆扮演许多角色, \n而演出共分为七个阶段。\n\n————威廉·莎士比亚",
|
||||
"Custom Theme": "自定义主题",
|
||||
"Theme Name": "主题名称",
|
||||
"Text Color": "文本颜色",
|
||||
"Background Color": "背景颜色",
|
||||
"Preview": "预览",
|
||||
"Contrast": "对比",
|
||||
"Sunset": "日落"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"Hyphenation": "斷字",
|
||||
"Identifier:": "識別碼",
|
||||
"Import Books": "導入書籍",
|
||||
"Invert Colors in Dark Mode": "深色主題下反色",
|
||||
"Language:": "語言",
|
||||
"Layout": "版面",
|
||||
"Light Mode": "淺色主題",
|
||||
@@ -235,5 +234,14 @@
|
||||
"Continuous Scroll": "連續滾動",
|
||||
"Fullscreen": "全螢幕",
|
||||
"No supported files found. Supported formats: {{formats}}": "未找到支援的檔案。支援的格式:{{formats}}",
|
||||
"Drop to Import Books": "拖放導入書籍"
|
||||
"Drop to Import Books": "拖放導入書籍",
|
||||
"Custom": "自定義",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "世界是一個舞台,\n所有的男人和女人都只是演員:\n他們有出口,也有入口;\n一個人在他的時代扮演許多角色。 \n\n— 威廉·莎士比亞",
|
||||
"Custom Theme": "自定義主題",
|
||||
"Theme Name": "主題名稱",
|
||||
"Text Color": "文字顏色",
|
||||
"Background Color": "背景顏色",
|
||||
"Preview": "預覽",
|
||||
"Contrast": "對比",
|
||||
"Sunset": "日落"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { SketchPicker, ColorResult } from 'react-color';
|
||||
|
||||
type ColorInputProps = {
|
||||
label: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
const ColorInput: React.FC<ColorInputProps> = ({ label, value, onChange }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const pickerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (pickerRef.current && !pickerRef.current.contains(event.target as Node)) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
}
|
||||
if (isOpen) {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
} else {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
}
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, [isOpen]);
|
||||
|
||||
const handlePickerChange = (colorResult: ColorResult) => {
|
||||
onChange(colorResult.hex);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='mb-3'>
|
||||
<label className='mb-1 block text-sm font-medium'>{label}</label>
|
||||
<div className='flex items-center'>
|
||||
<div
|
||||
className='border-base-200 relative mr-2 flex h-7 w-8 cursor-pointer items-center justify-center overflow-hidden rounded border'
|
||||
style={{ backgroundColor: value }}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
/>
|
||||
|
||||
<input
|
||||
type='text'
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
className='bg-base-100 text-base-content border-base-200 min-w-4 max-w-36 flex-1 rounded border p-1 font-mono text-sm'
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isOpen && (
|
||||
<div ref={pickerRef} className='relative z-50 mt-2'>
|
||||
<div className='absolute'>
|
||||
<SketchPicker
|
||||
width='100%'
|
||||
color={value}
|
||||
onChange={handlePickerChange}
|
||||
disableAlpha={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColorInput;
|
||||
@@ -1,96 +1,183 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { MdOutlineLightMode, MdOutlineDarkMode } from 'react-icons/md';
|
||||
import { MdRadioButtonUnchecked, MdRadioButtonChecked } from 'react-icons/md';
|
||||
import { CgColorPicker } from 'react-icons/cg';
|
||||
import { TbSunMoon } from 'react-icons/tb';
|
||||
|
||||
import { themes } from '@/styles/themes';
|
||||
import { PiPlus } from 'react-icons/pi';
|
||||
import {
|
||||
applyCustomTheme,
|
||||
CustomTheme,
|
||||
generateDarkPalette,
|
||||
generateLightPalette,
|
||||
Theme,
|
||||
themes,
|
||||
} from '@/styles/themes';
|
||||
import { getStyles } from '@/utils/style';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import ThemeEditor from './ThemeEditor';
|
||||
|
||||
const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const _ = useTranslation();
|
||||
const { themeMode, themeColor, themeCode, isDarkMode, setThemeMode, setThemeColor } =
|
||||
useThemeStore();
|
||||
const {
|
||||
themeMode,
|
||||
themeColor,
|
||||
themeCode,
|
||||
isDarkMode,
|
||||
setThemeMode,
|
||||
setThemeColor,
|
||||
saveCustomTheme,
|
||||
} = useThemeStore();
|
||||
const { envConfig } = useEnv();
|
||||
const { settings, setSettings } = useSettingsStore();
|
||||
const { getViews, getViewSettings } = useReaderStore();
|
||||
const viewSettings = getViewSettings(bookKey)!;
|
||||
const iconSize16 = useResponsiveSize(16);
|
||||
const iconSize24 = useResponsiveSize(24);
|
||||
const [editTheme, setEditTheme] = useState<CustomTheme | null>(null);
|
||||
const [customThems, setCustomThemes] = useState<Theme[]>([]);
|
||||
const [showCustomThemeEditor, setShowCustomThemeEditor] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
getViews().forEach((view) => {
|
||||
view.renderer.setStyles?.(getStyles(viewSettings!));
|
||||
view.renderer.setStyles?.(getStyles(viewSettings));
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [themeCode]);
|
||||
|
||||
useEffect(() => {
|
||||
const customThemes = settings.globalReadSettings.customThemes ?? [];
|
||||
setCustomThemes(
|
||||
customThemes.map((customTheme) => ({
|
||||
name: customTheme.name,
|
||||
label: customTheme.label,
|
||||
colors: {
|
||||
light: generateLightPalette(customTheme.colors.light),
|
||||
dark: generateDarkPalette(customTheme.colors.dark),
|
||||
},
|
||||
isCustomizale: true,
|
||||
})),
|
||||
);
|
||||
}, [settings]);
|
||||
|
||||
const handleSaveCustomTheme = (customTheme: CustomTheme) => {
|
||||
applyCustomTheme(customTheme);
|
||||
saveCustomTheme(envConfig, settings, customTheme);
|
||||
|
||||
setSettings({ ...settings });
|
||||
setThemeColor(customTheme.name);
|
||||
setShowCustomThemeEditor(false);
|
||||
};
|
||||
|
||||
const handleDeleteCustomTheme = (customTheme: CustomTheme) => {
|
||||
saveCustomTheme(envConfig, settings, customTheme, true);
|
||||
|
||||
setSettings({ ...settings });
|
||||
setThemeColor('default');
|
||||
setShowCustomThemeEditor(false);
|
||||
};
|
||||
|
||||
const handleEditTheme = (name: string) => {
|
||||
const customTheme = settings.globalReadSettings.customThemes.find((t) => t.name === name);
|
||||
if (customTheme) {
|
||||
setEditTheme(customTheme);
|
||||
setShowCustomThemeEditor(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='my-4 w-full space-y-6'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<h2 className='font-medium'>{_('Theme Mode')}</h2>
|
||||
<div className='flex gap-4'>
|
||||
<div className='lg:tooltip lg:tooltip-bottom' data-tip={_('Auto Mode')}>
|
||||
<button
|
||||
className={`btn btn-ghost btn-circle btn-sm ${themeMode === 'auto' ? 'btn-active bg-base-300' : ''}`}
|
||||
onClick={() => setThemeMode('auto')}
|
||||
>
|
||||
<TbSunMoon />
|
||||
</button>
|
||||
{showCustomThemeEditor ? (
|
||||
<ThemeEditor
|
||||
customTheme={editTheme}
|
||||
onSave={handleSaveCustomTheme}
|
||||
onDelete={handleDeleteCustomTheme}
|
||||
onCancel={() => setShowCustomThemeEditor(false)}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<div className='flex items-center justify-between'>
|
||||
<h2 className='font-medium'>{_('Theme Mode')}</h2>
|
||||
<div className='flex gap-4'>
|
||||
<div className='lg:tooltip lg:tooltip-bottom' data-tip={_('Auto Mode')}>
|
||||
<button
|
||||
className={`btn btn-ghost btn-circle btn-sm ${themeMode === 'auto' ? 'btn-active bg-base-300' : ''}`}
|
||||
onClick={() => setThemeMode('auto')}
|
||||
>
|
||||
<TbSunMoon />
|
||||
</button>
|
||||
</div>
|
||||
<div className='lg:tooltip lg:tooltip-bottom' data-tip={_('Light Mode')}>
|
||||
<button
|
||||
className={`btn btn-ghost btn-circle btn-sm ${themeMode === 'light' ? 'btn-active bg-base-300' : ''}`}
|
||||
onClick={() => setThemeMode('light')}
|
||||
>
|
||||
<MdOutlineLightMode />
|
||||
</button>
|
||||
</div>
|
||||
<div className='lg:tooltip lg:tooltip-bottom' data-tip={_('Dark Mode')}>
|
||||
<button
|
||||
className={`btn btn-ghost btn-circle btn-sm ${themeMode === 'dark' ? 'btn-active bg-base-300' : ''}`}
|
||||
onClick={() => setThemeMode('dark')}
|
||||
>
|
||||
<MdOutlineDarkMode />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='lg:tooltip lg:tooltip-bottom' data-tip={_('Light Mode')}>
|
||||
<button
|
||||
className={`btn btn-ghost btn-circle btn-sm ${themeMode === 'light' ? 'btn-active bg-base-300' : ''}`}
|
||||
onClick={() => setThemeMode('light')}
|
||||
>
|
||||
<MdOutlineLightMode />
|
||||
</button>
|
||||
<div>
|
||||
<h2 className='mb-2 font-medium'>{_('Theme Color')}</h2>
|
||||
<div className='grid grid-cols-3 gap-4'>
|
||||
{themes.concat(customThems).map(({ name, label, colors, isCustomizale }) => (
|
||||
<label
|
||||
key={name}
|
||||
className={`relative flex cursor-pointer flex-col items-center justify-center rounded-lg p-4 shadow-md ${
|
||||
themeColor === name ? 'ring-2 ring-indigo-500 ring-offset-2' : ''
|
||||
}`}
|
||||
style={{
|
||||
backgroundColor: isDarkMode
|
||||
? colors.dark['base-100']
|
||||
: colors.light['base-100'],
|
||||
color: isDarkMode ? colors.dark['base-content'] : colors.light['base-content'],
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type='radio'
|
||||
name='theme'
|
||||
value={name}
|
||||
checked={themeColor === name}
|
||||
onChange={() => setThemeColor(name)}
|
||||
className='hidden'
|
||||
/>
|
||||
{themeColor === name ? (
|
||||
<MdRadioButtonChecked size={iconSize24} />
|
||||
) : (
|
||||
<MdRadioButtonUnchecked size={iconSize24} />
|
||||
)}
|
||||
<span>{_(label)}</span>
|
||||
{isCustomizale && themeColor === name && (
|
||||
<button onClick={() => handleEditTheme(name)}>
|
||||
<CgColorPicker size={iconSize16} className='absolute right-2 top-2' />
|
||||
</button>
|
||||
)}
|
||||
</label>
|
||||
))}
|
||||
<label
|
||||
className={`relative flex cursor-pointer flex-col items-center justify-center rounded-lg border border-dashed p-4 shadow-md`}
|
||||
onClick={() => setShowCustomThemeEditor(true)}
|
||||
>
|
||||
<PiPlus size={iconSize24} />
|
||||
<span>{_('Custom')}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='lg:tooltip lg:tooltip-bottom' data-tip={_('Dark Mode')}>
|
||||
<button
|
||||
className={`btn btn-ghost btn-circle btn-sm ${themeMode === 'dark' ? 'btn-active bg-base-300' : ''}`}
|
||||
onClick={() => setThemeMode('dark')}
|
||||
>
|
||||
<MdOutlineDarkMode />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className='mb-2 font-medium'>{_('Theme Color')}</h2>
|
||||
<div className='grid grid-cols-3 gap-4'>
|
||||
{themes.map(({ name, label, colors }) => (
|
||||
<label
|
||||
key={name}
|
||||
className={`relative flex cursor-pointer flex-col items-center justify-center rounded-lg p-4 shadow-md ${
|
||||
themeColor === name ? 'ring-2 ring-indigo-500 ring-offset-2' : ''
|
||||
}`}
|
||||
style={{
|
||||
backgroundColor: isDarkMode ? colors.dark['base-100'] : colors.light['base-100'],
|
||||
color: isDarkMode ? colors.dark['base-content'] : colors.light['base-content'],
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type='radio'
|
||||
name='theme'
|
||||
value={name}
|
||||
checked={themeColor === name}
|
||||
onChange={() => setThemeColor(name)}
|
||||
className='hidden'
|
||||
/>
|
||||
{themeColor === name ? (
|
||||
<MdRadioButtonChecked size={iconSize24} />
|
||||
) : (
|
||||
<MdRadioButtonUnchecked size={iconSize24} />
|
||||
)}
|
||||
<span>{_(label)}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { CustomTheme } from '@/styles/themes';
|
||||
import { md5Fingerprint } from '@/utils/md5';
|
||||
import { CUSTOM_THEME_TEMPLATES } from '@/services/constants';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import clsx from 'clsx';
|
||||
import ColorInput from './ColorInput';
|
||||
|
||||
type ThemeEditorProps = {
|
||||
customTheme: CustomTheme | null;
|
||||
onSave: (customTheme: CustomTheme) => void;
|
||||
onDelete: (customTheme: CustomTheme) => void;
|
||||
onCancel: () => void;
|
||||
};
|
||||
|
||||
const ThemeEditor: React.FC<ThemeEditorProps> = ({ customTheme, onSave, onDelete, onCancel }) => {
|
||||
const _ = useTranslation();
|
||||
const { settings } = useSettingsStore();
|
||||
const template =
|
||||
CUSTOM_THEME_TEMPLATES[Math.floor(Math.random() * CUSTOM_THEME_TEMPLATES.length)]!;
|
||||
const [lightTextColor, setLightTextColor] = useState(
|
||||
customTheme?.colors.light.fg || template.light.fg,
|
||||
);
|
||||
const [lightBackgroundColor, setLightBackgroundColor] = useState(
|
||||
customTheme?.colors.light.bg || template.light.bg,
|
||||
);
|
||||
const [darkTextColor, setDarkTextColor] = useState(
|
||||
customTheme?.colors.dark.fg || template.dark.fg,
|
||||
);
|
||||
const [darkBackgroundColor, setDarkBackgroundColor] = useState(
|
||||
customTheme?.colors.dark.bg || template.dark.bg,
|
||||
);
|
||||
|
||||
const [themeName, setThemeName] = useState(customTheme?.label || _('Custom'));
|
||||
|
||||
const ThemePreview: React.FC<{
|
||||
textColor: string;
|
||||
backgroundColor: string;
|
||||
label: string;
|
||||
}> = ({ textColor, backgroundColor, label }) => (
|
||||
<div className='mb-2 mt-4'>
|
||||
<label className='mb-1 block text-sm font-medium'>{label}</label>
|
||||
<div
|
||||
className='border-base-300 overflow-hidden rounded border p-3'
|
||||
style={{
|
||||
backgroundColor: backgroundColor,
|
||||
color: textColor,
|
||||
}}
|
||||
>
|
||||
<p className='mb-2 whitespace-pre-line text-sm'>
|
||||
{_(
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const getCustomTheme = () => {
|
||||
return {
|
||||
name: md5Fingerprint(themeName),
|
||||
label: themeName,
|
||||
colors: {
|
||||
light: {
|
||||
fg: lightTextColor,
|
||||
bg: lightBackgroundColor,
|
||||
primary: '#3b82f6',
|
||||
},
|
||||
dark: {
|
||||
fg: darkTextColor,
|
||||
bg: darkBackgroundColor,
|
||||
primary: '#60a5fa',
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='mt-6 rounded-lg'>
|
||||
<div className='mb-4'>
|
||||
<div className='mb-4 flex items-center justify-between'>
|
||||
<label className='text-sm font-medium'>{_('Custom Theme')}</label>
|
||||
<div className='flex w-[calc(50%-12px)] justify-between'>
|
||||
<button
|
||||
className='btn btn-ghost btn-sm text-base-content px-2'
|
||||
onClick={() => onSave(getCustomTheme())}
|
||||
>
|
||||
{_('Save')}
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={clsx(
|
||||
'btn btn-ghost btn-sm px-2',
|
||||
!settings.globalReadSettings.customThemes.find(
|
||||
(theme) => theme.name === md5Fingerprint(themeName),
|
||||
) && 'btn-disabled',
|
||||
)}
|
||||
onClick={() => onDelete(getCustomTheme())}
|
||||
>
|
||||
{_('Delete')}
|
||||
</button>
|
||||
|
||||
<button className='btn btn-ghost btn-sm px-2' onClick={onCancel}>
|
||||
{_('Cancel')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mb-4 flex items-center justify-between'>
|
||||
<label className='flex-1 text-sm font-medium'>{_('Theme Name')}</label>
|
||||
<input
|
||||
type='text'
|
||||
value={themeName}
|
||||
onChange={(e) => setThemeName(e.target.value)}
|
||||
className='bg-base-100 text-base-content border-base-200 w-[calc(50%-12px)] rounded border p-2 text-sm'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-2 gap-6'>
|
||||
<div className='bg-base-200 rounded-lg p-3'>
|
||||
<h3 className='mb-3 text-center font-medium'>{_('Light Mode')}</h3>
|
||||
|
||||
<ColorInput label={_('Text Color')} value={lightTextColor} onChange={setLightTextColor} />
|
||||
|
||||
<ColorInput
|
||||
label={_('Background Color')}
|
||||
value={lightBackgroundColor}
|
||||
onChange={setLightBackgroundColor}
|
||||
/>
|
||||
|
||||
<ThemePreview
|
||||
textColor={lightTextColor}
|
||||
backgroundColor={lightBackgroundColor}
|
||||
label={_('Preview')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='bg-base-300 rounded-lg p-3'>
|
||||
<h3 className='mb-3 text-center font-medium'>{_('Dark Mode')}</h3>
|
||||
|
||||
<ColorInput label={_('Text Color')} value={darkTextColor} onChange={setDarkTextColor} />
|
||||
|
||||
<ColorInput
|
||||
label={_('Background Color')}
|
||||
value={darkBackgroundColor}
|
||||
onChange={setDarkBackgroundColor}
|
||||
/>
|
||||
|
||||
<ThemePreview
|
||||
textColor={darkTextColor}
|
||||
backgroundColor={darkBackgroundColor}
|
||||
label={_('Preview')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeEditor;
|
||||
@@ -1,8 +1,18 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { applyCustomTheme } from '@/styles/themes';
|
||||
|
||||
export const useTheme = () => {
|
||||
const { settings } = useSettingsStore();
|
||||
const { themeColor, isDarkMode } = useThemeStore();
|
||||
useEffect(() => {
|
||||
const customThemes = settings.globalReadSettings?.customThemes ?? [];
|
||||
customThemes.forEach((customTheme) => {
|
||||
applyCustomTheme(customTheme);
|
||||
});
|
||||
}, [settings]);
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.setAttribute(
|
||||
'data-theme',
|
||||
|
||||
@@ -39,6 +39,7 @@ export const DEFAULT_READSETTINGS: ReadSettings = {
|
||||
autohideCursor: true,
|
||||
translateTargetLang: 'EN',
|
||||
|
||||
customThemes: [],
|
||||
highlightStyle: 'highlight',
|
||||
highlightStyles: {
|
||||
highlight: 'yellow',
|
||||
@@ -416,3 +417,42 @@ export const HIGHLIGHT_COLOR_HEX: Record<HighlightColor, string> = {
|
||||
blue: '#60a5fa', // blue-400
|
||||
violet: '#a78bfa', // violet-400
|
||||
};
|
||||
|
||||
export const CUSTOM_THEME_TEMPLATES = [
|
||||
{
|
||||
light: {
|
||||
fg: '#2b2b2b',
|
||||
bg: '#f3f3f3',
|
||||
primary: '#3c5a72',
|
||||
},
|
||||
dark: {
|
||||
fg: '#d0d0d0',
|
||||
bg: '#1a1c1f',
|
||||
primary: '#486e8a',
|
||||
},
|
||||
},
|
||||
{
|
||||
light: {
|
||||
fg: '#3f2f3c',
|
||||
bg: '#f5ecf8',
|
||||
primary: '#7b5291',
|
||||
},
|
||||
dark: {
|
||||
fg: '#d6cadd',
|
||||
bg: '#3a2c3d',
|
||||
primary: '#bda0cc',
|
||||
},
|
||||
},
|
||||
{
|
||||
light: {
|
||||
fg: '#2b2b2b',
|
||||
bg: '#defcd9',
|
||||
primary: '#00796b',
|
||||
},
|
||||
dark: {
|
||||
fg: '#c8e6c9',
|
||||
bg: '#273c33',
|
||||
primary: '#26a69a',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { create } from 'zustand';
|
||||
import { getThemeCode, ThemeCode } from '@/utils/style';
|
||||
import { Palette, ThemeMode } from '@/styles/themes';
|
||||
import { isWebAppPlatform } from '@/services/environment';
|
||||
import { CustomTheme, Palette, ThemeMode } from '@/styles/themes';
|
||||
import { EnvConfigType, isWebAppPlatform } from '@/services/environment';
|
||||
import { SystemSettings } from '@/types/settings';
|
||||
|
||||
interface ThemeState {
|
||||
themeMode: ThemeMode;
|
||||
@@ -12,6 +13,12 @@ interface ThemeState {
|
||||
setThemeMode: (mode: ThemeMode) => void;
|
||||
setThemeColor: (color: string) => void;
|
||||
updateAppTheme: (color: keyof Palette) => void;
|
||||
saveCustomTheme: (
|
||||
envConfig: EnvConfigType,
|
||||
settings: SystemSettings,
|
||||
theme: CustomTheme,
|
||||
isDelete?: boolean,
|
||||
) => void;
|
||||
}
|
||||
|
||||
const getInitialThemeMode = (): ThemeMode => {
|
||||
@@ -81,5 +88,24 @@ export const useThemeStore = create<ThemeState>((set, get) => {
|
||||
document.querySelector('meta[name="theme-color"]')?.setAttribute('content', palette[color]);
|
||||
}
|
||||
},
|
||||
saveCustomTheme: async (envConfig, settings, theme, isDelete) => {
|
||||
const customThemes = settings.globalReadSettings.customThemes || [];
|
||||
const index = customThemes.findIndex((t) => t.name === theme.name);
|
||||
if (isDelete) {
|
||||
if (index > -1) {
|
||||
customThemes.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
if (index > -1) {
|
||||
customThemes[index] = theme;
|
||||
} else {
|
||||
customThemes.push(theme);
|
||||
}
|
||||
}
|
||||
settings.globalReadSettings.customThemes = customThemes;
|
||||
localStorage.setItem('customThemes', JSON.stringify(customThemes));
|
||||
const appService = await envConfig.getAppService();
|
||||
await appService.saveSettings(settings);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { stubTranslation as _ } from '../utils/misc';
|
||||
import { getContrastOklch, hexToOklch } from '../utils/color';
|
||||
|
||||
type BaseColor = {
|
||||
export type BaseColor = {
|
||||
bg: string;
|
||||
fg: string;
|
||||
primary: string;
|
||||
};
|
||||
|
||||
export type ThemeMode = 'auto' | 'light' | 'dark';
|
||||
|
||||
export type Palette = {
|
||||
'base-100': string;
|
||||
'base-200': string;
|
||||
@@ -20,7 +22,26 @@ export type Palette = {
|
||||
accent: string;
|
||||
};
|
||||
|
||||
const generateLightPalette = ({ bg, fg, primary }: BaseColor) => {
|
||||
export type Theme = {
|
||||
name: string;
|
||||
label: string;
|
||||
colors: {
|
||||
light: Palette;
|
||||
dark: Palette;
|
||||
};
|
||||
isCustomizale?: boolean;
|
||||
};
|
||||
|
||||
export type CustomTheme = {
|
||||
name: string;
|
||||
label: string;
|
||||
colors: {
|
||||
light: BaseColor;
|
||||
dark: BaseColor;
|
||||
};
|
||||
};
|
||||
|
||||
export const generateLightPalette = ({ bg, fg, primary }: BaseColor) => {
|
||||
return {
|
||||
'base-100': bg, // Main background
|
||||
'base-200': tinycolor(bg).darken(5).toHexString(), // Slightly darker
|
||||
@@ -34,7 +55,7 @@ const generateLightPalette = ({ bg, fg, primary }: BaseColor) => {
|
||||
} as Palette;
|
||||
};
|
||||
|
||||
const generateDarkPalette = ({ bg, fg, primary }: BaseColor) => {
|
||||
export const generateDarkPalette = ({ bg, fg, primary }: BaseColor) => {
|
||||
return {
|
||||
'base-100': bg, // Main background
|
||||
'base-200': tinycolor(bg).lighten(5).toHexString(), // Slightly lighter
|
||||
@@ -121,4 +142,89 @@ export const themes = [
|
||||
dark: generateDarkPalette({ fg: '#d8dee9', bg: '#2e3440', primary: '#88c0d0' }),
|
||||
},
|
||||
},
|
||||
];
|
||||
{
|
||||
name: 'contrast',
|
||||
label: _('Contrast'),
|
||||
colors: {
|
||||
light: generateLightPalette({ fg: '#000000', bg: '#ffffff', primary: '#4488cc' }),
|
||||
dark: generateDarkPalette({ fg: '#ffffff', bg: '#000000', primary: '#88ccee' }),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'sunset',
|
||||
label: _('Sunset'),
|
||||
colors: {
|
||||
light: generateLightPalette({ fg: '#423126', bg: '#fff7f0', primary: '#fe6b64' }),
|
||||
dark: generateDarkPalette({ fg: '#f6e1d7', bg: '#3c2b25', primary: '#ff9c94' }),
|
||||
},
|
||||
},
|
||||
] as Theme[];
|
||||
|
||||
const generateCustomThemeVariables = (palette: Palette): string => {
|
||||
return `
|
||||
--b1: ${hexToOklch(palette['base-100'])};
|
||||
--b2: ${hexToOklch(palette['base-200'])};
|
||||
--b3: ${hexToOklch(palette['base-300'])};
|
||||
--bc: ${hexToOklch(palette['base-content'])};
|
||||
|
||||
--p: ${hexToOklch(palette.primary)};
|
||||
--pc: ${getContrastOklch(palette.primary)};
|
||||
|
||||
--s: ${hexToOklch(palette.secondary)};
|
||||
--sc: ${getContrastOklch(palette.secondary)};
|
||||
|
||||
--a: ${hexToOklch(palette.accent)};
|
||||
--ac: ${getContrastOklch(palette.accent)};
|
||||
|
||||
--n: ${hexToOklch(palette.neutral)};
|
||||
--nc: ${hexToOklch(palette['neutral-content'])};
|
||||
|
||||
--in: 69.37% 0.047 231;
|
||||
--inc: 100% 0 0;
|
||||
--su: 78.15% 0.12 160;
|
||||
--suc: 100% 0 0;
|
||||
--wa: 90.69% 0.123 84;
|
||||
--wac: 0% 0 0;
|
||||
--er: 70.9% 0.184 22;
|
||||
--erc: 100% 0 0;
|
||||
`;
|
||||
};
|
||||
|
||||
export const applyCustomTheme = (customTheme: CustomTheme) => {
|
||||
const lightPalette = generateLightPalette(customTheme.colors.light);
|
||||
const darkPalette = generateDarkPalette(customTheme.colors.dark);
|
||||
|
||||
const lightThemeName = `${customTheme.name}-light`;
|
||||
const darkThemeName = `${customTheme.name}-dark`;
|
||||
|
||||
const css = `
|
||||
[data-theme="${lightThemeName}"] {
|
||||
${generateCustomThemeVariables(lightPalette)}
|
||||
}
|
||||
|
||||
[data-theme="${darkThemeName}"] {
|
||||
${generateCustomThemeVariables(darkPalette)}
|
||||
}
|
||||
|
||||
:root {
|
||||
--${lightThemeName}: 1;
|
||||
--${darkThemeName}: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const styleElement = document.createElement('style');
|
||||
styleElement.id = `theme-${lightThemeName}-styles`;
|
||||
styleElement.textContent = css;
|
||||
|
||||
const existingStyle = document.getElementById(styleElement.id);
|
||||
if (existingStyle) {
|
||||
existingStyle.remove();
|
||||
}
|
||||
|
||||
document.head.appendChild(styleElement);
|
||||
|
||||
return {
|
||||
light: lightThemeName,
|
||||
dark: darkThemeName,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CustomTheme } from '@/styles/themes';
|
||||
import { HighlightColor, HighlightStyle, ViewSettings } from './book';
|
||||
|
||||
export type ThemeType = 'light' | 'dark' | 'auto';
|
||||
@@ -12,6 +13,7 @@ export interface ReadSettings {
|
||||
|
||||
highlightStyle: HighlightStyle;
|
||||
highlightStyles: Record<HighlightStyle, HighlightColor>;
|
||||
customThemes: CustomTheme[];
|
||||
}
|
||||
|
||||
export interface SystemSettings {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import tinycolor from 'tinycolor2';
|
||||
|
||||
function srgbToLinear(v: number): number {
|
||||
// Standard formula for gamma decoding of sRGB
|
||||
return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
export function hexToOklch(hexColor: string): string {
|
||||
// 1) Convert from hex → sRGB (0..255) → [0..1]
|
||||
const { r, g, b } = tinycolor(hexColor).toRgb();
|
||||
const R = srgbToLinear(r / 255);
|
||||
const G = srgbToLinear(g / 255);
|
||||
const B = srgbToLinear(b / 255);
|
||||
|
||||
// 2) Convert linear sRGB → L'M'S' (the Oklab-specific "LMS" space)
|
||||
const l_ = Math.cbrt(0.4122214708 * R + 0.5363325363 * G + 0.0514459929 * B);
|
||||
const m_ = Math.cbrt(0.2119034982 * R + 0.6806995451 * G + 0.1073969566 * B);
|
||||
const s_ = Math.cbrt(0.0883024619 * R + 0.2817188376 * G + 0.6299787005 * B);
|
||||
|
||||
// 3) Convert L'M'S' → Oklab
|
||||
const L = 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_;
|
||||
const a = 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_;
|
||||
const b_ = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_;
|
||||
|
||||
// 4) Convert Oklab → Oklch
|
||||
const C = Math.sqrt(a * a + b_ * b_);
|
||||
let h = Math.atan2(b_, a) * (180 / Math.PI);
|
||||
if (h < 0) h += 360;
|
||||
|
||||
// 5) Format as l% c h, with a bit of rounding
|
||||
const lPercent = (L * 100).toFixed(4);
|
||||
const cValue = C.toFixed(6);
|
||||
const hValue = h.toFixed(6);
|
||||
|
||||
return `${lPercent}% ${cValue} ${hValue}`;
|
||||
}
|
||||
|
||||
export const getContrastOklch = (hexColor: string): string => {
|
||||
return tinycolor(hexColor).isDark() ? '100% 0 0' : '0% 0 0';
|
||||
};
|
||||
@@ -5,7 +5,14 @@ import {
|
||||
FALLBACK_FONTS,
|
||||
} from '@/services/constants';
|
||||
import { ViewSettings } from '@/types/book';
|
||||
import { themes, Palette, ThemeMode } from '@/styles/themes';
|
||||
import {
|
||||
themes,
|
||||
Palette,
|
||||
ThemeMode,
|
||||
CustomTheme,
|
||||
generateLightPalette,
|
||||
generateDarkPalette,
|
||||
} from '@/styles/themes';
|
||||
|
||||
import fontfacesCSS from '!!raw-loader!../styles/fonts.css';
|
||||
import { getOSPlatform } from './misc';
|
||||
@@ -260,14 +267,30 @@ export const getThemeCode = () => {
|
||||
let themeMode = 'auto';
|
||||
let themeColor = 'default';
|
||||
let systemIsDarkMode = false;
|
||||
let customThemes: CustomTheme[] = [];
|
||||
if (typeof window !== 'undefined') {
|
||||
themeColor = localStorage.getItem('themeColor') || 'default';
|
||||
themeMode = localStorage.getItem('themeMode') as ThemeMode;
|
||||
customThemes = JSON.parse(localStorage.getItem('customThemes') || '[]');
|
||||
systemIsDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
const isDarkMode = themeMode === 'dark' || (themeMode === 'auto' && systemIsDarkMode);
|
||||
const defaultTheme = themes.find((theme) => theme.name === themeColor);
|
||||
const defaultPalette = isDarkMode ? defaultTheme!.colors.dark : defaultTheme!.colors.light;
|
||||
let currentTheme = themes.find((theme) => theme.name === themeColor);
|
||||
if (!currentTheme) {
|
||||
const customTheme = customThemes.find((theme) => theme.name === themeColor);
|
||||
if (customTheme) {
|
||||
currentTheme = {
|
||||
name: customTheme.name,
|
||||
label: customTheme.label,
|
||||
colors: {
|
||||
light: generateLightPalette(customTheme.colors.light),
|
||||
dark: generateDarkPalette(customTheme.colors.dark),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!currentTheme) currentTheme = themes[0];
|
||||
const defaultPalette = isDarkMode ? currentTheme!.colors.dark : currentTheme!.colors.light;
|
||||
return {
|
||||
bg: defaultPalette['base-100'],
|
||||
fg: defaultPalette['base-content'],
|
||||
|
||||
Generated
+70
@@ -131,6 +131,9 @@ importers:
|
||||
react:
|
||||
specifier: 19.0.0
|
||||
version: 19.0.0
|
||||
react-color:
|
||||
specifier: ^2.19.3
|
||||
version: 2.19.3(react@19.0.0)
|
||||
react-dom:
|
||||
specifier: 19.0.0
|
||||
version: 19.0.0(react@19.0.0)
|
||||
@@ -165,6 +168,9 @@ importers:
|
||||
'@types/react':
|
||||
specifier: 18.3.12
|
||||
version: 18.3.12
|
||||
'@types/react-color':
|
||||
specifier: ^3.0.13
|
||||
version: 3.0.13(@types/react@18.3.12)
|
||||
'@types/react-dom':
|
||||
specifier: 18.3.1
|
||||
version: 18.3.1
|
||||
@@ -985,6 +991,11 @@ packages:
|
||||
resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==}
|
||||
engines: {node: '>=18.18'}
|
||||
|
||||
'@icons/material@0.2.4':
|
||||
resolution: {integrity: sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
|
||||
'@img/sharp-darwin-arm64@0.33.5':
|
||||
resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
@@ -1819,12 +1830,22 @@ packages:
|
||||
'@types/prop-types@15.7.13':
|
||||
resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==}
|
||||
|
||||
'@types/react-color@3.0.13':
|
||||
resolution: {integrity: sha512-2c/9FZ4ixC5T3JzN0LP5Cke2Mf0MKOP2Eh0NPDPWmuVH3NjPyhEjqNMQpN1Phr5m74egAy+p2lYNAFrX1z9Yrg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
|
||||
'@types/react-dom@18.3.1':
|
||||
resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==}
|
||||
|
||||
'@types/react@18.3.12':
|
||||
resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==}
|
||||
|
||||
'@types/reactcss@1.2.13':
|
||||
resolution: {integrity: sha512-gi3S+aUi6kpkF5vdhUsnkwbiSEIU/BEJyD7kBy2SudWBUuKmJk8AQKE0OVcQQeEy40Azh0lV6uynxlikYIJuwg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
|
||||
'@types/resolve@1.20.2':
|
||||
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
||||
|
||||
@@ -3278,6 +3299,9 @@ packages:
|
||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
lodash-es@4.17.21:
|
||||
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
||||
|
||||
lodash.debounce@4.0.8:
|
||||
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
|
||||
|
||||
@@ -3313,6 +3337,9 @@ packages:
|
||||
matchmediaquery@0.4.2:
|
||||
resolution: {integrity: sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==}
|
||||
|
||||
material-colors@1.2.6:
|
||||
resolution: {integrity: sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==}
|
||||
|
||||
merge-stream@2.0.0:
|
||||
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||
|
||||
@@ -3782,6 +3809,11 @@ packages:
|
||||
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
||||
hasBin: true
|
||||
|
||||
react-color@2.19.3:
|
||||
resolution: {integrity: sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
|
||||
react-dom@18.3.1:
|
||||
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
|
||||
peerDependencies:
|
||||
@@ -3827,6 +3859,11 @@ packages:
|
||||
resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
reactcss@1.2.3:
|
||||
resolution: {integrity: sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
|
||||
read-cache@1.0.0:
|
||||
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
||||
|
||||
@@ -5818,6 +5855,10 @@ snapshots:
|
||||
|
||||
'@humanwhocodes/retry@0.4.1': {}
|
||||
|
||||
'@icons/material@0.2.4(react@19.0.0)':
|
||||
dependencies:
|
||||
react: 19.0.0
|
||||
|
||||
'@img/sharp-darwin-arm64@0.33.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-darwin-arm64': 1.0.4
|
||||
@@ -6728,6 +6769,11 @@ snapshots:
|
||||
|
||||
'@types/prop-types@15.7.13': {}
|
||||
|
||||
'@types/react-color@3.0.13(@types/react@18.3.12)':
|
||||
dependencies:
|
||||
'@types/react': 18.3.12
|
||||
'@types/reactcss': 1.2.13(@types/react@18.3.12)
|
||||
|
||||
'@types/react-dom@18.3.1':
|
||||
dependencies:
|
||||
'@types/react': 18.3.12
|
||||
@@ -6737,6 +6783,10 @@ snapshots:
|
||||
'@types/prop-types': 15.7.13
|
||||
csstype: 3.1.3
|
||||
|
||||
'@types/reactcss@1.2.13(@types/react@18.3.12)':
|
||||
dependencies:
|
||||
'@types/react': 18.3.12
|
||||
|
||||
'@types/resolve@1.20.2': {}
|
||||
|
||||
'@types/tinycolor2@1.4.6': {}
|
||||
@@ -8389,6 +8439,8 @@ snapshots:
|
||||
dependencies:
|
||||
p-locate: 5.0.0
|
||||
|
||||
lodash-es@4.17.21: {}
|
||||
|
||||
lodash.debounce@4.0.8: {}
|
||||
|
||||
lodash.merge@4.6.2: {}
|
||||
@@ -8420,6 +8472,8 @@ snapshots:
|
||||
dependencies:
|
||||
css-mediaquery: 0.1.2
|
||||
|
||||
material-colors@1.2.6: {}
|
||||
|
||||
merge-stream@2.0.0: {}
|
||||
|
||||
merge2@1.4.1: {}
|
||||
@@ -8831,6 +8885,17 @@ snapshots:
|
||||
strip-json-comments: 2.0.1
|
||||
optional: true
|
||||
|
||||
react-color@2.19.3(react@19.0.0):
|
||||
dependencies:
|
||||
'@icons/material': 0.2.4(react@19.0.0)
|
||||
lodash: 4.17.21
|
||||
lodash-es: 4.17.21
|
||||
material-colors: 1.2.6
|
||||
prop-types: 15.8.1
|
||||
react: 19.0.0
|
||||
reactcss: 1.2.3(react@19.0.0)
|
||||
tinycolor2: 1.6.0
|
||||
|
||||
react-dom@18.3.1(react@18.3.1):
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
@@ -8871,6 +8936,11 @@ snapshots:
|
||||
|
||||
react@19.0.0: {}
|
||||
|
||||
reactcss@1.2.3(react@19.0.0):
|
||||
dependencies:
|
||||
lodash: 4.17.21
|
||||
react: 19.0.0
|
||||
|
||||
read-cache@1.0.0:
|
||||
dependencies:
|
||||
pify: 2.3.0
|
||||
|
||||
Reference in New Issue
Block a user