add favicon and breadcumbs

This commit is contained in:
Aleksandr Trushkin
2024-01-11 11:55:37 +03:00
parent 5fd0861e2d
commit 067c63baa8
15 changed files with 141 additions and 16 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
./assets/kurious binary

6
assets/kurious/about.txt Normal file
View 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))

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -5,7 +5,7 @@ import (
"net/http" "net/http"
) )
//go:embed robots.txt static/* //go:embed *
var root embed.FS var root embed.FS
func AsHTTPFileHandler() http.Handler { func AsHTTPFileHandler() http.Handler {

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 B

BIN
assets/kurious/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View 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"}

View File

@ -14,11 +14,6 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
const (
pathParamLearningType = "learning_type"
pathParamThematicType = "thematic_type"
)
func makePathTemplate(params ...string) string { func makePathTemplate(params ...string) string {
var sb strings.Builder var sb strings.Builder
for _, param := range params { 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) router.HandleFunc("/updatedesc", coursesAPI.UdpateDescription).Methods(http.MethodPost)
coursesRouter := router.PathPrefix("/courses").Subrouter() coursesRouter := router.PathPrefix("/courses").Subrouter()
coursesRouter.HandleFunc("/", coursesAPI.List).Methods(http.MethodGet) coursesRouter.HandleFunc("/", coursesAPI.List).Methods(http.MethodGet)
coursesListLearningOnlyPath := makePathTemplate(pathParamLearningType) coursesListLearningOnlyPath := makePathTemplate(xhttp.LearningTypePathParam)
coursesRouter.HandleFunc(coursesListLearningOnlyPath, coursesAPI.List).Methods(http.MethodGet) 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) coursesRouter.HandleFunc(coursesListFullPath, coursesAPI.List).Methods(http.MethodGet)
courseRouter := router.PathPrefix("/course").PathPrefix("/{course_id}").Subrouter() 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/")) fs := http.FileServer(http.Dir("./assets/kurious/static/"))
router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", fs)).Methods(http.MethodGet) router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", fs)).Methods(http.MethodGet)
router.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) { registerFile := func(filepath string) {
http.ServeFile(w, r, "./assets/kurious/robots.txt") if !strings.HasPrefix(filepath, "/") {
}).Methods(http.MethodGet) 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 { } else {
fs := kurious.AsHTTPFileHandler() fs := kurious.AsHTTPFileHandler()
router.PathPrefix("/").Handler(fs).Methods(http.MethodGet) router.PathPrefix("/").Handler(fs).Methods(http.MethodGet)

View File

@ -322,7 +322,7 @@ func (r *ydbCourseRepository) ListCourseThematics(
learningTypeValue := types.TextValue(params.LearningTypeID) learningTypeValue := types.TextValue(params.LearningTypeID)
d := queryTemplateDeclaration{ d := queryTemplateDeclaration{
Name: "course_thematic", Name: "learning_type",
Type: learningTypeValue.Type().String(), Type: learningTypeValue.Type().String(),
} }
qtParams.Declares = append(qtParams.Declares, d) qtParams.Declares = append(qtParams.Declares, d)

View File

@ -44,6 +44,11 @@ func parsePaginationFromQuery(r *http.Request) (out pagination, err error) {
return out, nil return out, nil
} }
const (
LearningTypePathParam = "learning_type"
ThematicTypePathParam = "thematic_type"
)
func parseListCoursesParams(r *http.Request) (out listCoursesParams, err error) { func parseListCoursesParams(r *http.Request) (out listCoursesParams, err error) {
out.pagination, err = parsePaginationFromQuery(r) out.pagination, err = parsePaginationFromQuery(r)
if err != nil { if err != nil {
@ -51,8 +56,8 @@ func parseListCoursesParams(r *http.Request) (out listCoursesParams, err error)
} }
vars := mux.Vars(r) vars := mux.Vars(r)
out.learningType = vars["learning_type"] out.learningType = vars[LearningTypePathParam]
out.courseThematic = vars["thematic_type"] out.courseThematic = vars[ThematicTypePathParam]
return out, nil return out, nil
} }
@ -82,9 +87,23 @@ type subcategoryInfo struct {
Courses []domain.Course Courses []domain.Course
} }
type IDNamePair struct {
ID string
Name string
IsActive bool
}
type listCoursesTemplateParams struct { type listCoursesTemplateParams struct {
Categories []categoryInfo Categories []categoryInfo
NextPageToken string NextPageToken string
AvailableLearningTypes []IDNamePair
AvailableCourseThematics []IDNamePair
ActiveLearningType string
LearningTypeName string
ActiveCourseThematic string
CourseThematicName string
} }
func mapDomainCourseToTemplate(in ...domain.Course) listCoursesTemplateParams { func mapDomainCourseToTemplate(in ...domain.Course) listCoursesTemplateParams {
@ -149,6 +168,45 @@ func (c courseServer) List(w http.ResponseWriter, r *http.Request) {
templateCourses := mapDomainCourseToTemplate(courses...) templateCourses := mapDomainCourseToTemplate(courses...)
templateCourses.NextPageToken = result.NextPageToken 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) err = getCoreTemplate(ctx, c.log).ExecuteTemplate(w, "courses", templateCourses)
if handleError(ctx, err, w, c.log, "unable to execute template") { if handleError(ctx, err, w, c.log, "unable to execute template") {
return return

View File

@ -8,6 +8,11 @@
<script src="https://unpkg.com/htmx.org/dist/ext/json-enc.js"></script> <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="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> </head>
{{ end }} {{ end }}

View File

@ -34,6 +34,45 @@
<div class="section"> <div class="section">
<div class="container"> <div class="container">
<h1>Welcome to the Course Aggregator</h1> <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"> <div id="category-course-list">
{{ range $category := .Categories }} {{ range $category := .Categories }}
<div class="title">{{ $category.Name }}</div> <div class="title">{{ $category.Name }}</div>