feat: Added first version of settings dialog

This commit is contained in:
florian 2023-07-21 18:30:51 +02:00
parent b26ff70f52
commit f846b57dc8
17 changed files with 363 additions and 37 deletions

View File

@ -5,6 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/slidestr.svg" /> <link rel="icon" type="image/svg+xml" href="/slidestr.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>slidestr.net</title> <title>slidestr.net</title>
<link href="/fonts/outfit/outfit.css" rel="stylesheet">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@ -0,0 +1,72 @@
/* latin-ext */
@font-face {
font-family: 'Outfit';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(outfit_400_latin-ext.woff2) format('woff2');
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Outfit';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(outfit_400_latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
font-family: 'Outfit';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(outfit_500_latin-ext.woff2) format('woff2');
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Outfit';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(outfit_500_latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
font-family: 'Outfit';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(outfit_600_latin-ext.woff2) format('woff2');
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Outfit';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(outfit_600_latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
font-family: 'Outfit';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(outfit_700_latin-ext.woff2) format('woff2');
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Outfit';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(outfit_700_latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,11 +1,13 @@
import { useParams } from "react-router-dom"; import { useParams, useSearchParams } from "react-router-dom";
import SlideShow from "./components/SlideShow"; import SlideShow from "./components/SlideShow";
import "./App.css"; import "./App.css";
const App = () => { const App = () => {
const { tags, npub } = useParams(); const { tags, npub } = useParams();
const [ searchParams ] = useSearchParams();
const nsfw = searchParams.get("nsfw") === "true";
return <SlideShow tags={tags} npub={npub} />; return <SlideShow tags={tags} npub={npub} showNsfw={nsfw} />;
}; };
export default App; export default App;

View File

@ -0,0 +1,73 @@
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.settings {
position: fixed;
width: 80vw;
height: 80vh;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(10px);
justify-content: center;
align-items: center;
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.18);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
font-size: 1.2rem;
animation: fadeIn 0.5s ease-in-out;
z-index: 500;
padding: 2em;
}
.settings form {
display: flex;
flex-direction: column;
gap: 24px;
}
@media (max-width: 768px) {
.settings {
width: 90vw;
height: 90vh;
}
}
@media (max-width: 576px) {
.settings {
width: 95vw;
height: 95vh;
}
}
.settings .settings-content {
flex-grow: 0;
}
.settings .settings-footer {
flex-shrink: 1;
display: flex;
justify-content: end;
}
.content-warning {
padding: 16px;
border-radius: 16px;
border: 1px solid #ff563f;
display: flex;
gap: 12px;
}
.warning {
color: #ff563f;
font-weight: 500;
font-size: 1.2rem;
margin-bottom: 0.5em;
}

View File

@ -0,0 +1,48 @@
import { FormEvent, useState } from "react";
import "./Settings.css";
import { useNavigate, useSearchParams } from "react-router-dom";
const Settings = () => {
const [showNsfw, setShowNsfw] = useState(false);
const navigate = useNavigate();
const [_, setSearchParams] = useSearchParams();
const onSubmit = (e: FormEvent) => {
e.preventDefault();
//navigate(`/tags/foodstr?nsfw=${showNsfw}`);
setSearchParams({ nsfw: showNsfw.toString() });
};
return (
<div className="settings">
<div className="settings-content">
{/*
<label htmlFor="tags">Tags</label>
<input type="text" name="tags" id="tags" />
<label htmlFor="npub">Npub</label>
<input type="text" name="npub" id="npub" />
*/}
<div className="content-warning">
<div>
<input
type="checkbox"
checked={showNsfw}
onChange={(e) => setShowNsfw(e.target.checked)}
/>
</div>
<div>
<div className="warning">NSFW Content</div>
Allow NSFW to be shown and ignore content warnings.
</div>
</div>
</div>
<div className="settings-footer">
<button type="submit" className="btn btn-primary" onClick={onSubmit}>
Save
</button>
</div>
</div>
);
};
export default Settings;

View File

@ -17,6 +17,7 @@ import {
urlFix, urlFix,
} from "./nostrImageDownload"; } from "./nostrImageDownload";
import { appName, nsfwPubKeys } from "./env"; import { appName, nsfwPubKeys } from "./env";
import Settings from "./Settings";
/* /*
FEATURES: FEATURES:
@ -50,18 +51,21 @@ let eventsReceived = 0;
type SlideShowProps = { type SlideShowProps = {
tags?: string; tags?: string;
npub?: string; npub?: string;
showNsfw: boolean;
}; };
const SlideShow = ({ tags, npub }: SlideShowProps) => { const SlideShow = ({ tags, npub, showNsfw = false }: SlideShowProps) => {
const { ndk, getProfile, loadNdk } = useNDK(); const { ndk, getProfile, loadNdk } = useNDK();
const [posts, setPosts] = useState<any[]>([]); const [posts, setPosts] = useState<any[]>([]);
const images = useRef<NostrImage[]>([]); const images = useRef<NostrImage[]>([]);
const [activeImages, setActiveImages] = useState<NostrImage[]>([]); const [activeImages, setActiveImages] = useState<NostrImage[]>([]);
const [history, setHistory] = useState<NostrImage[]>([]); const [history, setHistory] = useState<NostrImage[]>([]);
const [paused, setPaused] = useState(false);
const [showSettings, setShowSettings] = useState(false);
const upcommingImage = useRef<NostrImage>(); const upcommingImage = useRef<NostrImage>();
const [title, setTitle] = useState(appName); const [title, setTitle] = useState(appName);
const [paused, setPaused] = useState(false);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [activeNpub, setActiveNpub] = useState<string | undefined>(undefined); const [activeNpub, setActiveNpub] = useState<string | undefined>(undefined);
const [activeContent, setActiveContent] = useState<string | undefined>( const [activeContent, setActiveContent] = useState<string | undefined>(
@ -91,10 +95,11 @@ const SlideShow = ({ tags, npub }: SlideShowProps) => {
setPosts((oldPosts) => { setPosts((oldPosts) => {
if ( if (
!isReply(event) && !isReply(event) &&
(npub !== undefined || !hasContentWarning(event)) && // only allow content warnings on profile content oldPosts.findIndex((p) => p.id === event.id) === -1 &&
(npub !== undefined || !hasNsfwTag(event)) && // only allow nsfw on profile content (showNsfw ||
(npub !== undefined || !nsfwPubKeys.includes(event.pubkey.toLowerCase()) ) && // block nsfw authors (!hasContentWarning(event) && // only allow content warnings on profile content
oldPosts.findIndex((p) => p.id === event.id) === -1 !hasNsfwTag(event))) && // only allow nsfw on profile content
!nsfwPubKeys.includes(event.pubkey.toLowerCase()) // block nsfw authors
) { ) {
return [...oldPosts, event]; return [...oldPosts, event];
} }
@ -119,7 +124,6 @@ const SlideShow = ({ tags, npub }: SlideShowProps) => {
}; };
useEffect(() => { useEffect(() => {
loadNdk([ loadNdk([
"wss://relay.nostr.band", "wss://relay.nostr.band",
"wss://nos.lol", "wss://nos.lol",
@ -141,7 +145,10 @@ const SlideShow = ({ tags, npub }: SlideShowProps) => {
if (images.current.length > 0) { if (images.current.length > 0) {
const randomImage = const randomImage =
images.current[Math.floor(Math.random() * images.current.length)]; images.current[Math.floor(Math.random() * images.current.length)];
// TODO this creates potential duplicates when images are loaded from multiple relays
images.current = images.current.filter((i) => i !== randomImage); images.current = images.current.filter((i) => i !== randomImage);
setHistory((oldHistory) => [...oldHistory, randomImage]); setHistory((oldHistory) => [...oldHistory, randomImage]);
newActiveImages.push(randomImage); newActiveImages.push(randomImage);
upcommingImage.current = randomImage; upcommingImage.current = randomImage;
@ -164,7 +171,9 @@ const SlideShow = ({ tags, npub }: SlideShowProps) => {
url, url,
author: p.author.npub, author: p.author.npub,
content: prepareContent(p.content), content: prepareContent(p.content),
tags: p.tags.filter((t: string[]) => t[0] === "t").map((t: string[]) => t[1].toLowerCase()), tags: p.tags
.filter((t: string[]) => t[0] === "t")
.map((t: string[]) => t[1].toLowerCase()),
})); }));
}); });
console.log(images.current.length); console.log(images.current.length);
@ -185,6 +194,9 @@ const SlideShow = ({ tags, npub }: SlideShowProps) => {
if (event.key === "p" || event.key === " " || event.key === "P") { if (event.key === "p" || event.key === " " || event.key === "P") {
setPaused((p) => !p); setPaused((p) => !p);
} }
if (event.key === "Escape") {
setShowSettings((s) => !s);
}
}; };
useEffect(() => { useEffect(() => {
@ -240,6 +252,9 @@ const SlideShow = ({ tags, npub }: SlideShowProps) => {
<Helmet> <Helmet>
<title>{title}</title> <title>{title}</title>
</Helmet> </Helmet>
{showSettings && <Settings></Settings>}
{!fullScreen && ( {!fullScreen && (
<div className="controls"> <div className="controls">
<button <button

View File

@ -3,47 +3,67 @@ import { nip19 } from "nostr-tools";
export const appName = "slidestr.net"; export const appName = "slidestr.net";
export const defaultHashTags = [ export const defaultHashTags = [
"photography",
"photostr",
"artstr",
"art", "art",
"artstr",
"catstr", "catstr",
"dogstr", "dogstr",
"nature", "nature",
"naturephotography", "naturephotography",
"photography",
"photostr",
"streetphotography", "streetphotography",
]; ];
export const nfswTags = [ export const nfswTags = [
"nsfw", "ass",
"blowjob",
"boobstr", "boobstr",
"titstr", "buttstr",
"erostr",
"erotic",
"freethenipple",
"friskyfriday",
"naked",
"nakedart",
"nasstr", "nasstr",
"nudeart", "nsfw",
"pornstr",
"nude", "nude",
"nudeart",
"pornhub",
"pornstr",
"pussy",
"suicidegirls",
"tits",
"titstr",
]; ];
export const nsfwPubKeys = [ export const nsfwPubKeys = [
"npub1xfu7047thly6aghl79z97kckkvwfvtcx88n6wq7c2tlng484d8xqv0kuvv", // Erandis Vol "npub13806pd9p833wkgyemeqddjzdksunlq9gszq4yjnhw4l57sjjhwlq6m79nj", // Orvalho
"npub1rv08kght99a7xwckm0qpmzw09m5gwppequgqd8lwu74eakgaavwsp5cjtw", // CuratedNSFW "npub13n6ednsew67xk7hgse670z7849q5h8su5rgydxtl4lq3r5cx4ecqsd9af4", // Everybody, Every Body
"npub1femd0mrawr0jmtjr2jwa2nm90haxrpglzdt6tt0djrsav39e53asf74aer", // FemDom Raw
"npub19xwjw7f23nsmnsd0j72mvhrdswt4cp6urc5el2zuu8se3yfu87ess524je", // Gone Wild (NSFW) "npub19xwjw7f23nsmnsd0j72mvhrdswt4cp6urc5el2zuu8se3yfu87ess524je", // Gone Wild (NSFW)
"npub1ylrnf0xfp9wsmqthxlqjqyqj9yy27pnchjwjq93v3mq66ts7ftjs6x7dcq", // Welcome To The Jungle
"npub1ga79p6qsjh0xd343q3du2unf2gl6gk0rde36c06mafxkrssmnnesxyzcss", // Orange Incest 🔞
"npub1jjtzhxzu8dlf7yn480sz67tesnfl7gpzfpkgpez05d2z9y3lya5sxvky0y", // Selfie Girls
"npub1ve4ztpqvlgu3v6hgrvc4lrdl2ernue7lq2h8tcgaksrkxlm7gnsqkjmz4e", // bluntkaraoke
"npub1kul999wnt8gwa6l2vyuewhnmmp25gq7dly9zmgsw52x8csmqjgts7278rx", // 𝓟𝓮𝓽𝓲𝓽𝓮 𝓟𝓻𝓲𝓷𝓬𝓮𝓼𝓼
"npub1ulafm4d3n7ukl7yzg4hfnhfjut74nym5p83e3d67l3j62yc6ysqqrancw2", // naked
"npub13806pd9p833wkgyemeqddjzdksunlq9gszq4yjnhw4l57sjjhwlq6m79nj", // Orvalho
"npub1pl0qa9x3n8wt55em0x3zwuy02rtl5t3jsretr0egjqgkx2f6jztqt0xwew", // nude
"npub1af9lxfzeq5rxmu9zz7d85tn2ex8zvvlx0duqcemcdhkz9cvlt29st3rcgd", // Happy Nut "npub1af9lxfzeq5rxmu9zz7d85tn2ex8zvvlx0duqcemcdhkz9cvlt29st3rcgd", // Happy Nut
"npub1mgusda7ujnyuhhudwkyrp763k4dd9xspktekl0tg5v0j76yph8ssyrfdpm", // anisyia
"npub1jge7z2kpmpdra6g58vg95uznve8ctcenmlyp9ntr3kjymscyuqpqty2cdh", // Storm
"npub1y77j6jm5hw34xl5m85aumltv88arh2s7q383allkpfe4muarzc5qzfgru0", // sexy-models
"npub1jp9v034z3a26cp5hajwyuzl0hety5akdpwdnjaqgfd7pm2ts4dwsc29va8", // curatedbliss
"npub1e4n8nah09he25slv00dz3kav3jsu5jvp83aya234ejumcmu2xseqwrp6pl", // Svenno(NSFW) "npub1e4n8nah09he25slv00dz3kav3jsu5jvp83aya234ejumcmu2xseqwrp6pl", // Svenno(NSFW)
"npub1suddec4n2jv50pgn9eea35r4k83ahr4mcj0zv2uec36w6jeuwagq82xjgl" // quiet.enjoyer "npub1femd0mrawr0jmtjr2jwa2nm90haxrpglzdt6tt0djrsav39e53asf74aer", // FemDom Raw
"npub1ga79p6qsjh0xd343q3du2unf2gl6gk0rde36c06mafxkrssmnnesxyzcss", // Orange Incest 🔞
"npub1j0xvl8l2s4w25vcavf3jv6fgyakyc0hplxc9we8hc8mja0ct7epss0qlkj", // Thicc Pics (NSFW)
"npub1j0y6f9gl9w39ggarr9x76lyh2swv7mpgddguv49mhmzqlz8tm69qcwpl55", // NeoMobius
"npub1jge7z2kpmpdra6g58vg95uznve8ctcenmlyp9ntr3kjymscyuqpqty2cdh", // Storm
"npub1jjtzhxzu8dlf7yn480sz67tesnfl7gpzfpkgpez05d2z9y3lya5sxvky0y", // Selfie Girls
"npub1jp9v034z3a26cp5hajwyuzl0hety5akdpwdnjaqgfd7pm2ts4dwsc29va8", // curatedbliss
"npub1kul999wnt8gwa6l2vyuewhnmmp25gq7dly9zmgsw52x8csmqjgts7278rx", // 𝓟𝓮𝓽𝓲𝓽𝓮 𝓟𝓻𝓲𝓷𝓬𝓮𝓼𝓼
"npub1m5fdz9gqa2qeudpy47zllmv9gqe3zzj44dkt9lh2kes3mlex7e6se348vy", // Marble Sculture
"npub1mgusda7ujnyuhhudwkyrp763k4dd9xspktekl0tg5v0j76yph8ssyrfdpm", // anisyia
"npub1pl0qa9x3n8wt55em0x3zwuy02rtl5t3jsretr0egjqgkx2f6jztqt0xwew", // nude
"npub1rv08kght99a7xwckm0qpmzw09m5gwppequgqd8lwu74eakgaavwsp5cjtw", // CuratedNSFW
"npub1suddec4n2jv50pgn9eea35r4k83ahr4mcj0zv2uec36w6jeuwagq82xjgl", // quiet.enjoyer
"npub1t252vm7u5qmfwv3k70g6rl2ue7ctvtvrnd60vy8jh5suglv8pw2snyyzfq", // 20th Century Foxes (NSFW)
"npub1thsprukxnc8rxqggnesqp2wg2temhaadzhhg7n4pttpveyqedlwsqgge9q", // Harmony. Corrupted.
"npub1ulafm4d3n7ukl7yzg4hfnhfjut74nym5p83e3d67l3j62yc6ysqqrancw2", // naked
"npub1ve4ztpqvlgu3v6hgrvc4lrdl2ernue7lq2h8tcgaksrkxlm7gnsqkjmz4e", // bluntkaraoke
"npub1xfu7047thly6aghl79z97kckkvwfvtcx88n6wq7c2tlng484d8xqv0kuvv", // Erandis Vol
"npub1y77j6jm5hw34xl5m85aumltv88arh2s7q383allkpfe4muarzc5qzfgru0", // sexy-models
"npub1ylrnf0xfp9wsmqthxlqjqyqj9yy27pnchjwjq93v3mq66ts7ftjs6x7dcq", // Welcome To The Jungle
].map((npub) => (nip19.decode(npub).data as string).toLowerCase()); ].map((npub) => (nip19.decode(npub).data as string).toLowerCase());
export const spamAccounts = []; export const spamAccounts = [];

View File

@ -22,15 +22,15 @@ export const buildFilter = (
}; };
if (npub) { if (npub) {
filter.authors = [nip19.decode(npub).data as string]; filter.authors = [nip19.decode(npub).data as string];
} else { } else {
if (tags) { if (tags) {
setTitle("#" + tags.replace(",", " #") + ` | ${appName}`); setTitle("#" + tags.replace(",", " #") + ` | ${appName}`);
filter["#t"] = tags.split(","); filter["#t"] = tags.split(",");
} else { } else {
setTitle(`Random photos from popular hashtags | ${appName}`); // setTitle(`Random photos from popular hashtags | ${appName}`);
filter["#t"] = defaultHashTags; // filter["#t"] = defaultHashTags;
setTitle(`Random photos from global feed | ${appName}`);
} }
} }

View File

@ -1,5 +1,7 @@
:root { :root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; font-family: "Outfit", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
line-height: 1.5; line-height: 1.5;
font-weight: 400; font-weight: 400;
@ -67,3 +69,96 @@ button:focus-visible {
background-color: #f9f9f9; background-color: #f9f9f9;
} }
} }
input[type="checkbox"] {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 4px;
border: 2px solid #333;
background-color: transparent;
}
input[type="checkbox"]:after {
content: " ";
position: relative;
left: 40%;
top: 20%;
width: 15%;
height: 40%;
border: solid #fff;
border-width: 0 2px 2px 0;
transform: rotate(50deg);
display: none;
}
input[type="checkbox"]:checked:after {
display: block;
}
div.paper {
background: #171717;
border-radius: 16px;
padding: 8px 16px;
display: flex;
gap: 10px;
align-items: center;
}
.btn {
border: none;
outline: none;
cursor: pointer;
font-weight: 700;
font-size: 16px;
line-height: 20px;
padding: 8px 16px;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-border {
border: 1px solid transparent;
color: inherit;
background: linear-gradient(black, black) padding-box,
linear-gradient(94.73deg, #2bd9ff 0%, #f838d9 100%) border-box;
transition: 0.3s;
}
.btn-border:hover {
background: linear-gradient(black, black) padding-box,
linear-gradient(94.73deg, #14b4d8 0%, #ba179f 100%) border-box;
}
.btn-primary {
background: #fff;
color: #0a0a0a;
}
.btn-primary:hover {
opacity: 0.9;
}
.btn-secondary {
color: white;
background: #222;
}
.btn-warning {
background: #ff563f;
color: white;
}
.btn > span {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}