v0.7.0
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:
parent
6b2fdf543c
commit
580ac885de
36
README.md
36
README.md
@ -1,6 +1,36 @@
|
|||||||
# Danbooru Viewer
|
# 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
|
## Usage
|
||||||
- Click this [link](https://danbooru.defaultkavy.com).
|
- Enter this URL: [https://danbooru.defaultkavy.com](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.
|
- 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.
|
1
dist/assets/index-Ai_LXv7l.js
vendored
1
dist/assets/index-Ai_LXv7l.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/index-B5ohILm0.js
vendored
Normal file
1
dist/assets/index-B5ohILm0.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/index-CPDn8S3u.css
vendored
1
dist/assets/index-CPDn8S3u.css
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/index-U9yaKy4a.css
vendored
Normal file
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
4
dist/index.html
vendored
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
gtag('config', 'G-59HBGP98WR');
|
gtag('config', 'G-59HBGP98WR');
|
||||||
</script>
|
</script>
|
||||||
<script type="module" crossorigin src="/assets/index-Ai_LXv7l.js"></script>
|
<script type="module" crossorigin src="/assets/index-B5ohILm0.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-CPDn8S3u.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-U9yaKy4a.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
</body>
|
</body>
|
||||||
|
11
index.scss
11
index.scss
@ -3,6 +3,7 @@
|
|||||||
@import '/src/component/PostTile/$PostTile';
|
@import '/src/component/PostTile/$PostTile';
|
||||||
@import '/src/component/Searchbar/$Searchbar';
|
@import '/src/component/Searchbar/$Searchbar';
|
||||||
@import '/src/component/IconButton/$IconButton';
|
@import '/src/component/IconButton/$IconButton';
|
||||||
|
@import '/src/component/IonIcon/$IonIcon';
|
||||||
@import '/src/component/Drawer/$Drawer';
|
@import '/src/component/Drawer/$Drawer';
|
||||||
// routes
|
// routes
|
||||||
@import '/src/route/post/$post_route';
|
@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 {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--secondary-color-9);
|
color: var(--secondary-color-9);
|
||||||
|
@ -2,7 +2,12 @@
|
|||||||
"name": "danbooru-viewer",
|
"name": "danbooru-viewer",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"type": "module",
|
"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": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"vite": "^5.4.8",
|
"vite": "^5.4.8",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { $Container } from "elexis";
|
import { $Container } from "elexis";
|
||||||
import { Booru } from "../../structure/Booru";
|
import { Booru } from "../../structure/Booru";
|
||||||
import { numberFormat } from "../../modules";
|
import { numberFormat } from "../../modules";
|
||||||
|
import { danbooru, safebooru } from "../../main";
|
||||||
|
|
||||||
export class $Drawer extends $Container {
|
export class $Drawer extends $Container {
|
||||||
$filter = $('div').class('filter');
|
$filter = $('div').class('filter');
|
||||||
@ -37,12 +38,16 @@ export class $Drawer extends $Container {
|
|||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
$('div').class('nav').content([
|
$('div').class('nav').content([
|
||||||
$('div').class('login')
|
$('icon-button').icon('log-in-outline').content('Login').link('/login', true)
|
||||||
.content([ $('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)))),
|
.self(($div => Booru.events.on('login', () => $div.hide(true)).on('logout', () => $div.hide(false)))),
|
||||||
$('div').class('logout').hide(true)
|
$('icon-button').icon('log-in-outline').content('Logout').on('dblclick', () => Booru.used.logout()).hide(true)
|
||||||
.content([ $('icon-button').icon('log-in-outline').content('Logout').on('dblclick', () => Booru.used.logout()) ])
|
|
||||||
.self(($div => Booru.events.on('login', () => $div.hide(false)).on('logout', () => $div.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())
|
this.$filter.on('click', () => $.back())
|
||||||
|
@ -56,7 +56,7 @@ drawer {
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
button.icon {
|
button.icon {
|
||||||
|
justify-content: start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,11 @@ export class $IonIcon extends $Container {
|
|||||||
this.attribute('size', size);
|
this.attribute('size', size);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disable(disable: boolean) {
|
||||||
|
this.attribute('disable', disable);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
link(url: string, replace = false) {
|
link(url: string, replace = false) {
|
||||||
this.on('click', () => replace ? $.replace(url) : $.open(url));
|
this.on('click', () => replace ? $.replace(url) : $.open(url));
|
||||||
|
14
src/component/IonIcon/_$IonIcon.scss
Normal file
14
src/component/IonIcon/_$IonIcon.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
15
src/main.ts
15
src/main.ts
@ -54,14 +54,17 @@ $(document.body).content([
|
|||||||
$('ion-icon').class('search').name('search-outline').title('Search')
|
$('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)}))
|
.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()),
|
.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
|
// 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'),
|
$('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
|
// Menu Button
|
||||||
$('ion-icon').class('menu').name('menu-outline').title('Menu').hide(false)
|
$('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)) })
|
.self(($icon) => { Booru.events.on('login', () => $icon.hide(true)).on('logout', () => $icon.hide(false)) })
|
||||||
|
@ -10,42 +10,58 @@ import { ClientUser } from "../../structure/ClientUser";
|
|||||||
export const post_route = $('route').path('/posts/:id').id('post').builder(({$route, params}) => {
|
export const post_route = $('route').path('/posts/:id').id('post').builder(({$route, params}) => {
|
||||||
if (!Number(params.id)) return $('h1').content('404: POST NOT FOUND');
|
if (!Number(params.id)) return $('h1').content('404: POST NOT FOUND');
|
||||||
const post = Post.get(Booru.used, +params.id);
|
const post = Post.get(Booru.used, +params.id);
|
||||||
const $viewerPanel =
|
const events = $.events<{
|
||||||
$('div').class('viewer-panel').content([
|
viewerPanel_hide: [],
|
||||||
$('div').class('panel').content([
|
viewerPanel_show: [],
|
||||||
$('ion-icon').name('heart-outline').self($heart => {
|
viewerPanel_switch: [],
|
||||||
ClientUser.events.on('favoriteUpdate', (user) => {
|
original_size: []
|
||||||
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);
|
|
||||||
return [
|
return [
|
||||||
$('div').class('viewer').content(async () => {
|
$('div').class('viewer').content(async () => {
|
||||||
await post.ready;
|
await post.ready;
|
||||||
return [
|
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
|
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)
|
? $('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))
|
||||||
|
})
|
||||||
]
|
]
|
||||||
})
|
}).self($div => {
|
||||||
.on('pointermove', (e) => {
|
$div
|
||||||
if (e.pointerType === 'mouse' || e.pointerType === 'pen') $viewerPanel.hide(false);
|
.on('pointermove', (e) => {
|
||||||
})
|
if (e.pointerType === 'mouse' || e.pointerType === 'pen') events.fire('viewerPanel_show');
|
||||||
.on('pointerup', (e) => {
|
})
|
||||||
console.debug(e.movementX)
|
.on('pointerup', (e) => {
|
||||||
if (e.pointerType === 'touch') $viewerPanel.hide(!$viewerPanel.hide());
|
if (e.pointerType === 'touch') events.fire('viewerPanel_hide');
|
||||||
})
|
})
|
||||||
.on('mouseleave', () => {
|
.on('mouseleave', () => {
|
||||||
$viewerPanel.hide(true);
|
events.fire('viewerPanel_hide');
|
||||||
}),
|
})
|
||||||
|
}),
|
||||||
$('div').class('content').content([
|
$('div').class('content').content([
|
||||||
$('h3').content(`Artist's Commentary`),
|
$('h3').content(`Artist's Commentary`),
|
||||||
$('section').class('commentary').content(async ($comentary) => {
|
$('section').class('commentary').content(async ($comentary) => {
|
||||||
|
@ -50,6 +50,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.overlay {
|
div.overlay {
|
||||||
|
@ -136,6 +136,7 @@ export class Post extends $EventManager<{update: []}> {
|
|||||||
get booruUrl() { return `${this.booru.origin}/posts/${this.id}` }
|
get booruUrl() { return `${this.booru.origin}/posts/${this.id}` }
|
||||||
get url() { return `https://danbooru.defaultkavy.com/posts/${this.id}` }
|
get url() { return `https://danbooru.defaultkavy.com/posts/${this.id}` }
|
||||||
get isFileSource() { return this.source.startsWith('file://') }
|
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 {
|
export interface PostData extends PostOptions {
|
||||||
|
Loading…
Reference in New Issue
Block a user