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
|
||||
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.
|
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');
|
||||
</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>
|
||||
|
11
index.scss
11
index.scss
@ -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);
|
||||
|
@ -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",
|
||||
|
@ -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())
|
||||
|
@ -56,7 +56,7 @@ drawer {
|
||||
gap: 1rem;
|
||||
flex-direction: column;
|
||||
button.icon {
|
||||
|
||||
justify-content: start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
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')
|
||||
.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)) })
|
||||
|
@ -10,41 +10,57 @@ 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([
|
||||
const events = $.events<{
|
||||
viewerPanel_hide: [],
|
||||
viewerPanel_show: [],
|
||||
viewerPanel_switch: [],
|
||||
original_size: []
|
||||
}>();
|
||||
return [
|
||||
$('div').class('viewer').content(async () => {
|
||||
await post.ready;
|
||||
return [
|
||||
$('div').class('viewer-panel').hide(true).content([
|
||||
$('div').class('panel').content([
|
||||
$('ion-icon').name('heart-outline').self($heart => {
|
||||
$('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');
|
||||
}).on('click', () => {
|
||||
$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')
|
||||
]).hide(true);
|
||||
return [
|
||||
$('div').class('viewer').content(async () => {
|
||||
await post.ready;
|
||||
return [
|
||||
$viewerPanel,
|
||||
]).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))
|
||||
})
|
||||
]
|
||||
}).self($div => {
|
||||
$div
|
||||
.on('pointermove', (e) => {
|
||||
if (e.pointerType === 'mouse' || e.pointerType === 'pen') $viewerPanel.hide(false);
|
||||
if (e.pointerType === 'mouse' || e.pointerType === 'pen') events.fire('viewerPanel_show');
|
||||
})
|
||||
.on('pointerup', (e) => {
|
||||
console.debug(e.movementX)
|
||||
if (e.pointerType === 'touch') $viewerPanel.hide(!$viewerPanel.hide());
|
||||
if (e.pointerType === 'touch') events.fire('viewerPanel_hide');
|
||||
})
|
||||
.on('mouseleave', () => {
|
||||
$viewerPanel.hide(true);
|
||||
events.fire('viewerPanel_hide');
|
||||
})
|
||||
}),
|
||||
$('div').class('content').content([
|
||||
$('h3').content(`Artist's Commentary`),
|
||||
|
@ -50,6 +50,7 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
div.overlay {
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user