feat: another UI change

This commit is contained in:
Damillora 2025-02-24 13:03:20 +00:00
parent 4c2fb159a9
commit 1fd9e364bb
16 changed files with 373 additions and 281 deletions

View File

@ -112,6 +112,7 @@ func postGetOne(c *gin.Context) {
Width: post.Blob.Width,
Height: post.Blob.Height,
Uploader: post.User.Username,
UploadDate: post.CreatedAt,
})
}

View File

@ -1,5 +1,7 @@
package models
import "time"
type PostReadModel struct {
ID string `json:"id"`
ImagePreviewPath string `json:"preview_path"`
@ -9,6 +11,7 @@ type PostReadModel struct {
Width int `json:"width"`
Height int `json:"height"`
Uploader string `json:"uploader"`
UploadDate time.Time `json:"upload_date"`
}
type TagReadModel struct {

View File

@ -10,6 +10,7 @@
"dependencies": {
"axios": "^1.4.0",
"bulma": "^1.0.3",
"date-fns": "^4.1.0",
"query-string": "^8.1.0"
},
"devDependencies": {
@ -1853,6 +1854,16 @@
"node": ">=4"
}
},
"node_modules/date-fns": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",

View File

@ -30,6 +30,7 @@
"dependencies": {
"axios": "^1.4.0",
"bulma": "^1.0.3",
"date-fns": "^4.1.0",
"query-string": "^8.1.0"
}
}

View File

@ -0,0 +1,38 @@
<script>
import { postDelete } from "$lib/api";
let { id, toggleDeleteMenu, onDelete } = $props();
let deleteLoading = $state(false);
const deletePost = async (e) => {
e.preventDefault();
deleteLoading = true;
const success = await postDelete({ id });
deleteLoading = false;
toggleDeleteMenu(e);
onDelete(success);
};
</script>
<div class="block">
<div class="panel is-danger">
<p class="panel-heading">Delete Post</p>
{#if !deleteLoading}
<div class="panel-block">
Are you sure to delete post {id}?
</div>
<div class="panel-block column">
<button onclick={deletePost} class="button is-danger"
>Delete</button
>
<button class="button" onclick={toggleDeleteMenu}>Cancel</button
>
</div>
{:else}
<div class="panel-block column">
<progress class="progress is-small is-danger" max="100"
></progress>
</div>
{/if}
</div>
</div>

View File

@ -11,6 +11,8 @@
let { isActive = $bindable(false), post, onSubmit }: Props = $props();
let editLoading = $state(false);
const toggleEditModal = (e) => {
e.preventDefault();
isActive = !isActive;
@ -37,9 +39,10 @@
const onFormSubmit = async (e) => {
e.preventDefault();
editLoading = true;
const response = await postUpdate(post.id, form);
toggleEditModal();
editLoading = false;
toggleEditModal(e);
onSubmit();
};
@ -51,6 +54,7 @@
<form onsubmit={onFormSubmit}>
<div class="panel is-warning">
<p class="panel-heading">Edit Post</p>
{#if !editLoading}
<div class="panel-block column">
<div class="row">
<strong>Uploader:</strong>
@ -115,5 +119,10 @@
>Cancel</button
>
</div>
{:else}
<div class="panel-block column">
<progress class="progress is-small is-warning" max="100"></progress>
</div>
{/if}
</div>
</form>

View File

@ -2,16 +2,12 @@
import { onMount } from "svelte";
import { updateTagNotes } from "$lib/api";
let {
tag,
data,
toggleEditMenu,
onSubmit
} = $props();
let { tag, data, toggleEditMenu, onSubmit } = $props();
let form = $state({
note: "",
});
let editNotesLoading = $state(false);
const getData = async () => {
form.note = data.tagNote;
@ -19,7 +15,9 @@
const onFormSubmit = async (e) => {
e.preventDefault();
editNotesLoading = true;
await updateTagNotes(tag, form);
editNotesLoading = false;
toggleEditMenu();
onSubmit();
@ -33,18 +31,21 @@
<form onsubmit={onFormSubmit}>
<div class="panel is-warning">
<p class="panel-heading">Edit Notes</p>
<div class="panel-block column">
<textarea
bind:value={form.note}
class="textarea has-fixed-size"
></textarea>
<div class="content"></div>
</div>
<div class="panel-block column">
<button type="submit" class="button is-primary">Save</button>
<button onclick={toggleEditMenu} class="button"
>Cancel</button
>
</div>
{#if !editNotesLoading}
<div class="panel-block column">
<textarea bind:value={form.note} class="textarea has-fixed-size"
></textarea>
<div class="content"></div>
</div>
<div class="panel-block column">
<button type="submit" class="button is-primary">Save</button>
<button onclick={toggleEditMenu} class="button">Cancel</button>
</div>
{:else}
<div class="panel-block column">
<progress class="progress is-small is-warning" max="100"
></progress>
</div>
{/if}
</div>
</form>

View File

@ -2,19 +2,16 @@
import { onMount } from "svelte";
import { getTagTypes, updateTag } from "$lib/api";
import { goto } from "$app/navigation";
let {
tag,
data,
toggleRenameMenu,
onSubmit
} = $props();
let { tag, data, toggleRenameMenu, onSubmit } = $props();
let tagTypes = $state([]);
let form = $state({
name: "",
tagTypeId: 1,
});
let editTagLoading = $state(false);
const getData = async () => {
tagTypes = await getTagTypes();
@ -25,10 +22,9 @@
const onFormSubmit = async (e) => {
e.preventDefault();
editTagLoading = true;
await updateTag(tag, form);
goto("/tags/" + form.name);
editTagLoading = false;
onSubmit(form.name);
};
@ -40,56 +36,54 @@
<form onsubmit={onFormSubmit}>
<div class="panel is-warning">
<p class="panel-heading">Edit Tag</p>
<div class="panel-block column">
<div class="row">
<strong>Name:</strong>
</div>
<div class="row">
<div class="field">
<div class="control">
<input
class="input"
type="text"
bind:value={form.name}
/>
{#if !editTagLoading}
<div class="panel-block column">
<div class="row">
<strong>Name:</strong>
</div>
<div class="row">
<div class="field">
<div class="control">
<input
class="input"
type="text"
bind:value={form.name}
/>
</div>
</div>
</div>
</div>
</div>
<div class="panel-block column">
<div class="row">
<strong>Category:</strong>
</div>
<div class="row">
<div class="field">
<div class="select">
<select bind:value={form.tagTypeId}>
{#each tagTypes as tagType}
<option
value={tagType.id}
selected={form.tagTypeId === tagType.id}
>
{tagType.name}
</option>
{/each}
</select>
<div class="panel-block column">
<div class="row">
<strong>Category:</strong>
</div>
<div class="row">
<div class="field">
<div class="select">
<select bind:value={form.tagTypeId}>
{#each tagTypes as tagType}
<option
value={tagType.id}
selected={form.tagTypeId === tagType.id}
>
{tagType.name}
</option>
{/each}
</select>
</div>
</div>
</div>
</div>
</div>
<div class="panel-block column">
<div class="row">
<strong>Posts:</strong>
<div class="panel-block column">
<button class="button is-primary" type="submit">Submit</button>
<button onclick={toggleRenameMenu} class="button">Cancel</button
>
</div>
<div class="row">
{data.postCount} (<a href="/posts?tags={tag}">Browse</a>)
{:else}
<div class="panel-block column">
<progress class="progress is-small is-warning" max="100"
></progress>
</div>
</div>
<div class="panel-block column">
<button class="button is-primary" type="submit">Submit</button>
<button onclick={toggleRenameMenu} class="button"
>Cancel</button
>
</div>
{/if}
</div>
</form>

View File

@ -1,8 +1,14 @@
<script lang="ts">
import AuthCheck from "$lib/components/checks/AuthCheck.svelte";
import TagLinkNumbered from "$lib/components/ui/TagLinkNumbered.svelte";
let { post, toggleEditMenu, toggleDeleteMenu } = $props();
import { format, formatDistanceToNow } from "date-fns";
let { post } = $props();
let tabPage = $state(1);
const changeTab = (tab) => {
tabPage = tab;
}
const trimUrl = (str) => {
if (str.length > 30) {
return str.substring(0, 30) + "...";
@ -13,6 +19,27 @@
<div class="panel is-primary">
<p class="panel-heading">Post</p>
<div class="panel-tabs">
<a href={"#"} class:is-active="{tabPage == 1}" onclick={() => changeTab(1)}>Tags</a>
<a href={"#"} class:is-active="{tabPage == 2}" onclick={() => changeTab(2)}>Information</a>
</div>
{#if tabPage === 1}
{#if post.tags}
{#each post.tags as tag (tag)}
<TagLinkNumbered
tag={tag.tagType + ":" + tag.tagName}
num={tag.postCount}
/>
{/each}
{/if}
{:else if tabPage == 2}
<div class="panel-block column">
<div class="row">
<strong>Upload Date:</strong>
</div>
<div class="row"><time title={format(post.upload_date, "dd MMMM yyyy HH:mm:ss")} datetime={post.upload_date} >{formatDistanceToNow(post.upload_date, {addSuffix: true })}</time></div>
</div>
<div class="panel-block column">
<div class="row">
<strong>Uploader:</strong>
@ -43,38 +70,5 @@
<a href={post.source_url}>{trimUrl(post.source_url)}</a>
</div>
</div>
<div class="panel-block column">
<div class="row">
<p><strong>Tags:</strong></p>
</div>
<div class="row">
<div class="menu">
<ul class="menu-list">
{#if post.tags}
{#each post.tags as tag (tag)}
<li>
<TagLinkNumbered
class=""
tag={tag.tagType + ":" + tag.tagName}
num={tag.postCount}
/>
</li>
{/each}
{/if}
</ul>
</div>
</div>
</div>
<AuthCheck>
<p class="panel-block column">
<button
onclick={toggleEditMenu}
class="button is-primary">Edit</button
>
<button
onclick={toggleDeleteMenu}
class="button is-danger">Delete</button
>
</p>
</AuthCheck>
{/if}
</div>

View File

@ -8,6 +8,12 @@
let { tag, data, toggleRenameMenu } = $props();
let related_tags = $state([]);
let tabPage = $state(1);
const changeTab = (tab) => {
tabPage = tab;
};
const getData = async () => {
related_tags = await getRelatedTags({ tag });
related_tags = related_tags
@ -21,52 +27,45 @@
<div class="panel is-primary">
<p class="panel-heading">Tag</p>
<div class="panel-block column">
<div class="row">
<strong>Name:</strong>
</div>
<div class="row">{data.tagName}</div>
<div class="panel-tabs">
<a
href={"#"}
class:is-active={tabPage == 1}
onclick={() => changeTab(1)}>Information</a
>
<a
href={"#"}
class:is-active={tabPage == 2}
onclick={() => changeTab(2)}>Related Tags</a
>
</div>
<div class="panel-block column">
<div class="row">
<strong>Category:</strong>
{#if tabPage === 1}
<div class="panel-block column">
<div class="row">
<strong>Name:</strong>
</div>
<div class="row">{data.tagName}</div>
</div>
<div class="row"><TagTypeIndicator tagType={data.tagType} /></div>
</div>
<div class="panel-block column">
<div class="row">
<strong>Posts:</strong>
<div class="panel-block column">
<div class="row">
<strong>Category:</strong>
</div>
<div class="row"><TagTypeIndicator tagType={data.tagType} /></div>
</div>
<div class="row">
{data.postCount} (<a href="/posts?tags={tag}">Browse</a>)
</div>
</div>
<div class="panel-block column">
<div class="row">
<strong>Related Tags:</strong>
</div>
<div class="row">
<div class="menu">
<ul class="menu-list">
{#each related_tags as tag (tag)}
<li>
<TagLinkNumbered
class=""
tag={tag.tagType + ":" + tag.tagName}
num={tag.postCount}
/>
</li>
{/each}
</ul>
<div class="panel-block column">
<div class="row">
<strong>Posts:</strong>
</div>
<div class="row">
{data.postCount}
</div>
</div>
</div>
<AuthCheck>
<div class="panel-block column">
<button
onclick={toggleRenameMenu}
class="button is-primary">Rename</button
>
</div>
</AuthCheck>
{:else if tabPage === 2}
{#each related_tags as tag (tag)}
<TagLinkNumbered
tag={tag.tagType + ":" + tag.tagName}
num={tag.postCount}
/>
{/each}
{/if}
</div>

View File

@ -8,10 +8,10 @@
let tagDisplay = tagName.split("_").join(" ");
</script>
<a href="/posts?tags={tagName}">
<a class="panel-block is-block" href="/posts?tags={tagName}">
<span>
{tagDisplay}
</span>
<TagTypeIndicator tagType={tagType}></TagTypeIndicator>
<TagTypeIndicator {tagType}></TagTypeIndicator>
<span class="is-pulled-right">{num}</span>
</a>

View File

@ -7,15 +7,17 @@
import { page } from "$app/stores";
import ShiorikoImage from "$lib/components/ui/ShiorikoImage.svelte";
import AuthCheck from "$lib/components/checks/AuthCheck.svelte";
import DeletePostPanel from "$lib/components/panels/DeletePostPanel.svelte";
const { id } = $page.params;
let post: any = $state();
const getData = async () => {
post = null;
const data = await getPost({ id });
post = data;
imagePercentage = ((1000 * 100) / post.width).toFixed(0) + "%";
};
let loading = $state(false);
let isOriginal = $state(false);
const trimUrl = (str: string) => {
@ -25,25 +27,22 @@
return str;
};
let deleteMenuShown = $state(false);
const onSubmitEdit = () => {
getData();
editMenuShown = false;
}
};
onMount(() => {
getData();
});
const deletePost = async (e) => {
e.preventDefault();
toggleDeleteMenu(e);
const success = await postDelete({ id });
const onDelete = async (success) => {
if (success) {
goto("/posts");
}
};
const toggleDeleteMenu = (e) => {
e.preventDefault();
deleteMenuShown = !deleteMenuShown;
@ -64,68 +63,85 @@
<div class="columns">
<div class="column is-one-third">
{#if post}
{#if editMenuShown == false && deleteMenuShown == false}
<ViewPostPanel
{post}
{toggleDeleteMenu}
{toggleEditMenu}
/>
{:else if editMenuShown == true}
<EditPostPanel
bind:isActive={editMenuShown}
{post}
onSubmit={onSubmitEdit}
/>
{:else if deleteMenuShown == true}
<div class="panel is-danger">
<p class="panel-heading">Delete Post</p>
<div class="panel-block">
Are you sure to delete post {post.id}?
</div>
<div class="panel-block column">
<button
onclick={deletePost}
class="button is-danger">Delete</button
>
<button
class="button"
onclick={toggleDeleteMenu}>Cancel</button
>
</div>
<div class="block">
<ViewPostPanel {post} />
</div>
{#if editMenuShown == true && deleteMenuShown == false}
<div class="block">
<EditPostPanel
bind:isActive={editMenuShown}
{post}
onSubmit={onSubmitEdit}
/>
</div>
{:else if deleteMenuShown == true}
<DeletePostPanel
id={post.id}
{toggleDeleteMenu}
{onDelete}
/>
{:else}
<AuthCheck>
<div class="panel is-info">
<div class="panel-heading">Post Actions</div>
<a
class="panel-block"
href={post.image_path}
target="_blank">View Original</a
>
<a
href={"#"}
onclick={toggleEditMenu}
class="panel-block">Edit</a
>
<a
href={"#"}
onclick={toggleDeleteMenu}
class="panel-block">Delete</a
>
</div>
</AuthCheck>
{/if}
{:else}
<div class="skeleton-block"></div>
{/if}
</div>
<div class="column box">
<div class="column is-two-thirds">
{#if post}
{#if post.width > 1000 && isOriginal == false}
<div class="notification is-info">
Resized to {imagePercentage} of the original image.
<a
onclick={() => {
isOriginal = true;
}}>View original</a
>
</div>
<figure class="image">
<ShiorikoImage
alt={post.id}
src={post.preview_path}
/>
</figure>
{:else}
<div class="notification is-primary">
Currently viewing original image.
</div>
<figure class="image">
<ShiorikoImage
alt={post.id}
src={post.image_path}
/>
</figure>
{/if}
<div class="block">
{#if post.width > 1000 && isOriginal == false}
<div class="notification is-info">
Resized to {imagePercentage} of the original image.
<a
href={"#"}
onclick={() => {
isOriginal = true;
}}>View original</a
>
</div>
<div class="box">
<figure class="image">
<ShiorikoImage
alt={post.id}
src={post.preview_path}
/>
</figure>
</div>
{:else}
<div class="notification is-primary">
Currently viewing original image.
</div>
<div class="box">
<figure class="image">
<ShiorikoImage
alt={post.id}
src={post.image_path}
/>
</figure>
</div>
{/if}
</div>
{:else}
<div class="skeleton-block"></div>
{/if}

View File

@ -127,32 +127,17 @@
</div>
</form>
</div>
<div class="panel-block column">
{#if !loading}
<div class="row">
<strong>Tags:</strong>
</div>
<div class="row">
<div class="menu">
<ul class="menu-list">
{#each tags as tag (tag)}
<li>
<TagLinkNumbered
class=""
tag={tag.tagType +
":" +
tag.tagName}
num={tag.postCount}
/>
</li>
{/each}
</ul>
</div>
</div>
{:else}
<div class="skeleton-block"></div>
{/if}
</div>
{#if !loading}
{#each tags as tag (tag)}
<TagLinkNumbered
class=""
tag={tag.tagType + ":" + tag.tagName}
num={tag.postCount}
/>
{/each}
{:else}
<div class="skeleton-block"></div>
{/if}
</div>
{#if tagInfo}
<div class="panel is-info">

View File

@ -1,52 +1,63 @@
<script>
import { run } from 'svelte/legacy';
import { run } from "svelte/legacy";
import { getTags } from "$lib/api";
import { afterNavigate } from '$app/navigation';
import TagTypeIndicator from '$lib/components/ui/TagTypeIndicator.svelte';
import { afterNavigate } from "$app/navigation";
import TagTypeIndicator from "$lib/components/ui/TagTypeIndicator.svelte";
let tags = $state([]);
let loading = $state(false);
let highestCount = $state(1);
const getData = async () => {
const data = await getTags();
tags = data;
loading = false;
highestCount = Math.max(...data.map((x) => x.postCount));
if (highestCount <= 0) {
highestCount = 1;
}
};
afterNavigate(() => {
loading = true;
getData();
})
});
</script>
<section class="section">
<div class="container">
<h1 class="title">Tag List</h1>
{#if !loading}
<table class="table is-fullwidth">
<thead>
<tr>
<th >Tag</th>
<th style="width: 30%;">Tag Type</th>
<th style="width: 10%;">Post Count</th>
</tr>
</thead>
<tbody>
{#each tags as tag}
<table class="table is-fullwidth">
<thead>
<tr>
<td>
<a href="/tags/{tag.tagName}">{tag.tagName}</a>
</td>
<td><TagTypeIndicator tagType={tag.tagType} /></td>
<td>{tag.postCount}</td>
<th>Tag</th>
<th>Post Count</th>
</tr>
{/each}
</tbody>
</table>
</thead>
<tbody>
{#each tags as tag}
<tr>
<td>
<a href="/tags/{tag.tagName}"
>{tag.tagName}
<TagTypeIndicator
tagType={tag.tagType}
/></a
>
</td>
<td>
<span class="is-pulled-right"
>{tag.postCount}</span
></td
>
</tr>
{/each}
</tbody>
</table>
{:else}
<div class="skeleton-block"></div>
<div class="skeleton-block"></div>
{/if}
</div>
</section>

View File

@ -9,6 +9,7 @@
import PostGallery from "$lib/components/ui/PostGallery.svelte";
import { page } from "$app/stores";
import AuthCheck from "$lib/components/checks/AuthCheck.svelte";
let { tag } = $state($page.params);
let data = $state();
@ -16,6 +17,7 @@
const getData = async () => {
if (tag) {
data = null;
data = await getTag({ tag });
const response = await getPosts({
page: 1,
@ -40,6 +42,7 @@
const onTagSubmit = (newName) => {
tag = newName;
toggleEditMenu();
getData();
};
@ -53,15 +56,28 @@
<div class="columns">
<div class="column is-one-third">
{#if data}
<div class="block">
<ViewTagPanel {tag} {data} />
</div>
{#if renameMenuShown}
<div class="block">
<EditTagPanel
{tag}
{data}
{toggleRenameMenu}
onSubmit={onTagSubmit}
/>
</div>
{:else}
<ViewTagPanel {tag} {data} {toggleRenameMenu} />
<AuthCheck>
<div class="panel is-info">
<div class="panel-heading">Tag Actions</div>
<a class="panel-block" href="/posts?tags={tag}">Browse Posts</a>
<a onclick={toggleRenameMenu} class="panel-block"
>Rename</a
>
</div>
</AuthCheck>
{/if}
{:else}
<div class="skeleton-block"></div>

View File

@ -10,6 +10,7 @@
let fileName = $state("");
let similar = $state([]);
let previewUrl = $state("");
let loading = $state(false);
let form = $state({
blob_id: "",
@ -23,6 +24,7 @@
};
const onFileChange = async (e) => {
loading = true;
var file = e.target.files[0];
fileName = "";
previewUrl = "";
@ -38,6 +40,7 @@
fileName = file.name;
previewUrl = response.previewUrl;
}
loading = false;
};
const onTagChange = (value) => {
@ -94,6 +97,17 @@
</div>
</div>
</div>
{#if currentProgress > 0 && currentProgress < 100}
<div class="panel-block column">
<progress
class="progress is-primary is-small"
value={currentProgress}
max="100"
>
{currentProgress}%
</progress>
</div>
{/if}
<div class="panel-block column">
<div class="row">
<label for="source" class="label"
@ -143,7 +157,7 @@
</div>
</div>
<div class="column is-two-thirds">
<div class="box">
<div class="block">
{#if fileName}
{#if similar.length > 0}
<div class="notification is-warning">
@ -157,21 +171,11 @@
{/each}
</div>
{:else}
<div class="notification is-primary">
<div class="notification is-success">
{fileName} has been succesfully uploaded.
</div>
{/if}
<figure class="image">
<ShiorikoImage alt={fileName} src={previewUrl} />
</figure>
{:else if currentProgress > 0 && currentProgress < 100}
<progress
class="progress is-primary"
value={currentProgress}
max="100"
>
{currentProgress}%
</progress>
<div class="notification is-info">
Your image is currently uploading...
</div>
@ -181,6 +185,15 @@
</div>
{/if}
</div>
{#if fileName}
<div class="box">
<figure class="image">
<ShiorikoImage alt={fileName} src={previewUrl} />
</figure>
</div>
{:else if loading && !(currentProgress > 0 && currentProgress < 100)}
<div class="skeleton-block"></div>
{/if}
</div>
</div>
</div>