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" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>slidestr.net</title>
<link href="/fonts/outfit/outfit.css" rel="stylesheet">
</head>
<body>
<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 "./App.css";
const App = () => {
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;

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

View File

@ -3,47 +3,67 @@ import { nip19 } from "nostr-tools";
export const appName = "slidestr.net";
export const defaultHashTags = [
"photography",
"photostr",
"artstr",
"art",
"artstr",
"catstr",
"dogstr",
"nature",
"naturephotography",
"photography",
"photostr",
"streetphotography",
];
export const nfswTags = [
"nsfw",
"ass",
"blowjob",
"boobstr",
"titstr",
"buttstr",
"erostr",
"erotic",
"freethenipple",
"friskyfriday",
"naked",
"nakedart",
"nasstr",
"nudeart",
"pornstr",
"nsfw",
"nude",
"nudeart",
"pornhub",
"pornstr",
"pussy",
"suicidegirls",
"tits",
"titstr",
];
export const nsfwPubKeys = [
"npub1xfu7047thly6aghl79z97kckkvwfvtcx88n6wq7c2tlng484d8xqv0kuvv", // Erandis Vol
"npub1rv08kght99a7xwckm0qpmzw09m5gwppequgqd8lwu74eakgaavwsp5cjtw", // CuratedNSFW
"npub1femd0mrawr0jmtjr2jwa2nm90haxrpglzdt6tt0djrsav39e53asf74aer", // FemDom Raw
"npub13806pd9p833wkgyemeqddjzdksunlq9gszq4yjnhw4l57sjjhwlq6m79nj", // Orvalho
"npub13n6ednsew67xk7hgse670z7849q5h8su5rgydxtl4lq3r5cx4ecqsd9af4", // Everybody, Every Body
"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
"npub1mgusda7ujnyuhhudwkyrp763k4dd9xspktekl0tg5v0j76yph8ssyrfdpm", // anisyia
"npub1jge7z2kpmpdra6g58vg95uznve8ctcenmlyp9ntr3kjymscyuqpqty2cdh", // Storm
"npub1y77j6jm5hw34xl5m85aumltv88arh2s7q383allkpfe4muarzc5qzfgru0", // sexy-models
"npub1jp9v034z3a26cp5hajwyuzl0hety5akdpwdnjaqgfd7pm2ts4dwsc29va8", // curatedbliss
"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());
export const spamAccounts = [];

View File

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

View File

@ -1,5 +1,7 @@
: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;
font-weight: 400;
@ -67,3 +69,96 @@ button:focus-visible {
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;
}