User preferences #104
@ -6,7 +6,7 @@ import { faBell, faMessage } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { RootState } from "State/Store";
|
||||
import { init, UserPreferences } from "State/Login";
|
||||
import { init, setPreferences, UserPreferences } from "State/Login";
|
||||
import { HexKey, RawEvent, TaggedRawEvent } from "Nostr";
|
||||
import { RelaySettings } from "Nostr/Connection";
|
||||
import { System } from "Nostr/System"
|
||||
@ -39,14 +39,26 @@ export default function Layout() {
|
||||
}
|
||||
}, [relays]);
|
||||
|
||||
useEffect(() => {
|
||||
function setTheme(theme: "light" | "dark") {
|
||||
const elm = document.documentElement;
|
||||
if (prefs.theme === "light") {
|
||||
if (theme === "light" && !elm.classList.contains("light")) {
|
||||
elm.classList.add("light");
|
||||
} else {
|
||||
} else if (theme === "dark" && elm.classList.contains("light")) {
|
||||
elm.classList.remove("light");
|
||||
}
|
||||
}, [prefs]);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let osTheme = window.matchMedia("(prefers-color-scheme: light)");
|
||||
setTheme(prefs.theme === "system" && osTheme.matches ? "light" : prefs.theme === "light" ? "light" : "dark");
|
||||
|
||||
osTheme.onchange = (e) => {
|
||||
if (prefs.theme === "system") {
|
||||
setTheme(e.matches ? "light" : "dark");
|
||||
}
|
||||
}
|
||||
return () => { osTheme.onchange = null; }
|
||||
}, [prefs.theme]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(init());
|
||||
|
@ -8,10 +8,10 @@ export default function SettingsPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="settings-page">
|
||||
<h2 onClick={() => navigate("/settings")}>Settings</h2>
|
||||
<>
|
||||
<h2 onClick={() => navigate("/settings")} className="pointer">Settings</h2>
|
||||
<Outlet />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
verbiricha
commented
Review
```suggestion
background-color: var(--note-bg);
cursor: pointer;
```
verbiricha
commented
Review
```suggestion
background-color: var(--note-bg);
cursor: pointer;
```
|
||||
.settings-nav h3 {
|
||||
```suggestion
background-color: var(--note-bg);
cursor: pointer;
```
|
||||
background-color: var(--note-bg);
|
||||
```suggestion
background-color: var(--note-bg);
cursor: pointer;
```
|
||||
.settings-nav .card {
|
||||
```suggestion
background-color: var(--note-bg);
cursor: pointer;
```
|
||||
cursor: pointer;
|
||||
```suggestion
background-color: var(--note-bg);
cursor: pointer;
```
|
||||
}
|
@ -16,7 +16,8 @@ const PreferencesPage = () => {
|
||||
Wdyt about keeping current default (use This would be similar to other preferences, keeping the current default but offering customization. Wdyt about keeping current default (use `system` theme)? If user explicitly sets a theme we can override it but if not use system theme. Have my devices configured to use dark/light depending on time of the day, don't want to be manually changing it from preferences.
This would be similar to other preferences, keeping the current default but offering customization.
Wdyt about keeping current default (use This would be similar to other preferences, keeping the current default but offering customization. Wdyt about keeping current default (use `system` theme)? If user explicitly sets a theme we can override it but if not use system theme. Have my devices configured to use dark/light depending on time of the day, don't want to be manually changing it from preferences.
This would be similar to other preferences, keeping the current default but offering customization.
|
||||
<div>Theme</div>
|
||||
</div>
|
||||
<div>
|
||||
<select value={perf.theme} onChange={e => dispatch(setPreferences({ ...perf, theme: "light" === e.target.value ? "light" : "dark" }))}>
|
||||
Wdyt about keeping current default (use This would be similar to other preferences, keeping the current default but offering customization. Wdyt about keeping current default (use `system` theme)? If user explicitly sets a theme we can override it but if not use system theme. Have my devices configured to use dark/light depending on time of the day, don't want to be manually changing it from preferences.
This would be similar to other preferences, keeping the current default but offering customization.
|
||||
<select value={perf.theme} onChange={e => dispatch(setPreferences({ ...perf, theme: e.target.value} as UserPreferences))}>
|
||||
Wdyt about keeping current default (use This would be similar to other preferences, keeping the current default but offering customization. Wdyt about keeping current default (use `system` theme)? If user explicitly sets a theme we can override it but if not use system theme. Have my devices configured to use dark/light depending on time of the day, don't want to be manually changing it from preferences.
This would be similar to other preferences, keeping the current default but offering customization.
|
||||
<option value="system">System (Default)</option>
|
||||
Wdyt about keeping current default (use This would be similar to other preferences, keeping the current default but offering customization. Wdyt about keeping current default (use `system` theme)? If user explicitly sets a theme we can override it but if not use system theme. Have my devices configured to use dark/light depending on time of the day, don't want to be manually changing it from preferences.
This would be similar to other preferences, keeping the current default but offering customization.
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
</select>
|
||||
|
||||
Wdyt about keeping current default (use This would be similar to other preferences, keeping the current default but offering customization. Wdyt about keeping current default (use `system` theme)? If user explicitly sets a theme we can override it but if not use system theme. Have my devices configured to use dark/light depending on time of the day, don't want to be manually changing it from preferences.
This would be similar to other preferences, keeping the current default but offering customization.
Wdyt about keeping current default (use This would be similar to other preferences, keeping the current default but offering customization. Wdyt about keeping current default (use `system` theme)? If user explicitly sets a theme we can override it but if not use system theme. Have my devices configured to use dark/light depending on time of the day, don't want to be manually changing it from preferences.
This would be similar to other preferences, keeping the current default but offering customization.
|
@ -24,7 +24,7 @@ export interface UserPreferences {
|
||||
/**
|
||||
* Select between light/dark theme
|
||||
*/
|
||||
theme: "light" | "dark",
|
||||
theme: "system" | "light" | "dark",
|
||||
|
||||
/**
|
||||
* Ask for confirmation when reposting notes
|
||||
@ -103,7 +103,7 @@ const InitState = {
|
||||
preferences: {
|
||||
enableReactions: true,
|
||||
autoLoadMedia: true,
|
||||
theme: "dark",
|
||||
theme: "system",
|
||||
confirmReposts: false
|
||||
}
|
||||
} as LoginStore;
|
||||
@ -146,10 +146,6 @@ const LoginSlice = createSlice({
|
||||
let pref = window.localStorage.getItem(UserPreferencesKey);
|
||||
if (pref) {
|
||||
state.preferences = JSON.parse(pref);
|
||||
} else {
|
||||
// get os defaults
|
||||
const osTheme = window.matchMedia("(prefers-color-scheme: light)");
|
||||
state.preferences.theme = osTheme.matches ? "light" : "dark";
|
||||
}
|
||||
},
|
||||
setPrivateKey: (state, action: PayloadAction<HexKey>) => {
|
||||
|
544
src/index.css
@ -92,316 +92,310 @@ code {
|
||||
}
|
||||
|
||||
@media (min-width: 720px) {
|
||||
<<<<<<< HEAD .card {
|
||||
.card {
|
||||
margin-bottom: 24px;
|
||||
padding: 12px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
.card {
|
||||
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.05);
|
||||
html.light .card {
|
||||
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
=======.card {
|
||||
margin-bottom: 24px;
|
||||
padding: 24px;
|
||||
>>>>>>>3e41614 (feat: user preferences)
|
||||
}
|
||||
}
|
||||
|
||||
.card .header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.card .header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card>.footer {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.card>.footer {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--font-color);
|
||||
border: 1px solid;
|
||||
display: inline-block;
|
||||
}
|
||||
.btn {
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--font-color);
|
||||
border: 1px solid;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-warn {
|
||||
border-color: var(--error);
|
||||
}
|
||||
.btn-warn {
|
||||
border-color: var(--error);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
border-color: var(--success);
|
||||
}
|
||||
.btn-success {
|
||||
border-color: var(--success);
|
||||
}
|
||||
|
||||
.btn.active {
|
||||
border: 2px solid;
|
||||
background-color: var(--gray-secondary);
|
||||
color: var(--font-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
.btn.active {
|
||||
border: 2px solid;
|
||||
background-color: var(--gray-secondary);
|
||||
color: var(--font-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn.disabled {
|
||||
color: var(--gray-light);
|
||||
}
|
||||
.btn.disabled {
|
||||
color: var(--gray-light);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: var(--gray);
|
||||
}
|
||||
.btn:hover {
|
||||
background-color: var(--gray);
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 5px;
|
||||
}
|
||||
.btn-sm {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.btn-rnd {
|
||||
border-radius: 100%;
|
||||
}
|
||||
.btn-rnd {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
textarea {
|
||||
font: inherit;
|
||||
}
|
||||
textarea {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
input[type="text"], input[type="password"], input[type="number"], textarea, select {
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
border: 0;
|
||||
background-color: var(--gray);
|
||||
color: var(--font-color);
|
||||
}
|
||||
input[type="text"], input[type="password"], input[type="number"], textarea, select {
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
border: 0;
|
||||
background-color: var(--gray);
|
||||
color: var(--font-color);
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
input[type="checkbox"] {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
color: var(--gray-medium);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
input:disabled {
|
||||
color: var(--gray-medium);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
textarea:placeholder {
|
||||
color: var(--gray-superlight);
|
||||
}
|
||||
textarea:placeholder {
|
||||
color: var(--gray-superlight);
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.f-center {
|
||||
justify-content: center;
|
||||
}
|
||||
.f-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.f-1 {
|
||||
flex: 1;
|
||||
}
|
||||
.f-1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.f-2 {
|
||||
flex: 2;
|
||||
}
|
||||
.f-2 {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.f-grow {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.f-grow {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.f-shrink {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
.f-shrink {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.f-ellipsis {
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
.f-ellipsis {
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.f-col {
|
||||
flex-direction: column;
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
.f-col {
|
||||
flex-direction: column;
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
|
||||
.w-max {
|
||||
width: 100%;
|
||||
width: -moz-available;
|
||||
width: -webkit-fill-available;
|
||||
width: fill-available;
|
||||
}
|
||||
.w-max {
|
||||
width: 100%;
|
||||
width: -moz-available;
|
||||
width: -webkit-fill-available;
|
||||
width: fill-available;
|
||||
}
|
||||
|
||||
.w-max-w {
|
||||
max-width: 100%;
|
||||
max-width: -moz-available;
|
||||
max-width: -webkit-fill-available;
|
||||
max-width: fill-available;
|
||||
}
|
||||
.w-max-w {
|
||||
max-width: 100%;
|
||||
max-width: -moz-available;
|
||||
max-width: -webkit-fill-available;
|
||||
max-width: fill-available;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
a {
|
||||
color: inherit;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
a.ext {
|
||||
word-break: break-all;
|
||||
white-space: initial;
|
||||
a.ext {
|
||||
word-break: break-all;
|
||||
white-space: initial;
|
||||
}
|
||||
|
||||
div.form-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
div.form-group>div {
|
||||
padding: 3px 5px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
div.form-group>div:nth-child(1) {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
div.form-group>div:nth-child(2) {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
div.form-group>div:nth-child(2) input {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.modal .modal-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal .modal-content>div {
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
background-color: var(--gray);
|
||||
margin-top: 5vh;
|
||||
}
|
||||
|
||||
body.scroll-lock {
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.m5 {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.m10 {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.mr10 {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.mr5 {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.ml5 {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.mb10 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.tabs>div {
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tabs>div:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tabs .active {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.bg-error {
|
||||
background-color: var(--error);
|
||||
}
|
||||
|
||||
.bg-success {
|
||||
background-color: var(--success);
|
||||
}
|
||||
|
||||
.root-tabs {
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.root-tab {
|
||||
border-bottom: 3px solid var(--gray-secondary);
|
||||
}
|
||||
|
||||
.root-tab.active {
|
||||
border-bottom: 3px solid var(--highlight);
|
||||
}
|
||||
|
||||
.tweet {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tweet div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tweet div .twitter-tweet {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.tweet div .twitter-tweet>iframe {
|
||||
max-height: unset;
|
||||
}
|
||||
|
||||
@media(max-width: 720px) {
|
||||
.page {
|
||||
width: calc(100vw - 8px);
|
||||
}
|
||||
|
||||
div.form-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
div.form-group>div {
|
||||
padding: 3px 5px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
div.form-group>div:nth-child(1) {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
div.form-group>div:nth-child(2) {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
div.form-group>div:nth-child(2) input {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.modal .modal-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal .modal-content>div {
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
background-color: var(--gray);
|
||||
margin-top: 5vh;
|
||||
}
|
||||
|
||||
body.scroll-lock {
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.m5 {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.m10 {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.mr10 {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.mr5 {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.ml5 {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.mb10 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.tabs>div {
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tabs>div:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tabs .active {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.bg-error {
|
||||
background-color: var(--error);
|
||||
}
|
||||
|
||||
.bg-success {
|
||||
background-color: var(--success);
|
||||
}
|
||||
|
||||
.root-tabs {
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.root-tab {
|
||||
border-bottom: 3px solid var(--gray-secondary);
|
||||
}
|
||||
|
||||
.root-tab.active {
|
||||
border-bottom: 3px solid var(--highlight);
|
||||
}
|
||||
|
||||
.tweet {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tweet div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tweet div .twitter-tweet {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.tweet div .twitter-tweet>iframe {
|
||||
max-height: unset;
|
||||
}
|
||||
|
||||
@media(max-width: 720px) {
|
||||
.page {
|
||||
width: calc(100vw - 8px);
|
||||
}
|
||||
|
||||
div.form-group {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: var(--highlight);
|
||||
}
|
||||
.highlight {
|
||||
color: var(--highlight);
|
||||
}
|