feat: functioning upload frontend

This commit is contained in:
Damillora 2021-05-11 00:37:27 +07:00
parent dbff5649d7
commit f04575bc5c
15 changed files with 5511 additions and 1973 deletions

View File

@ -14,7 +14,7 @@ func InitializeFrontendRoutes(g *gin.Engine) {
func frontendHome(c *gin.Context) {
baseURL := config.CurrentConfig.BaseURL
c.HTML(http.StatusOK, "index.html", gin.H{
"title": "Home",
"title": "Shioriko",
"base_url": baseURL,
})
}

View File

@ -14,24 +14,35 @@ func GetTagAll() []database.Tag {
return tags
}
func CreateOrUpdateTag(tagSyntax string) (*database.Tag, error) {
tagFields := strings.Split(tagSyntax, ":")
var tagName string
var tagType database.TagType
if len(tagFields) == 1 {
tagName = tagFields[0]
func CreateOrUpdateTagGeneric(tagName string) (*database.Tag, error) {
var tag database.Tag
result := database.DB.Where("name = ?", tagName).First(&tag)
if result.Error != nil {
var tagType database.TagType
database.DB.Where("name = ?", "general").First(&tagType)
} else if len(tagFields) == 2 {
tagName = tagFields[1]
result := database.DB.Where("name = ?", tagFields[0]).First(&tagType)
tag = database.Tag{
ID: uuid.NewString(),
Name: tagName,
TagTypeID: tagType.ID,
}
result = database.DB.Create(&tag)
if result.Error != nil {
return nil, result.Error
}
} else {
return nil, errors.New("Malformed tag syntax")
}
return &tag, nil
}
func CreateOrUpdateTagComplex(tagName string, tagTypeString string) (*database.Tag, error) {
var tag database.Tag
result := database.DB.Where("name = ? AND tag_type_id = ? ", tagName, tagType.ID).First(&tag)
var tagType database.TagType
result := database.DB.Where("name = ?", tagTypeString).First(&tagType)
if result.Error != nil {
return nil, result.Error
}
result = database.DB.Where("name = ? AND tag_type_id = ? ", tagName, tagType.ID).First(&tag)
if result.Error != nil {
tag = database.Tag{
@ -47,6 +58,21 @@ func CreateOrUpdateTag(tagSyntax string) (*database.Tag, error) {
}
return &tag, nil
}
func CreateOrUpdateTag(tagSyntax string) (*database.Tag, error) {
tagFields := strings.Split(tagSyntax, ":")
var tagName string
var tagType string
if len(tagFields) == 1 {
tagName = tagFields[0]
return CreateOrUpdateTagGeneric(tagName)
} else if len(tagFields) == 2 {
tagType = tagFields[0]
tagName = tagFields[1]
return CreateOrUpdateTagComplex(tagName, tagType)
} else {
return nil, errors.New("Malformed tag syntax")
}
}
func GetTag(tagSyntax string) (*database.Tag, error) {
tagFields := strings.Split(tagSyntax, ":")

View File

@ -18,8 +18,14 @@
"svelte": "^3.0.0"
},
"dependencies": {
"axios": "^0.21.1",
"bulma": "^0.9.2",
"node-sass": "^6.0.0",
"postcss": "^8.2.14",
"query-string": "^7.0.0",
"sirv-cli": "^1.0.0",
"svelte-routing": "^1.6.0"
"svelte-preprocess": "^4.7.3",
"svelte-routing": "^1.6.0",
"svelte-tags-input": "^2.7.1"
}
}

View File

@ -4,6 +4,7 @@ import resolve from '@rollup/plugin-node-resolve';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import css from 'rollup-plugin-css-only';
import sveltePreprocess from 'svelte-preprocess'
const production = !process.env.ROLLUP_WATCH;
@ -34,18 +35,30 @@ export default {
sourcemap: true,
format: 'iife',
name: 'app',
file: '../static/bundle.js'
dir: "../static",
assetFileNames: 'bundle.css',
entryFileNames: 'bundle.js'
},
plugins: [
svelte({
compilerOptions: {
// enable run-time checks when not in production
dev: !production
}
},
preprocess: sveltePreprocess({
sourceMap: !production,
scss: {
includePaths: [
'node_modules',
'src'
]
},
}),
}),
// we'll extract any component CSS out into
// a separate file - better for performance
css({ output: '../static/bundle.css' }),
css({ name: "bundle" }),
// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In

View File

@ -9,12 +9,10 @@
import Login from "./routes/Login.svelte";
import Logout from "./routes/Logout.svelte";
import Tag from "./routes/Tag.svelte";
import Upload from "./routes/Upload.svelte";
export let url = "";
let baseURL = window.BASE_URL;
</script>
<Router {url}>
@ -26,5 +24,25 @@
<Route path="/post/:id" component={Post} />
<Route path="/auth/login" component={Login} />
<Route path="/auth/logout" component={Logout} />
<Route path="/upload" component={Upload} />
</div>
</Router>
<style global lang="scss">
@import "./main.scss";
#tags .svelte-tags-input-tag {
background: $primary;
color: $text-invert;
}
#tags .svelte-tags-input-layout {
@extend .input;
height: inherit;
& .svelte-tags-input {
margin-top: 0 !important;
font-size: 13.3333px;
}
}
</style>

View File

@ -0,0 +1,106 @@
<script>
import { Link } from "svelte-routing";
export let posts = [];
export let page = 1;
export let totalPages = 1;
export let handlePage = (i) => { };
export let url = "/posts";
</script>
<section class="section">
<div class="container">
<nav class="pagination" role="navigation" aria-label="pagination">
{#if page > 1}
<Link
on:click={handlePage(page - 1)}
to="{url}?page={page - 1}"
class="pagination-previous"
aria-label="Previous">Previous</Link
>
{/if}
{#if page < totalPages}
<Link
on:click={handlePage(page + 1)}
to="{url}?page={page + 1}"
class="pagination-next"
aria-label="Next">Next</Link
>
{/if}
<ul class="pagination-list">
{#if page > 3}
<li>
<Link
on:click={handlePage(1)}
to="{url}?page={1}"
class="pagination-link"
aria-label="Goto page 1">1</Link
>
</li>
<li>
<span class="pagination-ellipsis">&hellip;</span>
</li>
{/if}
{#each [...Array(5).keys()].map((x) => x + page - 2) as i}
{#if i >= 1 && i <= totalPages}
{#if i == page}
<li>
<Link
on:click={handlePage(i)}
to="{url}?page={i}"
class="pagination-link is-current"
aria-label="Goto page {i}">{i}</Link
>
</li>
{:else}
<li>
<Link
on:click={handlePage(i)}
to="{url}?page={i}"
class="pagination-link"
aria-label="Goto page {i}">{i}</Link
>
</li>
{/if}
{/if}
{/each}
{#if totalPages - page > 2}
<li>
<span class="pagination-ellipsis">&hellip;</span>
</li>
<li>
<Link
on:click={handlePage(totalPages)}
to="{url}?page={totalPages}"
class="pagination-link"
aria-label="Goto page {totalPages}"
>{totalPages}</Link
>
</li>
{/if}
</ul>
</nav>
<div class="columns is-multiline">
{#each posts as post (post.id)}
<div class="column is-one-quarter card">
<div class="card-image">
<figure class="image">
<Link to="/post/{post.id}">
<img alt={post.id} src={post.image_path} />
</Link>
</figure>
</div>
<div class="card-content">
<div class="content">
{#each post.tags as tag (tag)}
<p>
<Link to="/tag/{tag}">{tag}</Link>
</p>
{/each}
</div>
</div>
</div>
{/each}
</div>
</div>
</section>

View File

@ -1,41 +1,78 @@
import { token } from "./stores.js"
import axios from "axios";
let url = window.BASE_URL;
let current_token;
const unsub_token = token.subscribe(value => {
current_token = token;
token.subscribe(value => {
current_token = value;
})
export async function login({ username, password }) {
const endpoint = url + "/api/auth/login";
const response = await fetch(endpoint, {
const response = await axios({
url: endpoint,
method: "POST",
body: JSON.stringify({
data: JSON.stringify({
username,
password,
}),
})
const data = await response.json();
token.set(data.token);
return data;
token.set(response.data.token);
return response.data;
}
export async function getPosts({ page }) {
const endpoint = url + "/api/post?page=" + page;
const response = await fetch(endpoint);
const data = await response.json();
return data;
const response = await axios.get(endpoint);
return response.data;
}
export async function getPostsTag({ page, tag }) {
const endpoint = url + "/api/post/tag/" + tag + "?page=" + page;
const response = await fetch(endpoint);
const data = await response.json();
return data;
const response = await axios(endpoint);
return response.data;
}
export async function getPost({ id }) {
const endpoint = url + "/api/post/" + id;
const response = await fetch(endpoint);
const data = await response.json();
return data;
const response = await axios(endpoint);
return response.data;
}
export async function uploadBlob({ file, onProgress }) {
var formData = new FormData();
formData.append("file", file);
const endpoint = url + "/api/blob/upload";
const response = await axios({
url: endpoint,
method: "POST",
headers: {
'Authorization': 'Bearer ' + current_token,
'Content-Type': 'multipart/form-data',
},
withCredentials: true,
data: formData,
onUploadProgress: e => {
if (onProgress) {
onProgress(e)
}
}
})
return response.data;
}
export async function postCreate({ blob_id, source_url, tags }) {
const endpoint = url + "/api/post/create";
const response = await axios({
url: endpoint,
method: "POST",
headers: {
'Authorization': 'Bearer ' + current_token,
},
withCredentials: true,
data: {
blob_id, source_url, tags
}
})
return response.data;
}

9
web/app/src/main.scss Normal file
View File

@ -0,0 +1,9 @@
@import "../node_modules/bulma/sass/utilities/_all";
@import "../node_modules/bulma/sass/base/_all";
@import "../node_modules/bulma/sass/elements/_all";
@import "../node_modules/bulma/sass/form/_all";
@import "../node_modules/bulma/sass/components/_all";
@import "../node_modules/bulma/sass/grid/_all";
@import "../node_modules/bulma/sass/helpers/_all";
@import "../node_modules/bulma/sass/layout/_all";

View File

@ -3,6 +3,7 @@
import { getPosts } from "../api.js";
import { Link } from "svelte-routing";
import queryString from "query-string";
import PostPaginator from "../PostPaginator.svelte";
export let location;
@ -39,99 +40,4 @@
</div>
</section>
<section class="section">
<div class="container">
<nav class="pagination" role="navigation" aria-label="pagination">
{#if page > 1}
<Link
on:click={handlePage(page - 1)}
to="/posts?page={page - 1}"
class="pagination-previous"
aria-label="Previous">Previous</Link
>
{/if}
{#if page < totalPages}
<Link
on:click={handlePage(page + 1)}
to="/posts?page={page + 1}"
class="pagination-next"
aria-label="Next">Next</Link
>
{/if}
<ul class="pagination-list">
{#if page > 3}
<li>
<Link
on:click={handlePage(1)}
to="/posts?page={1}"
class="pagination-link"
aria-label="Goto page 1">1</Link
>
</li>
<li>
<span class="pagination-ellipsis">&hellip;</span>
</li>
{/if}
{#each [...Array(5).keys()].map((x) => x + page - 2) as i}
{#if i >= 1 && i <= totalPages}
{#if i == page}
<li>
<Link
on:click={handlePage(i)}
to="/posts?page={i}"
class="pagination-link is-current"
aria-label="Goto page {i}">{i}</Link
>
</li>
{:else}
<li>
<Link
on:click={handlePage(i)}
to="/posts?page={i}"
class="pagination-link"
aria-label="Goto page {i}">{i}</Link
>
</li>
{/if}
{/if}
{/each}
{#if totalPages - page > 2}
<li>
<span class="pagination-ellipsis">&hellip;</span>
</li>
<li>
<Link
on:click={handlePage(totalPages)}
to="/posts?page={totalPages}"
class="pagination-link"
aria-label="Goto page {totalPages}"
>{totalPages}</Link
>
</li>
{/if}
</ul>
</nav>
<div class="columns is-multiline">
{#each posts as post (post.id)}
<div class="column is-one-quarter card">
<div class="card-image">
<figure class="image">
<Link to="/post/{post.id}">
<img alt={post.id} src={post.image_path} />
</Link>
</figure>
</div>
<div class="card-content">
<div class="content">
{#each post.tags as tag (tag)}
<p>
<Link to="/tag/{tag}">{tag}</Link>
</p>
{/each}
</div>
</div>
</div>
{/each}
</div>
</div>
</section>
<PostPaginator url="/posts" posts={posts} page={page} totalPages={totalPages} handlePage={handlePage} />

View File

@ -1,7 +1,7 @@
<script>
import { onMount } from "svelte";
import { getPostsTag } from "../api.js";
import { Link } from "svelte-routing";
import PostPaginator from "../PostPaginator.svelte";
import queryString from "query-string";
export let location;
@ -44,99 +44,4 @@
</div>
</section>
<section class="section">
<div class="container">
<nav class="pagination" role="navigation" aria-label="pagination">
{#if page > 1}
<Link
on:click={handlePage(page - 1)}
to="/tag/{id}?page={page - 1}"
class="pagination-previous"
aria-label="Previous">Previous</Link
>
{/if}
{#if page < totalPages}
<Link
on:click={handlePage(page + 1)}
to="/tag/{id}?page={page + 1}"
class="pagination-next"
aria-label="Next">Next</Link
>
{/if}
<ul class="pagination-list">
{#if page > 3}
<li>
<Link
on:click={handlePage(1)}
to="/tag/{id}?page={1}"
class="pagination-link"
aria-label="Goto page 1">1</Link
>
</li>
<li>
<span class="pagination-ellipsis">&hellip;</span>
</li>
{/if}
{#each [...Array(5).keys()].map((x) => x + page - 2) as i}
{#if i >= 1 && i <= totalPages}
{#if i == page}
<li>
<Link
on:click={handlePage(i)}
to="/tag/{id}?page={i}"
class="pagination-link is-current"
aria-label="Goto page {i}">{i}</Link
>
</li>
{:else}
<li>
<Link
on:click={handlePage(i)}
to="/tag/{id}?page={i}"
class="pagination-link"
aria-label="Goto page {i}">{i}</Link
>
</li>
{/if}
{/if}
{/each}
{#if totalPages - page > 2}
<li>
<span class="pagination-ellipsis">&hellip;</span>
</li>
<li>
<Link
on:click={handlePage(totalPages)}
to="/tag/{id}?page={totalPages}"
class="pagination-link"
aria-label="Goto page {totalPages}"
>{totalPages}</Link
>
</li>
{/if}
</ul>
</nav>
<div class="columns is-multiline">
{#each posts as post (post.id)}
<div class="column is-one-quarter card">
<div class="card-image">
<figure class="image">
<Link to="/post/{post.id}">
<img alt={post.id} src={post.image_path} />
</Link>
</figure>
</div>
<div class="card-content">
<div class="content">
{#each post.tags as tag (tag)}
<p>
<Link to="/tag/{tag}">{tag}</Link>
</p>
{/each}
</div>
</div>
</div>
{/each}
</div>
</div>
</section>
<PostPaginator url="/tag/{id}" posts={posts} page={page} totalPages={totalPages} handlePage={handlePage} />

View File

@ -0,0 +1,99 @@
<script>
import { uploadBlob, postCreate } from "../api.js";
import { navigate } from "svelte-routing";
import Tags from "svelte-tags-input";
let currentProgress = 0;
let fileName = "";
let form = {
blob_id: "",
source_url: "",
tags: [],
};
const onProgress = (e) => {
var percentCompleted = Math.round((e.loaded * 100) / e.total);
currentProgress = percentCompleted;
};
const onFileChange = async (e) => {
fileName = "";
var file = e.target.files[0];
if (file) {
var response = await uploadBlob({ file, onProgress });
form.blob_id = response.id;
fileName = file.name;
}
};
const onTagChange = (value) => {
form.tags = value.detail.tags;
}
const onSubmit = async () => {
const response = await postCreate(form);
navigate(`/post/${response.id}`);
};
</script>
<section class="hero is-primary">
<div class="hero-body">
<p class="title">Upload</p>
</div>
</section>
<section class="section">
<div class="container">
<form on:submit|preventDefault={onSubmit}>
<div class="field">
<label for="file" class="label">Image File</label>
<div class="control">
<div class="file">
<label class="file-label">
<input
id="file"
class="file-input"
type="file"
name="resume"
on:change={onFileChange}
/>
<span class="file-cta">
<span class="file-icon" />
<span class="file-label"> Choose a file… </span>
</span>
</label>
</div>
</div>
{#if currentProgress > 0 && currentProgress < 100}
<p class="help">{currentProgress}%</p>
{/if}
{#if fileName !== ""}
<p class="help">{fileName} uploaded</p>
{/if}
</div>
<div class="field">
<label for="source" class="label">Source URL</label>
<div class="control">
<input
id="source"
class="input"
type="url"
placeholder="Source URL"
bind:value={form.source_url}
/>
</div>
</div>
<div class="field">
<label for="tags" class="label">Tags</label>
<div class="control" id="tags">
<Tags on:tags={onTagChange} />
</div>
</div>
<div class="control">
<button type="submit" class="button is-primary">Submit</button>
</div>
</form>
</div>
</section>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -5,8 +5,8 @@
<title>{{ .title }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="UTF-8" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.2/css/bulma.min.css">
<script defer src="/static/bundle.js"></script>
<link rel="stylesheet" href="/static/bundle.css">
<script>
window.BASE_URL = {{ .base_url }}
</script>