fix: calculate actual progress percentage for continue watching
This commit is contained in:
@@ -260,6 +260,7 @@ func (h *Handler) HandleSaveProgress(w http.ResponseWriter, r *http.Request) {
|
|||||||
TitleJapanese: sql.NullString{String: anime.TitleJapanese, Valid: anime.TitleJapanese != ""},
|
TitleJapanese: sql.NullString{String: anime.TitleJapanese, Valid: anime.TitleJapanese != ""},
|
||||||
ImageUrl: anime.ImageURL(),
|
ImageUrl: anime.ImageURL(),
|
||||||
Airing: sql.NullBool{Bool: anime.Airing, Valid: true},
|
Airing: sql.NullBool{Bool: anime.Airing, Valid: true},
|
||||||
|
DurationSeconds: sql.NullFloat64{Float64: anime.DurationSeconds(), Valid: anime.DurationSeconds() > 0},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,6 +306,7 @@ func (h *Handler) HandleCompleteAnime(w http.ResponseWriter, r *http.Request) {
|
|||||||
TitleJapanese: sql.NullString{String: anime.TitleJapanese, Valid: anime.TitleJapanese != ""},
|
TitleJapanese: sql.NullString{String: anime.TitleJapanese, Valid: anime.TitleJapanese != ""},
|
||||||
ImageUrl: anime.ImageURL(),
|
ImageUrl: anime.ImageURL(),
|
||||||
Airing: sql.NullBool{Bool: anime.Airing, Valid: true},
|
Airing: sql.NullBool{Bool: anime.Airing, Valid: true},
|
||||||
|
DurationSeconds: sql.NullFloat64{Float64: anime.DurationSeconds(), Valid: anime.DurationSeconds() > 0},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,12 +50,18 @@ func (s *Service) SaveProgress(ctx context.Context, userID string, animeID int64
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var durationSeconds sql.NullFloat64
|
||||||
|
if animeSeed != nil {
|
||||||
|
durationSeconds = animeSeed.DurationSeconds
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := txQueries.UpsertContinueWatchingEntry(ctx, database.UpsertContinueWatchingEntryParams{
|
if _, err := txQueries.UpsertContinueWatchingEntry(ctx, database.UpsertContinueWatchingEntryParams{
|
||||||
ID: uuid.New().String(),
|
ID: uuid.New().String(),
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
AnimeID: animeID,
|
AnimeID: animeID,
|
||||||
CurrentEpisode: sql.NullInt64{Int64: int64(episode), Valid: true},
|
CurrentEpisode: sql.NullInt64{Int64: int64(episode), Valid: true},
|
||||||
CurrentTimeSeconds: timeSeconds,
|
CurrentTimeSeconds: timeSeconds,
|
||||||
|
DurationSeconds: durationSeconds,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return fmt.Errorf("failed to upsert continue entry: %w", err)
|
return fmt.Errorf("failed to upsert continue entry: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,46 @@ func (a Anime) ShortDuration() string {
|
|||||||
return a.Duration
|
return a.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a Anime) DurationSeconds() float64 {
|
||||||
|
if a.Duration == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var hours, minutes int
|
||||||
|
var isHours bool
|
||||||
|
var currentNum string
|
||||||
|
|
||||||
|
for _, c := range a.Duration {
|
||||||
|
if c >= '0' && c <= '9' {
|
||||||
|
currentNum += string(c)
|
||||||
|
} else if c == ' ' && currentNum != "" {
|
||||||
|
val := 0
|
||||||
|
fmt.Sscanf(currentNum, "%d", &val)
|
||||||
|
if isHours {
|
||||||
|
hours = val
|
||||||
|
} else {
|
||||||
|
minutes = val
|
||||||
|
}
|
||||||
|
currentNum = ""
|
||||||
|
} else if len(currentNum) > 0 && (c == 'h' || c == 'H') {
|
||||||
|
isHours = true
|
||||||
|
val := 0
|
||||||
|
fmt.Sscanf(currentNum, "%d", &val)
|
||||||
|
hours = val
|
||||||
|
currentNum = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if currentNum != "" {
|
||||||
|
val := 0
|
||||||
|
fmt.Sscanf(currentNum, "%d", &val)
|
||||||
|
if isHours {
|
||||||
|
hours = val
|
||||||
|
} else {
|
||||||
|
minutes = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return float64(hours*60 + minutes) * 60
|
||||||
|
}
|
||||||
|
|
||||||
func (a Anime) Premiered() string {
|
func (a Anime) Premiered() string {
|
||||||
if a.Season != "" && a.Year > 0 {
|
if a.Season != "" && a.Year > 0 {
|
||||||
return fmt.Sprintf("%s %d", seasonLabel(a.Season), a.Year)
|
return fmt.Sprintf("%s %d", seasonLabel(a.Season), a.Year)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type Anime struct {
|
|||||||
Airing sql.NullBool `json:"airing"`
|
Airing sql.NullBool `json:"airing"`
|
||||||
Status sql.NullString `json:"status"`
|
Status sql.NullString `json:"status"`
|
||||||
RelationsSyncedAt sql.NullTime `json:"relations_synced_at"`
|
RelationsSyncedAt sql.NullTime `json:"relations_synced_at"`
|
||||||
|
DurationSeconds sql.NullFloat64 `json:"duration_seconds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AnimeFetchRetry struct {
|
type AnimeFetchRetry struct {
|
||||||
@@ -42,6 +43,7 @@ type ContinueWatchingEntry struct {
|
|||||||
AnimeID int64 `json:"anime_id"`
|
AnimeID int64 `json:"anime_id"`
|
||||||
CurrentEpisode sql.NullInt64 `json:"current_episode"`
|
CurrentEpisode sql.NullInt64 `json:"current_episode"`
|
||||||
CurrentTimeSeconds float64 `json:"current_time_seconds"`
|
CurrentTimeSeconds float64 `json:"current_time_seconds"`
|
||||||
|
DurationSeconds sql.NullFloat64 `json:"duration_seconds"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,14 +16,15 @@ SELECT * FROM session WHERE id = ? LIMIT 1;
|
|||||||
DELETE FROM session WHERE id = ?;
|
DELETE FROM session WHERE id = ?;
|
||||||
|
|
||||||
-- name: UpsertAnime :one
|
-- name: UpsertAnime :one
|
||||||
INSERT INTO anime (id, title_original, title_english, title_japanese, image_url, airing)
|
INSERT INTO anime (id, title_original, title_english, title_japanese, image_url, airing, duration_seconds)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
ON CONFLICT (id) DO UPDATE SET
|
ON CONFLICT (id) DO UPDATE SET
|
||||||
title_original = excluded.title_original,
|
title_original = excluded.title_original,
|
||||||
title_english = excluded.title_english,
|
title_english = excluded.title_english,
|
||||||
title_japanese = excluded.title_japanese,
|
title_japanese = excluded.title_japanese,
|
||||||
image_url = excluded.image_url,
|
image_url = excluded.image_url,
|
||||||
airing = excluded.airing
|
airing = excluded.airing,
|
||||||
|
duration_seconds = excluded.duration_seconds
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: GetAnime :one
|
-- name: GetAnime :one
|
||||||
@@ -47,11 +48,12 @@ SET current_episode = ?,
|
|||||||
WHERE user_id = ? AND anime_id = ?;
|
WHERE user_id = ? AND anime_id = ?;
|
||||||
|
|
||||||
-- name: UpsertContinueWatchingEntry :one
|
-- name: UpsertContinueWatchingEntry :one
|
||||||
INSERT INTO continue_watching_entry (id, user_id, anime_id, current_episode, current_time_seconds, updated_at)
|
INSERT INTO continue_watching_entry (id, user_id, anime_id, current_episode, current_time_seconds, duration_seconds, updated_at)
|
||||||
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||||
ON CONFLICT (user_id, anime_id) DO UPDATE SET
|
ON CONFLICT (user_id, anime_id) DO UPDATE SET
|
||||||
current_episode = excluded.current_episode,
|
current_episode = excluded.current_episode,
|
||||||
current_time_seconds = excluded.current_time_seconds,
|
current_time_seconds = excluded.current_time_seconds,
|
||||||
|
duration_seconds = excluded.duration_seconds,
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
@@ -66,12 +68,14 @@ SELECT
|
|||||||
c.anime_id,
|
c.anime_id,
|
||||||
c.current_episode,
|
c.current_episode,
|
||||||
c.current_time_seconds,
|
c.current_time_seconds,
|
||||||
|
c.duration_seconds,
|
||||||
c.created_at,
|
c.created_at,
|
||||||
c.updated_at,
|
c.updated_at,
|
||||||
a.title_original,
|
a.title_original,
|
||||||
a.title_english,
|
a.title_english,
|
||||||
a.title_japanese,
|
a.title_japanese,
|
||||||
a.image_url
|
a.image_url,
|
||||||
|
a.duration_seconds as anime_duration_seconds
|
||||||
FROM continue_watching_entry c
|
FROM continue_watching_entry c
|
||||||
JOIN anime a ON c.anime_id = a.id
|
JOIN anime a ON c.anime_id = a.id
|
||||||
WHERE c.user_id = ?
|
WHERE c.user_id = ?
|
||||||
|
|||||||
@@ -207,12 +207,14 @@ SELECT
|
|||||||
c.anime_id,
|
c.anime_id,
|
||||||
c.current_episode,
|
c.current_episode,
|
||||||
c.current_time_seconds,
|
c.current_time_seconds,
|
||||||
|
c.duration_seconds,
|
||||||
c.created_at,
|
c.created_at,
|
||||||
c.updated_at,
|
c.updated_at,
|
||||||
a.title_original,
|
a.title_original,
|
||||||
a.title_english,
|
a.title_english,
|
||||||
a.title_japanese,
|
a.title_japanese,
|
||||||
a.image_url
|
a.image_url,
|
||||||
|
a.duration_seconds as anime_duration_seconds
|
||||||
FROM continue_watching_entry c
|
FROM continue_watching_entry c
|
||||||
JOIN anime a ON c.anime_id = a.id
|
JOIN anime a ON c.anime_id = a.id
|
||||||
WHERE c.user_id = ?
|
WHERE c.user_id = ?
|
||||||
@@ -225,12 +227,14 @@ type GetContinueWatchingEntriesRow struct {
|
|||||||
AnimeID int64 `json:"anime_id"`
|
AnimeID int64 `json:"anime_id"`
|
||||||
CurrentEpisode sql.NullInt64 `json:"current_episode"`
|
CurrentEpisode sql.NullInt64 `json:"current_episode"`
|
||||||
CurrentTimeSeconds float64 `json:"current_time_seconds"`
|
CurrentTimeSeconds float64 `json:"current_time_seconds"`
|
||||||
|
DurationSeconds sql.NullFloat64 `json:"duration_seconds"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
TitleOriginal string `json:"title_original"`
|
TitleOriginal string `json:"title_original"`
|
||||||
TitleEnglish sql.NullString `json:"title_english"`
|
TitleEnglish sql.NullString `json:"title_english"`
|
||||||
TitleJapanese sql.NullString `json:"title_japanese"`
|
TitleJapanese sql.NullString `json:"title_japanese"`
|
||||||
ImageUrl string `json:"image_url"`
|
ImageUrl string `json:"image_url"`
|
||||||
|
AnimeDurationSeconds sql.NullFloat64 `json:"anime_duration_seconds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetContinueWatchingEntries(ctx context.Context, userID string) ([]GetContinueWatchingEntriesRow, error) {
|
func (q *Queries) GetContinueWatchingEntries(ctx context.Context, userID string) ([]GetContinueWatchingEntriesRow, error) {
|
||||||
@@ -248,12 +252,14 @@ func (q *Queries) GetContinueWatchingEntries(ctx context.Context, userID string)
|
|||||||
&i.AnimeID,
|
&i.AnimeID,
|
||||||
&i.CurrentEpisode,
|
&i.CurrentEpisode,
|
||||||
&i.CurrentTimeSeconds,
|
&i.CurrentTimeSeconds,
|
||||||
|
&i.DurationSeconds,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.TitleOriginal,
|
&i.TitleOriginal,
|
||||||
&i.TitleEnglish,
|
&i.TitleEnglish,
|
||||||
&i.TitleJapanese,
|
&i.TitleJapanese,
|
||||||
&i.ImageUrl,
|
&i.ImageUrl,
|
||||||
|
&i.AnimeDurationSeconds,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -744,15 +750,16 @@ func (q *Queries) UpdateAnimeStatus(ctx context.Context, arg UpdateAnimeStatusPa
|
|||||||
}
|
}
|
||||||
|
|
||||||
const upsertAnime = `-- name: UpsertAnime :one
|
const upsertAnime = `-- name: UpsertAnime :one
|
||||||
INSERT INTO anime (id, title_original, title_english, title_japanese, image_url, airing)
|
INSERT INTO anime (id, title_original, title_english, title_japanese, image_url, airing, duration_seconds)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
ON CONFLICT (id) DO UPDATE SET
|
ON CONFLICT (id) DO UPDATE SET
|
||||||
title_original = excluded.title_original,
|
title_original = excluded.title_original,
|
||||||
title_english = excluded.title_english,
|
title_english = excluded.title_english,
|
||||||
title_japanese = excluded.title_japanese,
|
title_japanese = excluded.title_japanese,
|
||||||
image_url = excluded.image_url,
|
image_url = excluded.image_url,
|
||||||
airing = excluded.airing
|
airing = excluded.airing,
|
||||||
RETURNING id, title_original, image_url, created_at, title_english, title_japanese, airing, status, relations_synced_at
|
duration_seconds = excluded.duration_seconds
|
||||||
|
RETURNING id, title_original, image_url, created_at, title_english, title_japanese, airing, status, relations_synced_at, duration_seconds
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpsertAnimeParams struct {
|
type UpsertAnimeParams struct {
|
||||||
@@ -762,6 +769,7 @@ type UpsertAnimeParams struct {
|
|||||||
TitleJapanese sql.NullString `json:"title_japanese"`
|
TitleJapanese sql.NullString `json:"title_japanese"`
|
||||||
ImageUrl string `json:"image_url"`
|
ImageUrl string `json:"image_url"`
|
||||||
Airing sql.NullBool `json:"airing"`
|
Airing sql.NullBool `json:"airing"`
|
||||||
|
DurationSeconds sql.NullFloat64 `json:"duration_seconds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpsertAnime(ctx context.Context, arg UpsertAnimeParams) (Anime, error) {
|
func (q *Queries) UpsertAnime(ctx context.Context, arg UpsertAnimeParams) (Anime, error) {
|
||||||
@@ -772,6 +780,7 @@ func (q *Queries) UpsertAnime(ctx context.Context, arg UpsertAnimeParams) (Anime
|
|||||||
arg.TitleJapanese,
|
arg.TitleJapanese,
|
||||||
arg.ImageUrl,
|
arg.ImageUrl,
|
||||||
arg.Airing,
|
arg.Airing,
|
||||||
|
arg.DurationSeconds,
|
||||||
)
|
)
|
||||||
var i Anime
|
var i Anime
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
@@ -784,6 +793,7 @@ func (q *Queries) UpsertAnime(ctx context.Context, arg UpsertAnimeParams) (Anime
|
|||||||
&i.Airing,
|
&i.Airing,
|
||||||
&i.Status,
|
&i.Status,
|
||||||
&i.RelationsSyncedAt,
|
&i.RelationsSyncedAt,
|
||||||
|
&i.DurationSeconds,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
@@ -807,13 +817,14 @@ func (q *Queries) UpsertAnimeRelation(ctx context.Context, arg UpsertAnimeRelati
|
|||||||
}
|
}
|
||||||
|
|
||||||
const upsertContinueWatchingEntry = `-- name: UpsertContinueWatchingEntry :one
|
const upsertContinueWatchingEntry = `-- name: UpsertContinueWatchingEntry :one
|
||||||
INSERT INTO continue_watching_entry (id, user_id, anime_id, current_episode, current_time_seconds, updated_at)
|
INSERT INTO continue_watching_entry (id, user_id, anime_id, current_episode, current_time_seconds, duration_seconds, updated_at)
|
||||||
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||||
ON CONFLICT (user_id, anime_id) DO UPDATE SET
|
ON CONFLICT (user_id, anime_id) DO UPDATE SET
|
||||||
current_episode = excluded.current_episode,
|
current_episode = excluded.current_episode,
|
||||||
current_time_seconds = excluded.current_time_seconds,
|
current_time_seconds = excluded.current_time_seconds,
|
||||||
|
duration_seconds = excluded.duration_seconds,
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
RETURNING id, user_id, anime_id, current_episode, current_time_seconds, created_at, updated_at
|
RETURNING id, user_id, anime_id, current_episode, current_time_seconds, duration_seconds, created_at, updated_at
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpsertContinueWatchingEntryParams struct {
|
type UpsertContinueWatchingEntryParams struct {
|
||||||
@@ -822,6 +833,7 @@ type UpsertContinueWatchingEntryParams struct {
|
|||||||
AnimeID int64 `json:"anime_id"`
|
AnimeID int64 `json:"anime_id"`
|
||||||
CurrentEpisode sql.NullInt64 `json:"current_episode"`
|
CurrentEpisode sql.NullInt64 `json:"current_episode"`
|
||||||
CurrentTimeSeconds float64 `json:"current_time_seconds"`
|
CurrentTimeSeconds float64 `json:"current_time_seconds"`
|
||||||
|
DurationSeconds sql.NullFloat64 `json:"duration_seconds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpsertContinueWatchingEntry(ctx context.Context, arg UpsertContinueWatchingEntryParams) (ContinueWatchingEntry, error) {
|
func (q *Queries) UpsertContinueWatchingEntry(ctx context.Context, arg UpsertContinueWatchingEntryParams) (ContinueWatchingEntry, error) {
|
||||||
@@ -831,6 +843,7 @@ func (q *Queries) UpsertContinueWatchingEntry(ctx context.Context, arg UpsertCon
|
|||||||
arg.AnimeID,
|
arg.AnimeID,
|
||||||
arg.CurrentEpisode,
|
arg.CurrentEpisode,
|
||||||
arg.CurrentTimeSeconds,
|
arg.CurrentTimeSeconds,
|
||||||
|
arg.DurationSeconds,
|
||||||
)
|
)
|
||||||
var i ContinueWatchingEntry
|
var i ContinueWatchingEntry
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
@@ -839,6 +852,7 @@ func (q *Queries) UpsertContinueWatchingEntry(ctx context.Context, arg UpsertCon
|
|||||||
&i.AnimeID,
|
&i.AnimeID,
|
||||||
&i.CurrentEpisode,
|
&i.CurrentEpisode,
|
||||||
&i.CurrentTimeSeconds,
|
&i.CurrentTimeSeconds,
|
||||||
|
&i.DurationSeconds,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
)
|
)
|
||||||
|
|||||||
5
migrations/015_add_duration.sql
Normal file
5
migrations/015_add_duration.sql
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
-- Add duration column to anime table to store episode duration in seconds
|
||||||
|
ALTER TABLE anime ADD COLUMN duration_seconds REAL;
|
||||||
|
|
||||||
|
-- Add duration_seconds column to continue_watching_entry to track episode duration
|
||||||
|
ALTER TABLE continue_watching_entry ADD COLUMN duration_seconds REAL;
|
||||||
@@ -21,10 +21,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{if .CurrentTimeSeconds}}
|
{{if and .CurrentTimeSeconds .AnimeDurationSeconds.Valid}}
|
||||||
<!-- Progress calculation would go here if total duration was available -->
|
|
||||||
<div class="bg-foreground-muted/60 absolute bottom-0 left-0 h-1.5 w-full z-20">
|
<div class="bg-foreground-muted/60 absolute bottom-0 left-0 h-1.5 w-full z-20">
|
||||||
<div class="shadow-background/20 bg-accent h-full shadow-[0_-2px_5px_0]" style="width: 50%"></div>
|
<div class="shadow-background/20 bg-accent h-full shadow-[0_-2px_5px_0]" style="width: {{percent .CurrentTimeSeconds .AnimeDurationSeconds.Float64}}%"></div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -62,6 +62,21 @@ func GetRenderer() *Renderer {
|
|||||||
"sub": func(a, b int) int {
|
"sub": func(a, b int) int {
|
||||||
return a - b
|
return a - b
|
||||||
},
|
},
|
||||||
|
"mul": func(a, b float64) float64 {
|
||||||
|
return a * b
|
||||||
|
},
|
||||||
|
"div": func(a, b float64) float64 {
|
||||||
|
if b == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return a / b
|
||||||
|
},
|
||||||
|
"percent": func(current, total float64) float64 {
|
||||||
|
if total == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return (current / total) * 100
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pages, err := filepath.Glob(filepath.Join(".", "templates", "*.gohtml"))
|
pages, err := filepath.Glob(filepath.Join(".", "templates", "*.gohtml"))
|
||||||
|
|||||||
Reference in New Issue
Block a user