add favicon and breadcumbs
This commit is contained in:
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
./assets/kurious binary
|
||||
6
assets/kurious/about.txt
Normal file
6
assets/kurious/about.txt
Normal file
@ -0,0 +1,6 @@
|
||||
This favicon was generated using the following font:
|
||||
|
||||
- Font Title: Lemon
|
||||
- Font Author: Copyright 2011 The Lemon Project Authors (https://github.com/etunni/lemon) with Reserved Font Name "Lemon"
|
||||
- Font Source: http://fonts.gstatic.com/s/lemon/v17/HI_EiYEVKqRMq0jBSZXAQ4-d.ttf
|
||||
- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
|
||||
BIN
assets/kurious/android-chrome-192x192.png
Normal file
BIN
assets/kurious/android-chrome-192x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
BIN
assets/kurious/android-chrome-512x512.png
Normal file
BIN
assets/kurious/android-chrome-512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
assets/kurious/apple-touch-icon.png
Normal file
BIN
assets/kurious/apple-touch-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
@ -5,7 +5,7 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//go:embed robots.txt static/*
|
||||
//go:embed *
|
||||
var root embed.FS
|
||||
|
||||
func AsHTTPFileHandler() http.Handler {
|
||||
|
||||
BIN
assets/kurious/favicon-16x16.png
Normal file
BIN
assets/kurious/favicon-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 343 B |
BIN
assets/kurious/favicon-32x32.png
Normal file
BIN
assets/kurious/favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 797 B |
BIN
assets/kurious/favicon.ico
Normal file
BIN
assets/kurious/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
1
assets/kurious/site.webmanifest
Normal file
1
assets/kurious/site.webmanifest
Normal file
@ -0,0 +1 @@
|
||||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||
@ -14,11 +14,6 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
const (
|
||||
pathParamLearningType = "learning_type"
|
||||
pathParamThematicType = "thematic_type"
|
||||
)
|
||||
|
||||
func makePathTemplate(params ...string) string {
|
||||
var sb strings.Builder
|
||||
for _, param := range params {
|
||||
@ -41,9 +36,9 @@ func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server
|
||||
router.HandleFunc("/updatedesc", coursesAPI.UdpateDescription).Methods(http.MethodPost)
|
||||
coursesRouter := router.PathPrefix("/courses").Subrouter()
|
||||
coursesRouter.HandleFunc("/", coursesAPI.List).Methods(http.MethodGet)
|
||||
coursesListLearningOnlyPath := makePathTemplate(pathParamLearningType)
|
||||
coursesListLearningOnlyPath := makePathTemplate(xhttp.LearningTypePathParam)
|
||||
coursesRouter.HandleFunc(coursesListLearningOnlyPath, coursesAPI.List).Methods(http.MethodGet)
|
||||
coursesListFullPath := makePathTemplate(pathParamLearningType, pathParamThematicType)
|
||||
coursesListFullPath := makePathTemplate(xhttp.LearningTypePathParam, xhttp.ThematicTypePathParam)
|
||||
coursesRouter.HandleFunc(coursesListFullPath, coursesAPI.List).Methods(http.MethodGet)
|
||||
|
||||
courseRouter := router.PathPrefix("/course").PathPrefix("/{course_id}").Subrouter()
|
||||
@ -56,9 +51,29 @@ func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server
|
||||
fs := http.FileServer(http.Dir("./assets/kurious/static/"))
|
||||
router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", fs)).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, "./assets/kurious/robots.txt")
|
||||
}).Methods(http.MethodGet)
|
||||
registerFile := func(filepath string) {
|
||||
if !strings.HasPrefix(filepath, "/") {
|
||||
filepath = "/" + filepath
|
||||
}
|
||||
|
||||
relativePath := "./assets/kurious" + filepath
|
||||
router.HandleFunc(filepath, func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, relativePath)
|
||||
}).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
for _, file := range []string{
|
||||
"robots.txt",
|
||||
"android-chrome-192x192.png",
|
||||
"android-chrome-512x512.png",
|
||||
"apple-touch-icon.png",
|
||||
"favicon-16x16.png",
|
||||
"favicon-32x32.png",
|
||||
"favicon.ico",
|
||||
"site.webmanifest",
|
||||
} {
|
||||
registerFile(file)
|
||||
}
|
||||
} else {
|
||||
fs := kurious.AsHTTPFileHandler()
|
||||
router.PathPrefix("/").Handler(fs).Methods(http.MethodGet)
|
||||
|
||||
@ -322,7 +322,7 @@ func (r *ydbCourseRepository) ListCourseThematics(
|
||||
|
||||
learningTypeValue := types.TextValue(params.LearningTypeID)
|
||||
d := queryTemplateDeclaration{
|
||||
Name: "course_thematic",
|
||||
Name: "learning_type",
|
||||
Type: learningTypeValue.Type().String(),
|
||||
}
|
||||
qtParams.Declares = append(qtParams.Declares, d)
|
||||
|
||||
@ -44,6 +44,11 @@ func parsePaginationFromQuery(r *http.Request) (out pagination, err error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
const (
|
||||
LearningTypePathParam = "learning_type"
|
||||
ThematicTypePathParam = "thematic_type"
|
||||
)
|
||||
|
||||
func parseListCoursesParams(r *http.Request) (out listCoursesParams, err error) {
|
||||
out.pagination, err = parsePaginationFromQuery(r)
|
||||
if err != nil {
|
||||
@ -51,8 +56,8 @@ func parseListCoursesParams(r *http.Request) (out listCoursesParams, err error)
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
out.learningType = vars["learning_type"]
|
||||
out.courseThematic = vars["thematic_type"]
|
||||
out.learningType = vars[LearningTypePathParam]
|
||||
out.courseThematic = vars[ThematicTypePathParam]
|
||||
|
||||
return out, nil
|
||||
}
|
||||
@ -82,9 +87,23 @@ type subcategoryInfo struct {
|
||||
Courses []domain.Course
|
||||
}
|
||||
|
||||
type IDNamePair struct {
|
||||
ID string
|
||||
Name string
|
||||
IsActive bool
|
||||
}
|
||||
|
||||
type listCoursesTemplateParams struct {
|
||||
Categories []categoryInfo
|
||||
NextPageToken string
|
||||
Categories []categoryInfo
|
||||
NextPageToken string
|
||||
AvailableLearningTypes []IDNamePair
|
||||
AvailableCourseThematics []IDNamePair
|
||||
|
||||
ActiveLearningType string
|
||||
LearningTypeName string
|
||||
|
||||
ActiveCourseThematic string
|
||||
CourseThematicName string
|
||||
}
|
||||
|
||||
func mapDomainCourseToTemplate(in ...domain.Course) listCoursesTemplateParams {
|
||||
@ -149,6 +168,45 @@ func (c courseServer) List(w http.ResponseWriter, r *http.Request) {
|
||||
templateCourses := mapDomainCourseToTemplate(courses...)
|
||||
templateCourses.NextPageToken = result.NextPageToken
|
||||
|
||||
learningTypeList, err := c.app.Queries.ListLearningTypes.Handle(ctx, query.ListLearningTypes{})
|
||||
if handleError(ctx, err, w, c.log, "unable to list learning types") {
|
||||
return
|
||||
}
|
||||
|
||||
templateCourses.AvailableLearningTypes = xslices.Map(learningTypeList.LearningTypes, func(in query.LearningType) IDNamePair {
|
||||
if in.ID == params.learningType {
|
||||
templateCourses.LearningTypeName = in.Name
|
||||
}
|
||||
return IDNamePair{
|
||||
ID: in.ID,
|
||||
Name: in.Name,
|
||||
IsActive: in.ID == params.learningType,
|
||||
}
|
||||
})
|
||||
|
||||
templateCourses.ActiveLearningType = params.learningType
|
||||
templateCourses.ActiveCourseThematic = params.courseThematic
|
||||
|
||||
if params.learningType != "" {
|
||||
courseThematicsResult, err := c.app.Queries.ListCourseThematics.Handle(ctx, query.ListCourseThematics{
|
||||
LearningTypeID: params.learningType,
|
||||
})
|
||||
if handleError(ctx, err, w, c.log, "unable to list course thematics") {
|
||||
return
|
||||
}
|
||||
|
||||
templateCourses.AvailableCourseThematics = xslices.Map(courseThematicsResult.CourseThematics, func(in query.CourseThematic) IDNamePair {
|
||||
if in.ID == params.courseThematic {
|
||||
templateCourses.CourseThematicName = in.Name
|
||||
}
|
||||
return IDNamePair{
|
||||
ID: in.ID,
|
||||
Name: in.Name,
|
||||
IsActive: in.ID == params.courseThematic,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
err = getCoreTemplate(ctx, c.log).ExecuteTemplate(w, "courses", templateCourses)
|
||||
if handleError(ctx, err, w, c.log, "unable to execute template") {
|
||||
return
|
||||
|
||||
@ -8,6 +8,11 @@
|
||||
<script src="https://unpkg.com/htmx.org/dist/ext/json-enc.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
|
||||
</head>
|
||||
{{ end }}
|
||||
|
||||
|
||||
@ -34,6 +34,45 @@
|
||||
<div class="section">
|
||||
<div class="container">
|
||||
<h1>Welcome to the Course Aggregator</h1>
|
||||
<nav class="breadcrumb" aria-label="breadcrumbs">
|
||||
<ul>
|
||||
<li><a href="/courses">Courses</a></li>
|
||||
{{ if ne .LearningTypeName "" }}
|
||||
<li><a href="/courses/{{.ActiveLearningType}}">{{ .LearningTypeName }}</a></li>
|
||||
{{ end }}
|
||||
{{ if ne .CourseThematicName "" }}
|
||||
<li><a href="/courses/{{.ActiveLearningType}}/{{.ActiveCourseThematic}}">{{ .CourseThematicName }}</a></li>
|
||||
{{ end }}
|
||||
<!-- <li class="is-active"><a href="#" aria-current="page">Breadcrumb</a></li> -->
|
||||
</ul>
|
||||
</nav>
|
||||
<form id="filter-form">
|
||||
<div class="select columns">
|
||||
<select id="learning-type-filter" name="learning_type">
|
||||
<option
|
||||
value=""
|
||||
>All Learning Types</option>
|
||||
{{ range $t := .AvailableLearningTypes }}
|
||||
<option
|
||||
value="{{$t.ID}}"
|
||||
{{ if eq $t.ID $.ActiveLearningType }}selected{{ end }}
|
||||
>{{ $t.Name }}</option>
|
||||
{{ end }}
|
||||
</select>
|
||||
<select id="course-thematic-filter" name="learning_type">
|
||||
<option
|
||||
value=""
|
||||
>All Course Thematics</option>
|
||||
{{ range $t := .AvailableCourseThematics }}
|
||||
<option
|
||||
value="{{$t.ID}}"
|
||||
{{ if eq $t.ID $.ActiveCourseThematic }}selected{{ end }}
|
||||
>{{ $t.Name }}</option>
|
||||
{{ end }}
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="category-course-list">
|
||||
{{ range $category := .Categories }}
|
||||
<div class="title">{{ $category.Name }}</div>
|
||||
|
||||
Reference in New Issue
Block a user