Embed resources into templates

This commit is contained in:
2019-12-23 23:56:57 +01:00
parent 269bf7eed1
commit 7b5723705c
87 changed files with 12669 additions and 7216 deletions

View File

@@ -6,21 +6,20 @@ import (
"net/http"
"fornaxian.com/pixeldrain-web/pixelapi"
"fornaxian.com/pixeldrain-web/webcontroller/forms"
"github.com/Fornaxian/log"
)
func (wc *WebController) adminGlobalsForm(td *TemplateData, r *http.Request) (f forms.Form) {
func (wc *WebController) adminGlobalsForm(td *TemplateData, r *http.Request) (f Form) {
if isAdmin, err := td.PixelAPI.UserIsAdmin(); err != nil {
td.Title = err.Error()
return forms.Form{Title: td.Title}
return Form{Title: td.Title}
} else if !isAdmin.IsAdmin {
td.Title = ";)"
return forms.Form{Title: td.Title}
return Form{Title: td.Title}
}
td.Title = "Pixeldrain global configuration"
f = forms.Form{
f = Form{
Name: "admin_globals",
Title: td.Title,
PreFormHTML: template.HTML("<p>Careful! The slightest typing error could bring the whole website down</p>"),
@@ -35,16 +34,17 @@ func (wc *WebController) adminGlobalsForm(td *TemplateData, r *http.Request) (f
}
var globalsMap = make(map[string]string)
for _, v := range globals.Globals {
f.Fields = append(f.Fields, forms.Field{
f.Fields = append(f.Fields, Field{
Name: v.Key,
DefaultValue: v.Value,
Label: v.Key,
Type: func() forms.FieldType {
Type: func() FieldType {
switch v.Key {
case
"email_address_change_body",
"email_password_reset_body":
return forms.FieldTypeTextarea
"email_password_reset_body",
"email_register_user_body":
return FieldTypeTextarea
case
"api_ratelimit_limit",
"api_ratelimit_rate",
@@ -52,9 +52,9 @@ func (wc *WebController) adminGlobalsForm(td *TemplateData, r *http.Request) (f
"file_inactive_expiry_days",
"max_file_size",
"pixelstore_min_redundancy":
return forms.FieldTypeNumber
return FieldTypeNumber
default:
return forms.FieldTypeText
return FieldTypeText
}
}(),
})

View File

@@ -1,4 +1,4 @@
package forms
package webcontroller
import (
"fmt"

View File

@@ -1,77 +0,0 @@
package webcontroller
import (
"html/template"
"net/http"
"net/url"
"time"
"fornaxian.com/pixeldrain-web/pixelapi"
"fornaxian.com/pixeldrain-web/webcontroller/forms"
"github.com/Fornaxian/log"
)
// TemplateData is a struct that every template expects when being rendered. In
// the field Other you can pass your own template-specific variables.
type TemplateData struct {
Authenticated bool
Username string
Email string
UserAgent string
UserStyle template.CSS
APIEndpoint template.URL
PixelAPI *pixelapi.PixelAPI
// Only used on file viewer page
Title string
OGData template.HTML
Other interface{}
URLQuery url.Values
// Only used for pages containing forms
Form forms.Form
}
func (wc *WebController) newTemplateData(w http.ResponseWriter, r *http.Request) *TemplateData {
var t = &TemplateData{
Authenticated: false,
Username: "",
UserAgent: r.UserAgent(),
UserStyle: userStyle(r),
APIEndpoint: template.URL(wc.conf.APIURLExternal),
URLQuery: r.URL.Query(),
}
if key, err := wc.getAPIKey(r); err == nil {
t.PixelAPI = pixelapi.New(wc.conf.APIURLInternal, key)
uinf, err := t.PixelAPI.UserInfo()
if err != nil {
// This session key doesn't work, or the backend is down, user
// cannot be authenticated
log.Debug("Session check for key '%s' failed: %s", key, err)
if err.Error() == "authentication_required" || err.Error() == "authentication_failed" {
// This key is invalid, delete it
log.Debug("Deleting invalid API key")
http.SetCookie(w, &http.Cookie{
Name: "pd_auth_key",
Value: "",
Path: "/",
Expires: time.Unix(0, 0),
Domain: wc.conf.SessionCookieDomain,
})
}
return t
}
// Authentication succeeded
t.Authenticated = true
t.Username = uinf.Username
t.Email = uinf.Email
} else {
t.PixelAPI = pixelapi.New(wc.conf.APIURLInternal, "")
}
return t
}

View File

@@ -1,147 +0,0 @@
package webcontroller
import (
"fmt"
"html/template"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"fornaxian.com/pixeldrain-api/util"
"github.com/Fornaxian/log"
)
// TemplateManager parses templates and provides utility functions to the
// templates' scripting language
type TemplateManager struct {
templates *template.Template
// Config
templateDir string
externalAPIEndpoint string
debugModeEnabled bool
}
// NewTemplateManager creates a new template manager
func NewTemplateManager(templateDir, externalAPIEndpoint string, debugMode bool) *TemplateManager {
return &TemplateManager{
templateDir: templateDir,
externalAPIEndpoint: externalAPIEndpoint,
debugModeEnabled: debugMode,
}
}
// ParseTemplates parses the templates in the template directory which is
// defined in the config file.
// If silent is false it will print an info log message for every template found
func (tm *TemplateManager) ParseTemplates(silent bool) {
var templatePaths []string
filepath.Walk(tm.templateDir, func(path string, f os.FileInfo, err error) error {
if f.IsDir() {
return nil
}
templatePaths = append(templatePaths, path)
if !silent {
log.Info("Template found: %s", path)
}
return nil
})
tpl := template.New("")
// Import template functions from funcs.go
tpl = tpl.Funcs(tm.funcMap())
var err error
tpl, err = tpl.ParseFiles(templatePaths...)
if err != nil {
log.Error("Template parsing failed: %v", err)
}
// Swap out the old templates with the new templates, to minimize
// modifications to the original variable.
tm.templates = tpl
}
// Get returns the templates, so they can be used to render views
func (tm *TemplateManager) Get() *template.Template {
if tm.debugModeEnabled {
tm.ParseTemplates(true)
}
return tm.templates
}
func (tm *TemplateManager) funcMap() template.FuncMap {
return template.FuncMap{
"bgPattern": tm.bgPattern,
"isBrave": tm.isBrave,
"debugMode": tm.debugMode,
"apiUrl": tm.apiURL,
"pageNr": tm.pageNr,
"add": tm.add,
"sub": tm.sub,
"formatData": tm.formatData,
}
}
func (tm *TemplateManager) bgPattern() string {
var now = time.Now()
if now.Weekday() == time.Wednesday && now.UnixNano()%10 == 0 {
return "checker_wednesday.png"
}
return fmt.Sprintf("checker%d.png", now.UnixNano()%17)
}
func (tm *TemplateManager) isBrave(useragent string) bool {
return strings.Contains(useragent, "Brave")
}
func (tm *TemplateManager) debugMode() bool {
return tm.debugModeEnabled
}
func (tm *TemplateManager) apiURL() string {
return tm.externalAPIEndpoint
}
func (tm *TemplateManager) pageNr(s string) (nr int) {
// Atoi returns 0 on error, which is fine for page numbers
if nr, _ = strconv.Atoi(s); nr < 0 {
return 0
}
return nr
}
func (tm *TemplateManager) add(a, b interface{}) int {
return detectInt(a) + detectInt(b)
}
func (tm *TemplateManager) sub(a, b interface{}) int {
return detectInt(a) - detectInt(b)
}
func (tm *TemplateManager) formatData(i int) string {
return util.FormatData(uint64(i))
}
func detectInt(i interface{}) int {
switch v := i.(type) {
case int:
return int(v)
case int8:
return int(v)
case int16:
return int(v)
case int32:
return int(v)
case int64:
return int(v)
case uint:
return int(v)
case uint8:
return int(v)
case uint16:
return int(v)
case uint32:
return int(v)
case uint64:
return int(v)
}
panic(fmt.Sprintf("%v is not an int", i))
}

258
webcontroller/templates.go Normal file
View File

@@ -0,0 +1,258 @@
package webcontroller
import (
"bytes"
"encoding/base64"
"fmt"
"html/template"
"io/ioutil"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"fornaxian.com/pixeldrain-api/util"
"fornaxian.com/pixeldrain-web/pixelapi"
"github.com/Fornaxian/log"
)
// TemplateData is a struct that every template expects when being rendered. In
// the field Other you can pass your own template-specific variables.
type TemplateData struct {
Authenticated bool
Username string
Email string
UserAgent string
Style pixeldrainStyleSheet
UserStyle template.CSS
APIEndpoint template.URL
PixelAPI *pixelapi.PixelAPI
// Only used on file viewer page
Title string
OGData template.HTML
Other interface{}
URLQuery url.Values
// Only used for pages containing forms
Form Form
}
func (wc *WebController) newTemplateData(w http.ResponseWriter, r *http.Request) *TemplateData {
var t = &TemplateData{
Authenticated: false,
Username: "",
UserAgent: r.UserAgent(),
Style: userStyle(r),
UserStyle: template.CSS(userStyle(r).String()),
APIEndpoint: template.URL(wc.conf.APIURLExternal),
URLQuery: r.URL.Query(),
}
if key, err := wc.getAPIKey(r); err == nil {
t.PixelAPI = pixelapi.New(wc.conf.APIURLInternal, key)
uinf, err := t.PixelAPI.UserInfo()
if err != nil {
// This session key doesn't work, or the backend is down, user
// cannot be authenticated
log.Debug("Session check for key '%s' failed: %s", key, err)
if err.Error() == "authentication_required" || err.Error() == "authentication_failed" {
// This key is invalid, delete it
log.Debug("Deleting invalid API key")
http.SetCookie(w, &http.Cookie{
Name: "pd_auth_key",
Value: "",
Path: "/",
Expires: time.Unix(0, 0),
Domain: wc.conf.SessionCookieDomain,
})
}
return t
}
// Authentication succeeded
t.Authenticated = true
t.Username = uinf.Username
t.Email = uinf.Email
} else {
t.PixelAPI = pixelapi.New(wc.conf.APIURLInternal, "")
}
return t
}
// TemplateManager parses templates and provides utility functions to the
// templates' scripting language
type TemplateManager struct {
tpl *template.Template
// Config
resourceDir string
externalAPIEndpoint string
debugModeEnabled bool
}
// NewTemplateManager creates a new template manager
func NewTemplateManager(resourceDir, externalAPIEndpoint string, debugMode bool) *TemplateManager {
return &TemplateManager{
resourceDir: resourceDir,
externalAPIEndpoint: externalAPIEndpoint,
debugModeEnabled: debugMode,
}
}
// ParseTemplates parses the templates in the template directory which is
// defined in the config file.
// If silent is false it will print an info log message for every template found
func (tm *TemplateManager) ParseTemplates(silent bool) {
var err error
var templatePaths []string
tpl := template.New("")
// Import template functions
tpl.Funcs(template.FuncMap{
"bgPattern": tm.bgPattern,
"isBrave": tm.isBrave,
"debugMode": tm.debugMode,
"apiUrl": tm.apiURL,
"pageNr": tm.pageNr,
"add": tm.add,
"sub": tm.sub,
"formatData": tm.formatData,
})
// Parse dynamic templates
if err = filepath.Walk(tm.resourceDir+"/template", func(path string, f os.FileInfo, err error) error {
if f == nil || f.IsDir() {
return nil
}
templatePaths = append(templatePaths, path)
if !silent {
log.Info("Template found: %s", path)
}
return nil
}); err != nil {
log.Error("Failed to parse templates: %s", err)
}
if _, err = tpl.ParseFiles(templatePaths...); err != nil {
log.Error("Template parsing failed: %v", err)
}
// Parse static resources
var file []byte
if err = filepath.Walk(tm.resourceDir+"/include", func(path string, f os.FileInfo, err error) error {
if f == nil || f.IsDir() {
return nil
}
if file, err = ioutil.ReadFile(path); err != nil {
return err
}
if strings.HasSuffix(path, ".png") {
file = []byte("data:image/png;base64," + base64.StdEncoding.EncodeToString(file))
} else if strings.HasSuffix(path, ".gif") {
file = []byte("data:image/gif;base64," + base64.StdEncoding.EncodeToString(file))
}
// Wrap the resources in a template definition
if _, err = tpl.Parse(
`{{define "` + f.Name() + `"}}` + string(file) + `{{end}}`,
); err != nil {
return err
}
if !silent {
log.Info("Template parsed: %s", path)
}
return nil
}); err != nil {
log.Error("Failed to parse templates: %s", err)
}
tm.tpl = tpl
}
// Get returns the templates, so they can be used to render views
func (tm *TemplateManager) Get() *template.Template {
if tm.debugModeEnabled {
tm.ParseTemplates(true)
}
return tm.tpl
}
// Templace functions. These can be called from within the template to execute
// more specialized actions
func (tm *TemplateManager) bgPattern() template.URL {
var now = time.Now()
var file string
if now.Weekday() == time.Wednesday && now.UnixNano()%10 == 0 {
file = "checker_wednesday.png"
} else {
file = fmt.Sprintf("checker%d.png", now.UnixNano()%17)
}
var buf = bytes.Buffer{}
if err := tm.tpl.ExecuteTemplate(&buf, file, nil); err != nil {
panic(err)
}
return template.URL(buf.String())
}
func (tm *TemplateManager) isBrave(useragent string) bool {
return strings.Contains(useragent, "Brave")
}
func (tm *TemplateManager) debugMode() bool {
return tm.debugModeEnabled
}
func (tm *TemplateManager) apiURL() string {
return tm.externalAPIEndpoint
}
func (tm *TemplateManager) pageNr(s string) (nr int) {
// Atoi returns 0 on error, which is fine for page numbers
if nr, _ = strconv.Atoi(s); nr < 0 {
return 0
}
return nr
}
func (tm *TemplateManager) add(a, b interface{}) int {
return detectInt(a) + detectInt(b)
}
func (tm *TemplateManager) sub(a, b interface{}) int {
return detectInt(a) - detectInt(b)
}
func (tm *TemplateManager) formatData(i int) string {
return util.FormatData(uint64(i))
}
func detectInt(i interface{}) int {
switch v := i.(type) {
case int:
return int(v)
case int8:
return int(v)
case int16:
return int(v)
case int32:
return int(v)
case int64:
return int(v)
case uint:
return int(v)
case uint8:
return int(v)
case uint16:
return int(v)
case uint32:
return int(v)
case uint64:
return int(v)
}
panic(fmt.Sprintf("%v is not an int", i))
}

View File

@@ -6,7 +6,6 @@ import (
"time"
"fornaxian.com/pixeldrain-web/pixelapi"
"fornaxian.com/pixeldrain-web/webcontroller/forms"
"github.com/Fornaxian/log"
"github.com/julienschmidt/httprouter"
)
@@ -26,7 +25,7 @@ func (wc *WebController) serveLogout(
http.Redirect(w, r, "/", http.StatusSeeOther)
}
func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f forms.Form) {
func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f Form) {
// This only runs on the first request
if wc.captchaSiteKey == "" {
capt, err := td.PixelAPI.GetRecaptcha()
@@ -47,16 +46,16 @@ func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f form
// Construct the form
td.Title = "Register a new Pixeldrain account"
f = forms.Form{
f = Form{
Name: "register",
Title: td.Title,
Fields: []forms.Field{
Fields: []Field{
{
Name: "username",
Label: "Username",
Description: "used for logging into your account",
Separator: true,
Type: forms.FieldTypeUsername,
Type: FieldTypeUsername,
}, {
Name: "e-mail",
Label: "E-mail address",
@@ -64,11 +63,11 @@ func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f form
"used for password resets and important account " +
"notifications",
Separator: true,
Type: forms.FieldTypeEmail,
Type: FieldTypeEmail,
}, {
Name: "password1",
Label: "Password",
Type: forms.FieldTypeNewPassword,
Type: FieldTypeNewPassword,
}, {
Name: "password2",
Label: "Password verification",
@@ -76,7 +75,7 @@ func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f form
"can verify that no typing errors were made, which would " +
"prevent you from logging into your new account",
Separator: true,
Type: forms.FieldTypeNewPassword,
Type: FieldTypeNewPassword,
}, {
Name: "recaptcha_response",
Label: "Turing test (click the white box)",
@@ -84,7 +83,7 @@ func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f form
"are not an evil robot that is trying to flood the " +
"website with fake accounts",
Separator: true,
Type: forms.FieldTypeCaptcha,
Type: FieldTypeCaptcha,
CaptchaSiteKey: wc.captchaKey(),
},
},
@@ -131,20 +130,20 @@ func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f form
return f
}
func (wc *WebController) loginForm(td *TemplateData, r *http.Request) (f forms.Form) {
func (wc *WebController) loginForm(td *TemplateData, r *http.Request) (f Form) {
td.Title = "Login"
f = forms.Form{
f = Form{
Name: "login",
Title: "Log in to your pixeldrain account",
Fields: []forms.Field{
Fields: []Field{
{
Name: "username",
Label: "Username / e-mail",
Type: forms.FieldTypeUsername,
Type: FieldTypeUsername,
}, {
Name: "password",
Label: "Password",
Type: forms.FieldTypeCurrentPassword,
Type: FieldTypeCurrentPassword,
},
},
BackLink: "/",
@@ -186,26 +185,26 @@ func (wc *WebController) loginForm(td *TemplateData, r *http.Request) (f forms.F
return f
}
func (wc *WebController) passwordResetForm(td *TemplateData, r *http.Request) (f forms.Form) {
func (wc *WebController) passwordResetForm(td *TemplateData, r *http.Request) (f Form) {
td.Title = "Recover lost password"
f = forms.Form{
f = Form{
Name: "password_reset",
Title: td.Title,
Fields: []forms.Field{
Fields: []Field{
{
Name: "email",
Label: "E-mail address",
Description: "we will send a password reset link to this " +
"e-mail address",
Separator: true,
Type: forms.FieldTypeEmail,
Type: FieldTypeEmail,
}, {
Name: "recaptcha_response",
Label: "Turing test (click the white box)",
Description: "the reCaptcha turing test verifies that you " +
"are not an evil robot that is trying hijack accounts",
Separator: true,
Type: forms.FieldTypeCaptcha,
Type: FieldTypeCaptcha,
CaptchaSiteKey: wc.captchaKey(),
},
},
@@ -223,7 +222,62 @@ func (wc *WebController) passwordResetForm(td *TemplateData, r *http.Request) (f
}
} else {
f.SubmitSuccess = true
f.SubmitMessages = []template.HTML{"Success! E-mail sent"}
f.SubmitMessages = []template.HTML{
"Success! Check your inbox for instructions to reset your password",
}
}
}
return f
}
func (wc *WebController) passwordResetConfirmForm(td *TemplateData, r *http.Request) (f Form) {
td.Title = "Reset lost password"
f = Form{
Name: "password_reset_confirm",
Title: td.Title,
Fields: []Field{
{
Name: "password1",
Label: "password",
Type: FieldTypeNewPassword,
}, {
Name: "password2",
Label: "password again",
Description: "you need to enter your password twice so we " +
"can verify that no typing errors were made, which would " +
"prevent you from logging into your new account",
Separator: true,
Type: FieldTypeNewPassword,
},
},
SubmitLabel: "Submit",
}
var resetKey = r.FormValue("key")
if resetKey == "" {
f.SubmitSuccess = false
f.SubmitMessages = []template.HTML{"Password reset key required"}
return f
}
if f.ReadInput(r) {
if f.FieldVal("password1") != f.FieldVal("password2") {
f.SubmitMessages = []template.HTML{
"Password verification failed. Please enter the same " +
"password in both password fields"}
return f
}
if err := td.PixelAPI.UserPasswordResetConfirm(resetKey, f.FieldVal("password1")); err != nil {
if err.Error() == "not_found" {
f.SubmitMessages = []template.HTML{template.HTML("Password reset key not found")}
} else {
log.Error("%s", err)
f.SubmitMessages = []template.HTML{"Internal Server Error"}
}
} else {
f.SubmitSuccess = true
f.SubmitMessages = []template.HTML{"Success! You can now log in with your new password"}
}
}
return f

View File

@@ -5,7 +5,6 @@ import (
"net/http"
"fornaxian.com/pixeldrain-web/pixelapi"
"fornaxian.com/pixeldrain-web/webcontroller/forms"
"github.com/Fornaxian/log"
"github.com/julienschmidt/httprouter"
)
@@ -24,9 +23,9 @@ func (wc *WebController) serveUserSettings(
td.Title = "Account settings"
td.Other = struct {
PasswordForm forms.Form
EmailForm forms.Form
UsernameForm forms.Form
PasswordForm Form
EmailForm Form
UsernameForm Form
}{
PasswordForm: wc.passwordForm(td, r),
EmailForm: wc.emailForm(td, r),
@@ -35,26 +34,26 @@ func (wc *WebController) serveUserSettings(
wc.templates.Get().ExecuteTemplate(w, "user_settings", td)
}
func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f forms.Form) {
f = forms.Form{
func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f Form) {
f = Form{
Name: "password_change",
Title: "Change password",
Fields: []forms.Field{
Fields: []Field{
{
Name: "old_password",
Label: "Old Password",
Type: forms.FieldTypeCurrentPassword,
Type: FieldTypeCurrentPassword,
}, {
Name: "new_password1",
Label: "New Password",
Type: forms.FieldTypeNewPassword,
Type: FieldTypeNewPassword,
}, {
Name: "new_password2",
Label: "New Password again",
Description: "we need you to repeat your password so you " +
"won't be locked out of your account if you make a " +
"typing error",
Type: forms.FieldTypeNewPassword,
Type: FieldTypeNewPassword,
},
},
SubmitLabel: "Submit",
@@ -89,11 +88,11 @@ func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f form
return f
}
func (wc *WebController) emailForm(td *TemplateData, r *http.Request) (f forms.Form) {
f = forms.Form{
func (wc *WebController) emailForm(td *TemplateData, r *http.Request) (f Form) {
f = Form{
Name: "email_change",
Title: "Change e-mail address",
Fields: []forms.Field{
Fields: []Field{
{
Name: "new_email",
Label: "New e-mail address",
@@ -101,7 +100,7 @@ func (wc *WebController) emailForm(td *TemplateData, r *http.Request) (f forms.F
"verify that it's real. The address will be saved once " +
"the link in the message is clicked. If the e-mail " +
"doesn't arrive right away please check your spam box too",
Type: forms.FieldTypeEmail,
Type: FieldTypeEmail,
},
},
SubmitLabel: "Submit",
@@ -150,11 +149,11 @@ func (wc *WebController) serveEmailConfirm(
wc.templates.Get().ExecuteTemplate(w, "email_confirm", td)
}
func (wc *WebController) usernameForm(td *TemplateData, r *http.Request) (f forms.Form) {
f = forms.Form{
func (wc *WebController) usernameForm(td *TemplateData, r *http.Request) (f Form) {
f = Form{
Name: "username_change",
Title: "Change username",
Fields: []forms.Field{
Fields: []Field{
{
Name: "new_username",
Label: "New username",
@@ -162,7 +161,7 @@ func (wc *WebController) usernameForm(td *TemplateData, r *http.Request) (f form
"used to log in. If you forget your username you can " +
"still log in using your e-mail address if you have one " +
"configured",
Type: forms.FieldTypeUsername,
Type: FieldTypeUsername,
},
},
SubmitLabel: "Submit",

View File

@@ -2,11 +2,10 @@ package webcontroller
import (
"fmt"
"html/template"
"net/http"
)
func userStyle(r *http.Request) (style template.CSS) {
func userStyle(r *http.Request) (style pixeldrainStyleSheet) {
var selectedStyle pixeldrainStyleSheet
if cookie, err := r.Cookie("style"); err != nil {
@@ -34,7 +33,40 @@ func userStyle(r *http.Request) (style template.CSS) {
}
}
return template.CSS(fmt.Sprintf(
return selectedStyle
}
type pixeldrainStyleSheet struct {
TextColor hsl
InputColor hsl // Buttons, text fields
InputTextColor hsl
HighlightColor hsl // Links, highlighted buttons, list navigation
HighlightTextColor hsl // Text on buttons
DangerColor hsl
FileBackgroundColor hsl
ScrollbarForegroundColor hsl
ScrollbarHoverColor hsl
ScrollbarBackgroundColor hsl
BackgroundColor hsl
BodyColor hsl
Layer1Color hsl // Deepest and darkest layer
Layer1Shadow int // Deep layers have little shadow
Layer2Color hsl
Layer2Shadow int
Layer3Color hsl
Layer3Shadow int
Layer4Color hsl // Highest and brightest layer
Layer4Shadow int // High layers have lots of shadow
ShadowColor hsl
ShadowSpread int // Pixels
ShadowIntensity int // Pixels
}
func (s pixeldrainStyleSheet) String() string {
return fmt.Sprintf(
`:root {
--text_color: %s;
--input_color: %s;
@@ -66,63 +98,35 @@ func userStyle(r *http.Request) (style template.CSS) {
--shadow_spread: %s;
--shadow_intensity: %s;
}`,
selectedStyle.TextColor.cssString(),
selectedStyle.InputColor.cssString(),
selectedStyle.InputColor.add(0, 0, -.03).cssString(),
selectedStyle.InputTextColor.cssString(),
selectedStyle.HighlightColor.cssString(),
selectedStyle.HighlightColor.add(0, 0, -.03).cssString(),
selectedStyle.HighlightTextColor.cssString(),
selectedStyle.DangerColor.cssString(),
selectedStyle.DangerColor.add(0, 0, -.03).cssString(),
selectedStyle.FileBackgroundColor.cssString(),
selectedStyle.ScrollbarForegroundColor.cssString(),
selectedStyle.ScrollbarHoverColor.cssString(),
selectedStyle.ScrollbarBackgroundColor.cssString(),
selectedStyle.BackgroundColor.cssString(),
selectedStyle.BodyColor.cssString(),
selectedStyle.Layer1Color.cssString(),
fmt.Sprintf("%dpx", selectedStyle.Layer1Shadow),
selectedStyle.Layer2Color.cssString(),
fmt.Sprintf("%dpx", selectedStyle.Layer2Shadow),
selectedStyle.Layer3Color.cssString(),
fmt.Sprintf("%dpx", selectedStyle.Layer3Shadow),
selectedStyle.Layer4Color.cssString(),
fmt.Sprintf("%dpx", selectedStyle.Layer4Shadow),
selectedStyle.ShadowColor.cssString(),
fmt.Sprintf("%dpx", selectedStyle.ShadowSpread),
fmt.Sprintf("%dpx", selectedStyle.ShadowIntensity),
))
s.TextColor.cssString(),
s.InputColor.cssString(),
s.InputColor.add(0, 0, -.03).cssString(),
s.InputTextColor.cssString(),
s.HighlightColor.cssString(),
s.HighlightColor.add(0, 0, -.03).cssString(),
s.HighlightTextColor.cssString(),
s.DangerColor.cssString(),
s.DangerColor.add(0, 0, -.03).cssString(),
s.FileBackgroundColor.cssString(),
s.ScrollbarForegroundColor.cssString(),
s.ScrollbarHoverColor.cssString(),
s.ScrollbarBackgroundColor.cssString(),
s.BackgroundColor.cssString(),
s.BodyColor.cssString(),
s.Layer1Color.cssString(),
fmt.Sprintf("%dpx", s.Layer1Shadow),
s.Layer2Color.cssString(),
fmt.Sprintf("%dpx", s.Layer2Shadow),
s.Layer3Color.cssString(),
fmt.Sprintf("%dpx", s.Layer3Shadow),
s.Layer4Color.cssString(),
fmt.Sprintf("%dpx", s.Layer4Shadow),
s.ShadowColor.cssString(),
fmt.Sprintf("%dpx", s.ShadowSpread),
fmt.Sprintf("%dpx", s.ShadowIntensity),
)
}
type pixeldrainStyleSheet struct {
TextColor hsl
InputColor hsl // Buttons, text fields
InputTextColor hsl
HighlightColor hsl // Links, highlighted buttons, list navigation
HighlightTextColor hsl // Text on buttons
DangerColor hsl
FileBackgroundColor hsl
ScrollbarForegroundColor hsl
ScrollbarHoverColor hsl
ScrollbarBackgroundColor hsl
BackgroundColor hsl
BodyColor hsl
Layer1Color hsl // Deepest and darkest layer
Layer1Shadow int // Deep layers have little shadow
Layer2Color hsl
Layer2Shadow int
Layer3Color hsl
Layer3Shadow int
Layer4Color hsl // Highest and brightest layer
Layer4Shadow int // High layers have lots of shadow
ShadowColor hsl
ShadowSpread int // Pixels
ShadowIntensity int // Pixels
}
type hsl struct {
Hue int
Saturation float64

View File

@@ -9,7 +9,6 @@ import (
"fornaxian.com/pixeldrain-web/init/conf"
"fornaxian.com/pixeldrain-web/pixelapi"
"fornaxian.com/pixeldrain-web/webcontroller/forms"
"github.com/Fornaxian/log"
"github.com/julienschmidt/httprouter"
)
@@ -17,9 +16,8 @@ import (
// WebController controls how requests are handled and makes sure they have
// proper context when running
type WebController struct {
conf *conf.PixelWebConfig
templates *TemplateManager
staticResourceDir string
conf *conf.PixelWebConfig
templates *TemplateManager
// page-specific variables
captchaSiteKey string
@@ -29,11 +27,10 @@ type WebController struct {
// and parsing all templates in the resource directory
func New(r *httprouter.Router, prefix string, conf *conf.PixelWebConfig) *WebController {
var wc = &WebController{
conf: conf,
staticResourceDir: conf.StaticResourceDir,
conf: conf,
}
wc.templates = NewTemplateManager(
conf.TemplateDir,
conf.ResourceDir,
conf.APIURLExternal,
conf.DebugMode,
)
@@ -42,7 +39,7 @@ func New(r *httprouter.Router, prefix string, conf *conf.PixelWebConfig) *WebCon
var p = prefix
// Serve static files
var fs = http.FileServer(http.Dir(wc.staticResourceDir))
var fs = http.FileServer(http.Dir(wc.conf.ResourceDir + "/static"))
r.GET(p+"/res/*filepath", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
w.Header().Set("Cache-Control", "public, max-age=86400") // Cache for one day
r.URL.Path = p.ByName("filepath")
@@ -89,9 +86,11 @@ func New(r *httprouter.Router, prefix string, conf *conf.PixelWebConfig) *WebCon
r.GET(p+"/user/filemanager" /**/, wc.serveTemplate("file_manager", true))
// User account settings
r.GET(p+"/user/settings" /* */, wc.serveUserSettings)
r.POST(p+"/user/settings" /* */, wc.serveUserSettings)
r.GET(p+"/user/confirm_email" /**/, wc.serveEmailConfirm)
r.GET(p+"/user/settings" /* */, wc.serveUserSettings)
r.POST(p+"/user/settings" /* */, wc.serveUserSettings)
r.GET(p+"/user/confirm_email" /* */, wc.serveEmailConfirm)
r.GET(p+"/user/password_reset_confirm" /* */, wc.serveForm(wc.passwordResetConfirmForm, false))
r.POST(p+"/user/password_reset_confirm" /**/, wc.serveForm(wc.passwordResetConfirmForm, false))
// Admin settings
r.GET(p+"/admin" /* */, wc.serveTemplate("admin_panel", true))
@@ -130,12 +129,12 @@ func (wc *WebController) serveFile(path string) httprouter.Handle {
r *http.Request,
p httprouter.Params,
) {
http.ServeFile(w, r, wc.staticResourceDir+path)
http.ServeFile(w, r, wc.conf.ResourceDir+"/static"+path)
}
}
func (wc *WebController) serveForm(
handler func(*TemplateData, *http.Request) forms.Form,
handler func(*TemplateData, *http.Request) Form,
requireAuth bool,
) httprouter.Handle {
return func(
@@ -167,7 +166,7 @@ func (wc *WebController) serveForm(
// Remove the recaptcha field if captcha is disabled
if wc.captchaKey() == "none" {
for i, field := range td.Form.Fields {
if field.Type == forms.FieldTypeCaptcha {
if field.Type == FieldTypeCaptcha {
td.Form.Fields = append(
td.Form.Fields[:i],
td.Form.Fields[i+1:]...,