change: $viewerPanel hide rely to events.
change: load post original image file if file is under 5MB.
new: $viewerPanel load original file button.
change: move switch booru button to $Drawer.
new: copy page link button.
change: README.md
This commit is contained in:
defaultkavy 2024-10-13 12:28:17 +08:00
parent 6b2fdf543c
commit 580ac885de
Signed by: defaultkavy
GPG Key ID: DFBB22C4E69D7826
16 changed files with 129 additions and 58 deletions

View File

@ -1,6 +1,36 @@
# Danbooru Viewer
A modern style viewer for [Danbooru](https://danbooru.donmai.us).
A modern style viewer for [Danbooru](https://danbooru.donmai.us) or other Booru API base site.
## Usage
- Click this [link](https://danbooru.defaultkavy.com).
- Replace `danbooru.donmai.us` to `danbooru.defaultkavy.com` without changing pathname and url parameters, will directly open the same page on Danbooru Viewer.
- Enter this URL: [https://danbooru.defaultkavy.com](https://danbooru.defaultkavy.com).
- Replace `danbooru.donmai.us` to `danbooru.defaultkavy.com` without changing pathname and url query, will directly open the same page on Danbooru Viewer.
- Clone this repository and run commands:
```sh
bun i
bun run build
bun run start
```
## Features
- Same path as the original website.
- Support URL query like `/posts?tags=ord:fav+minato_aqua`.
- Search tags with autocomplete.
- Infinite scroll posts with waterfall image layout.
- Mobile friendly with modern design.
## Roadmap to V1.0
- [x] Posts Page
- [x] Posts Search with any tags
- [x] Booru Account Login (Using API keys)
- [x] Favorite Post with Account
- [ ] Saved Searches
- [ ] User Page
- [ ] Post Commentary
- [ ] Post Detail Panel
- [ ] Forum Posts Page
- [ ] More...
## Tools
- [Elexis](https://git.defaultkavy.com/defaultkavy/elexis): Web Builder.
- [Elysia](https://elysiajs.com/): Server Framework.
- [ionicons](https://ionic.io/ionicons): Open Souces Icons.

File diff suppressed because one or more lines are too long

1
dist/assets/index-B5ohILm0.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/index-U9yaKy4a.css vendored Normal file

File diff suppressed because one or more lines are too long

4
dist/index.html vendored
View File

@ -16,8 +16,8 @@
gtag('config', 'G-59HBGP98WR');
</script>
<script type="module" crossorigin src="/assets/index-Ai_LXv7l.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CPDn8S3u.css">
<script type="module" crossorigin src="/assets/index-B5ohILm0.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-U9yaKy4a.css">
</head>
<body>
</body>

View File

@ -3,6 +3,7 @@
@import '/src/component/PostTile/$PostTile';
@import '/src/component/Searchbar/$Searchbar';
@import '/src/component/IconButton/$IconButton';
@import '/src/component/IonIcon/$IonIcon';
@import '/src/component/Drawer/$Drawer';
// routes
@import '/src/route/post/$post_route';
@ -200,16 +201,6 @@ button {
}
}
ion-icon {
font-size: 24px;
color: var(--primary-color);
cursor: pointer;
&:hover {
color: var(--secondary-color-9);
}
}
a {
text-decoration: none;
color: var(--secondary-color-9);

View File

@ -2,7 +2,12 @@
"name": "danbooru-viewer",
"module": "index.ts",
"type": "module",
"version": "0.6.3",
"version": "0.7.0",
"scripts": {
"dev": "bun x vite",
"build": "bun x vite build",
"start": "bun server.ts"
},
"devDependencies": {
"@types/bun": "latest",
"vite": "^5.4.8",

View File

@ -1,6 +1,7 @@
import { $Container } from "elexis";
import { Booru } from "../../structure/Booru";
import { numberFormat } from "../../modules";
import { danbooru, safebooru } from "../../main";
export class $Drawer extends $Container {
$filter = $('div').class('filter');
@ -37,12 +38,16 @@ export class $Drawer extends $Container {
})
]),
$('div').class('nav').content([
$('div').class('login')
.content([ $('icon-button').icon('log-in-outline').content('Login').link('/login', true) ])
$('icon-button').icon('log-in-outline').content('Login').link('/login', true)
.self(($div => Booru.events.on('login', () => $div.hide(true)).on('logout', () => $div.hide(false)))),
$('div').class('logout').hide(true)
.content([ $('icon-button').icon('log-in-outline').content('Logout').on('dblclick', () => Booru.used.logout()) ])
$('icon-button').icon('log-in-outline').content('Logout').on('dblclick', () => Booru.used.logout()).hide(true)
.self(($div => Booru.events.on('login', () => $div.hide(false)).on('logout', () => $div.hide(true)))),
$('icon-button').icon('swap-horizontal').content('Logout').class('switch').content('Switch Booru')
.on('click', () => {
if (Booru.used === danbooru) Booru.set(safebooru);
else Booru.set(danbooru);
}),
])
]),
this.$filter.on('click', () => $.back())

View File

@ -56,7 +56,7 @@ drawer {
gap: 1rem;
flex-direction: column;
button.icon {
justify-content: start;
}
}
}

View File

@ -15,6 +15,11 @@ export class $IonIcon extends $Container {
return this;
}
disable(disable: boolean) {
this.attribute('disable', disable);
return this;
}
link(url: string, replace = false) {
this.on('click', () => replace ? $.replace(url) : $.open(url));
return this;

View File

@ -0,0 +1,14 @@
ion-icon {
font-size: 24px;
color: var(--primary-color);
cursor: pointer;
&:hover {
color: var(--secondary-color-9);
}
&[disable="true"] {
color: var(--primary-color-darker);
pointer-events: none;
}
}

View File

@ -54,14 +54,17 @@ $(document.body).content([
$('ion-icon').class('search').name('search-outline').title('Search')
.self($self => $Router.events.on('stateChange', ({beforeURL, afterURL}) => {if (beforeURL.hash === '#search') $self.hide(false); if (afterURL.hash === '#search') $self.hide(true)}))
.on('click', () => $searchbar.open()),
// Switch Booru
$('ion-icon').class('switch').name('swap-horizontal').title('Switch Booru')
.on('click', () => {
if (Booru.used === danbooru) Booru.set(safebooru);
else Booru.set(danbooru);
}),
// Open Booru
$('a').content($('ion-icon').class('open').name('open-outline').title('Open in Original Site')).href(location.href.replace(location.origin, Booru.used.origin)).target('_blank'),
// Copy Button
$('ion-icon').class('copy').name('link-outline').title('Copy Page Link').hide(false)
.on('click', (e, $copy) => {
navigator.clipboard.writeText(`${location.origin}${location.pathname}${location.search}`)
$copy.name('checkmark-outline');
setTimeout(() => {
$copy.name('link-outline')
}, 2000);
}),
// Menu Button
$('ion-icon').class('menu').name('menu-outline').title('Menu').hide(false)
.self(($icon) => { Booru.events.on('login', () => $icon.hide(true)).on('logout', () => $icon.hide(false)) })

View File

@ -10,42 +10,58 @@ import { ClientUser } from "../../structure/ClientUser";
export const post_route = $('route').path('/posts/:id').id('post').builder(({$route, params}) => {
if (!Number(params.id)) return $('h1').content('404: POST NOT FOUND');
const post = Post.get(Booru.used, +params.id);
const $viewerPanel =
$('div').class('viewer-panel').content([
$('div').class('panel').content([
$('ion-icon').name('heart-outline').self($heart => {
ClientUser.events.on('favoriteUpdate', (user) => {
if (user.favorites.has(post.id)) $heart.name('heart');
else $heart.name('heart-outline');
})
if (Booru.used.user?.favorites.has(post.id)) $heart.name('heart');
}).on('click', () => {
if (Booru.used.user?.favorites.has(post.id)) post.deleteFavorite();
else post.createFavorite();
})
]),
$('div').class('overlay')
]).hide(true);
const events = $.events<{
viewerPanel_hide: [],
viewerPanel_show: [],
viewerPanel_switch: [],
original_size: []
}>();
return [
$('div').class('viewer').content(async () => {
await post.ready;
return [
$viewerPanel,
$('div').class('viewer-panel').hide(true).content([
$('div').class('panel').content([
$('ion-icon').title('Favorite').name('heart-outline').self($heart => {
ClientUser.events.on('favoriteUpdate', (user) => {
if (user.favorites.has(post.id)) $heart.name('heart');
else $heart.name('heart-outline');
})
if (Booru.used.user?.favorites.has(post.id)) $heart.name('heart');
$heart.on('click', () => {
if (Booru.used.user?.favorites.has(post.id)) post.deleteFavorite();
else post.createFavorite();
})
}),
$('ion-icon').title('Original Size').name('resize-outline').self($original => {
$original.on('click', () => { events.fire('original_size'); $original.disable(true); })
if (!post.isLargeFile || post.isVideo) $original.disable(true);
})
]),
$('div').class('overlay')
]).self($viewerPanel => {
events.on('viewerPanel_hide', () => $viewerPanel.hide(true))
.on('viewerPanel_show', () => $viewerPanel.hide(false))
.on('viewerPanel_switch', () => $viewerPanel.hide(!$viewerPanel.hide()))
}),
post.isVideo
? $('video').height(post.image_height).width(post.image_width).src(post.file_ext === 'zip' ? post.large_file_url : post.file_url).controls(true).autoplay(true).loop(true).disablePictureInPicture(true)
: $('img').src(post.large_file_url)//.once('load', (e, $img) => { $img.src(post.file_url)})
: $('img').src(post.isLargeFile ? post.large_file_url : post.file_url).self($img => {
events.on('original_size', () => $img.src(post.file_url))
})
]
})
.on('pointermove', (e) => {
if (e.pointerType === 'mouse' || e.pointerType === 'pen') $viewerPanel.hide(false);
})
.on('pointerup', (e) => {
console.debug(e.movementX)
if (e.pointerType === 'touch') $viewerPanel.hide(!$viewerPanel.hide());
})
.on('mouseleave', () => {
$viewerPanel.hide(true);
}),
}).self($div => {
$div
.on('pointermove', (e) => {
if (e.pointerType === 'mouse' || e.pointerType === 'pen') events.fire('viewerPanel_show');
})
.on('pointerup', (e) => {
if (e.pointerType === 'touch') events.fire('viewerPanel_hide');
})
.on('mouseleave', () => {
events.fire('viewerPanel_hide');
})
}),
$('div').class('content').content([
$('h3').content(`Artist's Commentary`),
$('section').class('commentary').content(async ($comentary) => {

View File

@ -50,6 +50,7 @@
display: flex;
justify-content: center;
padding: 1rem;
gap: 2rem;
}
div.overlay {

View File

@ -136,6 +136,7 @@ export class Post extends $EventManager<{update: []}> {
get booruUrl() { return `${this.booru.origin}/posts/${this.id}` }
get url() { return `https://danbooru.defaultkavy.com/posts/${this.id}` }
get isFileSource() { return this.source.startsWith('file://') }
get isLargeFile() { return this.file_size > 5_000_000 } // || this.image_height > innerHeight || this.image_width > innerWidth
}
export interface PostData extends PostOptions {