Compare commits
86 Commits
eb920c80fc
...
e932269f04
Author | SHA1 | Date |
---|---|---|
DutchEllie | e932269f04 | |
DutchEllie | eb21ce6d8e | |
DutchEllie | 89f5fd178f | |
DutchEllie | 7e171abfb8 | |
DutchEllie | 95e5a173f1 | |
DutchEllie | d1190c4e88 | |
DutchEllie | d66bb5afeb | |
DutchEllie | dbfdaf38bb | |
DutchEllie | 85d13a2905 | |
DutchEllie | f5fa2af536 | |
DutchEllie | 6a2ec9f099 | |
DutchEllie | 31abd9e138 | |
DutchEllie | ff7044ab9b | |
DutchEllie | acba8179fb | |
DutchEllie | 67358d1616 | |
DutchEllie | 55e3e13459 | |
DutchEllie | 1f59549cc4 | |
DutchEllie | e414c98866 | |
DutchEllie | 029b426757 | |
DutchEllie | 629b642d86 | |
DutchEllie | 8aaecbab69 | |
DutchEllie | 821f2c916e | |
DutchEllie | b765a88f27 | |
DutchEllie | d489dd744c | |
DutchEllie | b06ec9b955 | |
DutchEllie | 42eba16ef2 | |
DutchEllie | caf15aadcb | |
DutchEllie | a88651e4b7 | |
DutchEllie | 450bf3631c | |
DutchEllie | 98a4b28e8b | |
DutchEllie | ba1fae4547 | |
Quenten | 734d9e4e27 | |
Quenten | e2f4db7b97 | |
DutchEllie | be54e41135 | |
DutchEllie | 77ccef7c68 | |
DutchEllie | 65f5adc604 | |
DutchEllie | 14969944a8 | |
DutchEllie | 7ce4efb20e | |
DutchEllie | 035f3e21d0 | |
DutchEllie | 24f12a1e37 | |
DutchEllie | d0e7f90e9f | |
DutchEllie | 4cd86be5a0 | |
DutchEllie | 8d0b6a37c1 | |
DutchEllie | 85685b1da3 | |
DutchEllie | 111c6fd5de | |
DutchEllie | 2332cda5e4 | |
DutchEllie | 182fb13590 | |
DutchEllie | 4968a2a8ca | |
DutchEllie | 7e4a5a5302 | |
DutchEllie | b8e8cc3a60 | |
DutchEllie | e001227cd4 | |
DutchEllie | a92e64b7f2 | |
DutchEllie | 7389daba09 | |
DutchEllie | cd8d116fef | |
DutchEllie | 70c9f027b8 | |
DutchEllie | 75dab086ff | |
DutchEllie | 4eb00e38bc | |
DutchEllie | 5e688e3870 | |
DutchEllie | 08514b50c6 | |
DutchEllie | 7717210578 | |
DutchEllie | bb163abb0a | |
DutchEllie | d7c8ff80e4 | |
DutchEllie | acbdd113d4 | |
DutchEllie | 6b4c6bb447 | |
DutchEllie | 30d57404f9 | |
DutchEllie | 6482fcd6f1 | |
DutchEllie | 858abd350e | |
DutchEllie | 44da256e10 | |
DutchEllie | 715aac28b4 | |
DutchEllie | 5c4b46b65e | |
DutchEllie | a32ced531f | |
DutchEllie | bc38196fd3 | |
DutchEllie | 330385a357 | |
DutchEllie | cd07bef29e | |
DutchEllie | 73442b212c | |
DutchEllie | 0ca691e3ae | |
DutchEllie | 03a09fc452 | |
DutchEllie | 2e381108d1 | |
DutchEllie | 0b7b478cd3 | |
DutchEllie | 81d9e315a0 | |
DutchEllie | 73cf59ff86 | |
DutchEllie | f82f72ec1c | |
DutchEllie | 73385c9d39 | |
DutchEllie | fb7c79f570 | |
DutchEllie | f46005130e | |
DutchEllie | a2fbcf03ca |
111
.drone.yml
111
.drone.yml
|
@ -1,26 +1,54 @@
|
|||
kind: pipeline
|
||||
type: kubernetes
|
||||
name: default
|
||||
name: staging
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
|
||||
# STAGING!!!!!!!!!!!!!
|
||||
# STAGING!!!!
|
||||
steps:
|
||||
- name: docker
|
||||
- name: build-wasm
|
||||
image: golang:1.17.8-alpine
|
||||
volumes:
|
||||
- name: build-staging
|
||||
path: /drone/src/build
|
||||
environment:
|
||||
APIURL: https://api.quenten.nl/api/testing
|
||||
CGO_ENABLED: 0
|
||||
commands:
|
||||
- mkdir ./build/web
|
||||
- GOARCH=wasm GOOS=js go build -o ./build/web/app.wasm -ldflags="-X 'main.ApiURL=$APIURL'" ./src
|
||||
- name: build-server
|
||||
image: golang:1.17.8-alpine
|
||||
volumes:
|
||||
- name: build-staging
|
||||
path: /drone/src/build
|
||||
environment:
|
||||
APIURL: https://api.quenten.nl/api/testing
|
||||
CGO_ENABLED: 0
|
||||
commands:
|
||||
- go build -o ./build/app -ldflags="-X 'main.ApiURL=$APIURL'" ./src
|
||||
- name: build-publish-image
|
||||
image: plugins/docker
|
||||
privileged: true
|
||||
volumes:
|
||||
- name: build-staging
|
||||
path: /drone/src/build
|
||||
settings:
|
||||
username:
|
||||
cache_from:
|
||||
- "dutchellie/proper-website-2:dev"
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
dockerfile: Dockerfile
|
||||
repo: dutchellie/proper-website-2
|
||||
build_args:
|
||||
- APIURL=https://api.nicecock.eu/api/testingcomment
|
||||
tags:
|
||||
- dev
|
||||
- ${DRONE_COMMIT_SHA:0:8}
|
||||
tags:
|
||||
- dev-${DRONE_COMMIT_SHA:0:8}
|
||||
depends_on:
|
||||
- build-wasm
|
||||
- build-server
|
||||
- name: deploy-staging
|
||||
image: pelotech/drone-helm3
|
||||
settings:
|
||||
|
@ -30,10 +58,9 @@ steps:
|
|||
release: newsite-staging
|
||||
skip_tls_verify: true
|
||||
values_files:
|
||||
# - .drone/helm/values.yaml
|
||||
- .drone/helm/staging-val.yaml
|
||||
values:
|
||||
- "image=dutchellie/proper-website-2:${DRONE_COMMIT_SHA:0:8}"
|
||||
- "image=dutchellie/proper-website-2:dev-${DRONE_COMMIT_SHA:0:8}"
|
||||
kube_api_server:
|
||||
from_secret: staging_api_server
|
||||
kube_token:
|
||||
|
@ -42,12 +69,18 @@ steps:
|
|||
from_secret: staging_kube_certificate
|
||||
kube_service_account: drone-deploy
|
||||
dry_run: false
|
||||
depends_on:
|
||||
- build-publish-image
|
||||
|
||||
volumes:
|
||||
- name: build-staging
|
||||
temp: {}
|
||||
|
||||
---
|
||||
|
||||
kind: pipeline
|
||||
type: kubernetes
|
||||
name: deploy-prod
|
||||
name: production
|
||||
|
||||
trigger:
|
||||
event:
|
||||
|
@ -55,20 +88,49 @@ trigger:
|
|||
target:
|
||||
- production
|
||||
|
||||
# PRODUCTION!!!!
|
||||
steps:
|
||||
- name: docker
|
||||
- name: build-wasm
|
||||
image: golang:1.17.8-alpine
|
||||
volumes:
|
||||
- name: build
|
||||
path: /drone/src/build
|
||||
environment:
|
||||
APIURL: https://api.quenten.nl/api
|
||||
CGO_ENABLED: 0
|
||||
commands:
|
||||
- mkdir ./build/web
|
||||
- GOARCH=wasm GOOS=js go build -o ./build/web/app.wasm -ldflags="-X 'main.ApiURL=$APIURL'" ./src
|
||||
- name: build-server
|
||||
image: golang:1.17.8-alpine
|
||||
volumes:
|
||||
- name: build
|
||||
path: /drone/src/build
|
||||
environment:
|
||||
APIURL: https://api.quenten.nl/api
|
||||
CGO_ENABLED: 0
|
||||
commands:
|
||||
- go build -o ./build/app -ldflags="-X 'main.ApiURL=$APIURL'" ./src
|
||||
- name: build-publish-image
|
||||
image: plugins/docker
|
||||
privileged: true
|
||||
volumes:
|
||||
- name: build
|
||||
path: /drone/src/build
|
||||
settings:
|
||||
username:
|
||||
cache_from:
|
||||
- "dutchellie/proper-website-2:latest"
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
password:
|
||||
from_secret: docker_password
|
||||
dockerfile: Dockerfile
|
||||
repo: dutchellie/proper-website-2
|
||||
build_args:
|
||||
- APIURL=https://api.nicecock.eu/api/comment
|
||||
tags:
|
||||
- latest
|
||||
- ${DRONE_COMMIT_SHA:0:8}
|
||||
tags:
|
||||
- latest-${DRONE_COMMIT_SHA:0:8}
|
||||
depends_on:
|
||||
- build-wasm
|
||||
- build-server
|
||||
- name: deploy-production
|
||||
image: pelotech/drone-helm3
|
||||
settings:
|
||||
|
@ -78,10 +140,9 @@ steps:
|
|||
release: newsite-production
|
||||
skip_tls_verify: true
|
||||
values_files:
|
||||
# - .drone/helm/values.yaml
|
||||
- .drone/helm/prod-val.yaml
|
||||
values:
|
||||
- "image=dutchellie/proper-website-2:${DRONE_COMMIT_SHA:0:8}"
|
||||
- "image=dutchellie/proper-website-2:latest-${DRONE_COMMIT_SHA:0:8}"
|
||||
kube_api_server:
|
||||
from_secret: prod_api_server
|
||||
kube_token:
|
||||
|
@ -90,3 +151,9 @@ steps:
|
|||
from_secret: prod_kube_certificate
|
||||
kube_service_account: drone-deploy
|
||||
dry_run: false
|
||||
depends_on:
|
||||
- build-publish-image
|
||||
|
||||
volumes:
|
||||
- name: build
|
||||
temp: {}
|
|
@ -31,5 +31,9 @@ spec:
|
|||
ports:
|
||||
- name: http
|
||||
containerPort: 8000
|
||||
{{- if .Values.containerEnv }}
|
||||
env: {{ toYaml .Values.containerEnv | nindent 12 }}
|
||||
{{- end }}
|
||||
|
||||
|
||||
|
|
@ -6,6 +6,7 @@ podAnnotations: {}
|
|||
containerName: newsite
|
||||
image: dutchellie/proper-website-2:latest
|
||||
imagePullPolicy: Always
|
||||
containerEnv: []
|
||||
|
||||
service:
|
||||
name: newsite-service
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
name: newsite-prod
|
||||
containerEnv:
|
||||
- name: APIURL
|
||||
value: https://api.quenten.nl/api
|
||||
service:
|
||||
name: newsite-prod
|
||||
ingress:
|
||||
name: newsite-prod
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
external-dns.alpha.kubernetes.io/hostname: "newsite.dutchellie.nl"
|
||||
external-dns.alpha.kubernetes.io/hostname: "quenten.nl"
|
||||
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||
add_header Content-Security-Policy "frame-ancestors 'self' https://forestofunix.xyz";
|
||||
proxy_hide_header X-Frame-Options ;
|
||||
tls:
|
||||
- hosts:
|
||||
- newsite.dutchellie.nl
|
||||
- quenten.nl
|
||||
secretName: newsite-tls
|
||||
hosts:
|
||||
- host: newsite.dutchellie.nl
|
||||
- host: quenten.nl
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
|
@ -1,17 +1,23 @@
|
|||
name: newsite-staging
|
||||
containerEnv:
|
||||
- name: APIURL
|
||||
value: https://api.quenten.nl/api/testing
|
||||
service:
|
||||
name: newsite-staging
|
||||
ingress:
|
||||
name: newsite-staging
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-staging
|
||||
external-dns.alpha.kubernetes.io/hostname: "newsite.staging.dutchellie.nl"
|
||||
external-dns.alpha.kubernetes.io/hostname: "staging.quenten.nl"
|
||||
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||
add_header Content-Security-Policy "frame-ancestors 'self' https://forestofunix.xyz";
|
||||
proxy_hide_header X-Frame-Options ;
|
||||
tls:
|
||||
- hosts:
|
||||
- newsite.staging.dutchellie.nl
|
||||
- staging.quenten.nl
|
||||
secretName: newsite-staging-tls
|
||||
hosts:
|
||||
- host: newsite.staging.dutchellie.nl
|
||||
- host: staging.quenten.nl
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
25
Dockerfile
25
Dockerfile
|
@ -1,16 +1,17 @@
|
|||
FROM golang:1.17.8-alpine AS builder
|
||||
ARG APIURL
|
||||
WORKDIR /project
|
||||
ADD . /project/
|
||||
RUN go mod tidy
|
||||
RUN GOARCH=wasm GOOS=js go build -ldflags="-X 'dutchellie.nl/DutchEllie/proper-website-2/components.ApiURL=$APIURL'" -o web/app.wasm
|
||||
RUN go build -ldflags="-X 'dutchellie.nl/DutchEllie/proper-website-2/components.ApiURL=$APIURL'" -o app
|
||||
#FROM golang:1.17.8-alpine AS builder
|
||||
#ARG APIURL
|
||||
#WORKDIR /project
|
||||
#ADD . /project/
|
||||
#RUN go mod tidy
|
||||
#RUN GOARCH=wasm GOOS=js go build -o web/app.wasm -ldflags="-X 'main.ApiURL=$APIURL'" ./src
|
||||
#RUN go build -o app -ldflags="-X 'main.ApiURL=$APIURL'" ./src
|
||||
|
||||
FROM alpine:latest AS staging
|
||||
FROM alpine:latest
|
||||
RUN apk --no-cache add ca-certificates
|
||||
WORKDIR /root/
|
||||
COPY --from=builder /project/web ./web/
|
||||
COPY --from=builder /project/app ./
|
||||
WORKDIR /root
|
||||
RUN mkdir ./web
|
||||
COPY ./web ./web
|
||||
COPY ./build/web/app.wasm ./web/
|
||||
COPY ./build/app ./
|
||||
EXPOSE 8000
|
||||
ENV TESTING true
|
||||
CMD ["./app"]
|
22
Makefile
22
Makefile
|
@ -1,16 +1,26 @@
|
|||
APIURL_prod := https://api.nicecock.eu/api/comment
|
||||
APIURL_staging := https://api.nicecock.eu/api/testingcomment
|
||||
APIURL_prod := https://api.quenten.nl/api
|
||||
APIURL_staging := https://api.quenten.nl/api/testing
|
||||
|
||||
build:
|
||||
GOARCH=wasm GOOS=js go build -ldflags="-X 'dutchellie.nl/DutchEllie/proper-website-2/components.ApiURL=${APIURL_staging}'" -o web/app.wasm
|
||||
go build -ldflags="-X 'dutchellie.nl/DutchEllie/proper-website-2/components.ApiURL=${APIURL_staging}'" -o app
|
||||
GOARCH=wasm GOOS=js go build -o web/app.wasm -ldflags="-X 'main.ApiURL=${APIURL_staging}'" ./src
|
||||
go build -o app -ldflags="-X 'main.ApiURL=${APIURL_staging}'" ./src
|
||||
cp -r web staticsite/
|
||||
|
||||
build-new:
|
||||
GOARCH=wasm GOOS=js go build -o web/app.wasm ./src
|
||||
go build -o app ./src
|
||||
cp -r web staticsite/
|
||||
|
||||
build-prod:
|
||||
GOARCH=wasm GOOS=js go build -ldflags="-X 'dutchellie.nl/DutchEllie/proper-website-2/components.ApiURL=${APIURL_prod}'" -o web/app.wasm
|
||||
go build -ldflags="-X 'dutchellie.nl/DutchEllie/proper-website-2/components.ApiURL=${APIURL_prod}'" -o app
|
||||
GOARCH=wasm GOOS=js go build -o web/app.wasm -ldflags="-X 'main.ApiURL=${APIURL_prod}'" ./src
|
||||
go build -o app -ldflags="-X 'main.ApiURL=${APIURL_prod}'" ./src
|
||||
cp -r web staticsite/
|
||||
|
||||
run: build
|
||||
./app
|
||||
|
||||
run-new: build-new
|
||||
APIURL=${APIURL_staging} ./app
|
||||
|
||||
run-prod: build-prod
|
||||
./app
|
13
README.md
13
README.md
|
@ -1,14 +1,23 @@
|
|||
# proper-website-2
|
||||
|
||||
[![Build Status](https://drone.nicecock.eu/api/badges/DutchEllie/proper-website-2/status.svg)](https://drone.nicecock.eu/DutchEllie/proper-website-2)
|
||||
[![Build Status](https://drone.dutchellie.nl/api/badges/DutchEllie/proper-website-2/status.svg)](https://drone.dutchellie.nl/DutchEllie/proper-website-2)
|
||||
|
||||
A truly proper website this time™
|
||||
|
||||
**TODO**:
|
||||
- Redo the entire CSS with a framework
|
||||
- Change domain to quenten.nl and staging.quenten.nl
|
||||
- Dynamically make domains for other branches
|
||||
- Make a generic page component, so that you don't have to add the standard panels every time (such as navbar, leftbar, header, etc)
|
||||
|
||||
App notes:
|
||||
- leftbar (under nav) is default always the same.
|
||||
This is in a standardized "page" component.
|
||||
it can be changed by specifying it when creating the "page" component, or maybe in a prerender/onnav (or both).
|
||||
Structure will be:
|
||||
Generic page component IS IMPLEMENTED BY "specific page" WHERE OnNav/OnPreRender specify its details WHICH IS CREATED with its own "newXPage()" function. See https://github.com/maxence-charriere/go-app/blob/master/docs/src/actions-page.go for example.
|
||||
- "main content" is an array of app.UI elements.
|
||||
This can also be further abstracted using "block" components (the ones with the cool color)
|
||||
- THOSE BLOCK COMPONENTS' CONTENT can be set manually on creation, OR by passing HTML/MarkDown (not decided yet)
|
||||
|
||||
This website will be done with this:
|
||||
- Backend written in Golang
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
type ApiApplication interface {
|
||||
NewRouter() *mux.Router
|
||||
Visit(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
func NewApiApp() (ApiApplication, error) {
|
||||
cookiejar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &apiapp{
|
||||
cj: cookiejar,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type apiapp struct {
|
||||
router mux.Router
|
||||
cj *cookiejar.Jar
|
||||
}
|
||||
|
||||
func (a *apiapp) NewRouter() *mux.Router {
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc("/visit", a.Visit)
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
// Called when someone visits any page of the website
|
||||
// Calls for the spyware cookie and sets it if it doesn't yet exist
|
||||
func (a *apiapp) Visit(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("Visit called\n")
|
||||
|
||||
c, err := r.Cookie("spyware")
|
||||
if err != nil && err != http.ErrNoCookie {
|
||||
log.Printf("Error: %s\n", err.Error())
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
} else if err == http.ErrNoCookie {
|
||||
// Create cookie and send it
|
||||
log.Printf("No cookie sent by client, sending cookie to them!\n")
|
||||
c = &http.Cookie{Name: "spyware", Value: uuid.NewString(), Path: "/", MaxAge: 0}
|
||||
http.SetCookie(w, c)
|
||||
}
|
||||
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte("yeet"))
|
||||
|
||||
log.Printf("Someone visited: %s\n", c.Value)
|
||||
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package components
|
||||
|
||||
import (
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
type AboutPage struct {
|
||||
app.Compo
|
||||
}
|
||||
|
||||
func NewAboutPage() *AboutPage {
|
||||
return &AboutPage{}
|
||||
}
|
||||
|
||||
func (a *AboutPage) Render() app.UI {
|
||||
return app.Div().Body(
|
||||
&header{},
|
||||
&navbar{},
|
||||
&aboutPanel{},
|
||||
)
|
||||
}
|
||||
|
||||
type aboutPanel struct {
|
||||
app.Compo
|
||||
|
||||
aboutText string
|
||||
}
|
||||
|
||||
func (a *aboutPanel) Render() app.UI {
|
||||
return app.Div().Body(
|
||||
app.Img().Src("/web/static/images/rin-1.gif").Styles(map[string]string{"width": "100px", "position": "absolute", "top": "10px", "right": "10px"}),
|
||||
app.Raw(`<p class="content-text">I am a 21 year old computer science student, living and studying in The Netherlands. I like Docker, Kubernetes and Golang!
|
||||
<br>
|
||||
I made this website because I was inspired again by the amazing Neocities pages that I discovered because of my friends.
|
||||
They also have their own pages (you can find them on the friends tab, do check them out!) and I just had to get a good website of my own!
|
||||
<br>
|
||||
I am not that great at web development, especially design, but I love trying it regardless!
|
||||
<br><br>
|
||||
To say a bit more about me personally, I love all things computers. From servers to embedded devices! I love the cloud and all that it brings
|
||||
(except for big megacorps, but alright) and it's my goal to work for a big cloud company!
|
||||
<br>
|
||||
Aside from career path ambitions, ボーカロイドはすきです! I love vocaloid and other Japanese music and culture!!
|
||||
I also like Vtubers, especially from Hololive and it's my goal to one day finally understand them in their native language!
|
||||
<br><br>
|
||||
There is a lot more to say in words, but who cares about those! Have a look around my creative digital oasis and see what crazy stuff you can find!</p>`),
|
||||
).Class("content")
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package components
|
||||
|
||||
import (
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
type contentView struct {
|
||||
app.Compo
|
||||
|
||||
panels []app.UI
|
||||
}
|
||||
|
||||
func newContentView(panels ...app.UI) *contentView {
|
||||
return &contentView{panels: panels}
|
||||
}
|
||||
|
||||
func (c *contentView) Render() app.UI {
|
||||
return app.Div().Body(
|
||||
app.Range(c.panels).Slice(func(i int) app.UI {
|
||||
return c.panels[i]
|
||||
}),
|
||||
)
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
package components
|
||||
|
||||
import "github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
|
||||
type GalaxiesPage struct {
|
||||
app.Compo
|
||||
}
|
||||
|
||||
func NewGalaxiesPage() *GalaxiesPage {
|
||||
return &GalaxiesPage{}
|
||||
}
|
||||
|
||||
func (f *GalaxiesPage) Render() app.UI {
|
||||
return app.Div().Body(
|
||||
&header{},
|
||||
&navbar{},
|
||||
&galaxiesPanel{},
|
||||
).Class("main")
|
||||
}
|
||||
|
||||
type galaxiesPanel struct {
|
||||
app.Compo
|
||||
}
|
||||
|
||||
func (b *galaxiesPanel) Render() app.UI {
|
||||
return app.Div().Body(
|
||||
app.P().
|
||||
Text(`Galaxies`).
|
||||
Class("p-h1"),
|
||||
app.P().
|
||||
Class("content-text").
|
||||
Text(`Here you can find some really really really cool pages that I found on the internet.
|
||||
Some of these are blogs or even blogposts I found, but the ones on top are special!
|
||||
They're the websites of friends of mine! Please visit them, because they worked really hard
|
||||
on their websites as well!`),
|
||||
app.Div().
|
||||
Body(
|
||||
app.P().
|
||||
Class("p-h2 m-tb10").
|
||||
Text("My friends!"),
|
||||
app.Ul().Body(
|
||||
app.Li().Body(
|
||||
app.Div().Body(
|
||||
// TODO: Create a modal popup for each name!!!
|
||||
app.A().Href("https://forestofunix.xyz").
|
||||
Class("p-h3 m-t5").
|
||||
Text("Forest of Unix"),
|
||||
app.P().
|
||||
Class("m-t5").
|
||||
Text(`A website by Sebastiaan. A massive Linux fanboy, runs Gentoo on his
|
||||
ThinkPad. Absolutely based.`),
|
||||
),
|
||||
),
|
||||
app.Li().Body(
|
||||
app.Div().Body(
|
||||
// TODO: Create a modal popup for each name!!!
|
||||
app.A().Href("https://nymphali.neocities.org").
|
||||
Class("p-h3 m-t5").
|
||||
Text("Nymphali"),
|
||||
app.P().
|
||||
Class("m-t5").
|
||||
Text(`The website made by ■■■■■■, whoops Nymphali. They have an awesome
|
||||
minimalist website that's just lovely.`),
|
||||
),
|
||||
),
|
||||
app.Li().Body(
|
||||
app.Div().Body(
|
||||
// TODO: Create a modal popup for each name!!!
|
||||
app.A().Href("https://kristypixel.neocities.org").
|
||||
Class("p-h3 m-t5").
|
||||
Text("Kristy"),
|
||||
app.P().
|
||||
Class("m-t5").
|
||||
Text(`Website made by Kristy. Very cute website, I love it! Keep up the
|
||||
awesome work!`),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
).Class("content")
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
type guestbookForm struct {
|
||||
app.Compo
|
||||
|
||||
name string
|
||||
message string
|
||||
|
||||
gbModalOpen bool
|
||||
OnSubmit func(
|
||||
name string,
|
||||
message string,
|
||||
) // Handler to implement which calls the api
|
||||
}
|
||||
|
||||
func (g *guestbookForm) Render() app.UI {
|
||||
return app.Div().Body(
|
||||
app.Form().Body(
|
||||
app.Input().
|
||||
Type("text").
|
||||
Name("name").
|
||||
Placeholder("Name").
|
||||
Required(true).
|
||||
OnChange(g.ValueTo(&g.name)).
|
||||
Value(g.name),
|
||||
app.Input().
|
||||
Type("text").
|
||||
Name("message").
|
||||
Placeholder("Message").
|
||||
Required(true).
|
||||
OnChange(g.ValueTo(&g.message)).
|
||||
Value(g.message),
|
||||
app.Input().
|
||||
Type("submit").
|
||||
Name("submit"),
|
||||
).ID("form").
|
||||
OnSubmit(func(ctx app.Context, e app.Event) {
|
||||
// This was to prevent the page from reloading
|
||||
e.PreventDefault()
|
||||
if g.name == "" || g.message == "" {
|
||||
fmt.Printf("Error: one or more field(s) are empty. For now unhandled\n")
|
||||
return
|
||||
}
|
||||
if len(g.name) > 40 || len(g.message) > 360 {
|
||||
fmt.Printf("Error: Your message is too long fucker\n")
|
||||
g.gbModalOpen = true
|
||||
return
|
||||
}
|
||||
g.OnSubmit(g.name, g.message)
|
||||
g.clear()
|
||||
}),
|
||||
app.If(
|
||||
g.gbModalOpen,
|
||||
&guestbookAlertModal{
|
||||
OnClose: func() {
|
||||
g.gbModalOpen = false
|
||||
g.Update()
|
||||
},
|
||||
},
|
||||
),
|
||||
).Class("content")
|
||||
}
|
||||
|
||||
func (g *guestbookForm) clear() {
|
||||
g.name = ""
|
||||
g.message = ""
|
||||
}
|
||||
|
||||
type guestbookAlertModal struct {
|
||||
app.Compo
|
||||
|
||||
PreviousAttempts int
|
||||
OnClose func() // For when we close the modal
|
||||
}
|
||||
|
||||
func (g *guestbookAlertModal) Render() app.UI {
|
||||
return app.Div().
|
||||
Class("gb-modal").
|
||||
ID("gbModal").
|
||||
OnClick(func(ctx app.Context, e app.Event) {
|
||||
g.OnClose()
|
||||
}).
|
||||
Body(
|
||||
app.Div().
|
||||
Class("gb-modal-content").
|
||||
Body(
|
||||
app.Span().Class("close").Text("X").
|
||||
OnClick(func(ctx app.Context, e app.Event) {
|
||||
//modal := app.Window().GetElementByID("gbModal")
|
||||
//modal.Set("style", "none")
|
||||
g.OnClose()
|
||||
}),
|
||||
app.P().Text("Your name must be <= 40 and your message must be <= 360 characters"),
|
||||
),
|
||||
)
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
package components
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"dutchellie.nl/DutchEllie/proper-website-2/entity"
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
/*
|
||||
What is this supposed to do:
|
||||
- It should call on the API to give it a certain number of comments, in the range x to y (this has to be implemented in the api)
|
||||
- When it has called that, it should display those
|
||||
- Dynamic links are there to navigate the user along the pages
|
||||
- Comments are shown dynamically
|
||||
- This panel can be shown or hidden (maybe)
|
||||
|
||||
AND VERY IMPORTANT!
|
||||
- If a user submits a new comment, automatically put it on the page, no reloading
|
||||
|
||||
*/
|
||||
type guestbookPanel struct {
|
||||
app.Compo
|
||||
|
||||
comments []entity.Comment
|
||||
}
|
||||
|
||||
func newGuestbookPanel() *guestbookPanel {
|
||||
g := &guestbookPanel{}
|
||||
g.LoadComments()
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *guestbookPanel) Render() app.UI {
|
||||
return app.Div().Body(
|
||||
app.Range(g.comments).Slice(func(i int) app.UI {
|
||||
return &guestbookComment{
|
||||
Comment: g.comments[i],
|
||||
}
|
||||
}),
|
||||
).Class("content gbp")
|
||||
}
|
||||
|
||||
func (g *guestbookPanel) LoadComments() {
|
||||
// TODO: maybe you can put this in a localbrowser storage?
|
||||
url := ApiURL
|
||||
res, err := http.Get(url)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
jsondata, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(jsondata, &g.comments)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type guestbookComment struct {
|
||||
app.Compo
|
||||
|
||||
Comment entity.Comment
|
||||
time string
|
||||
}
|
||||
|
||||
func (c *guestbookComment) Render() app.UI {
|
||||
c.time = c.Comment.PostDate.Format(time.RFC1123)
|
||||
return app.Div().Body(
|
||||
app.Div().Class().Body(
|
||||
app.P().Text(c.Comment.Name).Class("name"),
|
||||
app.P().Text(c.time).Class("date"),
|
||||
).Class("comment-header"),
|
||||
app.Div().Class("comment-message").Body(
|
||||
app.P().Text(c.Comment.Message),
|
||||
),
|
||||
).Class("comment")
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package components
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"dutchellie.nl/DutchEllie/proper-website-2/entity"
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
var (
|
||||
ApiURL string
|
||||
)
|
||||
|
||||
type Homepage struct {
|
||||
app.Compo
|
||||
|
||||
showGuestbook bool
|
||||
|
||||
page string
|
||||
}
|
||||
|
||||
func NewHomepage() *Homepage {
|
||||
return &Homepage{showGuestbook: true, page: "home"}
|
||||
}
|
||||
|
||||
func (p *Homepage) Render() app.UI {
|
||||
gbp := newGuestbookPanel()
|
||||
return app.Div().Body(
|
||||
&header{},
|
||||
&navbar{},
|
||||
&homePanel{
|
||||
onShowClick: func() {
|
||||
p.showGuestbook = !p.showGuestbook
|
||||
},
|
||||
},
|
||||
&guestbookForm{
|
||||
OnSubmit: func(name, message string) {
|
||||
var comment entity.Comment
|
||||
comment.Name = name
|
||||
comment.Message = message
|
||||
|
||||
jsondata, err := json.Marshal(comment)
|
||||
if err != nil {
|
||||
fmt.Printf("err: %v\n", err)
|
||||
return
|
||||
}
|
||||
url := ApiURL
|
||||
|
||||
req, err := http.Post(url, "application/json", bytes.NewBuffer(jsondata))
|
||||
if err != nil {
|
||||
fmt.Printf("err: %v\n", err)
|
||||
return
|
||||
}
|
||||
if req.StatusCode == 200 {
|
||||
p.Update()
|
||||
}
|
||||
defer req.Body.Close()
|
||||
},
|
||||
},
|
||||
//app.If(p.showGuestbook, gbp),
|
||||
gbp.Render(),
|
||||
).Class("main")
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package components
|
||||
|
||||
import "github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
|
||||
type homePanel struct {
|
||||
app.Compo
|
||||
|
||||
onShowClick func()
|
||||
updateAvailable bool
|
||||
}
|
||||
|
||||
func newHomePanel() *homePanel {
|
||||
return &homePanel{}
|
||||
}
|
||||
|
||||
func (p *homePanel) Render() app.UI {
|
||||
return app.Div().Body(
|
||||
app.P().Text("Welcome, internet surfer!").Class("p-h1"),
|
||||
app.Raw(`<p class="content-text">This website is my creative outlet and a way of expressing myself.
|
||||
As of now, it's probably the most impressive thing I've ever coded.
|
||||
<br><br>
|
||||
Please enjoy yourself and do sign the guestbook!!</p>`),
|
||||
app.If(p.updateAvailable,
|
||||
app.Div().Body(
|
||||
app.P().
|
||||
Class("content-text").
|
||||
Text("An update is available! Reload to update!"),
|
||||
)),
|
||||
app.Div().Body(
|
||||
app.P().Text("Please sign my guestbook!").Class("small"),
|
||||
app.Img().Src("/web/static/images/email3.gif").Style("width", "40px").Style("position", "absolute").Style("bottom", "0px").Style("right", "0px"),
|
||||
).Style("position", "absolute").Style("bottom", "5px").Style("right", "5px").
|
||||
OnClick(func(ctx app.Context, e app.Event) {
|
||||
e.PreventDefault()
|
||||
p.onShowClick()
|
||||
}),
|
||||
).Class("content")
|
||||
}
|
||||
|
||||
func (p *homePanel) OnAppUpdate(ctx app.Context) {
|
||||
p.updateAvailable = true
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package components
|
||||
|
||||
import "github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
|
||||
type navbar struct {
|
||||
app.Compo
|
||||
|
||||
OnClickButton func(page string)
|
||||
}
|
||||
|
||||
func (n *navbar) Render() app.UI {
|
||||
return app.Div().Body(
|
||||
app.Ul().Body(
|
||||
app.Li().Body(
|
||||
app.A().Href("/").Text("Home"),
|
||||
),
|
||||
app.Li().Body(
|
||||
app.A().Href("/about").Text("About"),
|
||||
),
|
||||
app.Li().Body(
|
||||
app.A().Href("/galaxies").Text("Galaxies"),
|
||||
),
|
||||
),
|
||||
).Class("navbar")
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package components
|
||||
|
||||
import "github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
|
||||
type updater struct {
|
||||
app.Compo
|
||||
|
||||
updateAvailable bool
|
||||
}
|
||||
|
||||
func (u *updater) onAppUpdate(ctx app.Context) {
|
||||
u.updateAvailable = ctx.AppUpdateAvailable()
|
||||
}
|
||||
|
||||
func (u *updater) Render() app.UI {
|
||||
return app.Div().Body(
|
||||
app.If(u.updateAvailable,
|
||||
app.Div().Body(
|
||||
app.P().Text("An update for this website is available! Please click here to reload!"),
|
||||
).Styles(map[string]string{"position": "absolute", "width": "100px", "bottom": "10px", "right": "10px"}).OnClick(u.onUpdateClick),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (u *updater) onUpdateClick(ctx app.Context, e app.Event) {
|
||||
ctx.Reload()
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package entity
|
||||
|
||||
import "time"
|
||||
|
||||
// Blogpost contains the name, date and such of a blogpost
|
||||
// The actual post itself is hosted somewhere else in the form of an (html) document
|
||||
// This is put in the path field
|
||||
|
||||
type BlogPost struct {
|
||||
ID string `json:"id,omitempty" bson:"_id,omitempty"`
|
||||
Title string `json:"title" bson:"title"`
|
||||
Author string `json:"author" bson:"author"`
|
||||
Path string `json:"path" bson:"path"`
|
||||
PostDate time.Time `json:"time" bson:"time"`
|
||||
}
|
|
@ -9,4 +9,6 @@ type Comment struct {
|
|||
Email string `json:"email,omitempty" bson:"email,omitempty"`
|
||||
Message string `json:"message" bson:"message"`
|
||||
PostDate time.Time `json:"time" bson:"time"`
|
||||
|
||||
UUID string `json:"uuid" bson:"uuid"`
|
||||
}
|
||||
|
|
13
go.mod
13
go.mod
|
@ -2,6 +2,15 @@ module dutchellie.nl/DutchEllie/proper-website-2
|
|||
|
||||
go 1.17
|
||||
|
||||
require github.com/maxence-charriere/go-app/v9 v9.3.3
|
||||
require (
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/maxence-charriere/go-app/v9 v9.3.3
|
||||
golang.org/x/net v0.0.0-20220622184535-263ec571b305
|
||||
)
|
||||
|
||||
require github.com/google/uuid v1.3.0 // indirect
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.1 // indirect
|
||||
)
|
||||
|
|
15
go.sum
15
go.sum
|
@ -1,9 +1,16 @@
|
|||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
||||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/maxence-charriere/go-app/v9 v9.3.3 h1:vo+1oohWMfTQ0S3eg9JOjuFEy1n3bVNgeDi4eHNfMzA=
|
||||
github.com/maxence-charriere/go-app/v9 v9.3.3/go.mod h1:zo0n1kh4OMKn7P+MrTUUi7QwUMU2HOfHsZ293TITtxI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
@ -13,10 +20,16 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
|
|||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
|
||||
golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/net v0.0.0-20220622184535-263ec571b305 h1:dAgbJ2SP4jD6XYfMNLVj0BF21jo2PjChrtGaAvF5M3I=
|
||||
golang.org/x/net v0.0.0-20220622184535-263ec571b305/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
|
|
99
main.go
99
main.go
|
@ -1,99 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"dutchellie.nl/DutchEllie/proper-website-2/components"
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
//type application struct {
|
||||
// client *mongo.Client
|
||||
// database *mongo.Database
|
||||
// collection *mongo.Collection
|
||||
//}
|
||||
|
||||
const (
|
||||
apiurl = "https://quenten.nl:8007/"
|
||||
)
|
||||
|
||||
func main() {
|
||||
homepage := components.NewHomepage()
|
||||
aboutpage := components.NewAboutPage()
|
||||
galaxiespage := components.NewGalaxiesPage()
|
||||
app.Route("/", homepage)
|
||||
app.Route("/about", aboutpage)
|
||||
app.Route("/galaxies", galaxiespage)
|
||||
|
||||
// This is executed on the client side only.
|
||||
// It handles client side stuff
|
||||
// It exits immediately on the server side
|
||||
app.RunWhenOnBrowser()
|
||||
|
||||
icon := &app.Icon{
|
||||
Default: "/web/static/images/icon-small.png",
|
||||
Large: "/web/static/images/icon.png",
|
||||
}
|
||||
handler := &app.Handler{
|
||||
Name: "Internetica Galactica",
|
||||
Icon: *icon,
|
||||
BackgroundColor: "#362730",
|
||||
ThemeColor: "#362730",
|
||||
LoadingLabel: "Internetica Galactica",
|
||||
Title: "Internetica Galactica",
|
||||
Description: "A 1990's style PWA!",
|
||||
Author: "Quenten",
|
||||
Keywords: []string{
|
||||
"Based website",
|
||||
"Cool website",
|
||||
"PWA",
|
||||
"Programming",
|
||||
"Go", "Golang",
|
||||
"Webassembly", "WASM",
|
||||
"DutchEllie", "Quenten",
|
||||
},
|
||||
Styles: []string{
|
||||
"/web/static/style.css",
|
||||
"/web/static/adreena.css",
|
||||
"/web/static/anisha.css",
|
||||
"/web/static/havakana.css",
|
||||
},
|
||||
CacheableResources: []string{},
|
||||
}
|
||||
|
||||
app.GenerateStaticWebsite("./staticsite", handler)
|
||||
/*
|
||||
uri := "mongodb+srv://guestbook-database:5WUDzpvBKBBiiMCy@cluster0.wtt64.mongodb.net/myFirstDatabase?retryWrites=true&w=majority"
|
||||
|
||||
client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err = client.Disconnect(context.TODO()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Ping the primary
|
||||
if err := client.Ping(context.TODO(), readpref.Primary()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
db := client.Database("guestbook")
|
||||
coll := db.Collection("comments")
|
||||
|
||||
apiapp := &application{
|
||||
client: client,
|
||||
database: db,
|
||||
collection: coll,
|
||||
}
|
||||
*/
|
||||
http.Handle("/", handler)
|
||||
//http.HandleFunc("/api/comment", apiapp.Comment)
|
||||
|
||||
if err := http.ListenAndServe(":8000", nil); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
type AboutPage struct {
|
||||
app.Compo
|
||||
}
|
||||
|
||||
func NewAboutPage() *AboutPage {
|
||||
return &AboutPage{}
|
||||
}
|
||||
|
||||
func (a *AboutPage) Render() app.UI {
|
||||
return newPage().
|
||||
Title("About me").
|
||||
LeftBar(
|
||||
newHTMLBlock().
|
||||
Class("left").
|
||||
Class("leftbarblock").
|
||||
Src("/web/blocks/snippets/bannerpanel.html"),
|
||||
).
|
||||
Main(
|
||||
newHTMLBlock().
|
||||
Class("right").
|
||||
Class("contentblock").
|
||||
Src("/web/blocks/pages/about.html"),
|
||||
)
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
type htmlBlock struct {
|
||||
app.Compo
|
||||
|
||||
Iclass string
|
||||
Isrc string // HTML document source
|
||||
Iid string
|
||||
|
||||
// TODO: implement invisibility for other background functions
|
||||
}
|
||||
|
||||
func newHTMLBlock() *htmlBlock {
|
||||
return &htmlBlock{}
|
||||
}
|
||||
|
||||
func (b *htmlBlock) ID(v string) *htmlBlock {
|
||||
b.Iid = v
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *htmlBlock) Class(v string) *htmlBlock {
|
||||
b.Iclass = app.AppendClass(b.Iclass, v)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *htmlBlock) Src(v string) *htmlBlock {
|
||||
b.Isrc = v
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *htmlBlock) Render() app.UI {
|
||||
return app.Div().
|
||||
Class("block").
|
||||
Class(b.Iclass).
|
||||
Body(
|
||||
newRemoteHTMLDoc().
|
||||
Src(b.Isrc),
|
||||
)
|
||||
}
|
||||
|
||||
// ==================
|
||||
// UI element block
|
||||
// ==================
|
||||
|
||||
type uiBlock struct {
|
||||
app.Compo
|
||||
|
||||
Iclass string
|
||||
Iui []app.UI
|
||||
Iid string
|
||||
}
|
||||
|
||||
func newUIBlock() *uiBlock {
|
||||
return &uiBlock{}
|
||||
}
|
||||
|
||||
func (b *uiBlock) ID(v string) *uiBlock {
|
||||
b.Iid = v
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *uiBlock) Class(v string) *uiBlock {
|
||||
b.Iclass = app.AppendClass(b.Iclass, v)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *uiBlock) UI(v ...app.UI) *uiBlock {
|
||||
b.Iui = app.FilterUIElems(v...)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *uiBlock) Render() app.UI {
|
||||
return app.Div().
|
||||
Class("block").
|
||||
Class(b.Iclass).
|
||||
Body(
|
||||
app.Range(b.Iui).Slice(func(i int) app.UI {
|
||||
return b.Iui[i]
|
||||
}),
|
||||
)
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"dutchellie.nl/DutchEllie/proper-website-2/entity"
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
type blogLinks struct {
|
||||
app.Compo
|
||||
|
||||
blogposts []entity.BlogPost
|
||||
}
|
||||
|
||||
func (b *blogLinks) OnMount(ctx app.Context) {
|
||||
b.LoadPosts(ctx)
|
||||
}
|
||||
|
||||
func (b *blogLinks) Render() app.UI {
|
||||
return newUIBlock().
|
||||
Class("left").
|
||||
Class("leftbarblock").
|
||||
Class("blogpost-bar").
|
||||
UI(
|
||||
app.P().
|
||||
Class("p-h3").
|
||||
Style("margin-left", "10px").
|
||||
Style("margin-top", "10px").
|
||||
Style("text-decoration", "underline").
|
||||
Text("Posts"),
|
||||
app.Range(b.blogposts).Slice(func(i int) app.UI {
|
||||
return app.P().
|
||||
Class("blogpost-titles").
|
||||
Style("cursor", "pointer").
|
||||
Text(b.blogposts[i].Title).
|
||||
OnClick(func(ctx app.Context, e app.Event) {
|
||||
//e.PreventDefault()
|
||||
|
||||
// Calling the update-blogpost action defined in the blogpage.go file.
|
||||
// There it's updated
|
||||
ctx.NewAction("update-blogpost", app.T("blogpost", b.blogposts[i].Path))
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func (b *blogLinks) LoadPosts(ctx app.Context) {
|
||||
// TODO: maybe you can put this in a localbrowser storage?
|
||||
url := ApiURL + "/blogpost"
|
||||
ctx.Async(func() {
|
||||
res, err := http.Get(url)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
jsondata, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Dispatch(func(ctx app.Context) {
|
||||
err = json.Unmarshal(jsondata, &b.blogposts)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
type BlogPage struct {
|
||||
app.Compo
|
||||
|
||||
display bool
|
||||
currentPostPath string
|
||||
}
|
||||
|
||||
// TODO: write the backend API for this
|
||||
// TODO: Find a proper way of rendering blog posts in markdown
|
||||
// Backend ideas: In the DB, create an entry for each post and a link to where the html file is located!
|
||||
// That way, I don't have to parse and render markdown!!
|
||||
|
||||
// Layout, the leftbar contains the blogpost links and the mainbar contains the post itself!
|
||||
// Function: After pressing a link for a blog post, that blog post ID gets put in the state instead of the URL
|
||||
|
||||
func NewBlogPage() *BlogPage {
|
||||
return &BlogPage{}
|
||||
}
|
||||
|
||||
func (b *BlogPage) OnMount(ctx app.Context) {
|
||||
ctx.Handle("update-blogpost", b.onUpdateBlogPost)
|
||||
b.display = false
|
||||
}
|
||||
|
||||
func (b *BlogPage) Render() app.UI {
|
||||
return newPage().
|
||||
Title("Blog").
|
||||
LeftBar(
|
||||
&blogLinks{},
|
||||
).
|
||||
Main(
|
||||
app.If(b.display,
|
||||
newHTMLBlock().
|
||||
Class("right").
|
||||
Class("contentblock").
|
||||
Src(b.currentPostPath),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (b *BlogPage) onUpdateBlogPost(ctx app.Context, a app.Action) {
|
||||
fmt.Printf("Called the update-blogpost ActionHandler\n")
|
||||
blogpost := a.Tags.Get("blogpost")
|
||||
b.currentPostPath = blogpost
|
||||
b.display = true
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package main
|
||||
|
||||
import "github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
|
||||
type EmptyPage struct {
|
||||
app.Compo
|
||||
}
|
||||
|
||||
func NewEmptyPage() *EmptyPage {
|
||||
return &EmptyPage{}
|
||||
}
|
||||
|
||||
func (e *EmptyPage) Render() app.UI {
|
||||
return app.Head().Body()
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
type GalaxiesPage struct {
|
||||
app.Compo
|
||||
|
||||
gnuOver bool
|
||||
mousex, mousey int
|
||||
}
|
||||
|
||||
func NewGalaxiesPage() *GalaxiesPage {
|
||||
return &GalaxiesPage{}
|
||||
}
|
||||
|
||||
func (f *GalaxiesPage) Render() app.UI {
|
||||
return newPage().
|
||||
Title("Galaxies").
|
||||
LeftBar(
|
||||
newHTMLBlock().
|
||||
Class("left").
|
||||
Class("leftbarblock").
|
||||
Src("/web/blocks/snippets/bannerpanel.html"),
|
||||
).
|
||||
Main(
|
||||
/*
|
||||
newHTMLBlock().
|
||||
Class("right").
|
||||
Class("contentblock").
|
||||
Src("/web/blocks/pages/galaxies.html"),
|
||||
*/
|
||||
newUIBlock().
|
||||
Class("right").
|
||||
Class("contentblock").
|
||||
UI(
|
||||
app.Div().
|
||||
Body(
|
||||
app.P().
|
||||
Class("p-h1").
|
||||
Text("Galaxies"),
|
||||
app.P().
|
||||
Class("content-text").
|
||||
Text(`Here you can find some really really really cool pages that I found on the internet.
|
||||
Some of these are blogs or even blogposts I found, but the ones on top are special!
|
||||
They're the websites of friends of mine! Please visit them, because they worked really hard
|
||||
on their websites as well!`),
|
||||
app.Div().
|
||||
Body(
|
||||
app.P().
|
||||
Class("p-h2").
|
||||
Class("mt-20").
|
||||
Class("mb-10").
|
||||
Class("bold").
|
||||
Text("My friends!"),
|
||||
app.Ul().
|
||||
Body(
|
||||
app.Li().
|
||||
Body(
|
||||
newLinkWithStatus().
|
||||
Link("https://forestofunix.xyz").
|
||||
LinkText("Forest of Unix").
|
||||
Text("A website made by Sebastiaan. A massive Linux fanboy, runs Gentoo on his ThinkPad. Absolutely based. His website is written in Lisp, that's why it's often offline. That was the inspiration for the online/offline status text."),
|
||||
),
|
||||
app.Li().
|
||||
Body(
|
||||
newLinkWithStatus().
|
||||
Link("https://nymphali.neocities.org").
|
||||
LinkText("Nymphali").
|
||||
Text("The website made by ■■■■■■, whoops Nymphali. They have an awesome minimalist website that's just lovely."),
|
||||
),
|
||||
app.Li().
|
||||
Body(
|
||||
newLinkWithStatus().
|
||||
Link("https://kristypixel.neocities.org").
|
||||
LinkText("Kristypixel").
|
||||
Text("Website made by Kristy. Very cute website, I love it! Keep up the awesome work!"),
|
||||
),
|
||||
app.Li().
|
||||
Body(
|
||||
newLinkWithStatus().
|
||||
Link("https://leftonred.neocities.org").
|
||||
LinkText("Left on Red").
|
||||
Text("Kyu made this website, he's a friend of mine as well! Still very new, but their dark mode design is very cool!"),
|
||||
),
|
||||
),
|
||||
),
|
||||
app.Div().
|
||||
Body(
|
||||
app.P().
|
||||
Class("p-h2").
|
||||
Class("mt-20").
|
||||
Class("mb-10").
|
||||
Class("bold").
|
||||
Text("Neat webspaces"),
|
||||
app.P().
|
||||
Class("m-t5").
|
||||
Style("margin-left", "10px").
|
||||
Text("Just very neat websites I found and causes I support. Not necessarily by people I know. I just wanted to share them here!"),
|
||||
app.Ul().
|
||||
Body(
|
||||
app.Li().
|
||||
Body(
|
||||
newLinkWithStatus().
|
||||
Link("https://evillious.ylimegirl.com").
|
||||
LinkText("Evillious Chronicles fan guide").
|
||||
Text("A VERY cool website made by Ylimegirl! They wrote a whole website dedicated to Evillious Chronicles, which is a super good Japanese light novel and vocaloid series!! Definitely look it up!"),
|
||||
),
|
||||
app.Li().
|
||||
Body(
|
||||
newLinkWithStatus().
|
||||
Link("https://www.gnu.org").
|
||||
LinkBody(
|
||||
newTextWithTooltip().
|
||||
Text("The GNU Project").
|
||||
Tooltip(
|
||||
app.Img().
|
||||
Src("/web/static/images/gnu-head-sm.png").
|
||||
Width(129).
|
||||
Height(122).
|
||||
Alt("GNU"),
|
||||
),
|
||||
).
|
||||
Text("The official website of the GNU project. They advocate for free/libre software. This is not to be confused with 'open source' software. I highly recommend you read about them and their efforts."),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
func (f *GalaxiesPage) onMouseOverGnu(ctx app.Context, e app.Event) {
|
||||
|
||||
}*/
|
|
@ -0,0 +1,387 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"dutchellie.nl/DutchEllie/proper-website-2/entity"
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
type guestbook struct {
|
||||
app.Compo
|
||||
|
||||
comments []entity.Comment
|
||||
|
||||
name string
|
||||
email string
|
||||
website string
|
||||
message string
|
||||
|
||||
lastHash [32]byte
|
||||
gbModalOpen bool
|
||||
gbErrorModalOpen bool
|
||||
errorText string
|
||||
OnSubmit func(
|
||||
ctx app.Context,
|
||||
name string,
|
||||
email string,
|
||||
website string,
|
||||
message string,
|
||||
uuid string,
|
||||
) // Handler to implement which calls the api
|
||||
}
|
||||
|
||||
// TODO: The comments are loaded like 2 or 3 times every time the page is loaded...
|
||||
func (g *guestbook) OnMount(ctx app.Context) {
|
||||
ctx.Handle("guestbook-loadcomments", g.onHandleLoadComments)
|
||||
ctx.NewAction("guestbook-loadcomments")
|
||||
}
|
||||
|
||||
/*
|
||||
func (g *guestbook) OnNav(ctx app.Context) {
|
||||
g.LoadComments(ctx)
|
||||
}
|
||||
|
||||
func (g *guestbook) OnUpdate(ctx app.Context) {
|
||||
g.LoadComments(ctx)
|
||||
}*/
|
||||
|
||||
func (g guestbook) Render() app.UI {
|
||||
return app.Div().Body(
|
||||
app.Form().
|
||||
Class("guestbook-form").
|
||||
Body(
|
||||
app.Div().
|
||||
Class("input-groups").
|
||||
Body(
|
||||
app.Div().
|
||||
Class("fr").
|
||||
Body(
|
||||
app.Div().
|
||||
Class("input-group").
|
||||
Class("input-group-name").
|
||||
Body(
|
||||
app.Label().
|
||||
For("name").
|
||||
Text("Name:"),
|
||||
app.Input().
|
||||
Type("text").
|
||||
Name("name").
|
||||
Class("input").
|
||||
Required(true).
|
||||
OnChange(g.ValueTo(&g.name)),
|
||||
),
|
||||
app.Div().
|
||||
Class("input-group").
|
||||
Class("input-group-email").
|
||||
Body(
|
||||
app.Label().
|
||||
For("email").
|
||||
Text("Email: (optional)"),
|
||||
app.Input().
|
||||
Type("text").
|
||||
Name("email").
|
||||
Class("input").
|
||||
Required(false).
|
||||
OnChange(g.ValueTo(&g.email)),
|
||||
),
|
||||
),
|
||||
app.Div().
|
||||
Class("input-group").
|
||||
Class("input-group-website").
|
||||
Body(
|
||||
app.Label().
|
||||
For("website").
|
||||
Text("Website: (optional)"),
|
||||
app.Input().
|
||||
Type("text").
|
||||
Name("website").
|
||||
Class("input").
|
||||
Required(false).
|
||||
OnChange(g.ValueTo(&g.website)),
|
||||
),
|
||||
app.Div().
|
||||
Class("input-group").
|
||||
Class("input-group-message").
|
||||
Body(
|
||||
app.Label().
|
||||
For("message").
|
||||
Text("Message:"),
|
||||
app.Textarea().
|
||||
Name("message").
|
||||
Class("input").
|
||||
Rows(5).
|
||||
Cols(30).
|
||||
Required(true).
|
||||
OnChange(g.ValueTo(&g.message)),
|
||||
),
|
||||
app.Div().
|
||||
Class("submit-field").
|
||||
Body(
|
||||
app.Input().
|
||||
Type("submit").
|
||||
Value("Send!"),
|
||||
),
|
||||
),
|
||||
).OnSubmit(func(ctx app.Context, e app.Event) {
|
||||
// This was to prevent the page from reloading
|
||||
fmt.Println("Send clicked")
|
||||
e.PreventDefault()
|
||||
if g.name == "" || g.message == "" {
|
||||
fmt.Printf("Error: one or more field(s) are empty. For now unhandled\n")
|
||||
g.gbErrorModalOpen = true
|
||||
g.errorText = "One or more field(s) are empty. Fix that shit man."
|
||||
return
|
||||
}
|
||||
fmt.Println("Nothing too empty")
|
||||
if len(g.name) > 40 || len(g.message) > 360 {
|
||||
fmt.Printf("Error: Your message is too long fucker\n")
|
||||
g.gbModalOpen = true
|
||||
return
|
||||
}
|
||||
fmt.Println("Got through validity")
|
||||
var uuid string = ""
|
||||
// c := client.Jar.Cookies(app.Window().URL())
|
||||
// fmt.Println("Getting cookies")
|
||||
// for _, c2 := range c {
|
||||
// if c2.Name == "spyware" {
|
||||
// uuid = c2.Value
|
||||
// fmt.Println("Found spyware cookie")
|
||||
// }
|
||||
// }
|
||||
// // Check if uuid is set, if it's not, then clearly the cookie does not exist
|
||||
// if uuid == "" {
|
||||
// uuid = "undetermined"
|
||||
// g.errorText = "Failed sending message, couldn't insert UUID"
|
||||
// g.gbErrorModalOpen = true
|
||||
// return
|
||||
// }
|
||||
|
||||
g.OnSubmit(ctx, g.name, g.email, g.website, g.message, uuid)
|
||||
g.clear()
|
||||
ctx.NewAction("guestbook-loadcomments")
|
||||
//g.LoadComments(ctx)
|
||||
}),
|
||||
app.If(
|
||||
g.gbModalOpen,
|
||||
NewGuestbookAlertModal().
|
||||
OnClose(func() {
|
||||
g.gbModalOpen = false
|
||||
g.Update()
|
||||
}).
|
||||
Text("Your name must be <= 40 and your message must be <= 360 characters"),
|
||||
),
|
||||
app.If(
|
||||
g.gbErrorModalOpen,
|
||||
NewGuestbookAlertModal().
|
||||
OnClose(func() {
|
||||
g.gbErrorModalOpen = false
|
||||
g.Update()
|
||||
}).
|
||||
Text(fmt.Sprintf("Error placing comment: %s", g.errorText)),
|
||||
),
|
||||
app.Div().Body(
|
||||
app.Range(g.comments).Slice(func(i int) app.UI {
|
||||
return &guestbookComment{
|
||||
Comment: g.comments[i],
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (g *guestbook) SmartLoadComments(ctx app.Context) {
|
||||
{
|
||||
// Get the comments quickly to at least render something
|
||||
tmpjsondata := make([]byte, 0)
|
||||
err := ctx.LocalStorage().Get("comments", &tmpjsondata)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
ctx.Dispatch(func(ctx app.Context) {
|
||||
err = json.Unmarshal(tmpjsondata, &g.comments)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
g.Update()
|
||||
}
|
||||
var lasthash []byte
|
||||
err := ctx.LocalStorage().Get("lasthash", &lasthash)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
|
||||
if lasthash == nil {
|
||||
fmt.Printf("Program thinks lasthash is empty\n")
|
||||
g.LoadComments(ctx)
|
||||
return
|
||||
}
|
||||
url := ApiURL + "/commenthash"
|
||||
ctx.Async(func() {
|
||||
res, err := http.Get(url)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
hash, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
|
||||
//fmt.Printf("hash: %v\n", hash)
|
||||
//fmt.Printf("lasthash: %v\n", lasthash)
|
||||
|
||||
// If the hash is different, aka there was an update in the comments
|
||||
if !bytes.Equal(hash, lasthash) {
|
||||
fmt.Printf("Hash calculation is different\n")
|
||||
g.LoadComments(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// if the hash is not different, then there is no need to load new comments
|
||||
jsondata := make([]byte, 0)
|
||||
err = ctx.LocalStorage().Get("comments", &jsondata)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
//fmt.Printf("jsondata: %v\n", jsondata)
|
||||
ctx.Dispatch(func(ctx app.Context) {
|
||||
err = json.Unmarshal(jsondata, &g.comments)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
func (g *guestbook) LoadComments(ctx app.Context) {
|
||||
// TODO: maybe you can put this in a localbrowser storage?
|
||||
//fmt.Printf("Called LoadComments()\n")
|
||||
url := ApiURL + "/comment"
|
||||
ctx.Async(func() {
|
||||
res, err := http.Get(url)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
jsondata, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Dispatch(func(ctx app.Context) {
|
||||
err = json.Unmarshal(jsondata, &g.comments)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
ctx.LocalStorage().Set("comments", jsondata)
|
||||
// Calculating the hash
|
||||
//fmt.Printf("Calculating the hash from LoadComments\n")
|
||||
hash := sha256.Sum256(jsondata)
|
||||
//fmt.Printf("hash fresh from calculation: %v\n", hash)
|
||||
//g.lastHash = hash
|
||||
err = ctx.LocalStorage().Set("lasthash", []byte(fmt.Sprintf("%x\n", hash)))
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (g *guestbook) clear() {
|
||||
g.name = ""
|
||||
g.message = ""
|
||||
g.website = ""
|
||||
g.email = ""
|
||||
}
|
||||
|
||||
func (g *guestbook) onHandleLoadComments(ctx app.Context, a app.Action) {
|
||||
g.SmartLoadComments(ctx)
|
||||
g.Update()
|
||||
}
|
||||
|
||||
type guestbookAlertModal struct {
|
||||
app.Compo
|
||||
|
||||
PreviousAttempts int
|
||||
IOnClose func() // For when we close the modal
|
||||
IText string
|
||||
}
|
||||
|
||||
func NewGuestbookAlertModal() *guestbookAlertModal {
|
||||
return &guestbookAlertModal{}
|
||||
}
|
||||
|
||||
func (g *guestbookAlertModal) OnClose(v func()) *guestbookAlertModal {
|
||||
g.IOnClose = v
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *guestbookAlertModal) Text(v string) *guestbookAlertModal {
|
||||
g.IText = v
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *guestbookAlertModal) Render() app.UI {
|
||||
return app.Div().
|
||||
Class("gb-modal").
|
||||
ID("gbModal").
|
||||
OnClick(func(ctx app.Context, e app.Event) {
|
||||
g.IOnClose()
|
||||
}).
|
||||
Body(
|
||||
app.Div().
|
||||
Class("gb-modal-content").
|
||||
Body(
|
||||
app.Span().Class("close").Text("X").
|
||||
OnClick(func(ctx app.Context, e app.Event) {
|
||||
//modal := app.Window().GetElementByID("gbModal")
|
||||
//modal.Set("style", "none")
|
||||
g.IOnClose()
|
||||
}),
|
||||
app.P().Text(g.IText),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
type guestbookComment struct {
|
||||
app.Compo
|
||||
|
||||
Comment entity.Comment
|
||||
time string
|
||||
}
|
||||
|
||||
func (c *guestbookComment) Render() app.UI {
|
||||
c.time = c.Comment.PostDate.Format(time.RFC1123)
|
||||
return app.Div().Body(
|
||||
app.Div().Class().Body(
|
||||
app.P().Text(c.Comment.Name).Class("name"),
|
||||
app.P().Text(c.time).Class("date"),
|
||||
).Class("comment-header"),
|
||||
app.Div().Class("comment-message").Body(
|
||||
app.P().Text(c.Comment.Message),
|
||||
),
|
||||
).Class("comment")
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package components
|
||||
package main
|
||||
|
||||
import "github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
|
||||
|
@ -7,5 +7,10 @@ type header struct {
|
|||
}
|
||||
|
||||
func (h *header) Render() app.UI {
|
||||
return app.Div().Text("Internetica Galactica").Class("header")
|
||||
return app.Div().
|
||||
Class("header").
|
||||
Body(
|
||||
app.Text("Internetica Galactica"),
|
||||
//&updater{},
|
||||
)
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"dutchellie.nl/DutchEllie/proper-website-2/entity"
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
var (
|
||||
ApiURL string
|
||||
)
|
||||
|
||||
type Homepage struct {
|
||||
app.Compo
|
||||
}
|
||||
|
||||
func NewHomepage() *Homepage {
|
||||
return &Homepage{}
|
||||
}
|
||||
|
||||
func (p *Homepage) Render() app.UI {
|
||||
return newPage().
|
||||
Title("Homepage").
|
||||
LeftBar(
|
||||
newHTMLBlock().
|
||||
Class("left").
|
||||
Class("leftbarblock").
|
||||
Src("/web/blocks/snippets/bannerpanel.html"),
|
||||
).
|
||||
Main(
|
||||
newHTMLBlock().
|
||||
Class("right").
|
||||
Class("contentblock").
|
||||
Src("/web/blocks/pages/intro.html"),
|
||||
newUIBlock().
|
||||
Class("right").
|
||||
Class("contentblock").
|
||||
UI(
|
||||
&guestbook{
|
||||
OnSubmit: func(ctx app.Context, name, email, website, message, uuid string) {
|
||||
var comment entity.Comment
|
||||
comment.Name = name
|
||||
comment.Email = email
|
||||
comment.Website = website
|
||||
comment.Message = message
|
||||
comment.UUID = uuid
|
||||
|
||||
jsondata, err := json.Marshal(comment)
|
||||
if err != nil {
|
||||
fmt.Printf("err: %v\n", err)
|
||||
return
|
||||
}
|
||||
url := ApiURL + "/comment"
|
||||
|
||||
// This is not Async'ed, because otherwise you run into a race
|
||||
// condition where you reload the comments before the server had time
|
||||
// to process the request!
|
||||
{
|
||||
req, err := http.Post(url, "application/json", bytes.NewBuffer(jsondata))
|
||||
if err != nil {
|
||||
fmt.Printf("err: %v\n", err)
|
||||
return
|
||||
}
|
||||
if req.StatusCode == 200 {
|
||||
p.Update()
|
||||
}
|
||||
defer req.Body.Close()
|
||||
}
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
type htmlDoc struct {
|
||||
app.Compo
|
||||
|
||||
Ihtml string
|
||||
}
|
||||
|
||||
func newHTMLDoc() *htmlDoc {
|
||||
return &htmlDoc{}
|
||||
}
|
||||
|
||||
func (h *htmlDoc) HTML(v string) *htmlDoc {
|
||||
h.Ihtml = fmt.Sprintf("<div>%s</div>", v)
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *htmlDoc) Render() app.UI {
|
||||
return app.Raw(h.Ihtml)
|
||||
}
|
||||
|
||||
type remoteHTMLDoc struct {
|
||||
app.Compo
|
||||
|
||||
Isrc string
|
||||
|
||||
html htmlContent
|
||||
}
|
||||
|
||||
func newRemoteHTMLDoc() *remoteHTMLDoc {
|
||||
return &remoteHTMLDoc{}
|
||||
}
|
||||
|
||||
func (h *remoteHTMLDoc) Src(v string) *remoteHTMLDoc {
|
||||
h.Isrc = v
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *remoteHTMLDoc) OnMount(ctx app.Context) {
|
||||
h.load(ctx)
|
||||
}
|
||||
|
||||
func (h *remoteHTMLDoc) OnNav(ctx app.Context) {
|
||||
h.load(ctx)
|
||||
}
|
||||
|
||||
func (h *remoteHTMLDoc) load(ctx app.Context) {
|
||||
src := h.Isrc
|
||||
ctx.ObserveState(htmlState(src)).
|
||||
While(func() bool {
|
||||
return src == h.Isrc
|
||||
}).
|
||||
OnChange(func() {
|
||||
|
||||
}).
|
||||
Value(&h.html)
|
||||
|
||||
ctx.NewAction(getHTML, app.T("path", h.Isrc))
|
||||
}
|
||||
|
||||
func (h *remoteHTMLDoc) Render() app.UI {
|
||||
return app.Div().
|
||||
Body(
|
||||
app.If(h.html.Status == loaded,
|
||||
newHTMLDoc().
|
||||
HTML(h.html.Data),
|
||||
).Else(),
|
||||
)
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
const (
|
||||
getHTML = "/html/get"
|
||||
)
|
||||
|
||||
func handleGetHTML(ctx app.Context, a app.Action) {
|
||||
path := a.Tags.Get("path")
|
||||
if path == "" {
|
||||
app.Log(errors.New("getting html failed"))
|
||||
return
|
||||
}
|
||||
|
||||
state := htmlState(path)
|
||||
|
||||
var ht htmlContent
|
||||
ctx.GetState(state, &ht)
|
||||
switch ht.Status {
|
||||
case loading, loaded:
|
||||
return
|
||||
}
|
||||
|
||||
ht.Status = loading
|
||||
ht.Error = nil
|
||||
ctx.SetState(state, ht)
|
||||
|
||||
res, err := get(ctx, path)
|
||||
if err != nil {
|
||||
ht.Status = loadingErr
|
||||
ht.Error = err
|
||||
ctx.SetState(state, ht)
|
||||
return
|
||||
}
|
||||
|
||||
ht.Status = loaded
|
||||
ht.Data = string(res)
|
||||
ctx.SetState(state, ht)
|
||||
}
|
||||
|
||||
func htmlState(src string) string {
|
||||
return src
|
||||
}
|
||||
|
||||
type htmlContent struct {
|
||||
Status status
|
||||
Error error
|
||||
Data string
|
||||
}
|
||||
|
||||
type status int
|
||||
|
||||
const (
|
||||
neverLoaded status = iota
|
||||
loading
|
||||
loadingErr
|
||||
loaded
|
||||
)
|
|
@ -0,0 +1,41 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
func get(ctx app.Context, path string) ([]byte, error) {
|
||||
url := path
|
||||
if !strings.HasPrefix(url, "http") {
|
||||
u := ctx.Page().URL()
|
||||
u.Path = path
|
||||
url = u.String()
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("Error at getting html page\n")
|
||||
return nil, err
|
||||
}
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
// Which means either client or server error
|
||||
if res.StatusCode >= 400 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"os"
|
||||
|
||||
"dutchellie.nl/DutchEllie/proper-website-2/api"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
//type application struct {
|
||||
// client *mongo.Client
|
||||
// database *mongo.Database
|
||||
// collection *mongo.Collection
|
||||
//}
|
||||
|
||||
var jar http.CookieJar
|
||||
var client http.Client
|
||||
|
||||
func main() {
|
||||
// Create cookiejar
|
||||
var err error
|
||||
jar, err = cookiejar.New(nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating cookiejar: %s\n", err.Error())
|
||||
}
|
||||
|
||||
client = http.Client{
|
||||
Jar: jar,
|
||||
}
|
||||
|
||||
homepage := NewHomepage()
|
||||
aboutpage := NewAboutPage()
|
||||
galaxiespage := NewGalaxiesPage()
|
||||
undertalePage := NewUndertalePage()
|
||||
musicPage := NewMusicPage()
|
||||
app.Route("/", homepage)
|
||||
app.Route("/about", aboutpage)
|
||||
app.Route("/galaxies", galaxiespage)
|
||||
app.Route("/undertale", undertalePage)
|
||||
app.Route("/blog", NewBlogPage())
|
||||
app.Route("/music", musicPage)
|
||||
|
||||
app.Handle(getHTML, handleGetHTML)
|
||||
|
||||
// This is executed on the client side only.
|
||||
// It handles client side stuff
|
||||
// It exits immediately on the server side
|
||||
app.RunWhenOnBrowser()
|
||||
|
||||
icon := &app.Icon{
|
||||
Default: "/web/static/images/icon-small.png",
|
||||
Large: "/web/static/images/icon.png",
|
||||
}
|
||||
handler := &app.Handler{
|
||||
Name: "Internetica Galactica",
|
||||
Icon: *icon,
|
||||
BackgroundColor: "#362730",
|
||||
ThemeColor: "#362730",
|
||||
LoadingLabel: "Internetica Galactica",
|
||||
Title: "Internetica Galactica",
|
||||
Description: "A 1990's style PWA!",
|
||||
Author: "Quenten",
|
||||
Keywords: []string{
|
||||
"Based website",
|
||||
"Cool website",
|
||||
"PWA",
|
||||
"Programming",
|
||||
"Go", "Golang",
|
||||
"Webassembly", "WASM",
|
||||
"DutchEllie", "Quenten",
|
||||
},
|
||||
Styles: []string{
|
||||
"/web/static/style.css",
|
||||
"/web/static/adreena.css",
|
||||
"/web/static/anisha.css",
|
||||
"/web/static/havakana.css",
|
||||
"/web/static/form.css",
|
||||
},
|
||||
CacheableResources: []string{
|
||||
// Images
|
||||
"/web/static/images/email3.gif",
|
||||
"/web/static/images/rin-len1.webp",
|
||||
"/web/static/images/background_star.gif",
|
||||
"/web/static/images/kanata-1.gif",
|
||||
"/web/static/images/rin-1.gif",
|
||||
"/web/static/images/rin-2.gif",
|
||||
"/web/static/images/gnu-head-sm.png",
|
||||
// Pages
|
||||
"/web/blocks/pages/about.html",
|
||||
"/web/blocks/pages/intro.html",
|
||||
"/web/blocks/snippets/bannerpanel.html",
|
||||
// Music
|
||||
"https://music-website.s3.nl-ams.scw.cloud/Tokusya-Seizon%20Wonder-la-der%21%21.mp3",
|
||||
"https://music-website.s3.nl-ams.scw.cloud/kegarenaki-barajuuji.mp3",
|
||||
"https://music-website.s3.nl-ams.scw.cloud/error-towa.mp3",
|
||||
"https://music-website.s3.nl-ams.scw.cloud/diamond-city-lights-lazulight.opus",
|
||||
"https://music-website.s3.nl-ams.scw.cloud/tsunami-finana.opus",
|
||||
"https://music-website.s3.nl-ams.scw.cloud/%E7%A5%9E%E3%81%A3%E3%81%BD%E3%81%84%E3%81%AA.m4a",
|
||||
"https://music-website.s3.nl-ams.scw.cloud/Servant%20of%20Evil%20with%20English%20Sub%20-%20%E6%82%AA%E3%83%8E%E5%8F%AC%E4%BD%BF%20-%20Kagamine%20Len%20-%20HQ.m4a",
|
||||
"https://music-website.s3.nl-ams.scw.cloud/%E3%83%94%E3%83%8E%E3%82%AD%E3%82%AA%E3%83%94%E3%83%BC%20-%20%E3%81%8D%E3%81%BF%E3%82%82%E6%82%AA%E3%81%84%E4%BA%BA%E3%81%A7%E3%82%88%E3%81%8B%E3%81%A3%E3%81%9F%20feat.%20%E5%88%9D%E9%9F%B3%E3%83%9F%E3%82%AF%20_%20I%27m%20glad%20you%27re%20evil%20too.m4a",
|
||||
},
|
||||
}
|
||||
|
||||
app.GenerateStaticWebsite("./staticsite", handler)
|
||||
compressed := handlers.CompressHandlerLevel(handler, gzip.BestSpeed)
|
||||
|
||||
// Create spyware module
|
||||
spywareapi, err := api.NewApiApp()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
http.Handle("/", compressed)
|
||||
http.HandleFunc("/api/visit", spywareapi.Visit)
|
||||
|
||||
// router.HandleFunc("/api/visit", spywareapi.Visit)
|
||||
if os.Getenv("GEN_STATIC_SITE") == "true" {
|
||||
return
|
||||
}
|
||||
|
||||
if err := http.ListenAndServe(":8000", nil); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package main
|
||||
|
||||
import "github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
|
||||
type menu struct {
|
||||
app.Compo
|
||||
|
||||
Iclass string
|
||||
updateAvailable bool
|
||||
|
||||
IpaneWidth int
|
||||
OnClickButton func(page string)
|
||||
}
|
||||
|
||||
func newMenu() *menu {
|
||||
return &menu{}
|
||||
}
|
||||
|
||||
func (m *menu) PaneWidth(px int) *menu {
|
||||
if px > 0 {
|
||||
m.IpaneWidth = px
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *menu) Class(v string) *menu {
|
||||
m.Iclass = app.AppendClass(m.Iclass, v)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *menu) OnAppUpdate(ctx app.Context) {
|
||||
m.updateAvailable = ctx.AppUpdateAvailable()
|
||||
}
|
||||
|
||||
func (m *menu) Render() app.UI {
|
||||
return app.Div().
|
||||
Class("block").
|
||||
// Class("leftbarblock-nop").
|
||||
Class("navbar").
|
||||
Body(
|
||||
app.Ul().Body(
|
||||
newMenuLink().
|
||||
Link("/").
|
||||
Text("Home"),
|
||||
newMenuLink().
|
||||
Link("/about").
|
||||
Text("About"),
|
||||
newMenuLink().
|
||||
Link("/galaxies").
|
||||
Text("Galaxies"),
|
||||
newMenuLink().
|
||||
Link("/music").
|
||||
Text("Music"),
|
||||
// Disabled for now since there are none anyway
|
||||
app.Li().
|
||||
Body(
|
||||
app.A().Href("/blog").Text("Blog"),
|
||||
).Style("display", "none"),
|
||||
),
|
||||
app.If(m.updateAvailable,
|
||||
app.Div().Body(
|
||||
app.Img().
|
||||
Src("/web/static/images/hot1.gif").
|
||||
Class("update-img"),
|
||||
app.Span().
|
||||
Text("Update available! Click here to update!").
|
||||
Class("update-text"),
|
||||
).
|
||||
Class("update-div").
|
||||
OnClick(m.onUpdateClick),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (m *menu) onUpdateClick(ctx app.Context, e app.Event) {
|
||||
ctx.Reload()
|
||||
}
|
||||
|
||||
type menuLink struct {
|
||||
app.Compo
|
||||
|
||||
IText string
|
||||
ILink string
|
||||
}
|
||||
|
||||
func newMenuLink() *menuLink {
|
||||
return &menuLink{}
|
||||
}
|
||||
|
||||
func (m *menuLink) Text(v string) *menuLink {
|
||||
m.IText = v
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *menuLink) Link(v string) *menuLink {
|
||||
m.ILink = v
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *menuLink) Render() app.UI {
|
||||
return app.A().
|
||||
Class("menuitem-link").
|
||||
Href(m.ILink).
|
||||
Body(app.Div().
|
||||
Class("menuitem").
|
||||
Body(app.Span().
|
||||
Class("menuitem-text").
|
||||
Text(m.IText)),
|
||||
)
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
//#################################
|
||||
//## ###
|
||||
//## Link with status component ###
|
||||
//## ###
|
||||
//#################################
|
||||
|
||||
type linkWithStatus struct {
|
||||
app.Compo
|
||||
|
||||
Ilink string
|
||||
IText string
|
||||
ILinkText string
|
||||
ILinkBody app.UI
|
||||
status bool
|
||||
}
|
||||
|
||||
func newLinkWithStatus() *linkWithStatus {
|
||||
return &linkWithStatus{}
|
||||
}
|
||||
|
||||
func (f *linkWithStatus) Link(s string) *linkWithStatus {
|
||||
f.Ilink = s
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *linkWithStatus) LinkBody(v app.UI) *linkWithStatus {
|
||||
f.ILinkBody = v
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *linkWithStatus) LinkText(s string) *linkWithStatus {
|
||||
return f.LinkBody(app.Text(s))
|
||||
}
|
||||
|
||||
func (f *linkWithStatus) Text(s string) *linkWithStatus {
|
||||
f.IText = s
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *linkWithStatus) checkStatus(ctx app.Context) {
|
||||
ctx.Async(func() {
|
||||
data := struct {
|
||||
Url string `json:"url"`
|
||||
}{
|
||||
Url: f.Ilink,
|
||||
}
|
||||
jsondata, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", ApiURL+"/checkonline", bytes.NewBuffer(jsondata))
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonresp := struct {
|
||||
Status bool `json:"status"`
|
||||
}{}
|
||||
err = json.Unmarshal(body, &jsonresp)
|
||||
if err != nil {
|
||||
app.Log(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Dispatch(func(ctx app.Context) {
|
||||
f.status = jsonresp.Status
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (f *linkWithStatus) OnNav(ctx app.Context) {
|
||||
f.checkStatus(ctx)
|
||||
}
|
||||
|
||||
func (f *linkWithStatus) Render() app.UI {
|
||||
return app.Div().
|
||||
Body(
|
||||
app.Div().
|
||||
Style("display", "flex").
|
||||
Style("gap", "20px").
|
||||
Body(
|
||||
app.A().
|
||||
Href(f.Ilink).
|
||||
Class("p-h3").
|
||||
Class("m-t5").
|
||||
Body(f.ILinkBody),
|
||||
app.If(f.status,
|
||||
app.P().
|
||||
Style("color", "green").
|
||||
Style("width", "fit-content").
|
||||
Style("margin", "5px 0px 0px 0px").
|
||||
Text("Online"),
|
||||
).Else(
|
||||
app.P().
|
||||
Style("color", "red").
|
||||
Style("width", "fit-content").
|
||||
Style("margin", "5px 0px 0px 0px").
|
||||
Text("Offline"),
|
||||
),
|
||||
),
|
||||
app.P().
|
||||
Class("m-t5").
|
||||
Text(f.IText),
|
||||
)
|
||||
}
|
||||
|
||||
//#################################
|
||||
//## ###
|
||||
//## Text with tooltip ###
|
||||
//## ###
|
||||
//#################################
|
||||
|
||||
type textWithTooltip struct {
|
||||
app.Compo
|
||||
|
||||
ITooltip app.UI
|
||||
IClass string
|
||||
ITextClass string
|
||||
IText string
|
||||
|
||||
activated bool
|
||||
mousex, mousey int
|
||||
}
|
||||
|
||||
func newTextWithTooltip() *textWithTooltip {
|
||||
return &textWithTooltip{}
|
||||
}
|
||||
|
||||
func (f *textWithTooltip) Class(v string) *textWithTooltip {
|
||||
f.IClass = app.AppendClass(f.IClass, v)
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *textWithTooltip) TextClass(v string) *textWithTooltip {
|
||||
f.ITextClass = app.AppendClass(f.ITextClass, v)
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *textWithTooltip) Text(v string) *textWithTooltip {
|
||||
f.IText = v
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *textWithTooltip) Tooltip(v app.UI) *textWithTooltip {
|
||||
f.ITooltip = v
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *textWithTooltip) Render() app.UI {
|
||||
return app.Div().
|
||||
Class(f.IClass).
|
||||
Body(
|
||||
app.Span().
|
||||
Class(f.ITextClass).
|
||||
Text(f.IText).
|
||||
OnMouseOver(func(ctx app.Context, e app.Event) {
|
||||
f.activated = true
|
||||
}).
|
||||
OnMouseMove(func(ctx app.Context, e app.Event) {
|
||||
f.mousex, f.mousey = app.Window().CursorPosition()
|
||||
}).
|
||||
OnMouseOut(func(ctx app.Context, e app.Event) {
|
||||
f.activated = false
|
||||
}),
|
||||
app.If(f.activated,
|
||||
app.Div().
|
||||
Style("position", "fixed").
|
||||
Style("overflow", "hidden").
|
||||
Style("top", fmt.Sprintf("%dpx", f.mousey+20)).
|
||||
Style("left", fmt.Sprintf("%dpx", f.mousex+20)).
|
||||
Body(
|
||||
f.ITooltip,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
//#################################
|
||||
//## ###
|
||||
//## Tooltip ###
|
||||
//## ###
|
||||
//#################################
|
||||
|
||||
type toolTip struct {
|
||||
app.Compo
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package components
|
||||
package main
|
||||
|
||||
import "github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// Idea for this page:
|
||||
// - Make a navbar on the top for different genres and switch the pages content when clicked
|
||||
|
||||
package main
|
||||
|
||||
import "github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
|
||||
type MusicPage struct {
|
||||
app.Compo
|
||||
}
|
||||
|
||||
func NewMusicPage() *MusicPage {
|
||||
return &MusicPage{}
|
||||
}
|
||||
|
||||
func (f *MusicPage) Render() app.UI {
|
||||
return newPage().
|
||||
Title("Music!").
|
||||
LeftBar(
|
||||
newHTMLBlock().
|
||||
Class("left").
|
||||
Class("leftbarblock").
|
||||
Src("/web/blocks/snippets/bannerpanel.html"),
|
||||
).
|
||||
Main(
|
||||
// Genre navbar above this
|
||||
newUIBlock().
|
||||
Class("right").
|
||||
Class("contentblock").
|
||||
UI(
|
||||
app.Div().
|
||||
Body(
|
||||
app.P().
|
||||
Class("m-t5").
|
||||
Text(`I am quite picky with my music most of the time. I rarely enjoy an entire album of an artist and most artists for me have only a couple amazing songs.
|
||||
My tastes in music are almost exclusively Japanese songs. Vocaloid is how I began and nowadays I listen to all sorts of Japanese music.
|
||||
Here are some of the songs, artists and albums I like the most.`),
|
||||
app.P().
|
||||
Class("p-h3").
|
||||
Style("color", "red").
|
||||
Text("Warning! Player feature still in beta. Stuff can break and design is most certainly not final at all!"),
|
||||
app.P().
|
||||
Text("Just click one of the songs to play it."),
|
||||
app.P().
|
||||
Class("p-h2").
|
||||
Text("Songs"),
|
||||
newMusicPlayer(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
const (
|
||||
songRepoURL string = "" // URL where the music files are stored. Made a bit like the ApiURL
|
||||
)
|
||||
|
||||
type song struct {
|
||||
ITitle string
|
||||
IID string
|
||||
IURL string
|
||||
}
|
||||
|
||||
func newSong() *song {
|
||||
return &song{}
|
||||
}
|
||||
|
||||
func (f *song) Title(v string) *song {
|
||||
f.ITitle = v
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *song) URL(v string) *song {
|
||||
f.IURL = v
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *song) ID(v string) *song {
|
||||
f.IID = v
|
||||
return f
|
||||
}
|
||||
|
||||
type musicPlayer struct {
|
||||
app.Compo
|
||||
|
||||
songs map[string](*song)
|
||||
currentlySelectedSong string
|
||||
}
|
||||
|
||||
func newMusicPlayer() *musicPlayer {
|
||||
return &musicPlayer{
|
||||
songs: make(map[string]*song),
|
||||
}
|
||||
}
|
||||
|
||||
// On... handlers
|
||||
|
||||
func (f *musicPlayer) OnMount(ctx app.Context) {
|
||||
ctx.Handle("switchSong", f.handleSwitchSong)
|
||||
// Statically create all the music.
|
||||
// I am not making a database for this shit lmao
|
||||
// Also do not forget to at least set the currentlySelectedSong to the first one added, or at least make it point somewhere
|
||||
// f.songs["ievan-polka"] = newSong().
|
||||
// Title("Ievan Polka - Hatsune Miku").
|
||||
// URL("https://files.catbox.moe/lh229f.mp3").
|
||||
// ID("ievan-polka")
|
||||
f.songs["god-ish"] = newSong().
|
||||
Title("God-ish (神っぽいな) feat. Hatsune Miku - PinocchioP").
|
||||
URL("https://music-website.s3.nl-ams.scw.cloud/%E7%A5%9E%E3%81%A3%E3%81%BD%E3%81%84%E3%81%AA.m4a").
|
||||
ID("god-ish")
|
||||
f.songs["servant-of-evil"] = newSong().
|
||||
Title("Servant of Evil (悪ノ召使) feat. Kagamine Rin - mothy / AkunoP").
|
||||
URL("https://music-website.s3.nl-ams.scw.cloud/Servant%20of%20Evil%20with%20English%20Sub%20-%20%E6%82%AA%E3%83%8E%E5%8F%AC%E4%BD%BF%20-%20Kagamine%20Len%20-%20HQ.m4a").
|
||||
ID("servant-of-evil")
|
||||
f.songs["im-glad-youre-evil-too"] = newSong().
|
||||
Title("I'm glad you're evil too (feat. Hatsune Miku) - PinocchioP").
|
||||
URL("https://music-website.s3.nl-ams.scw.cloud/%E3%83%94%E3%83%8E%E3%82%AD%E3%82%AA%E3%83%94%E3%83%BC%20-%20%E3%81%8D%E3%81%BF%E3%82%82%E6%82%AA%E3%81%84%E4%BA%BA%E3%81%A7%E3%82%88%E3%81%8B%E3%81%A3%E3%81%9F%20feat.%20%E5%88%9D%E9%9F%B3%E3%83%9F%E3%82%AF%20_%20I%27m%20glad%20you%27re%20evil%20too.m4a").
|
||||
ID("im-glad-youre-evil-too")
|
||||
f.songs["tokusya-seizon"] = newSong().
|
||||
Title("Tokusya-Seizon Wonder-la-der!! - Amane Kanata").
|
||||
URL("https://music-website.s3.nl-ams.scw.cloud/Tokusya-Seizon%20Wonder-la-der%21%21.mp3").
|
||||
ID("tokusya-seizon")
|
||||
f.songs["kegarenaki-barajuuji"] = newSong().
|
||||
Title("Kegarenaki Barajuuji - Ariabl'eyeS").
|
||||
URL("https://music-website.s3.nl-ams.scw.cloud/kegarenaki-barajuuji.mp3").
|
||||
ID("kegarenaki-barajuuji")
|
||||
f.songs["error-towa"] = newSong().
|
||||
Title("-ERROR (Cover) - Tokoyami Towa").
|
||||
URL("https://music-website.s3.nl-ams.scw.cloud/error-towa.mp3").
|
||||
ID("error-towa")
|
||||
f.songs["diamond-city-lights"] = newSong().
|
||||
Title("Diamond City Lights - LazuLight").
|
||||
URL("https://music-website.s3.nl-ams.scw.cloud/diamond-city-lights-lazulight.opus").
|
||||
ID("diamond-city-lights")
|
||||
f.songs["tsunami-finana"] = newSong().
|
||||
Title("TSUNAMI - Finana Ryugu").
|
||||
URL("https://music-website.s3.nl-ams.scw.cloud/tsunami-finana.opus").
|
||||
ID("tsunami-finana")
|
||||
}
|
||||
|
||||
// Action handlers
|
||||
|
||||
// Call with a value called "title" to switch to the right song
|
||||
func (f *musicPlayer) handleSwitchSong(ctx app.Context, a app.Action) {
|
||||
title, ok := a.Value.(string)
|
||||
if !ok {
|
||||
app.Log("Error calling handleSwitchSong function. Title value was not found")
|
||||
return
|
||||
}
|
||||
v, ok := f.songs[title]
|
||||
if !ok {
|
||||
app.Log("Error getting song. Song with title does not exist")
|
||||
return
|
||||
}
|
||||
|
||||
f.currentlySelectedSong = v.IID
|
||||
f.Update()
|
||||
}
|
||||
|
||||
func (f *musicPlayer) Render() app.UI {
|
||||
// Don't forget to handle the possibility of no songs having been added and the currentlySelectedSong to be empty
|
||||
if f.currentlySelectedSong == "" {
|
||||
return app.Div().
|
||||
Body(
|
||||
app.Range(f.songs).Map(func(s string) app.UI {
|
||||
return app.Div().
|
||||
Style("border", "solid 1px red").
|
||||
Style("width", "fit-content").
|
||||
Body(
|
||||
app.Span().
|
||||
Style("text-decoration", "underline").
|
||||
Style("width", "fit-content").
|
||||
Class("finger-hover").
|
||||
Text(f.songs[s].ITitle).
|
||||
OnClick(func(ctx app.Context, e app.Event) {
|
||||
ctx.NewActionWithValue("switchSong", f.songs[s].IID)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
return app.Div().Body(
|
||||
app.P().
|
||||
Class("p-h3").
|
||||
Text(fmt.Sprintf("Currently playing: %s", f.songs[f.currentlySelectedSong].ITitle)),
|
||||
app.Audio().
|
||||
Src(f.songs[f.currentlySelectedSong].IURL).
|
||||
Controls(true).
|
||||
AutoPlay(true),
|
||||
// Lots of buttons of songs
|
||||
app.Div().
|
||||
Body(
|
||||
app.Range(f.songs).Map(func(s string) app.UI {
|
||||
return app.Div().
|
||||
Style("border", "solid 1px red").
|
||||
Style("width", "fit-content").
|
||||
Body(
|
||||
app.Span().
|
||||
Style("text-decoration", "underline").
|
||||
Style("width", "fit-content").
|
||||
Class("finger-hover").
|
||||
Text(f.songs[s].ITitle).
|
||||
OnClick(func(ctx app.Context, e app.Event) {
|
||||
ctx.NewActionWithValue("switchSong", f.songs[s].IID)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"dutchellie.nl/DutchEllie/proper-website-2/ui"
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
type navbar struct {
|
||||
app.Compo
|
||||
updateAvailable bool
|
||||
|
||||
OnClickButton func(page string)
|
||||
}
|
||||
|
||||
func (n *navbar) OnAppUpdate(ctx app.Context) {
|
||||
n.updateAvailable = ctx.AppUpdateAvailable()
|
||||
}
|
||||
|
||||
func (n *navbar) Render() app.UI {
|
||||
return ui.Menu().
|
||||
PaneWidth(250).
|
||||
Menu(
|
||||
newMenu(),
|
||||
).
|
||||
HamburgerMenu(
|
||||
newMenu(),
|
||||
)
|
||||
}
|
||||
|
||||
func (n *navbar) onUpdateClick(ctx app.Context, e app.Event) {
|
||||
ctx.Reload()
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
// Page is a generic page. By default it has a header, navbar and a default leftbar
|
||||
type page struct {
|
||||
app.Compo
|
||||
|
||||
Ititle string
|
||||
/*Description
|
||||
Blah blah
|
||||
etc*/
|
||||
|
||||
IbackgroundClass string
|
||||
Ibackground []app.UI
|
||||
IleftBar []app.UI
|
||||
Imain []app.UI
|
||||
|
||||
hideRightContent bool
|
||||
// TODO: Possibly add "updateavailable" here, so it shows up on every page
|
||||
}
|
||||
|
||||
func newPage() *page {
|
||||
return &page{
|
||||
hideRightContent: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *page) Background(v ...app.UI) *page {
|
||||
p.Ibackground = app.FilterUIElems(v...)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *page) BackgroundClass(t string) *page {
|
||||
p.IbackgroundClass = app.AppendClass(p.IbackgroundClass, t)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *page) Title(t string) *page {
|
||||
p.Ititle = t
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *page) LeftBar(v ...app.UI) *page {
|
||||
p.IleftBar = app.FilterUIElems(v...)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *page) Main(v ...app.UI) *page {
|
||||
p.Imain = app.FilterUIElems(v...)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *page) OnMount(ctx app.Context) {
|
||||
ctx.Handle("right-hide", p.hideRight)
|
||||
ctx.Handle("right-show", p.showRight)
|
||||
|
||||
// Send a visit request to the spyware API to track people
|
||||
ctx.Async(func() {
|
||||
resp, err := client.Get("/api/visit")
|
||||
if err != nil {
|
||||
app.Logf("Error while creating vist request %s\n", err.Error())
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
cook, err := resp.Request.Cookie("spyware")
|
||||
if err != nil {
|
||||
app.Logf("Error reading cookie from request: %s\n", err)
|
||||
}
|
||||
fmt.Printf("cook: %v\n", cook)
|
||||
|
||||
ctx.Dispatch(func(ctx app.Context) {
|
||||
fmt.Printf("jar: %v\n", jar)
|
||||
c := client.Jar.Cookies(&url.URL{Host: app.Window().URL().Hostname()})
|
||||
fmt.Printf("c: %v\n", c)
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (p *page) Render() app.UI {
|
||||
if p.IbackgroundClass == "" {
|
||||
p.IbackgroundClass = app.AppendClass(p.IbackgroundClass, "background")
|
||||
}
|
||||
return app.Div().
|
||||
Class("main").
|
||||
Body(
|
||||
// Header and navbar
|
||||
&header{},
|
||||
app.Div().
|
||||
Class("left").
|
||||
Body(
|
||||
&navbar{},
|
||||
app.Range(p.IleftBar).Slice(func(i int) app.UI {
|
||||
return p.IleftBar[i]
|
||||
}),
|
||||
),
|
||||
app.Div().
|
||||
Style("display", visible(p.hideRightContent)).
|
||||
Class("right").
|
||||
ID("right").
|
||||
Body(
|
||||
app.Range(p.Imain).Slice(func(i int) app.UI {
|
||||
return p.Imain[i]
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func visible(v bool) string {
|
||||
if v {
|
||||
return "none"
|
||||
}
|
||||
return "block"
|
||||
}
|
||||
|
||||
func (p *page) hideRight(ctx app.Context, a app.Action) {
|
||||
p.hideRightContent = true
|
||||
}
|
||||
|
||||
func (p *page) showRight(ctx app.Context, a app.Action) {
|
||||
p.hideRightContent = false
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package main
|
||||
|
||||
import "github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
|
||||
type UndertalePage struct {
|
||||
app.Compo
|
||||
}
|
||||
|
||||
// TODO: Autoplay Megalovania
|
||||
|
||||
func NewUndertalePage() *UndertalePage {
|
||||
return &UndertalePage{}
|
||||
}
|
||||
|
||||
func (u *UndertalePage) Render() app.UI {
|
||||
return newPage().
|
||||
Title("Undertale").
|
||||
BackgroundClass("undertale-bg").
|
||||
Background().
|
||||
LeftBar(
|
||||
newHTMLBlock().
|
||||
Class("left").
|
||||
Class("leftbarblock a").
|
||||
Src("/web/blocks/snippets/bannerpanel.html"),
|
||||
).
|
||||
Main(
|
||||
newHTMLBlock().
|
||||
Class("right").
|
||||
Class("contentblock").
|
||||
Src("/web/blocks/pages/undertale.html"),
|
||||
)
|
||||
}
|
|
@ -4,31 +4,40 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="static/style.css">
|
||||
<link rel="stylesheet" href="../web/static/style.css">
|
||||
<link rel="stylesheet" href="../web/static/form.css">
|
||||
<link rel="stylesheet" href="static/anisha.css?family=anisha">
|
||||
<link rel="stylesheet" href="static/adreena.css?family=adreena">
|
||||
<link rel="stylesheet" href="static/havakana.css?family=havakana">
|
||||
<title>Index</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
Internetica Galactica
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="navbar">
|
||||
<ul>
|
||||
<li><a href="base.html">Home</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="content">
|
||||
Dit is eigenlijk 1 van die dingen die steeds kan veranderen
|
||||
Deze doet dat
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<p class="p-h1">My friends!</p>
|
||||
<p>These are some of the websites of my friends!</p>
|
||||
</div>
|
||||
<div class="block right content">
|
||||
<form action="" class="guestbook-form">
|
||||
<div class="input-groups">
|
||||
<div class="fr">
|
||||
<div class="input-group input-group-name">
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" name="name" class="input">
|
||||
</div>
|
||||
<div class="input-group input-group-email">
|
||||
<label for="email">Email:</label>
|
||||
<input type="text" name="email" class="input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group input-group-website">
|
||||
<label for="website">Website:</label>
|
||||
<input type="text" name="website" class="input">
|
||||
</div>
|
||||
<div class="input-group input-group-message">
|
||||
<label for="message">Message:</label>
|
||||
<textarea name="message" cols="30" rows="5" class="input"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="submit-field">
|
||||
<input type="submit" value="Send!">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/maxence-charriere/go-app/v9/pkg/app"
|
||||
)
|
||||
|
||||
type IMenu interface {
|
||||
app.UI
|
||||
|
||||
ID(v string) IMenu
|
||||
Class(v string) IMenu
|
||||
PaneWidth(px int) IMenu
|
||||
HamburgerButton(v app.UI) IMenu
|
||||
HamburgerMenu(v ...app.UI) IMenu
|
||||
Menu(v ...app.UI) IMenu
|
||||
}
|
||||
|
||||
func Menu() IMenu {
|
||||
return &menu{}
|
||||
}
|
||||
|
||||
type menu struct {
|
||||
app.Compo
|
||||
|
||||
Iid string
|
||||
Iclass string
|
||||
Ipanewidth int
|
||||
IhamburgerButton app.UI
|
||||
IhamburgerMenu []app.UI
|
||||
Imenu []app.UI
|
||||
|
||||
hideMenu bool
|
||||
showHamburgerMenu bool
|
||||
width int
|
||||
}
|
||||
|
||||
func (m *menu) ID(v string) IMenu {
|
||||
m.Iid = v
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *menu) Class(v string) IMenu {
|
||||
m.Iclass = app.AppendClass(m.Iclass, v)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *menu) PaneWidth(px int) IMenu {
|
||||
if px > 0 {
|
||||
m.Ipanewidth = px
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *menu) HamburgerButton(v app.UI) IMenu {
|
||||
b := app.FilterUIElems(v)
|
||||
if len(b) != 0 {
|
||||
m.IhamburgerButton = b[0]
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *menu) HamburgerMenu(v ...app.UI) IMenu {
|
||||
m.IhamburgerMenu = app.FilterUIElems(v...)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *menu) Menu(v ...app.UI) IMenu {
|
||||
m.Imenu = app.FilterUIElems(v...)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *menu) OnPreRender(ctx app.Context) {
|
||||
m.refresh(ctx)
|
||||
}
|
||||
|
||||
func (m *menu) OnMount(ctx app.Context) {
|
||||
m.refresh(ctx)
|
||||
}
|
||||
|
||||
func (m *menu) OnResize(ctx app.Context) {
|
||||
m.refresh(ctx)
|
||||
}
|
||||
|
||||
func (m *menu) OnUpdate(ctx app.Context) {
|
||||
m.refresh(ctx)
|
||||
}
|
||||
|
||||
func (m *menu) Render() app.UI {
|
||||
visible := func(v bool) string {
|
||||
if v {
|
||||
return "block"
|
||||
}
|
||||
return "none"
|
||||
}
|
||||
|
||||
return app.Div().
|
||||
ID(m.Iid).
|
||||
Class(m.Iclass).
|
||||
Style("height", "100%").
|
||||
Body(
|
||||
app.Div().
|
||||
//Style("display", "flex").
|
||||
Style("display", visible(!m.hideMenu)).
|
||||
Style("width", "100%").
|
||||
Style("height", "100%").
|
||||
Style("overflow", "hidden").
|
||||
Body(
|
||||
app.Div().
|
||||
Style("position", "relative").
|
||||
Style("display", visible(!m.hideMenu)).
|
||||
Style("flex-shrink", "0").
|
||||
Style("flex-basis", pxToString(m.Ipanewidth)).
|
||||
Style("overflow", "hidden").
|
||||
Body(m.Imenu...),
|
||||
),
|
||||
app.Div().
|
||||
Style("display", visible(m.hideMenu && len(m.IhamburgerMenu) != 0)).
|
||||
Style("position", "absolute").
|
||||
Style("top", "0").
|
||||
Style("left", "0").
|
||||
Style("cursor", "pointer").
|
||||
OnClick(m.onHamburgerButtonClick).
|
||||
Body(
|
||||
app.If(m.IhamburgerButton == nil,
|
||||
app.Div().
|
||||
Class("goapp-shell-hamburger-button-default").
|
||||
Text("☰"),
|
||||
),
|
||||
),
|
||||
app.Div().
|
||||
Style("display", visible(m.hideMenu && m.showHamburgerMenu)).
|
||||
Style("position", "relative").
|
||||
// Style("top", "0").
|
||||
// Style("left", "0").
|
||||
// Style("right", "0").
|
||||
Style("width", "100%").
|
||||
Style("height", "100%").
|
||||
Style("overflow", "hidden").
|
||||
OnClick(m.hideHamburgerMenu).
|
||||
Body(m.IhamburgerMenu...),
|
||||
)
|
||||
}
|
||||
|
||||
func (m *menu) refresh(ctx app.Context) {
|
||||
w, _ := app.Window().Size()
|
||||
hideMenu := true
|
||||
if w >= 914 {
|
||||
hideMenu = false
|
||||
}
|
||||
|
||||
if hideMenu != m.hideMenu ||
|
||||
w != m.width {
|
||||
m.hideMenu = hideMenu
|
||||
m.width = w
|
||||
|
||||
ctx.Defer(func(app.Context) {
|
||||
m.ResizeContent()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func pxToString(px int) string {
|
||||
return strconv.Itoa(px) + "px"
|
||||
}
|
||||
|
||||
func (m *menu) onHamburgerButtonClick(ctx app.Context, e app.Event) {
|
||||
m.showHamburgerMenu = true
|
||||
ctx.NewAction("right-hide")
|
||||
}
|
||||
|
||||
func (m *menu) hideHamburgerMenu(ctx app.Context, e app.Event) {
|
||||
m.showHamburgerMenu = false
|
||||
ctx.NewAction("right-show")
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<p class="p-h1">Test blogpost</p>
|
||||
<p>This is some text under the title</p>
|
||||
<p class="p-h2">Header 2</p>
|
||||
<p class="p-h3">Header 3</p>
|
||||
<p>Regular text here</p>
|
||||
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Minus, omnis nesciunt beatae dolorem laudantium quidem? Eos fugiat doloribus, ipsam, suscipit repudiandae culpa laboriosam accusamus alias atque esse rerum, tenetur illum.</p>
|
|
@ -0,0 +1,22 @@
|
|||
<img src="/web/static/images/rin-1.gif" style="width:100px;position:absolute;top:10px;right:10px;">
|
||||
<p class="content-text">I am a 21 year old computer science student (they/them, he/him, she/her), living and studying in
|
||||
The Netherlands. I like Docker, Kubernetes and Golang!
|
||||
<br>
|
||||
I made this website because I was inspired again by the amazing Neocities pages that I discovered because of my
|
||||
friends.
|
||||
They also have their own pages (you can find them on the Galaxies page, do check them out!) and I just had to get a
|
||||
good website of my own!
|
||||
<br>
|
||||
I am not that great at web development, especially design, but I love trying it regardless!
|
||||
<br><br>
|
||||
To say a bit more about me personally, I love all things computers. From servers to embedded devices! I love the
|
||||
cloud and all that it brings
|
||||
(except for big megacorps, but alright) and it's my goal to work for a big cloud company!
|
||||
<br>
|
||||
Aside from career path ambitions,ボーカロイドはすきです! I love vocaloid and other Japanese music and culture!!
|
||||
I also like Vtubers, especially from Hololive and it's my goal to one day finally understand them in their native
|
||||
language!
|
||||
<br><br>
|
||||
There is a lot more to say in words, but who cares about those! Have a look around my creative digital oasis and see
|
||||
what crazy stuff you can find!
|
||||
</p>
|
|
@ -0,0 +1,65 @@
|
|||
<p class="p-h1">
|
||||
Galaxies
|
||||
</p>
|
||||
<p class="content-text">
|
||||
Here you can find some really really really cool pages that I found on the internet.
|
||||
Some of these are blogs or even blogposts I found, but the ones on top are special!
|
||||
They're the websites of friends of mine! Please visit them, because they worked really hard
|
||||
on their websites as well!
|
||||
</p>
|
||||
<div>
|
||||
<p class="p-h2 mt-20 mb-10 bold">My friends!</p>
|
||||
<ul>
|
||||
<li>
|
||||
<div><a href="https://forestofunix.xyz" class="p-h3 m-t5">Forest of Unix</a>
|
||||
<p class="m-t5">A website by Sebastiaan. A massive Linux fanboy, runs Gentoo on his
|
||||
ThinkPad. Absolutely based.</p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div><a href="https://nymphali.neocities.org" class="p-h3 m-t5">Nymphali</a>
|
||||
<p class="m-t5">The website made by ■■■■■■, whoops Nymphali. They have an awesome
|
||||
minimalist website that's just lovely.</p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div><a class="p-h3 m-t5" href="https://kristypixel.neocities.org">Kristy</a>
|
||||
<p class="m-t5">Website made by Kristy. Very cute website, I love it! Keep up the
|
||||
awesome work!</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p class="p-h2 mt-20 mb-10 bold">Neat webspaces</p>
|
||||
<p class="m-t5" style="margin-left:10px;">Just very neat websites I found. Not necessarily by people I know.
|
||||
I just thought it would be nice to share them here!</p>
|
||||
<ul>
|
||||
<li>
|
||||
<div><a href="https://evillious.ylimegirl.com/" class="p-h3 m-t5">Evillious Chronicles fan guide</a>
|
||||
<p class="m-t5">A VERY cool website made by Ylimegirl! They wrote a whole
|
||||
website dedicated to Evillious Chronicles, which is a super
|
||||
good Japanese light novel and vocaloid series!! Definitely look it up!</p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<a href="https://www.gnu.org/" class="p-h3 m-t5">The GNU Project</a>
|
||||
<div style="display:flex; gap:5px;">
|
||||
<div style="flex:70%">
|
||||
<p class="m-t5">
|
||||
The official website of the GNU project.
|
||||
They advocate for free/libre software.
|
||||
This is not to be confused with 'open source' software.
|
||||
I highly recommend you read about them and their efforts.
|
||||
</p>
|
||||
</div>
|
||||
<div style="flex:30%">
|
||||
<img src="/web/static/images/gnu-head-sm.png" alt="GNU" width="129" height="122" >
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
|
@ -0,0 +1,28 @@
|
|||
<p class="p-h1">Welcome, internet surfer!</p>
|
||||
<div style="position:absolute; top:10px; right:5px;">
|
||||
<p class="small">Please sign my guestbook</p>
|
||||
<img src="/web/static/images/email3.gif" alt="" style="width:40px; position:absolute; bottom:0px; right:0px;">
|
||||
</div>
|
||||
<img src="/web/static/images/rin-len1.webp" alt="" height="230" style="float:right; margin-bottom: 10px;">
|
||||
<p class="content-text">
|
||||
Welcome to my webspace! Whether you stumbled across this page by accident
|
||||
or were linked here, you're more than welcome! This is my personal project that I like
|
||||
to work on! I was inspired by a couple friends of mine, please do check their webspaces
|
||||
out as well under "Galaxies" on the left side there!
|
||||
If you like this page, there is a lot more, so have a look around! You can also leave a
|
||||
nice message for me in the guestbook! There is no registration (unlike the rest of the "modern"
|
||||
internet) so nothing of that sort!
|
||||
That said, this website is my creative outlet and a way to introduce myself, so be kind please!
|
||||
Also its code is entirely open-source and can be found
|
||||
<a href="https://dutchellie.nl/DutchEllie/proper-website-2">on my personal Gitea instance</a> so if you like that
|
||||
sort
|
||||
of stuff, be my guest it's cool!
|
||||
</p>
|
||||
<br>
|
||||
<p class="content-text">
|
||||
<img src="/web/static/images/rin-2.gif" alt="Kagamine Rin drawing" style="float:left; width:100px; margin-right: 20px;">
|
||||
There is a lot of stuff I want to add to this website! In fact, there is also a "staging" website, which might
|
||||
contain
|
||||
new features! It can be found at <a href="https://staging.quenten.nl">staging.quenten.nl</a>.
|
||||
Don't worry about the invalid SSL certificate, that's normal!
|
||||
</p>
|
|
@ -0,0 +1 @@
|
|||
<p>Test</p>
|
|
@ -0,0 +1,8 @@
|
|||
<div style="display: flex; gap:5px; padding:5px" >
|
||||
<div style="flex:50%;">
|
||||
<img src="/web/static/images/kanata-1.gif" alt="Amane Kanata excited!!" style="width:100%" >
|
||||
</div>
|
||||
<div style="flex:50%;">
|
||||
<img src="/web/static/images/kanata-1.gif" alt="Amane Kanata excited!!" style="width:100%" >
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,46 @@
|
|||
.input-groups {
|
||||
display:flex;
|
||||
flex-wrap:wrap;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.input-group-name {
|
||||
width: 50%;
|
||||
float:left;
|
||||
}
|
||||
|
||||
.input-group-email {
|
||||
width: 50%;
|
||||
float:right;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.input-group-website {
|
||||
width: 100%;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.input-group-message {
|
||||
width: 100%;
|
||||
padding-right: 16px;
|
||||
|
||||
}
|
||||
|
||||
.submit-field {
|
||||
text-align: right;
|
||||
padding-right: 10px;
|
||||
padding-bottom: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fr {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 384 B |
Binary file not shown.
After Width: | Height: | Size: 1.4 MiB |
Binary file not shown.
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
After Width: | Height: | Size: 192 KiB |
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
|
@ -1,9 +1,64 @@
|
|||
html {
|
||||
background-image: url(images/background_star.gif);
|
||||
overflow-y: scroll;
|
||||
/* overflow-y: scroll; */
|
||||
margin: 0;
|
||||
height: 99%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
height: 99%;
|
||||
background-image: url(images/background_star.gif);
|
||||
}
|
||||
|
||||
.background {
|
||||
background-image: url(images/background_star.gif);
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.undertale-bg {
|
||||
background-image: url(images/ut-bg.webp);
|
||||
width: 100%;
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
.header {
|
||||
border: 3px solid;
|
||||
border-radius: 4px;
|
||||
border-color: rgb(252, 230, 255);
|
||||
margin-bottom: 5px;
|
||||
background-color: rgb(54, 39, 48);
|
||||
font-size: 5em;
|
||||
font-family: anisha;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.update-div {
|
||||
font-size: 0.8rem;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.update-div:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.finger-hover:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.update-text {
|
||||
align-self: center;
|
||||
flex: 70%;
|
||||
}
|
||||
|
||||
.update-img {
|
||||
flex: 30%;
|
||||
}
|
||||
|
||||
.main {
|
||||
margin-top: 5px;
|
||||
width: 900px;
|
||||
position: relative;
|
||||
margin-left: auto;
|
||||
|
@ -11,23 +66,30 @@ body {
|
|||
color:aliceblue;
|
||||
font-family: havakana;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.header {
|
||||
border: 3px solid;
|
||||
border-radius: 4px;
|
||||
border-color: rgb(252, 230, 255);
|
||||
background-color: rgb(54, 39, 48);
|
||||
font-size: 5em;
|
||||
font-family: anisha;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.main {
|
||||
margin-top: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
text-decoration: none;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.navbar a, a:link, a:visited{
|
||||
text-decoration: none;
|
||||
color:#fce6ff
|
||||
}
|
||||
|
||||
.left {
|
||||
float:left;
|
||||
max-width: 256px;
|
||||
}
|
||||
|
||||
.right {
|
||||
float:right;
|
||||
max-width: 614px;
|
||||
}
|
||||
|
||||
.leftbar {
|
||||
border: 3px solid;
|
||||
border-radius: 4px;
|
||||
border-color: rgb(252, 230, 255);
|
||||
|
@ -35,13 +97,33 @@ body {
|
|||
position: relative;
|
||||
float:left;
|
||||
width: 250px;
|
||||
text-decoration: none;
|
||||
list-style: none;
|
||||
padding: 5px 0px;
|
||||
}
|
||||
|
||||
.navbar a, a:link, a:visited{
|
||||
text-decoration: none;
|
||||
color:rgb(252, 230, 255)
|
||||
.block {
|
||||
border: 3px solid;
|
||||
border-radius: 4px;
|
||||
border-color: rgb(252, 230, 255);
|
||||
background-color: rgb(54, 39, 48);
|
||||
margin-bottom: 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.leftbarblock-nop {
|
||||
float:left;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.leftbarblock {
|
||||
float:left;
|
||||
width: 250px;
|
||||
padding: 5px 0px;
|
||||
}
|
||||
|
||||
.contentblock {
|
||||
float:right;
|
||||
width: 614px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.content {
|
||||
|
@ -56,13 +138,28 @@ body {
|
|||
padding: 10px;
|
||||
}
|
||||
|
||||
.no-border {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
max-width: 80%;
|
||||
max-width: 75%;
|
||||
width: auto;
|
||||
margin-left: 10px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.blogpost-bar {
|
||||
|
||||
}
|
||||
|
||||
.blogpost-titles {
|
||||
margin-left: 10px;
|
||||
text-decoration: none;
|
||||
color: rgb(252, 230, 255);
|
||||
}
|
||||
|
||||
/*
|
||||
.content p {
|
||||
max-width: 80%;
|
||||
|
@ -71,10 +168,11 @@ body {
|
|||
margin-bottom: 5px;
|
||||
}*/
|
||||
|
||||
.gbp {
|
||||
|
||||
.guestbook-form-div {
|
||||
margin: 0px 0px 10px 0px;
|
||||
}
|
||||
|
||||
|
||||
.comment {
|
||||
border: 2px solid;
|
||||
border-radius: 6px;
|
||||
|
@ -200,6 +298,33 @@ div.comment-message p{
|
|||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.update-message {
|
||||
font-family: havakana;
|
||||
margin-top: 0px;
|
||||
|
||||
}
|
||||
|
||||
.pulsing {
|
||||
animation-name: pulsing;
|
||||
animation-duration: 0.4s;
|
||||
animation-timing-function: ease-out;
|
||||
animation-direction: alternate;
|
||||
animation-iteration-count: infinite;
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
@keyframes pulsing {
|
||||
0% {
|
||||
transform: scale(.7);
|
||||
}
|
||||
|
||||
50% {
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
}
|
||||
/*Margin top 5px*/
|
||||
.m-t5 {
|
||||
margin-top: 5px;
|
||||
|
@ -211,6 +336,94 @@ div.comment-message p{
|
|||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mt-20 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.mb-10 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.fit {
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 914px) {
|
||||
.header {
|
||||
font-size: 10vw;
|
||||
height: fit-content;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.main {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.block {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.leftbarblock {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.left {
|
||||
z-index: 1;
|
||||
max-width: unset;
|
||||
width: 100%;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
height: 100%;
|
||||
display: flexbox;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.navbar ul {
|
||||
padding-inline-end: 40px;
|
||||
}
|
||||
|
||||
.menuitem {
|
||||
padding: 3.5mm 10px;
|
||||
margin-bottom: 10px;
|
||||
min-width: 65px;
|
||||
flex: 1 1 auto;
|
||||
border: 0px none;
|
||||
border-radius: 6px;
|
||||
background-color: #4974a5;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.menuitem-text {
|
||||
|
||||
}
|
||||
|
||||
.menuitem-link {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.menuitem-link a {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue