feat: Improved mobile detail view

This commit is contained in:
florian 2023-08-16 13:40:36 +02:00
parent 4f490c1e06
commit 11ceac70c3
10 changed files with 86 additions and 30 deletions

View File

@ -1,11 +1,17 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/slidestr.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="manifest" href="manifest.json" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>slidestr.net</title>
<link href="/fonts/outfit/outfit.css" rel="stylesheet">
<link href="/fonts/outfit/outfit.css" rel="stylesheet" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- possible content values: default, black or black-translucent -->
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
</head>
<body>
<div id="root"></div>

10
public/manifest.json Normal file
View File

@ -0,0 +1,10 @@
{
"name": "slidestr.net",
"short_name": "slidestr",
"theme_color": "#4a49ff",
"background_color": "#7600ff",
"display": "standalone",
"scope": "/",
"start_url": "/"
}

View File

@ -158,15 +158,25 @@
.details {
overflow-y: scroll;
align-items: normal;
overflow-x: hidden;
padding: 0;
overscroll-behavior: none;
}
.details-contents {
grid-template-columns: 1fr;
grid-template-rows: auto 1fr;
}
.details-contents .detail-image {
border-radius: 0px;
}
.detail-description {
max-width: 100%;
width: 100%;
overflow: visible;
padding-left: 2em;
}
.closeButton {
top: 1.5em;
}
}

View File

@ -6,6 +6,7 @@ import GridImage from './GridImage';
import { Settings } from '../../utils/useNav';
import { useNDK } from '@nostr-dev-kit/ndk-react';
import AuthorProfile from '../AuthorProfile';
import { useSwipeable } from 'react-swipeable';
type GridViewProps = {
settings: Settings;
@ -24,20 +25,37 @@ const GridView = ({ settings, images }: GridViewProps) => {
[images, settings] // settings is not used here, but we need to include it to trigger a re-render when it changes
);
const showNextImage = () => {
setActiveImageIdx(idx => (idx !== undefined ? idx + 1 : 0));
};
const showPreviousImage = () => {
setActiveImageIdx(idx => (idx !== undefined && idx > 0 ? idx - 1 : idx));
};
const onKeyDown = (event: KeyboardEvent) => {
console.log(event);
if (event.key === 'ArrowRight') {
setActiveImageIdx(idx => idx !== undefined ? idx + 1 : 0);
showNextImage();
}
if (event.key === 'ArrowLeft') {
setActiveImageIdx(idx => (idx !== undefined && idx > 0 ? idx - 1 : idx));
showPreviousImage();
}
if (event.key === 'Escape') {
setActiveImageIdx(undefined);
}
};
const swipeHandlers = useSwipeable({
onSwipedLeft: () => {
showNextImage();
},
onSwipedRight: () => {
showPreviousImage();
},
});
useEffect(() => {
document.body.addEventListener('keydown', onKeyDown);
return () => {
@ -48,7 +66,7 @@ const GridView = ({ settings, images }: GridViewProps) => {
const activeProfile = settings.npubs.length == 1 && getProfile(settings.npubs[0]);
return (
<div className="gridview">
<div className="gridview" {...swipeHandlers}>
{activeImageIdx !== undefined ? (
<DetailsView images={sortedImages} activeImageIdx={activeImageIdx} setActiveImageIdx={setActiveImageIdx} />
) : null}

View File

@ -214,3 +214,9 @@
animation-iteration-count: infinite;
animation-timing-function: linear;
}
@media screen and (max-width: 768px) {
.controls {
top: 2em;
}
}

View File

@ -3,18 +3,20 @@ import { nip19 } from 'nostr-tools';
export const appName = 'slidestr.net';
export const defaultHashTags = [
'art',
'artstr',
'catstr',
'dogstr',
'nature',
'naturephotography',
'flowerstr',
'foodstr',
'gardenstr',
'grownostr',
'nostr',
'photography',
'photostr',
'streetphotography',
'picstr',
'plebchain',
'tavelstr',
'gardening',
'gardenstr',
'zapathon',
];
export const visibleHashTags = [
@ -27,6 +29,8 @@ export const visibleHashTags = [
'cute',
'dogstr',
'fashion',
'flowerstr',
'foodstr',
'freedom',
'gardening',
'gardenstr',
@ -47,8 +51,10 @@ export const visibleHashTags = [
'psychedelic',
'streetphotography',
'style',
'travelstr',
'tavelstr',
'travel',
'travelstr',
'zapathon',
];
/* All posts with the following hashtags are flagged as adult / NSFW are not shown
@ -150,6 +156,7 @@ export const adultNPubs = [
'npub1apr6dy5z4f0qs4cnswxj0gf37g46jxvh7xgwgs4wvzm6stu8f0asd4996r', // Anime Girl
'npub1acwrv7aqgu949mw0zxmw2akgsjqp574nnq4vcl9wln5355q79w5ssv9qxg', // Arianna
'npub1v3rnmlms82wgxejxwn7rr6kjruy3ty0l4084dx2zp3tn8dlxv28sjnp6pf', // High Elf Archer
'npub1jvp6kfs2d3m98lyw5wcyr4fnctr83s0rc3mj5p0f75ach6vcd8rst6wqnu', // VelectBlue Art
];
export const adultPublicKeys = adultNPubs.map(npub => (nip19.decode(npub).data as string).toLowerCase());

View File

@ -4,7 +4,6 @@
-moz-osx-font-smoothing: grayscale;
line-height: 1.5;
font-weight: 400;
color-scheme: dark;
color: rgba(255, 255, 255, 0.87);
background-color: #111111;
@ -51,19 +50,6 @@ button {
outline: none;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
input[type='checkbox'] {
-webkit-appearance: none;
-moz-appearance: none;

View File

@ -28,6 +28,10 @@ const MainInner = () => {
path: 'p/:npub',
element: <App />,
},
{
path: '/followers',
element: <App />,
},
{
path: '/:npub',
element: <App />,

View File

@ -7,7 +7,9 @@ declare global {
}
const useAutoLogin = () => {
const [autoLogin, setAutoLogin] = useState(JSON.parse(localStorage.getItem('autoLogin') as string) as boolean | undefined);
const [autoLogin, setAutoLogin] = useState(
JSON.parse(localStorage.getItem('autoLogin') as string) as boolean | undefined
);
useEffect(() => {
const autoLogin = JSON.parse(localStorage.getItem('autoLogin') as string) as boolean | undefined;

View File

@ -7,6 +7,7 @@ export type Settings = {
showReposts: boolean;
tags: string[];
npubs: string[];
followers: boolean;
};
const useNav = () => {
@ -18,8 +19,11 @@ const useNav = () => {
const adult = searchParams.get('adult') === 'true' || searchParams.get('nsfw') === 'true';
const replies = searchParams.get('replies') === 'true';
const reposts = searchParams.get('reposts') === 'true';
const followers = window.location.pathname.startsWith('/followers');
console.log(`tags = ${tags}, npub = ${npub}, adult = ${adult}, replies = ${replies}, reposts = ${reposts}`);
console.log(
`tags = ${tags}, npub = ${npub}, adult = ${adult}, replies = ${replies}, reposts = ${reposts}, followers = ${followers}`
);
const useTags = tags?.split(',') || [];
@ -29,6 +33,7 @@ const useNav = () => {
showAdult: adult,
showReplies: replies,
showReposts: reposts,
followers,
};
}, [tags, npub, searchParams]);
@ -49,7 +54,9 @@ const useNav = () => {
const postfix = searchParams.length > 0 ? `?${searchParams.join('&')}` : '';
if (validTags.length > 0) {
if (settings.followers) {
navigate(`/followers${postfix}`);
} else if (validTags.length > 0) {
navigate(`/tags/${validTags.join('%2C')}${postfix}`);
} else if (validNpubs.length == 1) {
navigate(`/p/${validNpubs[0]}${postfix}`);