mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-02 16:23:48 +03:00
refactor: migrate settings page styles from VE to SCSS + BEM
This commit is contained in:
parent
eed28d7444
commit
c51b61501e
@ -0,0 +1,27 @@
|
||||
@use "../../scss/globals.scss";
|
||||
|
||||
.add-download-source-modal {
|
||||
&__container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: globals.$spacing-unit;
|
||||
min-width: 500px;
|
||||
}
|
||||
|
||||
&__validation-result {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: calc(globals.$spacing-unit * 3);
|
||||
}
|
||||
|
||||
&__validation-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(globals.$spacing-unit / 2);
|
||||
}
|
||||
|
||||
&__validate-button {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ import { useCallback, useContext, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Button, Modal, TextField } from "@renderer/components";
|
||||
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||
import { settingsContext } from "@renderer/context";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
@ -11,6 +10,7 @@ import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { downloadSourcesTable } from "@renderer/dexie";
|
||||
import type { DownloadSourceValidationResult } from "@types";
|
||||
import { downloadSourcesWorker } from "@renderer/workers";
|
||||
import "./add-download-source-modal.scss";
|
||||
|
||||
interface AddDownloadSourceModalProps {
|
||||
visible: boolean;
|
||||
@ -138,14 +138,7 @@ export function AddDownloadSourceModal({
|
||||
description={t("add_download_source_description")}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
minWidth: "500px",
|
||||
}}
|
||||
>
|
||||
<div className="add-download-source-modal__container">
|
||||
<TextField
|
||||
{...register("url")}
|
||||
label={t("download_source_url")}
|
||||
@ -155,7 +148,7 @@ export function AddDownloadSourceModal({
|
||||
<Button
|
||||
type="button"
|
||||
theme="outline"
|
||||
style={{ alignSelf: "flex-end" }}
|
||||
className="add-download-source-modal__validate-button"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
disabled={isSubmitting || isLoading}
|
||||
>
|
||||
@ -165,21 +158,8 @@ export function AddDownloadSourceModal({
|
||||
/>
|
||||
|
||||
{validationResult && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginTop: `${SPACING_UNIT * 3}px`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: `${SPACING_UNIT / 2}px`,
|
||||
}}
|
||||
>
|
||||
<div className="add-download-source-modal__validation-result">
|
||||
<div className="add-download-source-modal__validation-info">
|
||||
<h4>{validationResult?.name}</h4>
|
||||
<small>
|
||||
{t("found_download_option", {
|
||||
|
67
src/renderer/src/pages/settings/settings-account.scss
Normal file
67
src/renderer/src/pages/settings/settings-account.scss
Normal file
@ -0,0 +1,67 @@
|
||||
@use "../../scss/globals.scss";
|
||||
|
||||
.settings-account {
|
||||
&__form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(globals.$spacing-unit * 2);
|
||||
}
|
||||
|
||||
&__section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: globals.$spacing-unit;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
gap: globals.$spacing-unit;
|
||||
margin-top: globals.$spacing-unit;
|
||||
}
|
||||
|
||||
&__subscription-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(globals.$spacing-unit * 2);
|
||||
}
|
||||
|
||||
&__subscription-button {
|
||||
place-self: flex-start;
|
||||
}
|
||||
|
||||
&__blocked-users {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: globals.$spacing-unit;
|
||||
}
|
||||
|
||||
&__blocked-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: globals.$spacing-unit;
|
||||
}
|
||||
|
||||
&__user-info {
|
||||
display: flex;
|
||||
gap: globals.$spacing-unit;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__user-avatar {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
&__unblock-button {
|
||||
color: globals.$danger-color;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
import { Avatar, Button, SelectField } from "@renderer/components";
|
||||
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import * as styles from "./settings-account.css";
|
||||
import { useDate, useToast, useUserDetails } from "@renderer/hooks";
|
||||
import { useCallback, useContext, useEffect, useState } from "react";
|
||||
import {
|
||||
@ -14,6 +11,7 @@ import {
|
||||
} from "@primer/octicons-react";
|
||||
import { settingsContext } from "@renderer/context";
|
||||
import { AuthPage } from "@shared";
|
||||
import "./settings-account.scss";
|
||||
|
||||
interface FormValues {
|
||||
profileVisibility: "PUBLIC" | "FRIENDS" | "PRIVATE";
|
||||
@ -145,7 +143,7 @@ export function SettingsAccount() {
|
||||
if (!userDetails) return null;
|
||||
|
||||
return (
|
||||
<form className={styles.form} onSubmit={handleSubmit(onSubmit)}>
|
||||
<form className="settings-account__form" onSubmit={handleSubmit(onSubmit)}>
|
||||
<Controller
|
||||
control={control}
|
||||
name="profileVisibility"
|
||||
@ -158,7 +156,7 @@ export function SettingsAccount() {
|
||||
};
|
||||
|
||||
return (
|
||||
<section>
|
||||
<section className="settings-account__section">
|
||||
<SelectField
|
||||
label={t("profile_visibility")}
|
||||
value={field.value}
|
||||
@ -177,19 +175,11 @@ export function SettingsAccount() {
|
||||
}}
|
||||
/>
|
||||
|
||||
<section>
|
||||
<section className="settings-account__section">
|
||||
<h4>{t("current_email")}</h4>
|
||||
<p>{userDetails?.email ?? t("no_email_account")}</p>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "start",
|
||||
alignItems: "center",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
marginTop: `${SPACING_UNIT * 2}px`,
|
||||
}}
|
||||
>
|
||||
<div className="settings-account__actions">
|
||||
<Button
|
||||
theme="outline"
|
||||
onClick={() => window.electron.openAuthWindow(AuthPage.UpdateEmail)}
|
||||
@ -210,28 +200,14 @@ export function SettingsAccount() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: `${SPACING_UNIT * 2}px`,
|
||||
}}
|
||||
>
|
||||
<section className="settings-account__section">
|
||||
<h3>Hydra Cloud</h3>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
}}
|
||||
>
|
||||
<div className="settings-account__subscription-info">
|
||||
{getHydraCloudSectionContent().description}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
style={{
|
||||
placeSelf: "flex-start",
|
||||
}}
|
||||
className="settings-account__subscription-button"
|
||||
theme="outline"
|
||||
onClick={() => window.electron.openCheckout()}
|
||||
>
|
||||
@ -240,29 +216,17 @@ export function SettingsAccount() {
|
||||
</Button>
|
||||
</section>
|
||||
|
||||
<section
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: `${SPACING_UNIT * 2}px`,
|
||||
}}
|
||||
>
|
||||
<section className="settings-account__section">
|
||||
<h3>{t("blocked_users")}</h3>
|
||||
|
||||
{blockedUsers.length > 0 ? (
|
||||
<ul className={styles.blockedUsersList}>
|
||||
<ul className="settings-account__blocked-users">
|
||||
{blockedUsers.map((user) => {
|
||||
return (
|
||||
<li key={user.id} className={styles.blockedUser}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<li key={user.id} className="settings-account__blocked-user">
|
||||
<div className="settings-account__user-info">
|
||||
<Avatar
|
||||
style={{ filter: "grayscale(100%)" }}
|
||||
className="settings-account__user-avatar"
|
||||
size={32}
|
||||
src={user.profileImageUrl}
|
||||
alt={user.displayName}
|
||||
@ -272,7 +236,7 @@ export function SettingsAccount() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={styles.unblockButton}
|
||||
className="settings-account__unblock-button"
|
||||
onClick={() => handleUnblockClick(user.id)}
|
||||
disabled={isUnblocking}
|
||||
>
|
||||
|
13
src/renderer/src/pages/settings/settings-behavior.scss
Normal file
13
src/renderer/src/pages/settings/settings-behavior.scss
Normal file
@ -0,0 +1,13 @@
|
||||
@use "../../scss/globals.scss";
|
||||
|
||||
.settings-behavior {
|
||||
&__checkbox-container {
|
||||
opacity: globals.$disabled-opacity;
|
||||
cursor: not-allowed;
|
||||
|
||||
&--enabled {
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { CheckboxField } from "@renderer/components";
|
||||
import { useAppSelector } from "@renderer/hooks";
|
||||
import { settingsContext } from "@renderer/context";
|
||||
import "./settings-behavior.scss";
|
||||
|
||||
export function SettingsBehavior() {
|
||||
const userPreferences = useAppSelector(
|
||||
@ -77,10 +78,11 @@ export function SettingsBehavior() {
|
||||
)}
|
||||
|
||||
{showRunAtStartup && (
|
||||
<div style={{ opacity: form.runAtStartup ? 1 : 0.5 }}>
|
||||
<div
|
||||
className={`settings-behavior__checkbox-container ${form.runAtStartup ? "settings-behavior__checkbox-container--enabled" : ""}`}
|
||||
>
|
||||
<CheckboxField
|
||||
label={t("launch_minimized")}
|
||||
style={{ cursor: form.runAtStartup ? "pointer" : "not-allowed" }}
|
||||
checked={form.runAtStartup && form.startMinimized}
|
||||
disabled={!form.runAtStartup}
|
||||
onChange={() => {
|
||||
|
@ -0,0 +1,56 @@
|
||||
@use "../../scss/globals.scss";
|
||||
|
||||
.settings-download-sources {
|
||||
&__list {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
gap: calc(globals.$spacing-unit * 2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: globals.$dark-background-color;
|
||||
border-radius: 8px;
|
||||
padding: calc(globals.$spacing-unit * 2);
|
||||
gap: globals.$spacing-unit;
|
||||
border: solid 1px globals.$border-color;
|
||||
transition: all ease 0.2s;
|
||||
|
||||
&--syncing {
|
||||
opacity: globals.$disabled-opacity;
|
||||
}
|
||||
}
|
||||
|
||||
&__item-header {
|
||||
margin-bottom: globals.$spacing-unit;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: globals.$spacing-unit;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__navigate-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: globals.$spacing-unit;
|
||||
color: globals.$muted-color;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@ import { useContext, useEffect, useState } from "react";
|
||||
import { TextField, Button, Badge } from "@renderer/components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import * as styles from "./settings-download-sources.css";
|
||||
import type { DownloadSource } from "@types";
|
||||
import { NoEntryIcon, PlusCircleIcon, SyncIcon } from "@primer/octicons-react";
|
||||
import { AddDownloadSourceModal } from "./add-download-source-modal";
|
||||
@ -14,6 +13,7 @@ import { downloadSourcesTable } from "@renderer/dexie";
|
||||
import { downloadSourcesWorker } from "@renderer/workers";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { setFilters, clearFilters } from "@renderer/features";
|
||||
import "./settings-download-sources.scss";
|
||||
|
||||
export function SettingsDownloadSources() {
|
||||
const [showAddDownloadSourceModal, setShowAddDownloadSourceModal] =
|
||||
@ -118,7 +118,7 @@ export function SettingsDownloadSources() {
|
||||
|
||||
<p>{t("download_sources_description")}</p>
|
||||
|
||||
<div className={styles.downloadSourcesHeader}>
|
||||
<div className="settings-download-sources__header">
|
||||
<Button
|
||||
type="button"
|
||||
theme="outline"
|
||||
@ -144,15 +144,13 @@ export function SettingsDownloadSources() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<ul className={styles.downloadSources}>
|
||||
<ul className="settings-download-sources__list">
|
||||
{downloadSources.map((downloadSource) => (
|
||||
<li
|
||||
key={downloadSource.id}
|
||||
className={styles.downloadSourceItem({
|
||||
isSyncing: isSyncingDownloadSources,
|
||||
})}
|
||||
className={`settings-download-sources__item ${isSyncingDownloadSources ? "settings-download-sources__item--syncing" : ""}`}
|
||||
>
|
||||
<div className={styles.downloadSourceItemHeader}>
|
||||
<div className="settings-download-sources__item-header">
|
||||
<h2>{downloadSource.name}</h2>
|
||||
|
||||
<div style={{ display: "flex" }}>
|
||||
@ -161,7 +159,7 @@ export function SettingsDownloadSources() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={styles.navigateToCatalogueButton}
|
||||
className="settings-download-sources__navigate-button"
|
||||
disabled={!downloadSource.fingerprint}
|
||||
onClick={() => navigateToCatalogue(downloadSource.fingerprint)}
|
||||
>
|
||||
|
12
src/renderer/src/pages/settings/settings-general.scss
Normal file
12
src/renderer/src/pages/settings/settings-general.scss
Normal file
@ -0,0 +1,12 @@
|
||||
@use "../../scss/globals.scss";
|
||||
|
||||
.settings-general {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: globals.$spacing-unit * 2;
|
||||
|
||||
&__notifications-title {
|
||||
margin-top: calc(globals.$spacing-unit * 2);
|
||||
margin-bottom: globals.$spacing-unit;
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import { changeLanguage } from "i18next";
|
||||
import languageResources from "@locales";
|
||||
import { orderBy } from "lodash-es";
|
||||
import { settingsContext } from "@renderer/context";
|
||||
import "./settings-general.scss";
|
||||
|
||||
interface LanguageOption {
|
||||
option: string;
|
||||
@ -114,7 +115,7 @@ export function SettingsGeneral() {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="settings-general">
|
||||
<TextField
|
||||
label={t("downloads_path")}
|
||||
value={form.downloadsPath}
|
||||
@ -138,7 +139,9 @@ export function SettingsGeneral() {
|
||||
}))}
|
||||
/>
|
||||
|
||||
<p>{t("notifications")}</p>
|
||||
<p className="settings-general__notifications-title">
|
||||
{t("notifications")}
|
||||
</p>
|
||||
|
||||
<CheckboxField
|
||||
label={t("enable_download_notifications")}
|
||||
@ -171,6 +174,6 @@ export function SettingsGeneral() {
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
18
src/renderer/src/pages/settings/settings-real-debrid.scss
Normal file
18
src/renderer/src/pages/settings/settings-real-debrid.scss
Normal file
@ -0,0 +1,18 @@
|
||||
@use "../../scss/globals.scss";
|
||||
|
||||
.settings-real-debrid {
|
||||
&__form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: globals.$spacing-unit;
|
||||
}
|
||||
|
||||
&__description {
|
||||
margin-bottom: calc(globals.$spacing-unit * 2);
|
||||
}
|
||||
|
||||
&__submit-button {
|
||||
align-self: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
@ -2,11 +2,10 @@ import { useContext, useEffect, useState } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
import { Button, CheckboxField, Link, TextField } from "@renderer/components";
|
||||
import * as styles from "./settings-real-debrid.css";
|
||||
import "./settings-real-debrid.scss";
|
||||
|
||||
import { useAppSelector, useToast } from "@renderer/hooks";
|
||||
|
||||
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||
import { settingsContext } from "@renderer/context";
|
||||
|
||||
const REAL_DEBRID_API_TOKEN_URL = "https://real-debrid.com/apitoken";
|
||||
@ -78,8 +77,10 @@ export function SettingsRealDebrid() {
|
||||
(form.useRealDebrid && !form.realDebridApiToken) || isLoading;
|
||||
|
||||
return (
|
||||
<form className={styles.form} onSubmit={handleFormSubmit}>
|
||||
<p className={styles.description}>{t("real_debrid_description")}</p>
|
||||
<form className="settings-real-debrid__form" onSubmit={handleFormSubmit}>
|
||||
<p className="settings-real-debrid__description">
|
||||
{t("real_debrid_description")}
|
||||
</p>
|
||||
|
||||
<CheckboxField
|
||||
label={t("enable_real_debrid")}
|
||||
@ -100,8 +101,12 @@ export function SettingsRealDebrid() {
|
||||
onChange={(event) =>
|
||||
setForm({ ...form, realDebridApiToken: event.target.value })
|
||||
}
|
||||
rightContent={
|
||||
<Button type="submit" disabled={isButtonDisabled}>
|
||||
{t("save_changes")}
|
||||
</Button>
|
||||
}
|
||||
placeholder="API Token"
|
||||
containerProps={{ style: { marginTop: `${SPACING_UNIT}px` } }}
|
||||
hint={
|
||||
<Trans i18nKey="real_debrid_api_token_hint" ns="settings">
|
||||
<Link to={REAL_DEBRID_API_TOKEN_URL} />
|
||||
@ -109,14 +114,6 @@ export function SettingsRealDebrid() {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
style={{ alignSelf: "flex-end", marginTop: `${SPACING_UNIT * 2}px` }}
|
||||
disabled={isButtonDisabled}
|
||||
>
|
||||
{t("save_changes")}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
27
src/renderer/src/pages/settings/settings.scss
Normal file
27
src/renderer/src/pages/settings/settings.scss
Normal file
@ -0,0 +1,27 @@
|
||||
@use "../../scss/globals.scss";
|
||||
|
||||
.settings {
|
||||
&__container {
|
||||
padding: 24px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__content {
|
||||
background-color: globals.$background-color;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: calc(globals.$spacing-unit * 3);
|
||||
border: solid 1px globals.$border-color;
|
||||
box-shadow: 0px 0px 15px 0px #000000;
|
||||
border-radius: 8px;
|
||||
gap: calc(globals.$spacing-unit * 2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__categories {
|
||||
display: flex;
|
||||
gap: globals.$spacing-unit;
|
||||
}
|
||||
}
|
@ -1,11 +1,8 @@
|
||||
import { Button } from "@renderer/components";
|
||||
|
||||
import * as styles from "./settings.css";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { SettingsRealDebrid } from "./settings-real-debrid";
|
||||
import { SettingsGeneral } from "./settings-general";
|
||||
import { SettingsBehavior } from "./settings-behavior";
|
||||
|
||||
import { SettingsDownloadSources } from "./settings-download-sources";
|
||||
import {
|
||||
SettingsContextConsumer,
|
||||
@ -14,10 +11,10 @@ import {
|
||||
import { SettingsAccount } from "./settings-account";
|
||||
import { useUserDetails } from "@renderer/hooks";
|
||||
import { useMemo } from "react";
|
||||
import "./settings.scss";
|
||||
|
||||
export default function Settings() {
|
||||
const { t } = useTranslation("settings");
|
||||
|
||||
const { userDetails } = useUserDetails();
|
||||
|
||||
const categories = useMemo(() => {
|
||||
@ -57,9 +54,9 @@ export default function Settings() {
|
||||
};
|
||||
|
||||
return (
|
||||
<section className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<section className={styles.settingsCategories}>
|
||||
<section className="settings__container">
|
||||
<div className="settings__content">
|
||||
<section className="settings__categories">
|
||||
{categories.map((category, index) => (
|
||||
<Button
|
||||
key={category}
|
||||
|
Loading…
Reference in New Issue
Block a user