Add web frontend
This commit is contained in:
parent
fe8c807b97
commit
081c774e75
21 changed files with 1186 additions and 1 deletions
10
.gitignore
vendored
10
.gitignore
vendored
|
|
@ -1 +1,9 @@
|
||||||
.env
|
.DS_STORE
|
||||||
|
|
||||||
|
.env
|
||||||
|
**/*_templ.go
|
||||||
|
**/*_templ.txt
|
||||||
|
|
||||||
|
**/configmap.yaml
|
||||||
|
|
||||||
|
tailwindcss
|
||||||
BIN
bin/web
Executable file
BIN
bin/web
Executable file
Binary file not shown.
33
build/docker/web/Dockerfile
Normal file
33
build/docker/web/Dockerfile
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
FROM golang:1.23-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apk --no-cache add curl nodejs npm
|
||||||
|
|
||||||
|
RUN npm install -D tailwindcss
|
||||||
|
|
||||||
|
RUN go install github.com/a-h/templ/cmd/templ@latest
|
||||||
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN templ generate
|
||||||
|
|
||||||
|
|
||||||
|
RUN npx tailwindcss -i ./internal/web/assets/css/input.css -o ./internal/web/assets/css/output.css
|
||||||
|
|
||||||
|
RUN go build -o web ./cmd/web
|
||||||
|
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=builder /app/web /app/web
|
||||||
|
|
||||||
|
ENV GIN_MODE=release
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/web"]
|
||||||
|
|
@ -9,3 +9,4 @@ data:
|
||||||
DB_HOST: "psql-service"
|
DB_HOST: "psql-service"
|
||||||
DB_PORT: "5432"
|
DB_PORT: "5432"
|
||||||
KEYGEN_HOST: "keygen-service"
|
KEYGEN_HOST: "keygen-service"
|
||||||
|
API_URL: "http://api-server-service"
|
||||||
|
|
|
||||||
38
build/kubernetes/web-deployment.yaml
Normal file
38
build/kubernetes/web-deployment.yaml
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: fastbin-web
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: fastbin-web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: fastbin-web
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: api-server
|
||||||
|
image: registry.lab.divyam.dev/fastbin-web:latest
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
env:
|
||||||
|
- name: API_URL
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: fastbin-config
|
||||||
|
key: API_URL
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: fastbin-web-service
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
selector:
|
||||||
|
app: fastbin-web
|
||||||
|
type: LoadBalancer
|
||||||
22
cmd/web/main.go
Normal file
22
cmd/web/main.go
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fastbin/internal/pkg/env"
|
||||||
|
webserver "fastbin/internal/web"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
port, err := strconv.Atoi(env.GetEnv("WEB_INTERNAL_PORT", "8080"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error listening port: %v, err: %v", port, err)
|
||||||
|
}
|
||||||
|
server := webserver.NewServer(port)
|
||||||
|
err = server.ListenAndServe()
|
||||||
|
if err != nil && err != http.ErrServerClosed {
|
||||||
|
panic(fmt.Sprintf("http server error: %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
1
go.mod
1
go.mod
|
|
@ -3,6 +3,7 @@ module fastbin
|
||||||
go 1.23.2
|
go 1.23.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/a-h/templ v0.2.793
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
google.golang.org/grpc v1.67.1
|
google.golang.org/grpc v1.67.1
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -1,3 +1,5 @@
|
||||||
|
github.com/a-h/templ v0.2.793 h1:Io+/ocnfGWYO4VHdR0zBbf39PQlnzVCVVD+wEEs6/qY=
|
||||||
|
github.com/a-h/templ v0.2.793/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w=
|
||||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||||
|
|
|
||||||
10
internal/web/assets/css/input.css
Normal file
10
internal/web/assets/css/input.css
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--primary-color: 29 29 29;
|
||||||
|
--border-color: 69 69 69;
|
||||||
|
}
|
||||||
|
}
|
||||||
740
internal/web/assets/css/output.css
Normal file
740
internal/web/assets/css/output.css
Normal file
|
|
@ -0,0 +1,740 @@
|
||||||
|
*, ::before, ::after {
|
||||||
|
--tw-border-spacing-x: 0;
|
||||||
|
--tw-border-spacing-y: 0;
|
||||||
|
--tw-translate-x: 0;
|
||||||
|
--tw-translate-y: 0;
|
||||||
|
--tw-rotate: 0;
|
||||||
|
--tw-skew-x: 0;
|
||||||
|
--tw-skew-y: 0;
|
||||||
|
--tw-scale-x: 1;
|
||||||
|
--tw-scale-y: 1;
|
||||||
|
--tw-pan-x: ;
|
||||||
|
--tw-pan-y: ;
|
||||||
|
--tw-pinch-zoom: ;
|
||||||
|
--tw-scroll-snap-strictness: proximity;
|
||||||
|
--tw-gradient-from-position: ;
|
||||||
|
--tw-gradient-via-position: ;
|
||||||
|
--tw-gradient-to-position: ;
|
||||||
|
--tw-ordinal: ;
|
||||||
|
--tw-slashed-zero: ;
|
||||||
|
--tw-numeric-figure: ;
|
||||||
|
--tw-numeric-spacing: ;
|
||||||
|
--tw-numeric-fraction: ;
|
||||||
|
--tw-ring-inset: ;
|
||||||
|
--tw-ring-offset-width: 0px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||||
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
|
--tw-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow-colored: 0 0 #0000;
|
||||||
|
--tw-blur: ;
|
||||||
|
--tw-brightness: ;
|
||||||
|
--tw-contrast: ;
|
||||||
|
--tw-grayscale: ;
|
||||||
|
--tw-hue-rotate: ;
|
||||||
|
--tw-invert: ;
|
||||||
|
--tw-saturate: ;
|
||||||
|
--tw-sepia: ;
|
||||||
|
--tw-drop-shadow: ;
|
||||||
|
--tw-backdrop-blur: ;
|
||||||
|
--tw-backdrop-brightness: ;
|
||||||
|
--tw-backdrop-contrast: ;
|
||||||
|
--tw-backdrop-grayscale: ;
|
||||||
|
--tw-backdrop-hue-rotate: ;
|
||||||
|
--tw-backdrop-invert: ;
|
||||||
|
--tw-backdrop-opacity: ;
|
||||||
|
--tw-backdrop-saturate: ;
|
||||||
|
--tw-backdrop-sepia: ;
|
||||||
|
--tw-contain-size: ;
|
||||||
|
--tw-contain-layout: ;
|
||||||
|
--tw-contain-paint: ;
|
||||||
|
--tw-contain-style: ;
|
||||||
|
}
|
||||||
|
|
||||||
|
::backdrop {
|
||||||
|
--tw-border-spacing-x: 0;
|
||||||
|
--tw-border-spacing-y: 0;
|
||||||
|
--tw-translate-x: 0;
|
||||||
|
--tw-translate-y: 0;
|
||||||
|
--tw-rotate: 0;
|
||||||
|
--tw-skew-x: 0;
|
||||||
|
--tw-skew-y: 0;
|
||||||
|
--tw-scale-x: 1;
|
||||||
|
--tw-scale-y: 1;
|
||||||
|
--tw-pan-x: ;
|
||||||
|
--tw-pan-y: ;
|
||||||
|
--tw-pinch-zoom: ;
|
||||||
|
--tw-scroll-snap-strictness: proximity;
|
||||||
|
--tw-gradient-from-position: ;
|
||||||
|
--tw-gradient-via-position: ;
|
||||||
|
--tw-gradient-to-position: ;
|
||||||
|
--tw-ordinal: ;
|
||||||
|
--tw-slashed-zero: ;
|
||||||
|
--tw-numeric-figure: ;
|
||||||
|
--tw-numeric-spacing: ;
|
||||||
|
--tw-numeric-fraction: ;
|
||||||
|
--tw-ring-inset: ;
|
||||||
|
--tw-ring-offset-width: 0px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||||
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
|
--tw-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow-colored: 0 0 #0000;
|
||||||
|
--tw-blur: ;
|
||||||
|
--tw-brightness: ;
|
||||||
|
--tw-contrast: ;
|
||||||
|
--tw-grayscale: ;
|
||||||
|
--tw-hue-rotate: ;
|
||||||
|
--tw-invert: ;
|
||||||
|
--tw-saturate: ;
|
||||||
|
--tw-sepia: ;
|
||||||
|
--tw-drop-shadow: ;
|
||||||
|
--tw-backdrop-blur: ;
|
||||||
|
--tw-backdrop-brightness: ;
|
||||||
|
--tw-backdrop-contrast: ;
|
||||||
|
--tw-backdrop-grayscale: ;
|
||||||
|
--tw-backdrop-hue-rotate: ;
|
||||||
|
--tw-backdrop-invert: ;
|
||||||
|
--tw-backdrop-opacity: ;
|
||||||
|
--tw-backdrop-saturate: ;
|
||||||
|
--tw-backdrop-sepia: ;
|
||||||
|
--tw-contain-size: ;
|
||||||
|
--tw-contain-layout: ;
|
||||||
|
--tw-contain-paint: ;
|
||||||
|
--tw-contain-style: ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
||||||
|
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
||||||
|
*/
|
||||||
|
|
||||||
|
*,
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* 1 */
|
||||||
|
border-width: 0;
|
||||||
|
/* 2 */
|
||||||
|
border-style: solid;
|
||||||
|
/* 2 */
|
||||||
|
border-color: #e5e7eb;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
--tw-content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Use a consistent sensible line-height in all browsers.
|
||||||
|
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||||
|
3. Use a more readable tab size.
|
||||||
|
4. Use the user's configured `sans` font-family by default.
|
||||||
|
5. Use the user's configured `sans` font-feature-settings by default.
|
||||||
|
6. Use the user's configured `sans` font-variation-settings by default.
|
||||||
|
7. Disable tap highlights on iOS
|
||||||
|
*/
|
||||||
|
|
||||||
|
html,
|
||||||
|
:host {
|
||||||
|
line-height: 1.5;
|
||||||
|
/* 1 */
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
/* 2 */
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
/* 3 */
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
/* 3 */
|
||||||
|
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
/* 4 */
|
||||||
|
font-feature-settings: normal;
|
||||||
|
/* 5 */
|
||||||
|
font-variation-settings: normal;
|
||||||
|
/* 6 */
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
/* 7 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Remove the margin in all browsers.
|
||||||
|
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
/* 1 */
|
||||||
|
line-height: inherit;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Add the correct height in Firefox.
|
||||||
|
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
||||||
|
3. Ensure horizontal rules are visible by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
hr {
|
||||||
|
height: 0;
|
||||||
|
/* 1 */
|
||||||
|
color: inherit;
|
||||||
|
/* 2 */
|
||||||
|
border-top-width: 1px;
|
||||||
|
/* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct text decoration in Chrome, Edge, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
abbr:where([title]) {
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the default font size and weight for headings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reset links to optimize for opt-in styling instead of opt-out.
|
||||||
|
*/
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct font weight in Edge and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Use the user's configured `mono` font-family by default.
|
||||||
|
2. Use the user's configured `mono` font-feature-settings by default.
|
||||||
|
3. Use the user's configured `mono` font-variation-settings by default.
|
||||||
|
4. Correct the odd `em` font sizing in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp,
|
||||||
|
pre {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
/* 1 */
|
||||||
|
font-feature-settings: normal;
|
||||||
|
/* 2 */
|
||||||
|
font-variation-settings: normal;
|
||||||
|
/* 3 */
|
||||||
|
font-size: 1em;
|
||||||
|
/* 4 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct font size in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
||||||
|
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
||||||
|
3. Remove gaps between table borders by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
table {
|
||||||
|
text-indent: 0;
|
||||||
|
/* 1 */
|
||||||
|
border-color: inherit;
|
||||||
|
/* 2 */
|
||||||
|
border-collapse: collapse;
|
||||||
|
/* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Change the font styles in all browsers.
|
||||||
|
2. Remove the margin in Firefox and Safari.
|
||||||
|
3. Remove default padding in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
/* 1 */
|
||||||
|
font-feature-settings: inherit;
|
||||||
|
/* 1 */
|
||||||
|
font-variation-settings: inherit;
|
||||||
|
/* 1 */
|
||||||
|
font-size: 100%;
|
||||||
|
/* 1 */
|
||||||
|
font-weight: inherit;
|
||||||
|
/* 1 */
|
||||||
|
line-height: inherit;
|
||||||
|
/* 1 */
|
||||||
|
letter-spacing: inherit;
|
||||||
|
/* 1 */
|
||||||
|
color: inherit;
|
||||||
|
/* 1 */
|
||||||
|
margin: 0;
|
||||||
|
/* 2 */
|
||||||
|
padding: 0;
|
||||||
|
/* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the inheritance of text transform in Edge and Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
2. Remove default button styles.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input:where([type='button']),
|
||||||
|
input:where([type='reset']),
|
||||||
|
input:where([type='submit']) {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
/* 1 */
|
||||||
|
background-color: transparent;
|
||||||
|
/* 2 */
|
||||||
|
background-image: none;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Use the modern Firefox focus style for all focusable elements.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:-moz-focusring {
|
||||||
|
outline: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
||||||
|
*/
|
||||||
|
|
||||||
|
:-moz-ui-invalid {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct vertical alignment in Chrome and Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Correct the cursor style of increment and decrement buttons in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-inner-spin-button,
|
||||||
|
::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Correct the odd appearance in Chrome and Safari.
|
||||||
|
2. Correct the outline style in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type='search'] {
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
/* 1 */
|
||||||
|
outline-offset: -2px;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the inner padding in Chrome and Safari on macOS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
2. Change font properties to `inherit` in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
/* 1 */
|
||||||
|
font: inherit;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct display in Chrome and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Removes the default spacing and border for appropriate elements.
|
||||||
|
*/
|
||||||
|
|
||||||
|
blockquote,
|
||||||
|
dl,
|
||||||
|
dd,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
hr,
|
||||||
|
figure,
|
||||||
|
p,
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
menu {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reset default styling for dialogs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
dialog {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prevent resizing textareas horizontally by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
||||||
|
2. Set the default placeholder color to the user's configured gray 400 color.
|
||||||
|
*/
|
||||||
|
|
||||||
|
input::-moz-placeholder, textarea::-moz-placeholder {
|
||||||
|
opacity: 1;
|
||||||
|
/* 1 */
|
||||||
|
color: #9ca3af;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
input::placeholder,
|
||||||
|
textarea::placeholder {
|
||||||
|
opacity: 1;
|
||||||
|
/* 1 */
|
||||||
|
color: #9ca3af;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the default cursor for buttons.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
[role="button"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Make sure disabled buttons don't get the pointer cursor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||||
|
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
||||||
|
This can trigger a poorly considered lint error in some tools but is included by design.
|
||||||
|
*/
|
||||||
|
|
||||||
|
img,
|
||||||
|
svg,
|
||||||
|
video,
|
||||||
|
canvas,
|
||||||
|
audio,
|
||||||
|
iframe,
|
||||||
|
embed,
|
||||||
|
object {
|
||||||
|
display: block;
|
||||||
|
/* 1 */
|
||||||
|
vertical-align: middle;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||||
|
*/
|
||||||
|
|
||||||
|
img,
|
||||||
|
video {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make elements with the HTML hidden attribute stay hidden by default */
|
||||||
|
|
||||||
|
[hidden]:where(:not([hidden="until-found"])) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--primary-color: 29 29 29;
|
||||||
|
--border-color: 69 69 69;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pointer-events-none {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-block {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-\[70px\] {
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-dvh {
|
||||||
|
height: 100dvh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-full {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-\[70px\] {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-dvw {
|
||||||
|
width: 100dvw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-1 {
|
||||||
|
flex: 1 1 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-none {
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-none {
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize {
|
||||||
|
resize: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-col {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-start {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-stretch {
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.self-stretch {
|
||||||
|
align-self: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-b-2 {
|
||||||
|
border-bottom-width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-none {
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-b-border {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-bottom-color: rgb(var(--border-color) / var(--tw-border-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-primary {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(var(--primary-color) / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-transparent {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.px-2\.5 {
|
||||||
|
padding-left: 0.625rem;
|
||||||
|
padding-right: 0.625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.px-\[20px\] {
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-\[10px\] {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-middle {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-4xl {
|
||||||
|
font-size: 2.25rem;
|
||||||
|
line-height: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-8xl {
|
||||||
|
font-size: 6rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-xl {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.italic {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-white {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.outline-none {
|
||||||
|
outline: 2px solid transparent;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter {
|
||||||
|
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.transition {
|
||||||
|
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
|
||||||
|
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
|
||||||
|
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition-duration: 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover\:bg-border\/50:hover {
|
||||||
|
background-color: rgb(var(--border-color) / 0.5);
|
||||||
|
}
|
||||||
1
internal/web/assets/js/htmx.min.js
vendored
Normal file
1
internal/web/assets/js/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
15
internal/web/efs.go
Normal file
15
internal/web/efs.go
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"io/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed assets
|
||||||
|
var f embed.FS
|
||||||
|
|
||||||
|
var Files fs.FS
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Files, _ = fs.Sub(f, "assets")
|
||||||
|
}
|
||||||
61
internal/web/renderer.go
Normal file
61
internal/web/renderer.go
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
// Credits: https://github.com/a-h/templ/blob/main/examples/integration-gin/gintemplrenderer/renderer.go
|
||||||
|
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/render"
|
||||||
|
|
||||||
|
"github.com/a-h/templ"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Default = &HTMLTemplRenderer{}
|
||||||
|
|
||||||
|
type HTMLTemplRenderer struct {
|
||||||
|
FallbackHtmlRenderer render.HTMLRender
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HTMLTemplRenderer) Instance(s string, d any) render.Render {
|
||||||
|
templData, ok := d.(templ.Component)
|
||||||
|
if !ok {
|
||||||
|
if r.FallbackHtmlRenderer != nil {
|
||||||
|
return r.FallbackHtmlRenderer.Instance(s, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Renderer{
|
||||||
|
Ctx: context.Background(),
|
||||||
|
Status: -1,
|
||||||
|
Component: templData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGinTemplRenderer(ctx context.Context, status int, component templ.Component) *Renderer {
|
||||||
|
return &Renderer{
|
||||||
|
Ctx: ctx,
|
||||||
|
Status: status,
|
||||||
|
Component: component,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Renderer struct {
|
||||||
|
Ctx context.Context
|
||||||
|
Status int
|
||||||
|
Component templ.Component
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Renderer) Render(w http.ResponseWriter) error {
|
||||||
|
t.WriteContentType(w)
|
||||||
|
if t.Status != -1 {
|
||||||
|
w.WriteHeader(t.Status)
|
||||||
|
}
|
||||||
|
if t.Component != nil {
|
||||||
|
return t.Component.Render(t.Ctx, w)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Renderer) WriteContentType(w http.ResponseWriter) {
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
}
|
||||||
110
internal/web/server.go
Normal file
110
internal/web/server.go
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"fastbin/internal/pkg/env"
|
||||||
|
"fastbin/internal/web/views"
|
||||||
|
)
|
||||||
|
|
||||||
|
type api_write_response struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type api_read_response struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(port int) *http.Server {
|
||||||
|
API_URL := env.GetEnv("API_URL", "localhost:8080")
|
||||||
|
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
engineHTMLRenderer := r.HTMLRender
|
||||||
|
r.HTMLRender = &HTMLTemplRenderer{FallbackHtmlRenderer: engineHTMLRenderer}
|
||||||
|
|
||||||
|
r.StaticFS("/assets", http.FS(Files))
|
||||||
|
|
||||||
|
r.GET("/", func(ctx *gin.Context) {
|
||||||
|
ctx.HTML(http.StatusOK, "", views.Write())
|
||||||
|
})
|
||||||
|
|
||||||
|
r.POST("/", func(ctx *gin.Context) {
|
||||||
|
|
||||||
|
text := ctx.Request.FormValue("text")
|
||||||
|
postBody, _ := json.Marshal(map[string]string{
|
||||||
|
"text": text,
|
||||||
|
})
|
||||||
|
|
||||||
|
url := API_URL + "/write"
|
||||||
|
response, err := http.Post(url, "application/json", bytes.NewBuffer(postBody))
|
||||||
|
if err != nil {
|
||||||
|
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
|
||||||
|
}
|
||||||
|
|
||||||
|
var api_res api_write_response
|
||||||
|
decoder := json.NewDecoder(response.Body)
|
||||||
|
|
||||||
|
err = decoder.Decode(&api_res)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.StatusCode == http.StatusOK {
|
||||||
|
ctx.Writer.Header().Add("Hx-Redirect", api_res.Key)
|
||||||
|
} else {
|
||||||
|
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
r.GET("/404", func(ctx *gin.Context) {
|
||||||
|
ctx.HTML(http.StatusOK, "", views.NotFound())
|
||||||
|
})
|
||||||
|
r.GET("/500", func(ctx *gin.Context) {
|
||||||
|
ctx.HTML(http.StatusOK, "", views.ServerError())
|
||||||
|
})
|
||||||
|
|
||||||
|
r.GET("/:key", func(ctx *gin.Context) {
|
||||||
|
url := API_URL + "/read/" + ctx.Param("key")
|
||||||
|
|
||||||
|
response, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
|
||||||
|
}
|
||||||
|
|
||||||
|
var api_res api_read_response
|
||||||
|
decoder := json.NewDecoder(response.Body)
|
||||||
|
|
||||||
|
err = decoder.Decode(&api_res)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.StatusCode == http.StatusOK {
|
||||||
|
ctx.HTML(http.StatusOK, "", views.Read(api_res.Text))
|
||||||
|
} else if response.StatusCode == http.StatusNotFound {
|
||||||
|
ctx.Redirect(http.StatusTemporaryRedirect, "/404")
|
||||||
|
} else {
|
||||||
|
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: fmt.Sprintf(":%d", port),
|
||||||
|
Handler: r,
|
||||||
|
IdleTimeout: time.Minute,
|
||||||
|
ReadTimeout: 10 * time.Second,
|
||||||
|
WriteTimeout: 30 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
return server
|
||||||
|
}
|
||||||
17
internal/web/views/404.templ
Normal file
17
internal/web/views/404.templ
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package views
|
||||||
|
|
||||||
|
templ NotFound() {
|
||||||
|
@Base("fastbin") {
|
||||||
|
@Header() {
|
||||||
|
<a href="/" class="inline-block text-center align-middle">
|
||||||
|
@Button() {
|
||||||
|
<span class="material-symbols-outlined">note_add</span>
|
||||||
|
}
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
<div class="flex flex-1 w-full text-white text-xl">
|
||||||
|
<div class="h-full px-2.5 select-none">></div>
|
||||||
|
<div class="flex w-full h-full bg-transparent resize-none text-white justify-center items-center text-8xl"><div>404: Page Not Found. </div></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
17
internal/web/views/500.templ
Normal file
17
internal/web/views/500.templ
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package views
|
||||||
|
|
||||||
|
templ ServerError() {
|
||||||
|
@Base("fastbin") {
|
||||||
|
@Header() {
|
||||||
|
<a href="/" class="inline-block text-center align-middle">
|
||||||
|
@Button() {
|
||||||
|
<span class="material-symbols-outlined">note_add</span>
|
||||||
|
}
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
<div class="flex flex-1 w-full text-white text-xl">
|
||||||
|
<div class="h-full px-2.5 select-none">></div>
|
||||||
|
<div class="flex w-full h-full bg-transparent resize-none text-white justify-center items-center text-8xl"><div>500: Internal Server Error</div></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
34
internal/web/views/base.templ
Normal file
34
internal/web/views/base.templ
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
package views
|
||||||
|
|
||||||
|
templ Base(title string) {
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined" />
|
||||||
|
<script src="assets/js/htmx.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="assets/css/output.css"/>
|
||||||
|
<title>{ title }</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="w-dvw h-dvh bg-primary flex flex-col items-start justify-center">
|
||||||
|
{ children... }
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ Header() {
|
||||||
|
<div class="w-full flex justify-between items-center text-white border-b-2 border-b-border">
|
||||||
|
<div class="px-[20px] py-[10px] text-4xl italic select-none pointer-events-none">fastbin</div>
|
||||||
|
<div class="flex self-stretch items-stretch">
|
||||||
|
{ children... }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ Button() {
|
||||||
|
<div class="w-[70px] h-[70px] flex justify-center align-middle items-center text-center cursor-pointer select-none hover:bg-border/50">
|
||||||
|
{ children... }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
17
internal/web/views/read.templ
Normal file
17
internal/web/views/read.templ
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package views
|
||||||
|
|
||||||
|
templ Read(text string) {
|
||||||
|
@Base("fastbin") {
|
||||||
|
@Header() {
|
||||||
|
<a href="/" class="inline-block text-center align-middle">
|
||||||
|
@Button() {
|
||||||
|
<span class="material-symbols-outlined">note_add</span>
|
||||||
|
}
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
<div class="flex flex-1 w-full text-white text-xl">
|
||||||
|
<div class="h-full px-2.5 select-none">></div>
|
||||||
|
<textarea required readonly name="text" id="input" autofocus wrap="off" spellcheck="false" class="flex-1 w-full h-full bg-transparent resize-none outline-none border-none text-white text-xl">{ text } </textarea>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
41
internal/web/views/write.templ
Normal file
41
internal/web/views/write.templ
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
package views
|
||||||
|
|
||||||
|
var onceHandle = templ.NewOnceHandle()
|
||||||
|
|
||||||
|
templ Write() {
|
||||||
|
@Base("fastbin") {
|
||||||
|
<form id="input_form" hx-post="/" class="w-dvw h-dvh bg-primary flex flex-col items-start justify-center">
|
||||||
|
@Header() {
|
||||||
|
<a href="/" class="inline-block text-center align-middle">
|
||||||
|
@Button() {
|
||||||
|
<span class="material-symbols-outlined">note_add</span>
|
||||||
|
}
|
||||||
|
</a>
|
||||||
|
<a>
|
||||||
|
<button type="submit">
|
||||||
|
@Button() {
|
||||||
|
<span class="material-symbols-outlined">save</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
<script type="text/javascript">
|
||||||
|
function keyDownHandler(e) {
|
||||||
|
if (e.key === "Tab") {
|
||||||
|
e.preventDefault()
|
||||||
|
e.currentTarget.setRangeText(
|
||||||
|
'\t',
|
||||||
|
e.currentTarget.selectionStart,
|
||||||
|
e.currentTarget.selectionStart,
|
||||||
|
'end'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<div class="flex flex-1 w-full text-white text-xl">
|
||||||
|
<div class="h-full px-2.5 select-none">></div>
|
||||||
|
<textarea required name="text" id="input" onkeydown="keyDownHandler(event)" autofocus wrap="off" spellcheck="false" class="flex-1 w-full h-full bg-transparent resize-none outline-none border-none text-white text-xl"></textarea>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
# API_INTERNAL_PORT=8080 # When using without docker
|
# API_INTERNAL_PORT=8080 # When using without docker
|
||||||
# KEYGEN_INTERNAL_PORT=8081 # When using without docker
|
# KEYGEN_INTERNAL_PORT=8081 # When using without docker
|
||||||
|
|
||||||
|
API_URL=http://api-server
|
||||||
|
|
||||||
KEYGEN_PORT=8080
|
KEYGEN_PORT=8080
|
||||||
KEYGEN_HOST=localhost
|
KEYGEN_HOST=localhost
|
||||||
|
|
||||||
|
|
|
||||||
15
tailwind.config.js
Normal file
15
tailwind.config.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
"./internal/**/*.{go,js,templ,html}"
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
"primary": "rgb(var(--primary-color) / <alpha-value>)",
|
||||||
|
"border": "rgb(var(--border-color) / <alpha-value>)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue