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) { func frontendHome(c *gin.Context) {
baseURL := config.CurrentConfig.BaseURL baseURL := config.CurrentConfig.BaseURL
c.HTML(http.StatusOK, "index.html", gin.H{ c.HTML(http.StatusOK, "index.html", gin.H{
"title": "Home", "title": "Shioriko",
"base_url": baseURL, "base_url": baseURL,
}) })
} }

View File

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

View File

@ -18,8 +18,14 @@
"svelte": "^3.0.0" "svelte": "^3.0.0"
}, },
"dependencies": { "dependencies": {
"axios": "^0.21.1",
"bulma": "^0.9.2",
"node-sass": "^6.0.0",
"postcss": "^8.2.14",
"query-string": "^7.0.0", "query-string": "^7.0.0",
"sirv-cli": "^1.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 livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser'; import { terser } from 'rollup-plugin-terser';
import css from 'rollup-plugin-css-only'; import css from 'rollup-plugin-css-only';
import sveltePreprocess from 'svelte-preprocess'
const production = !process.env.ROLLUP_WATCH; const production = !process.env.ROLLUP_WATCH;
@ -34,18 +35,30 @@ export default {
sourcemap: true, sourcemap: true,
format: 'iife', format: 'iife',
name: 'app', name: 'app',
file: '../static/bundle.js' dir: "../static",
assetFileNames: 'bundle.css',
entryFileNames: 'bundle.js'
}, },
plugins: [ plugins: [
svelte({ svelte({
compilerOptions: { compilerOptions: {
// enable run-time checks when not in production // enable run-time checks when not in production
dev: !production dev: !production
} },
preprocess: sveltePreprocess({
sourceMap: !production,
scss: {
includePaths: [
'node_modules',
'src'
]
},
}),
}), }),
// we'll extract any component CSS out into // we'll extract any component CSS out into
// a separate file - better for performance // a separate file - better for performance
css({ output: '../static/bundle.css' }), css({ name: "bundle" }),
// If you have external dependencies installed from // If you have external dependencies installed from
// npm, you'll most likely need these plugins. In // npm, you'll most likely need these plugins. In

View File

@ -9,12 +9,10 @@
import Login from "./routes/Login.svelte"; import Login from "./routes/Login.svelte";
import Logout from "./routes/Logout.svelte"; import Logout from "./routes/Logout.svelte";
import Tag from "./routes/Tag.svelte"; import Tag from "./routes/Tag.svelte";
import Upload from "./routes/Upload.svelte";
export let url = ""; export let url = "";
let baseURL = window.BASE_URL; let baseURL = window.BASE_URL;
</script> </script>
<Router {url}> <Router {url}>
@ -26,5 +24,25 @@
<Route path="/post/:id" component={Post} /> <Route path="/post/:id" component={Post} />
<Route path="/auth/login" component={Login} /> <Route path="/auth/login" component={Login} />
<Route path="/auth/logout" component={Logout} /> <Route path="/auth/logout" component={Logout} />
<Route path="/upload" component={Upload} />
</div> </div>
</Router> </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 { token } from "./stores.js"
import axios from "axios";
let url = window.BASE_URL; let url = window.BASE_URL;
let current_token; let current_token;
const unsub_token = token.subscribe(value => { token.subscribe(value => {
current_token = token; current_token = value;
}) })
export async function login({ username, password }) { export async function login({ username, password }) {
const endpoint = url + "/api/auth/login"; const endpoint = url + "/api/auth/login";
const response = await fetch(endpoint, { const response = await axios({
url: endpoint,
method: "POST", method: "POST",
body: JSON.stringify({ data: JSON.stringify({
username, username,
password, password,
}), }),
}) })
const data = await response.json(); token.set(response.data.token);
token.set(data.token); return response.data;
return data;
} }
export async function getPosts({ page }) { export async function getPosts({ page }) {
const endpoint = url + "/api/post?page=" + page; const endpoint = url + "/api/post?page=" + page;
const response = await fetch(endpoint); const response = await axios.get(endpoint);
const data = await response.json(); return response.data;
return data;
} }
export async function getPostsTag({ page, tag }) { export async function getPostsTag({ page, tag }) {
const endpoint = url + "/api/post/tag/" + tag + "?page=" + page; const endpoint = url + "/api/post/tag/" + tag + "?page=" + page;
const response = await fetch(endpoint); const response = await axios(endpoint);
const data = await response.json(); return response.data;
return data;
} }
export async function getPost({ id }) { export async function getPost({ id }) {
const endpoint = url + "/api/post/" + id; const endpoint = url + "/api/post/" + id;
const response = await fetch(endpoint); const response = await axios(endpoint);
const data = await response.json(); return response.data;
return 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 { getPosts } from "../api.js";
import { Link } from "svelte-routing"; import { Link } from "svelte-routing";
import queryString from "query-string"; import queryString from "query-string";
import PostPaginator from "../PostPaginator.svelte";
export let location; export let location;
@ -39,99 +40,4 @@
</div> </div>
</section> </section>
<section class="section"> <PostPaginator url="/posts" posts={posts} page={page} totalPages={totalPages} handlePage={handlePage} />
<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>

View File

@ -1,7 +1,7 @@
<script> <script>
import { onMount } from "svelte"; import { onMount } from "svelte";
import { getPostsTag } from "../api.js"; import { getPostsTag } from "../api.js";
import { Link } from "svelte-routing"; import PostPaginator from "../PostPaginator.svelte";
import queryString from "query-string"; import queryString from "query-string";
export let location; export let location;
@ -44,99 +44,4 @@
</div> </div>
</section> </section>
<section class="section"> <PostPaginator url="/tag/{id}" posts={posts} page={page} totalPages={totalPages} handlePage={handlePage} />
<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>

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> <title>{{ .title }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="UTF-8" /> <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> <script defer src="/static/bundle.js"></script>
<link rel="stylesheet" href="/static/bundle.css">
<script> <script>
window.BASE_URL = {{ .base_url }} window.BASE_URL = {{ .base_url }}
</script> </script>