mirror of
https://github.com/Dvorinka/ClubLogos.git
synced 2026-07-29 13:43:49 +00:00
Compare commits
18
Commits
functional
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1621b3ba53 | ||
|
|
3588c38223 | ||
|
|
b6ae5f0a5f | ||
|
|
b27fe6fe0e | ||
|
|
1824326b2f | ||
|
|
672ffbcdcd | ||
|
|
6a9f25ffe9 | ||
|
|
025f5beef1 | ||
|
|
e6bc2eedb3 | ||
|
|
e47059385c | ||
|
|
30e180886d | ||
|
|
eeff13d5bb | ||
|
|
f15b2b5630 | ||
|
|
df930e3d9c | ||
|
|
f964d43c50 | ||
|
|
72f5cce5e3 | ||
|
|
de415df1a9 | ||
|
|
07ed0c7a0c |
+50
@@ -0,0 +1,50 @@
|
||||
# Go build artifacts
|
||||
bin/
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.test
|
||||
*.out
|
||||
|
||||
# Go coverage
|
||||
coverage.out
|
||||
|
||||
# Node / frontend
|
||||
node_modules/
|
||||
frontend/node_modules/
|
||||
scripts/node_modules/
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Build outputs
|
||||
/dist/
|
||||
frontend/dist/
|
||||
frontend/public/dist/
|
||||
backend/logs/
|
||||
backend/logs/*
|
||||
|
||||
# Database & data
|
||||
*.db
|
||||
*.sqlite
|
||||
/data/db/
|
||||
/data/logs/
|
||||
|
||||
# Env files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# OS / editor noise
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Docker
|
||||
*.pid
|
||||
@@ -1,30 +0,0 @@
|
||||
# Binaries
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
main
|
||||
|
||||
# Test binary
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool
|
||||
*.out
|
||||
|
||||
# Database files
|
||||
*.sqlite
|
||||
*.db
|
||||
|
||||
# Logos directory
|
||||
logos/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
Binary file not shown.
Binary file not shown.
+9
-4
@@ -3,13 +3,18 @@ module czech-clubs-logos-api
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.9.2
|
||||
github.com/gin-contrib/cors v1.7.0
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/mattn/go-sqlite3 v1.14.19
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef
|
||||
golang.org/x/text v0.14.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||
github.com/bytedance/sonic v1.11.2 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||
@@ -30,10 +35,10 @@ require (
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
golang.org/x/arch v0.7.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/net v0.22.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
+50
-6
@@ -1,3 +1,7 @@
|
||||
github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
|
||||
github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
|
||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||
github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A=
|
||||
@@ -63,6 +67,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
@@ -77,19 +85,55 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
||||
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
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/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
|
||||
+335
-69
@@ -1,34 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
neturl "net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"unicode"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
var facrClient = NewFACRClient()
|
||||
|
||||
// ==================== Club Handlers ====================
|
||||
|
||||
func searchClubs(c *gin.Context) {
|
||||
query := c.Query("q")
|
||||
if query == "" {
|
||||
q := strings.TrimSpace(c.Query("q"))
|
||||
if q == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "query parameter 'q' is required"})
|
||||
return
|
||||
}
|
||||
|
||||
clubs, err := facrClient.SearchClubs(query)
|
||||
if err != nil {
|
||||
// Return demo data if FAČR API is unavailable
|
||||
c.JSON(http.StatusOK, getDemoClubs(query))
|
||||
clubs, err := scrapeFotbalSearch(q)
|
||||
if err != nil || len(clubs) == 0 {
|
||||
nq := removeDiacritics(strings.ToLower(q))
|
||||
if nq != strings.ToLower(q) {
|
||||
if c2, err2 := scrapeFotbalSearch(nq); err2 == nil && len(c2) > 0 {
|
||||
c.JSON(http.StatusOK, c2)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, getDemoClubs(q))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -42,8 +53,8 @@ func getClub(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
club, err := facrClient.GetClub(id)
|
||||
if err != nil {
|
||||
club, err := fetchClubByID(id)
|
||||
if err != nil || club == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "club not found"})
|
||||
return
|
||||
}
|
||||
@@ -51,6 +62,120 @@ func getClub(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, club)
|
||||
}
|
||||
|
||||
func scrapeFotbalSearch(q string) ([]Club, error) {
|
||||
vals := neturl.Values{}
|
||||
vals.Set("q", q)
|
||||
searchURL := "https://www.fotbal.cz/club/hledej?" + vals.Encode()
|
||||
req, _ := http.NewRequest("GET", searchURL, nil)
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36")
|
||||
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8")
|
||||
req.Header.Set("Accept-Language", "cs-CZ,cs;q=0.9,en;q=0.8")
|
||||
client := &http.Client{Timeout: 12 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
vals2 := neturl.Values{}
|
||||
vals2.Set("q", "\""+q+"\"")
|
||||
searchURL = "https://www.fotbal.cz/club/hledej?" + vals2.Encode()
|
||||
req2, _ := http.NewRequest("GET", searchURL, nil)
|
||||
req2.Header = req.Header.Clone()
|
||||
resp2, err2 := client.Do(req2)
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
defer resp2.Body.Close()
|
||||
if resp2.StatusCode != http.StatusOK {
|
||||
return []Club{}, nil
|
||||
}
|
||||
resp = resp2
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
_, _ = buf.ReadFrom(resp.Body)
|
||||
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(buf.Bytes()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clubs := []Club{}
|
||||
doc.Find("li.ListItemSplit").Each(func(_ int, li *goquery.Selection) {
|
||||
a := li.Find("a.Link--inverted").First()
|
||||
href := strings.TrimSpace(a.AttrOr("href", ""))
|
||||
if href == "" {
|
||||
return
|
||||
}
|
||||
name := strings.TrimSpace(a.Find("span.H7").First().Text())
|
||||
if name == "" {
|
||||
name = strings.TrimSpace(a.Text())
|
||||
}
|
||||
logoURL := strings.TrimSpace(a.Find("img").First().AttrOr("src", ""))
|
||||
address := strings.TrimSpace(li.Find(".ClubAddress p").First().Text())
|
||||
clubType := "football"
|
||||
if strings.Contains(strings.ToLower(href), "/futsal/") {
|
||||
clubType = "futsal"
|
||||
}
|
||||
parts := strings.Split(strings.TrimRight(href, "/"), "/")
|
||||
clubID := ""
|
||||
if len(parts) > 0 {
|
||||
clubID = parts[len(parts)-1]
|
||||
}
|
||||
if !strings.HasPrefix(href, "http://") && !strings.HasPrefix(href, "https://") {
|
||||
href = "https://www.fotbal.cz" + href
|
||||
}
|
||||
city := extractCityFromAddress(address)
|
||||
clubs = append(clubs, Club{ID: clubID, Name: name, City: city, Type: clubType, Website: "", LogoURL: logoURL})
|
||||
})
|
||||
return clubs, nil
|
||||
}
|
||||
|
||||
func fetchClubByID(id string) (*Club, error) {
|
||||
tryFetch := func(base string, typ string) (*Club, error) {
|
||||
url := fmt.Sprintf("%s/%s", base, id)
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36")
|
||||
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
|
||||
req.Header.Set("Accept-Language", "cs-CZ,cs;q=0.9,en;q=0.8")
|
||||
client := &http.Client{Timeout: 12 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("status %d", resp.StatusCode)
|
||||
}
|
||||
doc, err := goquery.NewDocumentFromReader(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := strings.TrimSpace(doc.Find("h1.H4 span").First().Text())
|
||||
address := strings.TrimSpace(doc.Find(".ClubAddress p").First().Text())
|
||||
city := extractCityFromAddress(address)
|
||||
logo := fmt.Sprintf("https://is1.fotbal.cz/media/kluby/%s/%s_crop.jpg", id, id)
|
||||
return &Club{ID: id, Name: name, City: city, Type: typ, Website: "", LogoURL: logo}, nil
|
||||
}
|
||||
if club, err := tryFetch("https://www.fotbal.cz/souteze/club/club", "football"); err == nil && club != nil && club.Name != "" {
|
||||
return club, nil
|
||||
}
|
||||
if club, err := tryFetch("https://www.fotbal.cz/futsal/club/club", "futsal"); err == nil && club != nil && club.Name != "" {
|
||||
return club, nil
|
||||
}
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
func removeDiacritics(s string) string {
|
||||
d := norm.NFD.String(s)
|
||||
b := make([]rune, 0, len(d))
|
||||
for _, r := range d {
|
||||
if unicode.Is(unicode.Mn, r) {
|
||||
continue
|
||||
}
|
||||
b = append(b, r)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// Demo data fallback
|
||||
func getDemoClubs(query string) []Club {
|
||||
demoClubs := []Club{
|
||||
@@ -191,18 +316,18 @@ func getDemoClubs(query string) []Club {
|
||||
|
||||
var results []Club
|
||||
lowerQuery := strings.ToLower(query)
|
||||
|
||||
|
||||
// Fuzzy matching: check contains in name, city, and partial matches
|
||||
for _, club := range demoClubs {
|
||||
lowerName := strings.ToLower(club.Name)
|
||||
lowerCity := strings.ToLower(club.City)
|
||||
|
||||
|
||||
// Exact contains match in name or city
|
||||
if strings.Contains(lowerName, lowerQuery) || strings.Contains(lowerCity, lowerQuery) {
|
||||
results = append(results, club)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
// Fuzzy match: check if query matches start of any word in name
|
||||
words := strings.Fields(lowerName)
|
||||
for _, word := range words {
|
||||
@@ -219,21 +344,21 @@ func getDemoClubs(query string) []Club {
|
||||
// ==================== Logo Handlers ====================
|
||||
|
||||
type LogoMetadata struct {
|
||||
ID string `json:"id"`
|
||||
ClubName string `json:"club_name"`
|
||||
ClubCity string `json:"club_city,omitempty"`
|
||||
ClubType string `json:"club_type,omitempty"`
|
||||
ClubWebsite string `json:"club_website,omitempty"`
|
||||
HasSVG bool `json:"has_svg"`
|
||||
HasPNG bool `json:"has_png"`
|
||||
PrimaryFormat string `json:"primary_format"`
|
||||
LogoURL string `json:"logo_url"`
|
||||
LogoURLSVG string `json:"logo_url_svg,omitempty"`
|
||||
LogoURLPNG string `json:"logo_url_png,omitempty"`
|
||||
FileSizeSVG int64 `json:"file_size_svg,omitempty"`
|
||||
FileSizePNG int64 `json:"file_size_png,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID string `json:"id"`
|
||||
ClubName string `json:"club_name"`
|
||||
ClubCity string `json:"club_city,omitempty"`
|
||||
ClubType string `json:"club_type,omitempty"`
|
||||
ClubWebsite string `json:"club_website,omitempty"`
|
||||
HasSVG bool `json:"has_svg"`
|
||||
HasPNG bool `json:"has_png"`
|
||||
PrimaryFormat string `json:"primary_format"`
|
||||
LogoURL string `json:"logo_url"`
|
||||
LogoURLSVG string `json:"logo_url_svg,omitempty"`
|
||||
LogoURLPNG string `json:"logo_url_png,omitempty"`
|
||||
FileSizeSVG int64 `json:"file_size_svg,omitempty"`
|
||||
FileSizePNG int64 `json:"file_size_png,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// getLogo returns the logo file (PNG preferred, SVG fallback)
|
||||
@@ -282,6 +407,10 @@ func getLogo(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Set CORS headers explicitly for file serving
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
c.Header("Access-Control-Allow-Methods", "GET, OPTIONS")
|
||||
c.Header("Access-Control-Allow-Headers", "*")
|
||||
c.Header("Content-Type", contentType)
|
||||
c.Header("Cache-Control", "public, max-age=31536000")
|
||||
c.File(logoPath)
|
||||
@@ -344,7 +473,7 @@ func getLogoWithMetadata(c *gin.Context) {
|
||||
scheme = "https"
|
||||
}
|
||||
baseURL := fmt.Sprintf("%s://%s", scheme, c.Request.Host)
|
||||
|
||||
|
||||
// Primary URL (PNG preferred)
|
||||
if metadata.HasPNG {
|
||||
metadata.LogoURL = fmt.Sprintf("%s/logos/%s?format=png", baseURL, id)
|
||||
@@ -365,13 +494,52 @@ func getLogoWithMetadata(c *gin.Context) {
|
||||
|
||||
// List all logos
|
||||
func listLogos(c *gin.Context) {
|
||||
rows, err := db.Query(`
|
||||
SELECT id, club_name, club_city, club_type, club_website,
|
||||
has_svg, has_png, primary_format,
|
||||
created_at, updated_at
|
||||
FROM logos
|
||||
ORDER BY club_name
|
||||
`)
|
||||
q := strings.TrimSpace(c.Query("q"))
|
||||
sortParam := c.DefaultQuery("sort", "name")
|
||||
limitStr := c.Query("limit")
|
||||
pageStr := c.Query("page")
|
||||
typeParam := strings.TrimSpace(strings.ToLower(c.Query("type")))
|
||||
|
||||
base := "SELECT id, club_name, club_city, club_type, club_website, has_svg, has_png, primary_format, created_at, updated_at FROM logos"
|
||||
whereParts := []string{}
|
||||
args := []interface{}{}
|
||||
if q != "" {
|
||||
like := "%" + strings.ToLower(q) + "%"
|
||||
whereParts = append(whereParts, "(LOWER(club_name) LIKE ? OR LOWER(club_city) LIKE ? OR id LIKE ?)")
|
||||
args = append(args, like, like, "%"+q+"%")
|
||||
}
|
||||
if typeParam == "football" || typeParam == "futsal" {
|
||||
whereParts = append(whereParts, "LOWER(club_type) = ?")
|
||||
args = append(args, typeParam)
|
||||
}
|
||||
where := ""
|
||||
if len(whereParts) > 0 {
|
||||
where = " WHERE " + strings.Join(whereParts, " AND ")
|
||||
}
|
||||
order := " ORDER BY club_name"
|
||||
if sortParam == "recent" {
|
||||
order = " ORDER BY datetime(updated_at) DESC, datetime(created_at) DESC"
|
||||
}
|
||||
limitClause := ""
|
||||
if limitStr != "" {
|
||||
if limit, err := strconv.Atoi(limitStr); err == nil && limit > 0 {
|
||||
limitClause = " LIMIT ?"
|
||||
args = append(args, limit)
|
||||
if pageStr != "" {
|
||||
if page, err := strconv.Atoi(pageStr); err == nil {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
offset := (page - 1) * limit
|
||||
limitClause += " OFFSET ?"
|
||||
args = append(args, offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query := base + where + order + limitClause
|
||||
rows, err := db.Query(query, args...)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "database error"})
|
||||
return
|
||||
@@ -388,8 +556,7 @@ func listLogos(c *gin.Context) {
|
||||
for rows.Next() {
|
||||
var logo LogoMetadata
|
||||
var hasSVG, hasPNG int
|
||||
|
||||
err := rows.Scan(
|
||||
if err := rows.Scan(
|
||||
&logo.ID,
|
||||
&logo.ClubName,
|
||||
&logo.ClubCity,
|
||||
@@ -400,14 +567,12 @@ func listLogos(c *gin.Context) {
|
||||
&logo.PrimaryFormat,
|
||||
&logo.CreatedAt,
|
||||
&logo.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
logo.HasSVG = hasSVG == 1
|
||||
logo.HasPNG = hasPNG == 1
|
||||
|
||||
if logo.HasPNG {
|
||||
logo.LogoURL = fmt.Sprintf("%s/logos/%s?format=png", baseURL, logo.ID)
|
||||
} else if logo.HasSVG {
|
||||
@@ -417,9 +582,98 @@ func listLogos(c *gin.Context) {
|
||||
logos = append(logos, logo)
|
||||
}
|
||||
|
||||
if q != "" && len(logos) == 0 {
|
||||
limitClause2 := ""
|
||||
args2 := []interface{}{}
|
||||
if limitStr != "" {
|
||||
if limit, err := strconv.Atoi(limitStr); err == nil && limit > 0 {
|
||||
limitClause2 = " LIMIT ?"
|
||||
args2 = append(args2, limit)
|
||||
if pageStr != "" {
|
||||
if page, err := strconv.Atoi(pageStr); err == nil {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
offset := (page - 1) * limit
|
||||
limitClause2 += " OFFSET ?"
|
||||
args2 = append(args2, offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
where2 := ""
|
||||
if typeParam == "football" || typeParam == "futsal" {
|
||||
where2 = " WHERE LOWER(club_type) = ?"
|
||||
args2 = append(args2, typeParam)
|
||||
}
|
||||
q2 := base + where2 + order + limitClause2
|
||||
rows2, err2 := db.Query(q2, args2...)
|
||||
if err2 == nil {
|
||||
defer rows2.Close()
|
||||
normQ := removeDiacritics(strings.ToLower(q))
|
||||
tmp := []LogoMetadata{}
|
||||
for rows2.Next() {
|
||||
var logo LogoMetadata
|
||||
var hasSVG2, hasPNG2 int
|
||||
if err := rows2.Scan(
|
||||
&logo.ID,
|
||||
&logo.ClubName,
|
||||
&logo.ClubCity,
|
||||
&logo.ClubType,
|
||||
&logo.ClubWebsite,
|
||||
&hasSVG2,
|
||||
&hasPNG2,
|
||||
&logo.PrimaryFormat,
|
||||
&logo.CreatedAt,
|
||||
&logo.UpdatedAt,
|
||||
); err != nil {
|
||||
continue
|
||||
}
|
||||
logo.HasSVG = hasSVG2 == 1
|
||||
logo.HasPNG = hasPNG2 == 1
|
||||
if logo.HasPNG {
|
||||
logo.LogoURL = fmt.Sprintf("%s/logos/%s?format=png", baseURL, logo.ID)
|
||||
} else if logo.HasSVG {
|
||||
logo.LogoURL = fmt.Sprintf("%s/logos/%s?format=svg", baseURL, logo.ID)
|
||||
}
|
||||
nameN := removeDiacritics(strings.ToLower(logo.ClubName))
|
||||
cityN := removeDiacritics(strings.ToLower(logo.ClubCity))
|
||||
if strings.Contains(nameN, normQ) || strings.Contains(cityN, normQ) || strings.Contains(strings.ToLower(logo.ID), strings.ToLower(q)) {
|
||||
tmp = append(tmp, logo)
|
||||
}
|
||||
}
|
||||
logos = tmp
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, logos)
|
||||
}
|
||||
|
||||
func deleteLogo(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "logo ID is required"})
|
||||
return
|
||||
}
|
||||
if _, err := uuid.Parse(id); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid UUID format"})
|
||||
return
|
||||
}
|
||||
|
||||
_, err := db.Exec("DELETE FROM logos WHERE id = ?", id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "database error"})
|
||||
return
|
||||
}
|
||||
|
||||
pngPath := filepath.Join("./logos/png", id+".png")
|
||||
svgPath := filepath.Join("./logos/svg", id+".svg")
|
||||
os.Remove(pngPath)
|
||||
os.Remove(svgPath)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "id": id})
|
||||
}
|
||||
|
||||
func uploadLogo(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
@@ -433,26 +687,38 @@ func uploadLogo(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get club name from form (required)
|
||||
// Read metadata from form
|
||||
clubName := c.PostForm("club_name")
|
||||
if clubName == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "club_name is required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Optional fields
|
||||
clubCity := c.PostForm("club_city")
|
||||
clubType := c.PostForm("club_type")
|
||||
clubWebsite := c.PostForm("club_website")
|
||||
|
||||
if clubName == "" {
|
||||
if club, err := fetchClubByID(id); err == nil && club != nil {
|
||||
if club.Name != "" {
|
||||
clubName = club.Name
|
||||
}
|
||||
if clubType == "" && club.Type != "" {
|
||||
clubType = club.Type
|
||||
}
|
||||
if clubCity == "" && club.City != "" {
|
||||
clubCity = club.City
|
||||
}
|
||||
if clubWebsite == "" && club.Website != "" {
|
||||
clubWebsite = club.Website
|
||||
}
|
||||
}
|
||||
if clubName == "" {
|
||||
clubName = "Club " + id
|
||||
}
|
||||
}
|
||||
|
||||
// Get uploaded file
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no file provided"})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate file type
|
||||
ext := strings.ToLower(filepath.Ext(file.Filename))
|
||||
if ext != ".svg" && ext != ".png" && ext != ".pdf" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "only .svg, .png and .pdf files are allowed"})
|
||||
@@ -466,16 +732,16 @@ func uploadLogo(c *gin.Context) {
|
||||
|
||||
if ext == ".svg" || ext == ".pdf" {
|
||||
pngPath = filepath.Join("./logos/png", id+".png")
|
||||
|
||||
|
||||
if ext == ".svg" {
|
||||
svgPath = filepath.Join("./logos/svg", id+".svg")
|
||||
|
||||
|
||||
// Save SVG
|
||||
if err := c.SaveUploadedFile(file, svgPath); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save SVG file"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Get SVG file size
|
||||
if stat, err := os.Stat(svgPath); err == nil {
|
||||
sizeSVG = stat.Size()
|
||||
@@ -502,12 +768,12 @@ func uploadLogo(c *gin.Context) {
|
||||
// PDF file - convert directly to PNG
|
||||
pdfTempPath := filepath.Join("./logos/temp", id+".pdf")
|
||||
os.MkdirAll("./logos/temp", 0755)
|
||||
|
||||
|
||||
if err := c.SaveUploadedFile(file, pdfTempPath); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save PDF file"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
log.Printf("Converting PDF to PNG for club: %s", clubName)
|
||||
if err := ConvertPDFToPNG(pdfTempPath, pngPath, 512); err != nil {
|
||||
log.Printf("Error: Failed to convert PDF to PNG: %v", err)
|
||||
@@ -515,15 +781,15 @@ func uploadLogo(c *gin.Context) {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to convert PDF to PNG"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Clean up temp PDF
|
||||
os.Remove(pdfTempPath)
|
||||
|
||||
|
||||
// Optimize PNG
|
||||
if err := OptimizePNG(pngPath); err != nil {
|
||||
log.Printf("Warning: Failed to optimize PNG: %v", err)
|
||||
}
|
||||
|
||||
|
||||
// Get PNG file size
|
||||
if stat, err := os.Stat(pngPath); err == nil {
|
||||
sizePNG = stat.Size()
|
||||
@@ -534,17 +800,17 @@ func uploadLogo(c *gin.Context) {
|
||||
} else {
|
||||
// PNG upload
|
||||
pngPath = filepath.Join("./logos/png", id+".png")
|
||||
|
||||
|
||||
if err := c.SaveUploadedFile(file, pngPath); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save PNG file"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Optimize PNG
|
||||
if err := OptimizePNG(pngPath); err != nil {
|
||||
log.Printf("Warning: Failed to optimize PNG: %v", err)
|
||||
}
|
||||
|
||||
|
||||
// Get PNG file size
|
||||
if stat, err := os.Stat(pngPath); err == nil {
|
||||
sizePNG = stat.Size()
|
||||
@@ -568,14 +834,14 @@ func uploadLogo(c *gin.Context) {
|
||||
}
|
||||
|
||||
response := gin.H{
|
||||
"success": true,
|
||||
"id": id,
|
||||
"club_name": clubName,
|
||||
"has_svg": hasSVG == 1,
|
||||
"has_png": hasPNG == 1,
|
||||
"size_svg": sizeSVG,
|
||||
"size_png": sizePNG,
|
||||
"message": "logo uploaded successfully",
|
||||
"success": true,
|
||||
"id": id,
|
||||
"club_name": clubName,
|
||||
"has_svg": hasSVG == 1,
|
||||
"has_png": hasPNG == 1,
|
||||
"size_svg": sizeSVG,
|
||||
"size_png": sizePNG,
|
||||
"message": "logo uploaded successfully",
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
|
||||
+67
-10
@@ -9,6 +9,9 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/srwiley/oksvg"
|
||||
"github.com/srwiley/rasterx"
|
||||
)
|
||||
|
||||
// ConvertSVGToPNG converts an SVG file to PNG format
|
||||
@@ -24,8 +27,12 @@ func ConvertSVGToPNG(svgPath, pngPath string, width int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If no converter available, copy SVG as fallback and log warning
|
||||
return fmt.Errorf("no SVG converter available (install ImageMagick or Inkscape)")
|
||||
// Try pure-Go conversion
|
||||
if err := convertWithGoRenderer(svgPath, pngPath, width); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("no SVG converter available (install ImageMagick or Inkscape, or ensure Go renderer deps)")
|
||||
}
|
||||
|
||||
// ConvertPDFToPNG converts a PDF file to PNG format
|
||||
@@ -39,14 +46,14 @@ func ConvertPDFToPNG(pdfPath, pngPath string, width int) error {
|
||||
fmt.Sprintf("%s[0]", pdfPath), // Only first page
|
||||
pngPath,
|
||||
)
|
||||
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("PDF conversion failed (install ImageMagick and Ghostscript): %v - %s", err, stderr.String())
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -58,14 +65,14 @@ func convertWithImageMagick(svgPath, pngPath string, width int) error {
|
||||
svgPath,
|
||||
pngPath,
|
||||
)
|
||||
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("imagemagick conversion failed: %v - %s", err, stderr.String())
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -76,10 +83,10 @@ func convertWithInkscape(svgPath, pngPath string, width int) error {
|
||||
fmt.Sprintf("--export-width=%d", width),
|
||||
svgPath,
|
||||
)
|
||||
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("inkscape conversion failed: %v - %s", err, stderr.String())
|
||||
}
|
||||
@@ -87,6 +94,56 @@ func convertWithInkscape(svgPath, pngPath string, width int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertWithGoRenderer(svgPath, pngPath string, width int) error {
|
||||
f, err := os.Open(svgPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open svg: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
icon, err := oksvg.ReadIconStream(f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse svg: %w", err)
|
||||
}
|
||||
|
||||
vb := icon.ViewBox
|
||||
targetW := width
|
||||
if targetW <= 0 {
|
||||
targetW = int(vb.W)
|
||||
if targetW <= 0 {
|
||||
targetW = 512
|
||||
}
|
||||
}
|
||||
var targetH int
|
||||
if vb.W != 0 {
|
||||
targetH = int(float64(targetW) * (vb.H / vb.W))
|
||||
} else {
|
||||
targetH = targetW
|
||||
}
|
||||
if targetH <= 0 {
|
||||
targetH = targetW
|
||||
}
|
||||
|
||||
icon.SetTarget(0, 0, float64(targetW), float64(targetH))
|
||||
|
||||
rgba := image.NewRGBA(image.Rect(0, 0, targetW, targetH))
|
||||
scanner := rasterx.NewScannerGV(targetW, targetH, rgba, rgba.Bounds())
|
||||
raster := rasterx.NewDasher(targetW, targetH, scanner)
|
||||
icon.Draw(raster, 1.0)
|
||||
|
||||
out, err := os.Create(pngPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create png: %w", err)
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
if err := png.Encode(out, rgba); err != nil {
|
||||
os.Remove(pngPath)
|
||||
return fmt.Errorf("encode png: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OptimizePNG optimizes a PNG file (basic implementation)
|
||||
func OptimizePNG(pngPath string) error {
|
||||
// Open the file
|
||||
|
||||
+20
-8
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
@@ -38,21 +39,31 @@ func main() {
|
||||
r := gin.Default()
|
||||
r.MaxMultipartMemory = 32 << 20 // 32 MB
|
||||
|
||||
// CORS middleware
|
||||
// CORS middleware - Allow all origins, methods, and headers
|
||||
r.Use(cors.New(cors.Config{
|
||||
AllowOrigins: []string{"*"},
|
||||
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||
AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"},
|
||||
ExposeHeaders: []string{"Content-Length"},
|
||||
AllowCredentials: true,
|
||||
AllowOriginFunc: func(origin string) bool {
|
||||
return true // Allow all origins
|
||||
},
|
||||
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "HEAD"},
|
||||
AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization", "X-Requested-With", "Range", "Accept-Language", "Accept-Encoding", "Cache-Control", "Pragma", "If-Modified-Since"},
|
||||
ExposeHeaders: []string{"*"},
|
||||
AllowCredentials: false,
|
||||
AllowOriginFunc: func(origin string) bool { return true },
|
||||
}))
|
||||
|
||||
// Routes
|
||||
setupRoutes(r)
|
||||
|
||||
// Global preflight handler for any path
|
||||
r.OPTIONS("/*path", func(c *gin.Context) {
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH, HEAD")
|
||||
reqHeaders := c.GetHeader("Access-Control-Request-Headers")
|
||||
if reqHeaders == "" {
|
||||
reqHeaders = "Origin, Content-Type, Accept, Authorization, X-Requested-With, Range, Accept-Language, Accept-Encoding, Cache-Control, Pragma, If-Modified-Since"
|
||||
}
|
||||
c.Header("Access-Control-Allow-Headers", reqHeaders)
|
||||
c.Status(http.StatusNoContent)
|
||||
})
|
||||
|
||||
// Start server
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
@@ -88,6 +99,7 @@ func setupRoutes(r *gin.Engine) {
|
||||
logos.GET("/:id", getLogo)
|
||||
logos.GET("/:id/json", getLogoWithMetadata)
|
||||
logos.POST("/:id", uploadLogo)
|
||||
logos.DELETE("/:id", deleteLogo)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 37 KiB |
+4
-194
@@ -1,203 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="cs" class="dark">
|
||||
<html lang="cs">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin - České Kluby Loga API</title>
|
||||
<link rel="stylesheet" href="/src/style.css">
|
||||
</head>
|
||||
<body class="bg-dark-bg text-white min-h-screen">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="border-b border-dark-border bg-dark-card/50 backdrop-blur-sm sticky top-0 z-50">
|
||||
<div class="container mx-auto px-6 py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<a href="/" class="text-2xl font-bold gradient-text">České Kluby Loga</a>
|
||||
<div class="flex gap-4">
|
||||
<a href="/" class="nav-link px-4 py-2 rounded-lg hover:bg-dark-border transition-smooth">Domů</a>
|
||||
<a href="/api-docs.html" class="nav-link px-4 py-2 rounded-lg hover:bg-dark-border transition-smooth">API Docs</a>
|
||||
<a href="/admin.html" class="nav-link px-4 py-2 rounded-lg bg-accent-blue/20 transition-smooth">Admin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Admin Header -->
|
||||
<header class="border-b border-dark-border bg-dark-card">
|
||||
<div class="container mx-auto px-6 py-8">
|
||||
<h1 class="text-3xl font-bold gradient-text mb-2">Administrace</h1>
|
||||
<p class="text-gray-400">Vyhledejte kluby a nahrajte jejich loga</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container mx-auto px-6 py-12">
|
||||
|
||||
<!-- Club Search Section -->
|
||||
<section class="mb-12">
|
||||
<div class="bg-dark-card rounded-xl p-6 border border-dark-border">
|
||||
<h2 class="text-2xl font-bold mb-6">🔍 Vyhledat Klub</h2>
|
||||
|
||||
<div class="relative mb-6">
|
||||
<input
|
||||
type="text"
|
||||
id="clubSearch"
|
||||
placeholder="Hledat české kluby (např. Sparta, Slavia)..."
|
||||
class="w-full bg-dark-bg border border-dark-border rounded-lg px-4 py-3 text-white focus:outline-none focus:border-accent-blue transition-smooth"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Search Results -->
|
||||
<div id="searchResults" class="space-y-3">
|
||||
<!-- Výsledky naplněné JavaScriptem -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Upload Section -->
|
||||
<section id="uploadSection" class="hidden">
|
||||
<div class="bg-dark-card rounded-xl p-6 border border-dark-border">
|
||||
<h2 class="text-2xl font-bold mb-6"><span style="font-size: 30px; display: inline-block; vertical-align: middle; line-height: 1;">⬆️</span> Nahrát Logo</h2>
|
||||
<form id="uploadForm" class="space-y-6">
|
||||
|
||||
<!-- Club UUID (Read-only) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-400 mb-2">
|
||||
UUID Klubu <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="clubUuid"
|
||||
readonly
|
||||
class="w-full bg-dark-bg/50 border border-dark-border rounded-lg px-4 py-3 text-gray-400 cursor-not-allowed"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Club Name (Required) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-400 mb-2">
|
||||
Název Klubu <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="clubName"
|
||||
required
|
||||
placeholder="AC Sparta Praha"
|
||||
class="w-full bg-dark-bg border border-dark-border rounded-lg px-4 py-3 text-white focus:outline-none focus:border-accent-blue transition-smooth"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-1">Povinné: Nahrání bude zamítnuto bez názvu klubu</p>
|
||||
</div>
|
||||
|
||||
<!-- Club Type -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-400 mb-2">Typ Klubu</label>
|
||||
<select
|
||||
id="clubType"
|
||||
class="w-full bg-dark-bg border border-dark-border rounded-lg px-4 py-3 text-white focus:outline-none focus:border-accent-blue transition-smooth"
|
||||
>
|
||||
<option value="football">Fotbal</option>
|
||||
<option value="futsal">Futsal</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Club Website with Search -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-400 mb-2">
|
||||
Web Klubu
|
||||
<button type="button" id="searchWebsite" class="ml-2 text-accent-blue hover:text-blue-400 text-xs">
|
||||
🔍 Hledat Online
|
||||
</button>
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
id="clubWebsite"
|
||||
placeholder="https://www.sparta.cz"
|
||||
class="w-full bg-dark-bg border border-dark-border rounded-lg px-4 py-3 text-white focus:outline-none focus:border-accent-blue transition-smooth"
|
||||
>
|
||||
<div id="websiteSearchResults" class="mt-2 hidden"></div>
|
||||
</div>
|
||||
|
||||
<!-- File Upload Area -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-400 mb-2">
|
||||
Soubor Loga <span class="text-red-500">*</span>
|
||||
</label>
|
||||
|
||||
<!-- URL Upload -->
|
||||
<div class="mb-3">
|
||||
<input
|
||||
type="url"
|
||||
id="logoUrl"
|
||||
placeholder="Nebo vložte URL obrázku (https://...)"
|
||||
class="w-full bg-dark-bg border border-dark-border rounded-lg px-4 py-3 text-white focus:outline-none focus:border-accent-blue transition-smooth"
|
||||
>
|
||||
<button type="button" id="loadFromUrl" class="mt-2 px-4 py-2 bg-blue-600 rounded-lg hover:bg-blue-700 transition-smooth text-sm">
|
||||
📥 Načíst z URL
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-dark-border"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center text-sm">
|
||||
<span class="px-2 bg-dark-card text-gray-400">nebo</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="uploadArea" class="upload-area rounded-lg p-12 text-center cursor-pointer border-2 border-dashed border-dark-border hover:border-accent-blue transition-smooth mt-3">
|
||||
<svg style="width: 75px;padding-top: 20px;" class="mx-auto h-12 w-12 text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
|
||||
</svg>
|
||||
<p class="text-lg mb-2">Přetáhněte logo sem nebo <span class="text-accent-blue font-semibold">procházet</span></p>
|
||||
<p class="text-sm text-gray-500">SVG, PNG nebo PDF • Preferováno průhledné pozadí</p>
|
||||
<p class="text-xs text-gray-600 mt-2">SVG a PDF soubory budou automaticky převedeny na PNG</p>
|
||||
<input type="file" id="fileInput" accept=".svg,.png,.pdf" class="hidden" multiple>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-gray-500 mt-2">💡 Můžete vybrat více souborů najednou pro nahrání variant</p>
|
||||
</div>
|
||||
|
||||
<!-- Files Preview -->
|
||||
<div id="filesPreviewArea" class="hidden">
|
||||
<h3 class="text-lg font-semibold mb-3">Vybrané soubory</h3>
|
||||
<div id="filesPreviewList" class="space-y-3">
|
||||
<!-- Files will be listed here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload Button -->
|
||||
<button
|
||||
type="submit"
|
||||
id="uploadSubmit"
|
||||
class="w-full px-6 py-4 bg-accent-green rounded-lg font-semibold hover:bg-green-600 transition-smooth disabled:opacity-50 disabled:cursor-not-allowed text-lg"
|
||||
>
|
||||
Nahrát Logo
|
||||
</button>
|
||||
|
||||
<!-- Requirements Notice -->
|
||||
<div class="bg-red-900/20 border border-red-800 rounded-lg p-4 text-sm">
|
||||
<p class="font-semibold text-red-400 mb-2">⚠️ Požadavky na nahrání:</p>
|
||||
<ul class="list-disc list-inside space-y-1 text-red-300/80">
|
||||
<li>Název klubu je povinný (automatické zamítnutí bez něj)</li>
|
||||
<li>UUID klubu musí být platné</li>
|
||||
<li>Akceptovány pouze SVG, PNG a PDF soubory</li>
|
||||
<li>Doporučeno průhledné pozadí</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="border-t border-dark-border mt-20">
|
||||
<div class="container mx-auto px-6 py-8 text-center text-gray-400">
|
||||
<p>🇨🇿 České Kluby Loga API | Administrace</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script type="module" src="/src/admin.js"></script>
|
||||
<body class="bg-dark-bg min-h-screen">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/admin-main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+4
-414
@@ -1,423 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="cs" class="dark">
|
||||
<html lang="cs">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>API Dokumentace - České Kluby Loga API</title>
|
||||
<link rel="stylesheet" href="/src/style.css">
|
||||
</head>
|
||||
<body class="bg-dark-bg text-white min-h-screen">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="border-b border-dark-border bg-dark-card/50 backdrop-blur-sm sticky top-0 z-50">
|
||||
<div class="container mx-auto px-6 py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<a href="/" class="text-2xl font-bold gradient-text">🇨🇿 České Kluby Loga</a>
|
||||
<div class="flex gap-4">
|
||||
<a href="/" class="nav-link px-4 py-2 rounded-lg hover:bg-dark-border transition-smooth">Domů</a>
|
||||
<a href="/api-docs.html" class="nav-link px-4 py-2 rounded-lg bg-accent-blue/20">API Docs</a>
|
||||
<a href="/admin.html" class="nav-link px-4 py-2 rounded-lg hover:bg-dark-border transition-smooth">Admin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Header -->
|
||||
<header class="border-b border-dark-border bg-dark-card">
|
||||
<div class="container mx-auto px-6 py-12">
|
||||
<h1 class="text-4xl font-bold gradient-text mb-3">📚 API Dokumentace</h1>
|
||||
<p class="text-xl text-gray-400">Kompletní referenční příručka pro České Kluby Loga API</p>
|
||||
<div class="mt-6 flex gap-4 items-center flex-wrap">
|
||||
<div>
|
||||
<span class="text-sm text-gray-400 mr-2">Frontend:</span>
|
||||
<code class="bg-dark-bg px-4 py-2 rounded text-accent-blue">http://localhost:3000</code>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm text-gray-400 mr-2">Backend API:</span>
|
||||
<code class="bg-dark-bg px-4 py-2 rounded text-accent-green">http://localhost:8080</code>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm text-gray-400 mt-3">💡 Ve vývojovém prostředí používejte relativní cesty (např. <code class="text-accent-blue">/logos</code>), Vite proxy je přesměruje na backend</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mx-auto px-6 py-12">
|
||||
|
||||
<!-- Quick Start -->
|
||||
<section class="mb-16">
|
||||
<h2 class="text-3xl font-bold mb-6">🚀 Rychlý Start</h2>
|
||||
<div class="bg-gradient-to-br from-accent-green/10 to-accent-blue/10 rounded-xl p-6 border-2 border-accent-green/30">
|
||||
<h3 class="text-xl font-semibold mb-4 flex items-center gap-2">
|
||||
<span class="text-2xl">⬆️</span>
|
||||
Nahrání loga klubu - Základní příkaz
|
||||
</h3>
|
||||
<pre class="bg-dark-bg rounded-lg p-4 overflow-x-auto"><code class="text-sm">curl -X POST http://localhost:8080/logos/{club-uuid} \
|
||||
-F "[email protected]" \
|
||||
-F "club_name=Název Klubu"</code></pre>
|
||||
<div class="mt-4 space-y-2">
|
||||
<p class="text-sm text-gray-300"><strong class="text-accent-green">Povinné:</strong> Club UUID v URL, soubor loga (SVG/PNG/PDF), název klubu</p>
|
||||
<p class="text-sm text-gray-300"><strong class="text-accent-blue">Volitelné:</strong> club_type, club_website, club_city</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-dark-card rounded-xl p-6 border border-dark-border mt-6">
|
||||
<h3 class="text-xl font-semibold mb-4 flex items-center gap-2">
|
||||
<span class="text-2xl">📥</span>
|
||||
Stažení loga klubu
|
||||
</h3>
|
||||
<pre class="bg-dark-bg rounded-lg p-4 overflow-x-auto"><code class="text-sm"># Přímo z backendu
|
||||
curl http://localhost:8080/logos/{uuid}
|
||||
|
||||
# Přes frontend proxy
|
||||
curl http://localhost:3000/api/logos/{uuid}</code></pre>
|
||||
<p class="text-gray-400 mt-3 text-sm">Vrátí PNG obrázek loga (SVG jako fallback)</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Endpoints -->
|
||||
<section class="mb-16">
|
||||
<h2 class="text-3xl font-bold mb-6">📡 Endpointy</h2>
|
||||
|
||||
<!-- List Logos -->
|
||||
<div class="bg-dark-card rounded-xl p-6 border border-dark-border mb-6">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<span class="px-3 py-1 bg-blue-600/20 text-blue-400 rounded font-mono text-sm">GET</span>
|
||||
<code class="text-lg">/logos</code>
|
||||
</div>
|
||||
<p class="text-gray-400 mb-4">Seznam všech nahraných log</p>
|
||||
|
||||
<div class="bg-dark-bg rounded-lg p-4">
|
||||
<h4 class="text-sm font-semibold text-gray-400 mb-2">Response 200:</h4>
|
||||
<pre class="text-sm overflow-x-auto"><code>[
|
||||
{
|
||||
"id": "uuid-here",
|
||||
"club_name": "AC Sparta Praha",
|
||||
"club_type": "football",
|
||||
"has_svg": true,
|
||||
"has_png": true,
|
||||
"logo_url": "http://localhost:8080/logos/uuid-here",
|
||||
"created_at": "2024-01-01T12:00:00Z"
|
||||
}
|
||||
]</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Get Logo File -->
|
||||
<div class="bg-dark-card rounded-xl p-6 border border-dark-border mb-6">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<span class="px-3 py-1 bg-blue-600/20 text-blue-400 rounded font-mono text-sm">GET</span>
|
||||
<code class="text-lg">/logos/:id</code>
|
||||
</div>
|
||||
<p class="text-gray-400 mb-4">Získání souboru loga (PNG preferováno, SVG jako fallback)</p>
|
||||
|
||||
<h4 class="text-sm font-semibold mb-2">Query Parameters (volitelné):</h4>
|
||||
<div class="bg-dark-bg rounded-lg p-4 mb-4">
|
||||
<code class="text-sm">format</code> <span class="text-gray-500">string</span> - "png" nebo "svg"
|
||||
</div>
|
||||
|
||||
<div class="bg-dark-bg rounded-lg p-4">
|
||||
<h4 class="text-sm font-semibold text-gray-400 mb-2">Response 200:</h4>
|
||||
<p class="text-sm text-gray-400">Binární data obrázku (image/png nebo image/svg+xml)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Get Logo Metadata -->
|
||||
<div class="bg-dark-card rounded-xl p-6 border border-dark-border mb-6">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<span class="px-3 py-1 bg-blue-600/20 text-blue-400 rounded font-mono text-sm">GET</span>
|
||||
<code class="text-lg">/logos/:id/json</code>
|
||||
</div>
|
||||
<p class="text-gray-400 mb-4">Získání metadat loga ve formátu JSON</p>
|
||||
|
||||
<div class="bg-dark-bg rounded-lg p-4">
|
||||
<h4 class="text-sm font-semibold text-gray-400 mb-2">Response 200:</h4>
|
||||
<pre class="text-sm overflow-x-auto"><code>{
|
||||
"id": "uuid-here",
|
||||
"club_name": "AC Sparta Praha",
|
||||
"club_type": "football",
|
||||
"club_website": "https://sparta.cz",
|
||||
"has_svg": true,
|
||||
"has_png": true,
|
||||
"primary_format": "png",
|
||||
"logo_url": "http://localhost:8080/logos/uuid-here",
|
||||
"logo_url_svg": "http://localhost:8080/logos/uuid-here?format=svg",
|
||||
"logo_url_png": "http://localhost:8080/logos/uuid-here?format=png",
|
||||
"file_size_svg": 12345,
|
||||
"file_size_png": 54321,
|
||||
"created_at": "2024-01-01T12:00:00Z",
|
||||
"updated_at": "2024-01-01T12:00:00Z"
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload Logo -->
|
||||
<div class="bg-dark-card rounded-xl p-6 border border-dark-border mb-6 border-2 border-accent-green/40">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<span class="px-3 py-1 bg-accent-green/20 text-accent-green rounded font-mono text-sm">POST</span>
|
||||
<code class="text-lg">/logos/:id</code>
|
||||
</div>
|
||||
<p class="text-gray-400 mb-4">Nahrání nového loga klubu s kompletními daty (ID klubu, název, logo soubory)</p>
|
||||
|
||||
<h4 class="text-sm font-semibold mb-2">URL Parameters:</h4>
|
||||
<div class="bg-dark-bg rounded-lg p-4 mb-4">
|
||||
<code class="text-sm">:id</code> <span class="text-red-400">*</span> <span class="text-gray-500">UUID</span> - Jedinečné ID klubu (např. <code class="text-xs">550e8400-e29b-41d4-a716-446655440000</code>)
|
||||
</div>
|
||||
|
||||
<h4 class="text-sm font-semibold mb-2">Content-Type:</h4>
|
||||
<div class="bg-dark-bg rounded-lg p-4 mb-4">
|
||||
<code class="text-sm">multipart/form-data</code>
|
||||
</div>
|
||||
|
||||
<h4 class="text-sm font-semibold mb-2">Form Data (Povinné pole):</h4>
|
||||
<div class="bg-dark-bg rounded-lg p-4 mb-4 space-y-3">
|
||||
<div class="border-l-2 border-red-400 pl-3">
|
||||
<code class="text-sm font-semibold text-red-400">file</code> <span class="text-red-400">*</span> <span class="text-gray-500">file (SVG nebo PNG)</span>
|
||||
<p class="text-xs text-gray-500 mt-1">Soubor loga. Podporované formáty: SVG (doporučeno), PNG, PDF</p>
|
||||
</div>
|
||||
<div class="border-l-2 border-red-400 pl-3">
|
||||
<code class="text-sm font-semibold text-red-400">club_name</code> <span class="text-red-400">*</span> <span class="text-gray-500">string</span>
|
||||
<p class="text-xs text-gray-500 mt-1">Název klubu (např. "AC Sparta Praha")</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="text-sm font-semibold mb-2">Form Data (Volitelné):</h4>
|
||||
<div class="bg-dark-bg rounded-lg p-4 mb-4 space-y-3">
|
||||
<div class="border-l-2 border-blue-400 pl-3">
|
||||
<code class="text-sm">club_type</code> <span class="text-gray-500">string</span>
|
||||
<p class="text-xs text-gray-500 mt-1">Typ klubu: <code>"football"</code> (výchozí) nebo <code>"futsal"</code></p>
|
||||
</div>
|
||||
<div class="border-l-2 border-blue-400 pl-3">
|
||||
<code class="text-sm">club_website</code> <span class="text-gray-500">string</span>
|
||||
<p class="text-xs text-gray-500 mt-1">URL webové stránky klubu (např. "https://sparta.cz")</p>
|
||||
</div>
|
||||
<div class="border-l-2 border-blue-400 pl-3">
|
||||
<code class="text-sm">club_city</code> <span class="text-gray-500">string</span>
|
||||
<p class="text-xs text-gray-500 mt-1">Město klubu (např. "Praha")</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-dark-bg rounded-lg p-4 mb-4">
|
||||
<h4 class="text-sm font-semibold text-gray-400 mb-2">Response 200 (Úspěch):</h4>
|
||||
<pre class="text-sm overflow-x-auto"><code>{
|
||||
"success": true,
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"club_name": "AC Sparta Praha",
|
||||
"has_svg": true,
|
||||
"has_png": true,
|
||||
"size_svg": 12543,
|
||||
"size_png": 45210,
|
||||
"message": "logo uploaded successfully"
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="bg-red-900/20 rounded-lg p-4 border border-red-600/30">
|
||||
<h4 class="text-sm font-semibold text-red-400 mb-2">Response 400 (Chyba):</h4>
|
||||
<pre class="text-sm overflow-x-auto"><code>{
|
||||
"error": "club_name is required"
|
||||
}</code></pre>
|
||||
<p class="text-xs text-gray-400 mt-2">Možné chyby: <code>"no file provided"</code>, <code>"invalid UUID format"</code>, <code>"only .svg, .png and .pdf files are allowed"</code></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<!-- Examples -->
|
||||
<section class="mb-16">
|
||||
<h2 class="text-3xl font-bold mb-6">💡 Příklady Použití - Nahrání Loga</h2>
|
||||
|
||||
<!-- cURL Example -->
|
||||
<div class="bg-dark-card rounded-xl p-6 border border-dark-border mb-6">
|
||||
<h3 class="text-xl font-semibold mb-4 flex items-center gap-2">
|
||||
<span>🔧</span> cURL (Terminal)
|
||||
</h3>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h4 class="text-sm font-semibold mb-2 text-accent-green">Minimální nahrání (pouze povinná pole):</h4>
|
||||
<pre class="bg-dark-bg rounded-lg p-4 overflow-x-auto"><code class="text-sm">curl -X POST http://localhost:8080/logos/550e8400-e29b-41d4-a716-446655440000 \
|
||||
-F "file=@sparta_logo.svg" \
|
||||
-F "club_name=AC Sparta Praha"</code></pre>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm font-semibold mb-2 text-accent-blue">Kompletní nahrání (všechna data):</h4>
|
||||
<pre class="bg-dark-bg rounded-lg p-4 overflow-x-auto"><code class="text-sm">curl -X POST http://localhost:8080/logos/550e8400-e29b-41d4-a716-446655440000 \
|
||||
-F "file=@sparta_logo.svg" \
|
||||
-F "club_name=AC Sparta Praha" \
|
||||
-F "club_type=football" \
|
||||
-F "club_website=https://sparta.cz" \
|
||||
-F "club_city=Praha"</code></pre>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm font-semibold mb-2 text-gray-400">Nahrání PNG místo SVG:</h4>
|
||||
<pre class="bg-dark-bg rounded-lg p-4 overflow-x-auto"><code class="text-sm">curl -X POST http://localhost:8080/logos/550e8400-e29b-41d4-a716-446655440000 \
|
||||
-F "file=@sparta_logo.png" \
|
||||
-F "club_name=AC Sparta Praha"</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- JavaScript Example -->
|
||||
<div class="bg-dark-card rounded-xl p-6 border border-dark-border mb-6">
|
||||
<h3 class="text-xl font-semibold mb-4 flex items-center gap-2">
|
||||
<span>📜</span> JavaScript (Fetch API)
|
||||
</h3>
|
||||
<pre class="bg-dark-bg rounded-lg p-4 overflow-x-auto"><code class="text-sm">// Funkce pro nahrání loga s kompletními daty
|
||||
async function uploadClubLogo(clubId, file, clubData) {
|
||||
const formData = new FormData();
|
||||
|
||||
// Povinná pole
|
||||
formData.append('file', file);
|
||||
formData.append('club_name', clubData.name);
|
||||
|
||||
// Volitelná pole
|
||||
if (clubData.type) formData.append('club_type', clubData.type);
|
||||
if (clubData.website) formData.append('club_website', clubData.website);
|
||||
if (clubData.city) formData.append('club_city', clubData.city);
|
||||
|
||||
const response = await fetch(`http://localhost:8080/logos/${clubId}`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
// Použití s file input
|
||||
const fileInput = document.getElementById('logoFile');
|
||||
const clubId = '550e8400-e29b-41d4-a716-446655440000';
|
||||
|
||||
const result = await uploadClubLogo(clubId, fileInput.files[0], {
|
||||
name: 'AC Sparta Praha',
|
||||
type: 'football',
|
||||
website: 'https://sparta.cz',
|
||||
city: 'Praha'
|
||||
});
|
||||
|
||||
console.log('Upload successful:', result);</code></pre>
|
||||
</div>
|
||||
|
||||
<!-- Python Example -->
|
||||
<div class="bg-dark-card rounded-xl p-6 border border-dark-border mb-6">
|
||||
<h3 class="text-xl font-semibold mb-4 flex items-center gap-2">
|
||||
<span>🐍</span> Python (requests)
|
||||
</h3>
|
||||
<pre class="bg-dark-bg rounded-lg p-4 overflow-x-auto"><code class="text-sm">import requests
|
||||
|
||||
def upload_club_logo(club_id, file_path, club_name, **optional_data):
|
||||
"""
|
||||
Nahraje logo klubu s kompletními daty
|
||||
|
||||
Args:
|
||||
club_id: UUID klubu
|
||||
file_path: Cesta k souboru loga
|
||||
club_name: Název klubu (povinný)
|
||||
**optional_data: club_type, club_website, club_city
|
||||
"""
|
||||
with open(file_path, 'rb') as f:
|
||||
files = {'file': f}
|
||||
data = {'club_name': club_name}
|
||||
data.update(optional_data)
|
||||
|
||||
response = requests.post(
|
||||
f"http://localhost:8080/logos/{club_id}",
|
||||
files=files,
|
||||
data=data
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
# Použití
|
||||
result = upload_club_logo(
|
||||
club_id='550e8400-e29b-41d4-a716-446655440000',
|
||||
file_path='sparta_logo.svg',
|
||||
club_name='AC Sparta Praha',
|
||||
club_type='football',
|
||||
club_website='https://sparta.cz',
|
||||
club_city='Praha'
|
||||
)
|
||||
|
||||
print(f"Upload úspěšný: {result['message']}")
|
||||
print(f"Has SVG: {result['has_svg']}, Has PNG: {result['has_png']}")</code></pre>
|
||||
</div>
|
||||
|
||||
<!-- PowerShell Example -->
|
||||
<div class="bg-dark-card rounded-xl p-6 border border-dark-border mb-6">
|
||||
<h3 class="text-xl font-semibold mb-4 flex items-center gap-2">
|
||||
<span>💻</span> PowerShell
|
||||
</h3>
|
||||
<pre class="bg-dark-bg rounded-lg p-4 overflow-x-auto"><code class="text-sm"># Nahrání loga s kompletními daty
|
||||
$clubId = "550e8400-e29b-41d4-a716-446655440000"
|
||||
$logoFile = "C:\logos\sparta_logo.svg"
|
||||
|
||||
$form = @{
|
||||
file = Get-Item -Path $logoFile
|
||||
club_name = "AC Sparta Praha"
|
||||
club_type = "football"
|
||||
club_website = "https://sparta.cz"
|
||||
club_city = "Praha"
|
||||
}
|
||||
|
||||
$result = Invoke-RestMethod `
|
||||
-Uri "http://localhost:8080/logos/$clubId" `
|
||||
-Method Post `
|
||||
-Form $form
|
||||
|
||||
Write-Host "Upload úspěšný: $($result.message)" -ForegroundColor Green
|
||||
Write-Host "Club: $($result.club_name)" -ForegroundColor Cyan</code></pre>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<!-- Error Codes -->
|
||||
<section>
|
||||
<h2 class="text-3xl font-bold mb-6">⚠️ Chybové Kódy</h2>
|
||||
<div class="bg-dark-card rounded-xl p-6 border border-dark-border">
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-start gap-4">
|
||||
<span class="px-3 py-1 bg-green-600/20 text-green-400 rounded text-sm font-mono">200</span>
|
||||
<div>
|
||||
<h4 class="font-semibold">OK</h4>
|
||||
<p class="text-gray-400 text-sm">Požadavek úspěšně dokončen</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start gap-4">
|
||||
<span class="px-3 py-1 bg-red-600/20 text-red-400 rounded text-sm font-mono">400</span>
|
||||
<div>
|
||||
<h4 class="font-semibold">Bad Request</h4>
|
||||
<p class="text-gray-400 text-sm">Neplatné parametry nebo chybějící povinná pole</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start gap-4">
|
||||
<span class="px-3 py-1 bg-red-600/20 text-red-400 rounded text-sm font-mono">404</span>
|
||||
<div>
|
||||
<h4 class="font-semibold">Not Found</h4>
|
||||
<p class="text-gray-400 text-sm">Logo nebo klub nenalezen</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start gap-4">
|
||||
<span class="px-3 py-1 bg-red-600/20 text-red-400 rounded text-sm font-mono">500</span>
|
||||
<div>
|
||||
<h4 class="font-semibold">Internal Server Error</h4>
|
||||
<p class="text-gray-400 text-sm">Interní chyba serveru</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="border-t border-dark-border mt-20">
|
||||
<div class="container mx-auto px-6 py-8 text-center text-gray-400">
|
||||
<p>🇨🇿 České Kluby Loga API</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<body class="bg-dark-bg min-h-screen">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/docs-main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+5
-173
@@ -1,181 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="cs" class="dark">
|
||||
<html lang="cs">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🇨🇿 České Kluby Loga API</title>
|
||||
<title>České Kluby Loga API</title>
|
||||
<link rel="stylesheet" href="/src/style.css">
|
||||
</head>
|
||||
<body class="bg-dark-bg text-white min-h-screen">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="border-b border-dark-border bg-dark-card/50 backdrop-blur-sm sticky top-0 z-50">
|
||||
<div class="container mx-auto px-6 py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<a href="/" class="text-2xl font-bold gradient-text">🇨🇿 České Kluby Loga</a>
|
||||
<div class="flex gap-4">
|
||||
<a href="/" class="nav-link px-4 py-2 rounded-lg hover:bg-dark-border transition-smooth">Domů</a>
|
||||
<a href="/api-docs.html" class="nav-link px-4 py-2 rounded-lg hover:bg-dark-border transition-smooth">API Docs</a>
|
||||
<a href="/admin.html" class="nav-link px-4 py-2 rounded-lg hover:bg-dark-border transition-smooth">Admin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<header class="relative overflow-hidden border-b border-dark-border">
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-blue-600/10 to-green-600/10"></div>
|
||||
<div class="container mx-auto px-6 py-20 relative z-10">
|
||||
<div class="text-center hero-content max-w-4xl mx-auto">
|
||||
<h1 class="text-5xl md:text-7xl font-bold mb-6">
|
||||
<span class="gradient-text">České Kluby Loga CDN</span>
|
||||
</h1>
|
||||
<p class="text-xl text-gray-400 mb-8">
|
||||
Vysoce kvalitní loga českých fotbalových a futsalových klubů s průhledným pozadím.
|
||||
Založeno na UUID, API-first, připraveno pro produkci.
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-4 justify-center">
|
||||
<button id="browseBtn" class="px-8 py-4 bg-accent-blue rounded-lg font-semibold hover:bg-blue-600 transition-smooth text-lg">
|
||||
🔍 Procházet Loga
|
||||
</button>
|
||||
<a href="/admin.html" class="px-8 py-4 bg-accent-green rounded-lg font-semibold hover:bg-green-600 transition-smooth text-lg">
|
||||
⬆️ Nahrát Logo
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Logo Gallery -->
|
||||
<section class="container mx-auto px-6 py-16" id="logoGallery">
|
||||
<div class="mb-8">
|
||||
<h2 class="text-3xl font-bold mb-4">Dostupná Loga Klubů</h2>
|
||||
<input
|
||||
type="text"
|
||||
id="gallerySearch"
|
||||
placeholder="Filtrovat podle názvu klubu..."
|
||||
class="w-full max-w-md bg-dark-card border border-dark-border rounded-lg px-4 py-3 text-white focus:outline-none focus:border-accent-blue transition-smooth"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div id="logoGrid" class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-6">
|
||||
<!-- Logos will be loaded here -->
|
||||
</div>
|
||||
|
||||
<div id="loadingState" class="text-center py-16">
|
||||
<div class="spinner mx-auto"></div>
|
||||
<p class="mt-4 text-gray-400">Načítání log klubů...</p>
|
||||
</div>
|
||||
|
||||
<div id="emptyState" class="text-center py-16 hidden">
|
||||
<div class="text-6xl mb-4">⚽</div>
|
||||
<p class="text-xl text-gray-400 mb-4">Zatím nebyla nahrána žádná loga</p>
|
||||
<a href="/admin.html" class="px-6 py-3 bg-accent-green rounded-lg font-semibold hover:bg-green-600 transition-smooth inline-block">
|
||||
Nahrát První Logo
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- API Documentation Preview -->
|
||||
<section class="bg-dark-card border-y border-dark-border py-16">
|
||||
<div class="container mx-auto px-6">
|
||||
<h2 class="text-3xl font-bold mb-8 text-center">Rychlá Referenční API</h2>
|
||||
<div class="grid md:grid-cols-2 gap-6 max-w-4xl mx-auto">
|
||||
<div class="bg-dark-bg rounded-xl p-6 border border-dark-border">
|
||||
<div class="flex items-start gap-4">
|
||||
<span class="px-3 py-1 bg-accent-blue/20 text-accent-blue rounded-md text-sm font-mono">GET</span>
|
||||
<div class="flex-1">
|
||||
<p class="font-mono text-sm mb-2">/logos</p>
|
||||
<p class="text-gray-400 text-sm">Zobrazit všechna dostupná loga</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-dark-bg rounded-xl p-6 border border-dark-border">
|
||||
<div class="flex items-start gap-4">
|
||||
<span class="px-3 py-1 bg-accent-blue/20 text-accent-blue rounded-md text-sm font-mono">GET</span>
|
||||
<div class="flex-1">
|
||||
<p class="font-mono text-sm mb-2">/logos/:id</p>
|
||||
<p class="text-gray-400 text-sm">Získat logo podle UUID (PNG/SVG)</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-dark-bg rounded-xl p-6 border border-dark-border">
|
||||
<div class="flex items-start gap-4">
|
||||
<span class="px-3 py-1 bg-accent-blue/20 text-accent-blue rounded-md text-sm font-mono">GET</span>
|
||||
<div class="flex-1">
|
||||
<p class="font-mono text-sm mb-2">/logos/:id/json</p>
|
||||
<p class="text-gray-400 text-sm">Získat metadata loga</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-dark-bg rounded-xl p-6 border border-dark-border">
|
||||
<div class="flex items-start gap-4">
|
||||
<span class="px-3 py-1 bg-accent-green/20 text-accent-green rounded-md text-sm font-mono">POST</span>
|
||||
<div class="flex-1">
|
||||
<p class="font-mono text-sm mb-2">/logos/:id</p>
|
||||
<p class="text-gray-400 text-sm">Nahrát nové logo</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="container mx-auto px-6 py-16">
|
||||
<h2 class="text-3xl font-bold mb-12 text-center">✨ Funkce</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
|
||||
|
||||
<div class="feature-card bg-dark-card rounded-xl p-6 border border-dark-border card-hover">
|
||||
<div class="text-3xl mb-4">⚽</div>
|
||||
<h3 class="text-xl font-semibold mb-2">Integrace s FAČR</h3>
|
||||
<p class="text-gray-400">Přímá integrace s oficiálním českým fotbalovým registrem</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card bg-dark-card rounded-xl p-6 border border-dark-border card-hover">
|
||||
<div class="text-3xl mb-4">🖼️</div>
|
||||
<h3 class="text-xl font-semibold mb-2">SVG & PNG</h3>
|
||||
<p class="text-gray-400">Nahrajte SVG, PNG se vygeneruje automaticky</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card bg-dark-card rounded-xl p-6 border border-dark-border card-hover">
|
||||
<div class="text-3xl mb-4">🔄</div>
|
||||
<h3 class="text-xl font-semibold mb-2">Založeno na UUID</h3>
|
||||
<p class="text-gray-400">Konzistentní identifikace napříč všemi platformami</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card bg-dark-card rounded-xl p-6 border border-dark-border card-hover">
|
||||
<div class="text-3xl mb-4">🌐</div>
|
||||
<h3 class="text-xl font-semibold mb-2">Připraveno pro CDN</h3>
|
||||
<p class="text-gray-400">Rychlé, cachovatelné, produkční API</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card bg-dark-card rounded-xl p-6 border border-dark-border card-hover">
|
||||
<div class="text-3xl mb-4">📝</div>
|
||||
<h3 class="text-xl font-semibold mb-2">Bohatá Metadata</h3>
|
||||
<p class="text-gray-400">Název klubu, město, typ, web v ceně</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card bg-dark-card rounded-xl p-6 border border-dark-border card-hover">
|
||||
<div class="text-3xl mb-4">🐳</div>
|
||||
<h3 class="text-xl font-semibold mb-2">Připraveno pro Docker</h3>
|
||||
<p class="text-gray-400">Nasazení jedním příkazem s Docker Compose</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="border-t border-dark-border mt-20">
|
||||
<div class="container mx-auto px-6 py-8 text-center text-gray-400">
|
||||
<p>🇨🇿 České Kluby Loga API | Vytvořeno s ❤️ pro český fotbal</p>
|
||||
<p class="text-sm mt-2">Poháněno FAČR Scraper API | Open Source MIT Licence</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script type="module" src="/src/home.js"></script>
|
||||
<body class="bg-dark-bg min-h-screen">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+4
-153
@@ -1,162 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="cs" class="dark">
|
||||
<html lang="cs">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Detail Loga - České Kluby Loga API</title>
|
||||
<link rel="stylesheet" href="/src/style.css">
|
||||
</head>
|
||||
<body class="bg-dark-bg text-white min-h-screen">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="border-b border-dark-border bg-dark-card/50 backdrop-blur-sm sticky top-0 z-50">
|
||||
<div class="container mx-auto px-6 py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<a href="/" class="text-2xl font-bold gradient-text">🇨🇿 České Kluby Loga</a>
|
||||
<div class="flex gap-4">
|
||||
<a href="/" class="nav-link px-4 py-2 rounded-lg hover:bg-dark-border transition-smooth">Domů</a>
|
||||
<a href="/api-docs.html" class="nav-link px-4 py-2 rounded-lg hover:bg-dark-border transition-smooth">API Docs</a>
|
||||
<a href="/admin.html" class="nav-link px-4 py-2 rounded-lg hover:bg-dark-border transition-smooth">Admin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mx-auto px-6 py-12">
|
||||
|
||||
<!-- Loading State -->
|
||||
<div id="loadingState" class="text-center py-12">
|
||||
<div class="spinner mx-auto mb-4"></div>
|
||||
<p class="text-gray-400">Načítání...</p>
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
<div id="errorState" class="hidden text-center py-12">
|
||||
<svg class="mx-auto h-16 w-16 text-red-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<h2 class="text-2xl font-bold mb-2">Logo nenalezeno</h2>
|
||||
<p class="text-gray-400 mb-4">Logo s tímto UUID neexistuje</p>
|
||||
<a href="/" class="px-4 py-2 bg-accent-blue rounded-lg hover:bg-blue-600 transition-smooth inline-block">
|
||||
Zpět na hlavní stránku
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Logo Detail -->
|
||||
<div id="logoDetail" class="hidden">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex items-start justify-between mb-8">
|
||||
<div>
|
||||
<h1 id="clubName" class="text-4xl font-bold gradient-text mb-2"></h1>
|
||||
<p id="clubMeta" class="text-gray-400"></p>
|
||||
</div>
|
||||
<a href="/admin.html" class="px-4 py-2 bg-accent-blue rounded-lg hover:bg-blue-600 transition-smooth">
|
||||
✏️ Upravit
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Logo Preview -->
|
||||
<section class="mb-8">
|
||||
<div class="bg-dark-card rounded-xl p-8 border border-dark-border">
|
||||
<h2 class="text-2xl font-bold mb-6">📷 Náhled Loga</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- Light Background -->
|
||||
<div class="bg-white rounded-lg p-8 flex items-center justify-center min-h-[300px]">
|
||||
<img id="logoPreviewLight" src="" alt="Logo na světlém pozadí" class="max-w-full max-h-64 object-contain">
|
||||
</div>
|
||||
<!-- Dark Background -->
|
||||
<div class="bg-gray-900 rounded-lg p-8 flex items-center justify-center min-h-[300px]">
|
||||
<img id="logoPreviewDark" src="" alt="Logo na tmavém pozadí" class="max-w-full max-h-64 object-contain">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Available Formats -->
|
||||
<section class="mb-8">
|
||||
<div class="bg-dark-card rounded-xl p-6 border border-dark-border">
|
||||
<h2 class="text-2xl font-bold mb-6">💾 Dostupné Formáty</h2>
|
||||
<div id="formatsGrid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- Formats will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Variants -->
|
||||
<section class="mb-8" id="variantsSection">
|
||||
<div class="bg-dark-card rounded-xl p-6 border border-dark-border">
|
||||
<h2 class="text-2xl font-bold mb-6">🎨 Varianty Loga</h2>
|
||||
<div id="variantsGrid" class="space-y-4">
|
||||
<!-- Variants will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Metadata -->
|
||||
<section class="mb-8">
|
||||
<div class="bg-dark-card rounded-xl p-6 border border-dark-border">
|
||||
<h2 class="text-2xl font-bold mb-6">ℹ️ Informace</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h3 class="text-sm font-medium text-gray-400 mb-2">UUID</h3>
|
||||
<p id="logoUuid" class="font-mono text-sm bg-dark-bg rounded px-3 py-2"></p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-medium text-gray-400 mb-2">Typ Klubu</h3>
|
||||
<p id="clubType" class="text-sm bg-dark-bg rounded px-3 py-2"></p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-medium text-gray-400 mb-2">Webová Stránka</h3>
|
||||
<p id="clubWebsite" class="text-sm bg-dark-bg rounded px-3 py-2"></p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-medium text-gray-400 mb-2">Datum Nahrání</h3>
|
||||
<p id="uploadDate" class="text-sm bg-dark-bg rounded px-3 py-2"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- API Usage -->
|
||||
<section>
|
||||
<div class="bg-dark-card rounded-xl p-6 border border-dark-border">
|
||||
<h2 class="text-2xl font-bold mb-6">🔗 Použití API</h2>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h3 class="text-sm font-medium text-gray-400 mb-2">GET Logo (PNG preferováno)</h3>
|
||||
<div class="bg-dark-bg rounded px-4 py-3 font-mono text-sm flex items-center justify-between">
|
||||
<code id="apiUrlDefault"></code>
|
||||
<button onclick="copyToClipboard('apiUrlDefault')" class="px-3 py-1 bg-accent-blue rounded text-xs hover:bg-blue-600 transition-smooth">
|
||||
Kopírovat
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-medium text-gray-400 mb-2">GET Logo s Metadaty (JSON)</h3>
|
||||
<div class="bg-dark-bg rounded px-4 py-3 font-mono text-sm flex items-center justify-between">
|
||||
<code id="apiUrlJson"></code>
|
||||
<button onclick="copyToClipboard('apiUrlJson')" class="px-3 py-1 bg-accent-blue rounded text-xs hover:bg-blue-600 transition-smooth">
|
||||
Kopírovat
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="border-t border-dark-border mt-20">
|
||||
<div class="container mx-auto px-6 py-8 text-center text-gray-400">
|
||||
<p>🇨🇿 České Kluby Loga API</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script type="module" src="/src/logo.js"></script>
|
||||
<body class="bg-dark-bg min-h-screen">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/logo-main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="cs">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Všechna Loga - České Kluby Loga API</title>
|
||||
<link rel="stylesheet" href="/src/style.css">
|
||||
</head>
|
||||
<body class="bg-dark-bg min-h-screen">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/logos-main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -16,6 +16,15 @@ server {
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
|
||||
add_header 'Access-Control-Allow-Headers' '*' always;
|
||||
add_header 'Access-Control-Expose-Headers' '*' always;
|
||||
add_header 'Access-Control-Max-Age' '3600' always;
|
||||
|
||||
if ($request_method = 'OPTIONS') {
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
|
||||
# API proxy to backend
|
||||
@@ -30,10 +39,33 @@ server {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# CORS headers - Allow all origins
|
||||
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, PATCH, HEAD' always;
|
||||
add_header 'Access-Control-Allow-Headers' '*' always;
|
||||
add_header 'Access-Control-Expose-Headers' '*' always;
|
||||
add_header 'Access-Control-Max-Age' '3600' always;
|
||||
|
||||
# Handle preflight requests
|
||||
if ($request_method = 'OPTIONS') {
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
|
||||
# SPA fallback
|
||||
location / {
|
||||
# CORS headers - Allow all origins for static content
|
||||
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, PATCH, HEAD' always;
|
||||
add_header 'Access-Control-Allow-Headers' '*' always;
|
||||
add_header 'Access-Control-Expose-Headers' '*' always;
|
||||
add_header 'Access-Control-Max-Age' '3600' always;
|
||||
|
||||
if ($request_method = 'OPTIONS') {
|
||||
return 204;
|
||||
}
|
||||
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
@@ -42,3 +74,4 @@ server {
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
}
|
||||
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\autoprefixer\bin\autoprefixer" %*
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\autoprefixer\bin\autoprefixer" %*
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\baseline-browser-mapping\dist\cli.js" %*
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\baseline-browser-mapping\dist\cli.js" %*
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\browserslist\cli.js" %*
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\browserslist\cli.js" %*
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\cssesc\bin\cssesc" %*
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\cssesc\bin\cssesc" %*
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\esbuild\bin\esbuild" %*
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\esbuild\bin\esbuild" %*
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\glob\dist\esm\bin.mjs" %*
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\glob\dist\esm\bin.mjs" %*
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\jiti\bin\jiti.js" %*
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\jiti\bin\jiti.js" %*
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\nanoid\bin\nanoid.cjs" %*
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\nanoid\bin\nanoid.cjs" %*
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\which\bin\node-which" %*
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\which\bin\node-which" %*
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\resolve\bin\resolve" %*
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\resolve\bin\resolve" %*
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\rollup\dist\bin\rollup" %*
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\rollup\dist\bin\rollup" %*
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\sucrase\bin\sucrase-node" %*
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\sucrase\bin\sucrase-node" %*
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\sucrase\bin\sucrase" %*
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\sucrase\bin\sucrase" %*
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\tailwindcss\lib\cli.js" %*
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\tailwindcss\lib\cli.js" %*
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\tailwindcss\lib\cli.js" %*
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\tailwindcss\lib\cli.js" %*
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\update-browserslist-db\cli.js" %*
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\update-browserslist-db\cli.js" %*
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\vite\bin\vite.js" %*
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\vite\bin\vite.js" %*
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\yaml\bin.mjs" %*
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\yaml\bin.mjs" %*
|
||||
|
||||
+240
@@ -17,6 +17,23 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
|
||||
@@ -140,6 +157,40 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz",
|
||||
"integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz",
|
||||
"integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz",
|
||||
@@ -168,6 +219,91 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@swc/core": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.3.tgz",
|
||||
"integrity": "sha512-Qd8eBPkUFL4eAONgGjycZXj1jFCBW8Fd+xF0PzdTlBCWQIV1xnUT7B93wUANtW3KGjl3TRcOyxwSx/u/jyKw/Q==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3",
|
||||
"@swc/types": "^0.1.25"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/swc"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-darwin-arm64": "1.15.3",
|
||||
"@swc/core-darwin-x64": "1.15.3",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.15.3",
|
||||
"@swc/core-linux-arm64-gnu": "1.15.3",
|
||||
"@swc/core-linux-arm64-musl": "1.15.3",
|
||||
"@swc/core-linux-x64-gnu": "1.15.3",
|
||||
"@swc/core-linux-x64-musl": "1.15.3",
|
||||
"@swc/core-win32-arm64-msvc": "1.15.3",
|
||||
"@swc/core-win32-ia32-msvc": "1.15.3",
|
||||
"@swc/core-win32-x64-msvc": "1.15.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/helpers": ">=0.5.17"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@swc/helpers": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-gnu": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.3.tgz",
|
||||
"integrity": "sha512-aKttAZnz8YB1VJwPQZtyU8Uk0BfMP63iDMkvjhJzRZVgySmqt/apWSdnoIcZlUoGheBrcqbMC17GGUmur7OT5A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-musl": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.3.tgz",
|
||||
"integrity": "sha512-oe8FctPu1gnUsdtGJRO2rvOUIkkIIaHqsO9xxN0bTR7dFTlPTGi2Fhk1tnvXeyAvCPxLIcwD8phzKg6wLv9yug==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/counter": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@swc/types": {
|
||||
"version": "0.1.25",
|
||||
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz",
|
||||
"integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
@@ -175,6 +311,44 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
|
||||
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "18.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
||||
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react-swc": {
|
||||
"version": "3.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.11.0.tgz",
|
||||
"integrity": "sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@rolldown/pluginutils": "1.0.0-beta.27",
|
||||
"@swc/core": "^1.12.11"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^4 || ^5 || ^6 || ^7"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||
@@ -481,6 +655,12 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
@@ -817,6 +997,11 @@
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||
@@ -837,6 +1022,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
@@ -1230,6 +1426,29 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
@@ -1351,6 +1570,14 @@
|
||||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@@ -1618,6 +1845,19 @@
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
||||
|
||||
+15
-15
@@ -1,15 +1,15 @@
|
||||
# Installation
|
||||
> `npm install --save @types/estree`
|
||||
|
||||
# Summary
|
||||
This package contains type definitions for estree (https://github.com/estree/estree).
|
||||
|
||||
# Details
|
||||
Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/estree.
|
||||
|
||||
### Additional Details
|
||||
* Last updated: Fri, 06 Jun 2025 00:04:33 GMT
|
||||
* Dependencies: none
|
||||
|
||||
# Credits
|
||||
These definitions were written by [RReverser](https://github.com/RReverser).
|
||||
# Installation
|
||||
> `npm install --save @types/estree`
|
||||
|
||||
# Summary
|
||||
This package contains type definitions for estree (https://github.com/estree/estree).
|
||||
|
||||
# Details
|
||||
Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/estree.
|
||||
|
||||
### Additional Details
|
||||
* Last updated: Fri, 06 Jun 2025 00:04:33 GMT
|
||||
* Dependencies: none
|
||||
|
||||
# Credits
|
||||
These definitions were written by [RReverser](https://github.com/RReverser).
|
||||
|
||||
+7
-7
@@ -1,8 +1,8 @@
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2015 Dmitry Ivanov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2015 Dmitry Ivanov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+11
-11
@@ -1,11 +1,11 @@
|
||||
A JSON with color names and its values. Based on http://dev.w3.org/csswg/css-color/#named-colors.
|
||||
|
||||
[](https://nodei.co/npm/color-name/)
|
||||
|
||||
|
||||
```js
|
||||
var colors = require('color-name');
|
||||
colors.red //[255,0,0]
|
||||
```
|
||||
|
||||
<a href="LICENSE"><img src="https://upload.wikimedia.org/wikipedia/commons/0/0c/MIT_logo.svg" width="120"/></a>
|
||||
A JSON with color names and its values. Based on http://dev.w3.org/csswg/css-color/#named-colors.
|
||||
|
||||
[](https://nodei.co/npm/color-name/)
|
||||
|
||||
|
||||
```js
|
||||
var colors = require('color-name');
|
||||
colors.red //[255,0,0]
|
||||
```
|
||||
|
||||
<a href="LICENSE"><img src="https://upload.wikimedia.org/wikipedia/commons/0/0c/MIT_logo.svg" width="120"/></a>
|
||||
|
||||
+152
-152
@@ -1,152 +1,152 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
"aliceblue": [240, 248, 255],
|
||||
"antiquewhite": [250, 235, 215],
|
||||
"aqua": [0, 255, 255],
|
||||
"aquamarine": [127, 255, 212],
|
||||
"azure": [240, 255, 255],
|
||||
"beige": [245, 245, 220],
|
||||
"bisque": [255, 228, 196],
|
||||
"black": [0, 0, 0],
|
||||
"blanchedalmond": [255, 235, 205],
|
||||
"blue": [0, 0, 255],
|
||||
"blueviolet": [138, 43, 226],
|
||||
"brown": [165, 42, 42],
|
||||
"burlywood": [222, 184, 135],
|
||||
"cadetblue": [95, 158, 160],
|
||||
"chartreuse": [127, 255, 0],
|
||||
"chocolate": [210, 105, 30],
|
||||
"coral": [255, 127, 80],
|
||||
"cornflowerblue": [100, 149, 237],
|
||||
"cornsilk": [255, 248, 220],
|
||||
"crimson": [220, 20, 60],
|
||||
"cyan": [0, 255, 255],
|
||||
"darkblue": [0, 0, 139],
|
||||
"darkcyan": [0, 139, 139],
|
||||
"darkgoldenrod": [184, 134, 11],
|
||||
"darkgray": [169, 169, 169],
|
||||
"darkgreen": [0, 100, 0],
|
||||
"darkgrey": [169, 169, 169],
|
||||
"darkkhaki": [189, 183, 107],
|
||||
"darkmagenta": [139, 0, 139],
|
||||
"darkolivegreen": [85, 107, 47],
|
||||
"darkorange": [255, 140, 0],
|
||||
"darkorchid": [153, 50, 204],
|
||||
"darkred": [139, 0, 0],
|
||||
"darksalmon": [233, 150, 122],
|
||||
"darkseagreen": [143, 188, 143],
|
||||
"darkslateblue": [72, 61, 139],
|
||||
"darkslategray": [47, 79, 79],
|
||||
"darkslategrey": [47, 79, 79],
|
||||
"darkturquoise": [0, 206, 209],
|
||||
"darkviolet": [148, 0, 211],
|
||||
"deeppink": [255, 20, 147],
|
||||
"deepskyblue": [0, 191, 255],
|
||||
"dimgray": [105, 105, 105],
|
||||
"dimgrey": [105, 105, 105],
|
||||
"dodgerblue": [30, 144, 255],
|
||||
"firebrick": [178, 34, 34],
|
||||
"floralwhite": [255, 250, 240],
|
||||
"forestgreen": [34, 139, 34],
|
||||
"fuchsia": [255, 0, 255],
|
||||
"gainsboro": [220, 220, 220],
|
||||
"ghostwhite": [248, 248, 255],
|
||||
"gold": [255, 215, 0],
|
||||
"goldenrod": [218, 165, 32],
|
||||
"gray": [128, 128, 128],
|
||||
"green": [0, 128, 0],
|
||||
"greenyellow": [173, 255, 47],
|
||||
"grey": [128, 128, 128],
|
||||
"honeydew": [240, 255, 240],
|
||||
"hotpink": [255, 105, 180],
|
||||
"indianred": [205, 92, 92],
|
||||
"indigo": [75, 0, 130],
|
||||
"ivory": [255, 255, 240],
|
||||
"khaki": [240, 230, 140],
|
||||
"lavender": [230, 230, 250],
|
||||
"lavenderblush": [255, 240, 245],
|
||||
"lawngreen": [124, 252, 0],
|
||||
"lemonchiffon": [255, 250, 205],
|
||||
"lightblue": [173, 216, 230],
|
||||
"lightcoral": [240, 128, 128],
|
||||
"lightcyan": [224, 255, 255],
|
||||
"lightgoldenrodyellow": [250, 250, 210],
|
||||
"lightgray": [211, 211, 211],
|
||||
"lightgreen": [144, 238, 144],
|
||||
"lightgrey": [211, 211, 211],
|
||||
"lightpink": [255, 182, 193],
|
||||
"lightsalmon": [255, 160, 122],
|
||||
"lightseagreen": [32, 178, 170],
|
||||
"lightskyblue": [135, 206, 250],
|
||||
"lightslategray": [119, 136, 153],
|
||||
"lightslategrey": [119, 136, 153],
|
||||
"lightsteelblue": [176, 196, 222],
|
||||
"lightyellow": [255, 255, 224],
|
||||
"lime": [0, 255, 0],
|
||||
"limegreen": [50, 205, 50],
|
||||
"linen": [250, 240, 230],
|
||||
"magenta": [255, 0, 255],
|
||||
"maroon": [128, 0, 0],
|
||||
"mediumaquamarine": [102, 205, 170],
|
||||
"mediumblue": [0, 0, 205],
|
||||
"mediumorchid": [186, 85, 211],
|
||||
"mediumpurple": [147, 112, 219],
|
||||
"mediumseagreen": [60, 179, 113],
|
||||
"mediumslateblue": [123, 104, 238],
|
||||
"mediumspringgreen": [0, 250, 154],
|
||||
"mediumturquoise": [72, 209, 204],
|
||||
"mediumvioletred": [199, 21, 133],
|
||||
"midnightblue": [25, 25, 112],
|
||||
"mintcream": [245, 255, 250],
|
||||
"mistyrose": [255, 228, 225],
|
||||
"moccasin": [255, 228, 181],
|
||||
"navajowhite": [255, 222, 173],
|
||||
"navy": [0, 0, 128],
|
||||
"oldlace": [253, 245, 230],
|
||||
"olive": [128, 128, 0],
|
||||
"olivedrab": [107, 142, 35],
|
||||
"orange": [255, 165, 0],
|
||||
"orangered": [255, 69, 0],
|
||||
"orchid": [218, 112, 214],
|
||||
"palegoldenrod": [238, 232, 170],
|
||||
"palegreen": [152, 251, 152],
|
||||
"paleturquoise": [175, 238, 238],
|
||||
"palevioletred": [219, 112, 147],
|
||||
"papayawhip": [255, 239, 213],
|
||||
"peachpuff": [255, 218, 185],
|
||||
"peru": [205, 133, 63],
|
||||
"pink": [255, 192, 203],
|
||||
"plum": [221, 160, 221],
|
||||
"powderblue": [176, 224, 230],
|
||||
"purple": [128, 0, 128],
|
||||
"rebeccapurple": [102, 51, 153],
|
||||
"red": [255, 0, 0],
|
||||
"rosybrown": [188, 143, 143],
|
||||
"royalblue": [65, 105, 225],
|
||||
"saddlebrown": [139, 69, 19],
|
||||
"salmon": [250, 128, 114],
|
||||
"sandybrown": [244, 164, 96],
|
||||
"seagreen": [46, 139, 87],
|
||||
"seashell": [255, 245, 238],
|
||||
"sienna": [160, 82, 45],
|
||||
"silver": [192, 192, 192],
|
||||
"skyblue": [135, 206, 235],
|
||||
"slateblue": [106, 90, 205],
|
||||
"slategray": [112, 128, 144],
|
||||
"slategrey": [112, 128, 144],
|
||||
"snow": [255, 250, 250],
|
||||
"springgreen": [0, 255, 127],
|
||||
"steelblue": [70, 130, 180],
|
||||
"tan": [210, 180, 140],
|
||||
"teal": [0, 128, 128],
|
||||
"thistle": [216, 191, 216],
|
||||
"tomato": [255, 99, 71],
|
||||
"turquoise": [64, 224, 208],
|
||||
"violet": [238, 130, 238],
|
||||
"wheat": [245, 222, 179],
|
||||
"white": [255, 255, 255],
|
||||
"whitesmoke": [245, 245, 245],
|
||||
"yellow": [255, 255, 0],
|
||||
"yellowgreen": [154, 205, 50]
|
||||
};
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
"aliceblue": [240, 248, 255],
|
||||
"antiquewhite": [250, 235, 215],
|
||||
"aqua": [0, 255, 255],
|
||||
"aquamarine": [127, 255, 212],
|
||||
"azure": [240, 255, 255],
|
||||
"beige": [245, 245, 220],
|
||||
"bisque": [255, 228, 196],
|
||||
"black": [0, 0, 0],
|
||||
"blanchedalmond": [255, 235, 205],
|
||||
"blue": [0, 0, 255],
|
||||
"blueviolet": [138, 43, 226],
|
||||
"brown": [165, 42, 42],
|
||||
"burlywood": [222, 184, 135],
|
||||
"cadetblue": [95, 158, 160],
|
||||
"chartreuse": [127, 255, 0],
|
||||
"chocolate": [210, 105, 30],
|
||||
"coral": [255, 127, 80],
|
||||
"cornflowerblue": [100, 149, 237],
|
||||
"cornsilk": [255, 248, 220],
|
||||
"crimson": [220, 20, 60],
|
||||
"cyan": [0, 255, 255],
|
||||
"darkblue": [0, 0, 139],
|
||||
"darkcyan": [0, 139, 139],
|
||||
"darkgoldenrod": [184, 134, 11],
|
||||
"darkgray": [169, 169, 169],
|
||||
"darkgreen": [0, 100, 0],
|
||||
"darkgrey": [169, 169, 169],
|
||||
"darkkhaki": [189, 183, 107],
|
||||
"darkmagenta": [139, 0, 139],
|
||||
"darkolivegreen": [85, 107, 47],
|
||||
"darkorange": [255, 140, 0],
|
||||
"darkorchid": [153, 50, 204],
|
||||
"darkred": [139, 0, 0],
|
||||
"darksalmon": [233, 150, 122],
|
||||
"darkseagreen": [143, 188, 143],
|
||||
"darkslateblue": [72, 61, 139],
|
||||
"darkslategray": [47, 79, 79],
|
||||
"darkslategrey": [47, 79, 79],
|
||||
"darkturquoise": [0, 206, 209],
|
||||
"darkviolet": [148, 0, 211],
|
||||
"deeppink": [255, 20, 147],
|
||||
"deepskyblue": [0, 191, 255],
|
||||
"dimgray": [105, 105, 105],
|
||||
"dimgrey": [105, 105, 105],
|
||||
"dodgerblue": [30, 144, 255],
|
||||
"firebrick": [178, 34, 34],
|
||||
"floralwhite": [255, 250, 240],
|
||||
"forestgreen": [34, 139, 34],
|
||||
"fuchsia": [255, 0, 255],
|
||||
"gainsboro": [220, 220, 220],
|
||||
"ghostwhite": [248, 248, 255],
|
||||
"gold": [255, 215, 0],
|
||||
"goldenrod": [218, 165, 32],
|
||||
"gray": [128, 128, 128],
|
||||
"green": [0, 128, 0],
|
||||
"greenyellow": [173, 255, 47],
|
||||
"grey": [128, 128, 128],
|
||||
"honeydew": [240, 255, 240],
|
||||
"hotpink": [255, 105, 180],
|
||||
"indianred": [205, 92, 92],
|
||||
"indigo": [75, 0, 130],
|
||||
"ivory": [255, 255, 240],
|
||||
"khaki": [240, 230, 140],
|
||||
"lavender": [230, 230, 250],
|
||||
"lavenderblush": [255, 240, 245],
|
||||
"lawngreen": [124, 252, 0],
|
||||
"lemonchiffon": [255, 250, 205],
|
||||
"lightblue": [173, 216, 230],
|
||||
"lightcoral": [240, 128, 128],
|
||||
"lightcyan": [224, 255, 255],
|
||||
"lightgoldenrodyellow": [250, 250, 210],
|
||||
"lightgray": [211, 211, 211],
|
||||
"lightgreen": [144, 238, 144],
|
||||
"lightgrey": [211, 211, 211],
|
||||
"lightpink": [255, 182, 193],
|
||||
"lightsalmon": [255, 160, 122],
|
||||
"lightseagreen": [32, 178, 170],
|
||||
"lightskyblue": [135, 206, 250],
|
||||
"lightslategray": [119, 136, 153],
|
||||
"lightslategrey": [119, 136, 153],
|
||||
"lightsteelblue": [176, 196, 222],
|
||||
"lightyellow": [255, 255, 224],
|
||||
"lime": [0, 255, 0],
|
||||
"limegreen": [50, 205, 50],
|
||||
"linen": [250, 240, 230],
|
||||
"magenta": [255, 0, 255],
|
||||
"maroon": [128, 0, 0],
|
||||
"mediumaquamarine": [102, 205, 170],
|
||||
"mediumblue": [0, 0, 205],
|
||||
"mediumorchid": [186, 85, 211],
|
||||
"mediumpurple": [147, 112, 219],
|
||||
"mediumseagreen": [60, 179, 113],
|
||||
"mediumslateblue": [123, 104, 238],
|
||||
"mediumspringgreen": [0, 250, 154],
|
||||
"mediumturquoise": [72, 209, 204],
|
||||
"mediumvioletred": [199, 21, 133],
|
||||
"midnightblue": [25, 25, 112],
|
||||
"mintcream": [245, 255, 250],
|
||||
"mistyrose": [255, 228, 225],
|
||||
"moccasin": [255, 228, 181],
|
||||
"navajowhite": [255, 222, 173],
|
||||
"navy": [0, 0, 128],
|
||||
"oldlace": [253, 245, 230],
|
||||
"olive": [128, 128, 0],
|
||||
"olivedrab": [107, 142, 35],
|
||||
"orange": [255, 165, 0],
|
||||
"orangered": [255, 69, 0],
|
||||
"orchid": [218, 112, 214],
|
||||
"palegoldenrod": [238, 232, 170],
|
||||
"palegreen": [152, 251, 152],
|
||||
"paleturquoise": [175, 238, 238],
|
||||
"palevioletred": [219, 112, 147],
|
||||
"papayawhip": [255, 239, 213],
|
||||
"peachpuff": [255, 218, 185],
|
||||
"peru": [205, 133, 63],
|
||||
"pink": [255, 192, 203],
|
||||
"plum": [221, 160, 221],
|
||||
"powderblue": [176, 224, 230],
|
||||
"purple": [128, 0, 128],
|
||||
"rebeccapurple": [102, 51, 153],
|
||||
"red": [255, 0, 0],
|
||||
"rosybrown": [188, 143, 143],
|
||||
"royalblue": [65, 105, 225],
|
||||
"saddlebrown": [139, 69, 19],
|
||||
"salmon": [250, 128, 114],
|
||||
"sandybrown": [244, 164, 96],
|
||||
"seagreen": [46, 139, 87],
|
||||
"seashell": [255, 245, 238],
|
||||
"sienna": [160, 82, 45],
|
||||
"silver": [192, 192, 192],
|
||||
"skyblue": [135, 206, 235],
|
||||
"slateblue": [106, 90, 205],
|
||||
"slategray": [112, 128, 144],
|
||||
"slategrey": [112, 128, 144],
|
||||
"snow": [255, 250, 250],
|
||||
"springgreen": [0, 255, 127],
|
||||
"steelblue": [70, 130, 180],
|
||||
"tan": [210, 180, 140],
|
||||
"teal": [0, 128, 128],
|
||||
"thistle": [216, 191, 216],
|
||||
"tomato": [255, 99, 71],
|
||||
"turquoise": [64, 224, 208],
|
||||
"violet": [238, 130, 238],
|
||||
"wheat": [245, 222, 179],
|
||||
"white": [255, 255, 255],
|
||||
"whitesmoke": [245, 245, 245],
|
||||
"yellow": [255, 255, 0],
|
||||
"yellowgreen": [154, 205, 50]
|
||||
};
|
||||
|
||||
+28
-28
@@ -1,28 +1,28 @@
|
||||
{
|
||||
"name": "color-name",
|
||||
"version": "1.1.4",
|
||||
"description": "A list of color names and its values",
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
"index.js"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "node test.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "[email protected]:colorjs/color-name.git"
|
||||
},
|
||||
"keywords": [
|
||||
"color-name",
|
||||
"color",
|
||||
"color-keyword",
|
||||
"keyword"
|
||||
],
|
||||
"author": "DY <[email protected]>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/colorjs/color-name/issues"
|
||||
},
|
||||
"homepage": "https://github.com/colorjs/color-name"
|
||||
}
|
||||
{
|
||||
"name": "color-name",
|
||||
"version": "1.1.4",
|
||||
"description": "A list of color names and its values",
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
"index.js"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "node test.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "[email protected]:colorjs/color-name.git"
|
||||
},
|
||||
"keywords": [
|
||||
"color-name",
|
||||
"color",
|
||||
"color-keyword",
|
||||
"keyword"
|
||||
],
|
||||
"author": "DY <[email protected]>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/colorjs/color-name/issues"
|
||||
},
|
||||
"homepage": "https://github.com/colorjs/color-name"
|
||||
}
|
||||
|
||||
+60
-60
@@ -1,60 +1,60 @@
|
||||
declare module 'Fraction';
|
||||
|
||||
export interface NumeratorDenominator {
|
||||
n: number;
|
||||
d: number;
|
||||
}
|
||||
|
||||
type FractionConstructor = {
|
||||
(fraction: Fraction): Fraction;
|
||||
(num: number | string): Fraction;
|
||||
(numerator: number, denominator: number): Fraction;
|
||||
(numbers: [number | string, number | string]): Fraction;
|
||||
(fraction: NumeratorDenominator): Fraction;
|
||||
(firstValue: Fraction | number | string | [number | string, number | string] | NumeratorDenominator, secondValue?: number): Fraction;
|
||||
};
|
||||
|
||||
export default class Fraction {
|
||||
constructor (fraction: Fraction);
|
||||
constructor (num: number | string);
|
||||
constructor (numerator: number, denominator: number);
|
||||
constructor (numbers: [number | string, number | string]);
|
||||
constructor (fraction: NumeratorDenominator);
|
||||
constructor (firstValue: Fraction | number | string | [number | string, number | string] | NumeratorDenominator, secondValue?: number);
|
||||
|
||||
s: number;
|
||||
n: number;
|
||||
d: number;
|
||||
|
||||
abs(): Fraction;
|
||||
neg(): Fraction;
|
||||
|
||||
add: FractionConstructor;
|
||||
sub: FractionConstructor;
|
||||
mul: FractionConstructor;
|
||||
div: FractionConstructor;
|
||||
pow: FractionConstructor;
|
||||
gcd: FractionConstructor;
|
||||
lcm: FractionConstructor;
|
||||
|
||||
mod(n?: number | string | Fraction): Fraction;
|
||||
|
||||
ceil(places?: number): Fraction;
|
||||
floor(places?: number): Fraction;
|
||||
round(places?: number): Fraction;
|
||||
|
||||
inverse(): Fraction;
|
||||
|
||||
simplify(eps?: number): Fraction;
|
||||
|
||||
equals(n: number | string | Fraction): boolean;
|
||||
compare(n: number | string | Fraction): number;
|
||||
divisible(n: number | string | Fraction): boolean;
|
||||
|
||||
valueOf(): number;
|
||||
toString(decimalPlaces?: number): string;
|
||||
toLatex(excludeWhole?: boolean): string;
|
||||
toFraction(excludeWhole?: boolean): string;
|
||||
toContinued(): number[];
|
||||
clone(): Fraction;
|
||||
}
|
||||
declare module 'Fraction';
|
||||
|
||||
export interface NumeratorDenominator {
|
||||
n: number;
|
||||
d: number;
|
||||
}
|
||||
|
||||
type FractionConstructor = {
|
||||
(fraction: Fraction): Fraction;
|
||||
(num: number | string): Fraction;
|
||||
(numerator: number, denominator: number): Fraction;
|
||||
(numbers: [number | string, number | string]): Fraction;
|
||||
(fraction: NumeratorDenominator): Fraction;
|
||||
(firstValue: Fraction | number | string | [number | string, number | string] | NumeratorDenominator, secondValue?: number): Fraction;
|
||||
};
|
||||
|
||||
export default class Fraction {
|
||||
constructor (fraction: Fraction);
|
||||
constructor (num: number | string);
|
||||
constructor (numerator: number, denominator: number);
|
||||
constructor (numbers: [number | string, number | string]);
|
||||
constructor (fraction: NumeratorDenominator);
|
||||
constructor (firstValue: Fraction | number | string | [number | string, number | string] | NumeratorDenominator, secondValue?: number);
|
||||
|
||||
s: number;
|
||||
n: number;
|
||||
d: number;
|
||||
|
||||
abs(): Fraction;
|
||||
neg(): Fraction;
|
||||
|
||||
add: FractionConstructor;
|
||||
sub: FractionConstructor;
|
||||
mul: FractionConstructor;
|
||||
div: FractionConstructor;
|
||||
pow: FractionConstructor;
|
||||
gcd: FractionConstructor;
|
||||
lcm: FractionConstructor;
|
||||
|
||||
mod(n?: number | string | Fraction): Fraction;
|
||||
|
||||
ceil(places?: number): Fraction;
|
||||
floor(places?: number): Fraction;
|
||||
round(places?: number): Fraction;
|
||||
|
||||
inverse(): Fraction;
|
||||
|
||||
simplify(eps?: number): Fraction;
|
||||
|
||||
equals(n: number | string | Fraction): boolean;
|
||||
compare(n: number | string | Fraction): number;
|
||||
divisible(n: number | string | Fraction): boolean;
|
||||
|
||||
valueOf(): number;
|
||||
toString(decimalPlaces?: number): string;
|
||||
toLatex(excludeWhole?: boolean): string;
|
||||
toFraction(excludeWhole?: boolean): string;
|
||||
toContinued(): number[];
|
||||
clone(): Fraction;
|
||||
}
|
||||
|
||||
+46
-46
@@ -1,46 +1,46 @@
|
||||
# read-cache [](https://travis-ci.org/TrySound/read-cache)
|
||||
|
||||
Reads and caches the entire contents of a file until it is modified.
|
||||
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
$ npm i read-cache
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
// foo.js
|
||||
var readCache = require('read-cache');
|
||||
|
||||
readCache('foo.js').then(function (contents) {
|
||||
console.log(contents);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
### readCache(path[, encoding])
|
||||
|
||||
Returns a promise that resolves with the file's contents.
|
||||
|
||||
### readCache.sync(path[, encoding])
|
||||
|
||||
Returns the content of the file.
|
||||
|
||||
### readCache.get(path[, encoding])
|
||||
|
||||
Returns the content of cached file or null.
|
||||
|
||||
### readCache.clear()
|
||||
|
||||
Clears the contents of the cache.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Bogdan Chadkin](mailto:[email protected])
|
||||
# read-cache [](https://travis-ci.org/TrySound/read-cache)
|
||||
|
||||
Reads and caches the entire contents of a file until it is modified.
|
||||
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
$ npm i read-cache
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
// foo.js
|
||||
var readCache = require('read-cache');
|
||||
|
||||
readCache('foo.js').then(function (contents) {
|
||||
console.log(contents);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
### readCache(path[, encoding])
|
||||
|
||||
Returns a promise that resolves with the file's contents.
|
||||
|
||||
### readCache.sync(path[, encoding])
|
||||
|
||||
Returns the content of the file.
|
||||
|
||||
### readCache.get(path[, encoding])
|
||||
|
||||
Returns the content of cached file or null.
|
||||
|
||||
### readCache.clear()
|
||||
|
||||
Clears the contents of the cache.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Bogdan Chadkin](mailto:[email protected])
|
||||
|
||||
+78
-78
@@ -1,78 +1,78 @@
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var pify = require('pify');
|
||||
|
||||
var stat = pify(fs.stat);
|
||||
var readFile = pify(fs.readFile);
|
||||
var resolve = path.resolve;
|
||||
|
||||
var cache = Object.create(null);
|
||||
|
||||
function convert(content, encoding) {
|
||||
if (Buffer.isEncoding(encoding)) {
|
||||
return content.toString(encoding);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
module.exports = function (path, encoding) {
|
||||
path = resolve(path);
|
||||
|
||||
return stat(path).then(function (stats) {
|
||||
var item = cache[path];
|
||||
|
||||
if (item && item.mtime.getTime() === stats.mtime.getTime()) {
|
||||
return convert(item.content, encoding);
|
||||
}
|
||||
|
||||
return readFile(path).then(function (data) {
|
||||
cache[path] = {
|
||||
mtime: stats.mtime,
|
||||
content: data
|
||||
};
|
||||
|
||||
return convert(data, encoding);
|
||||
});
|
||||
}).catch(function (err) {
|
||||
cache[path] = null;
|
||||
return Promise.reject(err);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.sync = function (path, encoding) {
|
||||
path = resolve(path);
|
||||
|
||||
try {
|
||||
var stats = fs.statSync(path);
|
||||
var item = cache[path];
|
||||
|
||||
if (item && item.mtime.getTime() === stats.mtime.getTime()) {
|
||||
return convert(item.content, encoding);
|
||||
}
|
||||
|
||||
var data = fs.readFileSync(path);
|
||||
|
||||
cache[path] = {
|
||||
mtime: stats.mtime,
|
||||
content: data
|
||||
};
|
||||
|
||||
return convert(data, encoding);
|
||||
} catch (err) {
|
||||
cache[path] = null;
|
||||
throw err;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports.get = function (path, encoding) {
|
||||
path = resolve(path);
|
||||
if (cache[path]) {
|
||||
return convert(cache[path].content, encoding);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
module.exports.clear = function () {
|
||||
cache = Object.create(null);
|
||||
};
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var pify = require('pify');
|
||||
|
||||
var stat = pify(fs.stat);
|
||||
var readFile = pify(fs.readFile);
|
||||
var resolve = path.resolve;
|
||||
|
||||
var cache = Object.create(null);
|
||||
|
||||
function convert(content, encoding) {
|
||||
if (Buffer.isEncoding(encoding)) {
|
||||
return content.toString(encoding);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
module.exports = function (path, encoding) {
|
||||
path = resolve(path);
|
||||
|
||||
return stat(path).then(function (stats) {
|
||||
var item = cache[path];
|
||||
|
||||
if (item && item.mtime.getTime() === stats.mtime.getTime()) {
|
||||
return convert(item.content, encoding);
|
||||
}
|
||||
|
||||
return readFile(path).then(function (data) {
|
||||
cache[path] = {
|
||||
mtime: stats.mtime,
|
||||
content: data
|
||||
};
|
||||
|
||||
return convert(data, encoding);
|
||||
});
|
||||
}).catch(function (err) {
|
||||
cache[path] = null;
|
||||
return Promise.reject(err);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.sync = function (path, encoding) {
|
||||
path = resolve(path);
|
||||
|
||||
try {
|
||||
var stats = fs.statSync(path);
|
||||
var item = cache[path];
|
||||
|
||||
if (item && item.mtime.getTime() === stats.mtime.getTime()) {
|
||||
return convert(item.content, encoding);
|
||||
}
|
||||
|
||||
var data = fs.readFileSync(path);
|
||||
|
||||
cache[path] = {
|
||||
mtime: stats.mtime,
|
||||
content: data
|
||||
};
|
||||
|
||||
return convert(data, encoding);
|
||||
} catch (err) {
|
||||
cache[path] = null;
|
||||
throw err;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports.get = function (path, encoding) {
|
||||
path = resolve(path);
|
||||
if (cache[path]) {
|
||||
return convert(cache[path].content, encoding);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
module.exports.clear = function () {
|
||||
cache = Object.create(null);
|
||||
};
|
||||
|
||||
+70
-70
@@ -1246,76 +1246,76 @@ yargsParser.camelCase = camelCase;
|
||||
yargsParser.decamelize = decamelize;
|
||||
yargsParser.looksLikeNumber = looksLikeNumber;
|
||||
|
||||
/******************************************************************************
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
***************************************************************************** */
|
||||
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
||||
|
||||
|
||||
function __addDisposableResource(env, value, async) {
|
||||
if (value !== null && value !== void 0) {
|
||||
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
||||
var dispose, inner;
|
||||
if (async) {
|
||||
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
||||
dispose = value[Symbol.asyncDispose];
|
||||
}
|
||||
if (dispose === void 0) {
|
||||
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
||||
dispose = value[Symbol.dispose];
|
||||
if (async) inner = dispose;
|
||||
}
|
||||
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
||||
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
||||
env.stack.push({ value: value, dispose: dispose, async: async });
|
||||
}
|
||||
else if (async) {
|
||||
env.stack.push({ async: true });
|
||||
}
|
||||
return value;
|
||||
|
||||
}
|
||||
|
||||
var _SuppressedError = typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
||||
var e = new Error(message);
|
||||
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
||||
};
|
||||
|
||||
function __disposeResources(env) {
|
||||
function fail(e) {
|
||||
env.error = env.hasError ? new _SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
||||
env.hasError = true;
|
||||
}
|
||||
var r, s = 0;
|
||||
function next() {
|
||||
while (r = env.stack.pop()) {
|
||||
try {
|
||||
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
||||
if (r.dispose) {
|
||||
var result = r.dispose.call(r.value);
|
||||
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
||||
}
|
||||
else s |= 1;
|
||||
}
|
||||
catch (e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
||||
if (env.hasError) throw env.error;
|
||||
}
|
||||
return next();
|
||||
/******************************************************************************
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
***************************************************************************** */
|
||||
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
||||
|
||||
|
||||
function __addDisposableResource(env, value, async) {
|
||||
if (value !== null && value !== void 0) {
|
||||
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
||||
var dispose, inner;
|
||||
if (async) {
|
||||
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
||||
dispose = value[Symbol.asyncDispose];
|
||||
}
|
||||
if (dispose === void 0) {
|
||||
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
||||
dispose = value[Symbol.dispose];
|
||||
if (async) inner = dispose;
|
||||
}
|
||||
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
||||
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
||||
env.stack.push({ value: value, dispose: dispose, async: async });
|
||||
}
|
||||
else if (async) {
|
||||
env.stack.push({ async: true });
|
||||
}
|
||||
return value;
|
||||
|
||||
}
|
||||
|
||||
var _SuppressedError = typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
||||
var e = new Error(message);
|
||||
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
||||
};
|
||||
|
||||
function __disposeResources(env) {
|
||||
function fail(e) {
|
||||
env.error = env.hasError ? new _SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
||||
env.hasError = true;
|
||||
}
|
||||
var r, s = 0;
|
||||
function next() {
|
||||
while (r = env.stack.pop()) {
|
||||
try {
|
||||
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
||||
if (r.dispose) {
|
||||
var result = r.dispose.call(r.value);
|
||||
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
||||
}
|
||||
else s |= 1;
|
||||
}
|
||||
catch (e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
||||
if (env.hasError) throw env.error;
|
||||
}
|
||||
return next();
|
||||
}
|
||||
|
||||
const toZeroIfInfinity = value => Number.isFinite(value) ? value : 0;
|
||||
|
||||
+77
-77
@@ -263,83 +263,83 @@ pify$1.all = pify$1;
|
||||
|
||||
var pifyExports = pify$2.exports;
|
||||
|
||||
var fs = require$$0__default;
|
||||
var path$3 = require$$0;
|
||||
var pify = pifyExports;
|
||||
|
||||
var stat = pify(fs.stat);
|
||||
var readFile = pify(fs.readFile);
|
||||
var resolve = path$3.resolve;
|
||||
|
||||
var cache = Object.create(null);
|
||||
|
||||
function convert(content, encoding) {
|
||||
if (Buffer.isEncoding(encoding)) {
|
||||
return content.toString(encoding);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
readCache$1.exports = function (path, encoding) {
|
||||
path = resolve(path);
|
||||
|
||||
return stat(path).then(function (stats) {
|
||||
var item = cache[path];
|
||||
|
||||
if (item && item.mtime.getTime() === stats.mtime.getTime()) {
|
||||
return convert(item.content, encoding);
|
||||
}
|
||||
|
||||
return readFile(path).then(function (data) {
|
||||
cache[path] = {
|
||||
mtime: stats.mtime,
|
||||
content: data
|
||||
};
|
||||
|
||||
return convert(data, encoding);
|
||||
});
|
||||
}).catch(function (err) {
|
||||
cache[path] = null;
|
||||
return Promise.reject(err);
|
||||
});
|
||||
};
|
||||
|
||||
readCache$1.exports.sync = function (path, encoding) {
|
||||
path = resolve(path);
|
||||
|
||||
try {
|
||||
var stats = fs.statSync(path);
|
||||
var item = cache[path];
|
||||
|
||||
if (item && item.mtime.getTime() === stats.mtime.getTime()) {
|
||||
return convert(item.content, encoding);
|
||||
}
|
||||
|
||||
var data = fs.readFileSync(path);
|
||||
|
||||
cache[path] = {
|
||||
mtime: stats.mtime,
|
||||
content: data
|
||||
};
|
||||
|
||||
return convert(data, encoding);
|
||||
} catch (err) {
|
||||
cache[path] = null;
|
||||
throw err;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
readCache$1.exports.get = function (path, encoding) {
|
||||
path = resolve(path);
|
||||
if (cache[path]) {
|
||||
return convert(cache[path].content, encoding);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
readCache$1.exports.clear = function () {
|
||||
cache = Object.create(null);
|
||||
var fs = require$$0__default;
|
||||
var path$3 = require$$0;
|
||||
var pify = pifyExports;
|
||||
|
||||
var stat = pify(fs.stat);
|
||||
var readFile = pify(fs.readFile);
|
||||
var resolve = path$3.resolve;
|
||||
|
||||
var cache = Object.create(null);
|
||||
|
||||
function convert(content, encoding) {
|
||||
if (Buffer.isEncoding(encoding)) {
|
||||
return content.toString(encoding);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
readCache$1.exports = function (path, encoding) {
|
||||
path = resolve(path);
|
||||
|
||||
return stat(path).then(function (stats) {
|
||||
var item = cache[path];
|
||||
|
||||
if (item && item.mtime.getTime() === stats.mtime.getTime()) {
|
||||
return convert(item.content, encoding);
|
||||
}
|
||||
|
||||
return readFile(path).then(function (data) {
|
||||
cache[path] = {
|
||||
mtime: stats.mtime,
|
||||
content: data
|
||||
};
|
||||
|
||||
return convert(data, encoding);
|
||||
});
|
||||
}).catch(function (err) {
|
||||
cache[path] = null;
|
||||
return Promise.reject(err);
|
||||
});
|
||||
};
|
||||
|
||||
readCache$1.exports.sync = function (path, encoding) {
|
||||
path = resolve(path);
|
||||
|
||||
try {
|
||||
var stats = fs.statSync(path);
|
||||
var item = cache[path];
|
||||
|
||||
if (item && item.mtime.getTime() === stats.mtime.getTime()) {
|
||||
return convert(item.content, encoding);
|
||||
}
|
||||
|
||||
var data = fs.readFileSync(path);
|
||||
|
||||
cache[path] = {
|
||||
mtime: stats.mtime,
|
||||
content: data
|
||||
};
|
||||
|
||||
return convert(data, encoding);
|
||||
} catch (err) {
|
||||
cache[path] = null;
|
||||
throw err;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
readCache$1.exports.get = function (path, encoding) {
|
||||
path = resolve(path);
|
||||
if (cache[path]) {
|
||||
return convert(cache[path].content, encoding);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
readCache$1.exports.clear = function () {
|
||||
cache = Object.create(null);
|
||||
};
|
||||
|
||||
var readCacheExports = readCache$1.exports;
|
||||
|
||||
Generated
+330
-1
@@ -8,12 +8,18 @@
|
||||
"name": "czech-clubs-logos-frontend",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"gsap": "^3.12.5"
|
||||
"gsap": "^3.12.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"@vitejs/plugin-react-swc": "^3.7.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"postcss": "^8.4.38",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^5.4.0",
|
||||
"vite": "^5.2.11"
|
||||
}
|
||||
},
|
||||
@@ -527,6 +533,12 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.52.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz",
|
||||
@@ -835,6 +847,219 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@swc/core": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.3.tgz",
|
||||
"integrity": "sha512-Qd8eBPkUFL4eAONgGjycZXj1jFCBW8Fd+xF0PzdTlBCWQIV1xnUT7B93wUANtW3KGjl3TRcOyxwSx/u/jyKw/Q==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3",
|
||||
"@swc/types": "^0.1.25"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/swc"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-darwin-arm64": "1.15.3",
|
||||
"@swc/core-darwin-x64": "1.15.3",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.15.3",
|
||||
"@swc/core-linux-arm64-gnu": "1.15.3",
|
||||
"@swc/core-linux-arm64-musl": "1.15.3",
|
||||
"@swc/core-linux-x64-gnu": "1.15.3",
|
||||
"@swc/core-linux-x64-musl": "1.15.3",
|
||||
"@swc/core-win32-arm64-msvc": "1.15.3",
|
||||
"@swc/core-win32-ia32-msvc": "1.15.3",
|
||||
"@swc/core-win32-x64-msvc": "1.15.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/helpers": ">=0.5.17"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@swc/helpers": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-darwin-arm64": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.3.tgz",
|
||||
"integrity": "sha512-AXfeQn0CvcQ4cndlIshETx6jrAM45oeUrK8YeEY6oUZU/qzz0Id0CyvlEywxkWVC81Ajpd8TQQ1fW5yx6zQWkQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-darwin-x64": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.3.tgz",
|
||||
"integrity": "sha512-p68OeCz1ui+MZYG4wmfJGvcsAcFYb6Sl25H9TxWl+GkBgmNimIiRdnypK9nBGlqMZAcxngNPtnG3kEMNnvoJ2A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.3.tgz",
|
||||
"integrity": "sha512-Nuj5iF4JteFgwrai97mUX+xUOl+rQRHqTvnvHMATL/l9xE6/TJfPBpd3hk/PVpClMXG3Uvk1MxUFOEzM1JrMYg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-gnu": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.3.tgz",
|
||||
"integrity": "sha512-2Nc/s8jE6mW2EjXWxO/lyQuLKShcmTrym2LRf5Ayp3ICEMX6HwFqB1EzDhwoMa2DcUgmnZIalesq2lG3krrUNw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-musl": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.3.tgz",
|
||||
"integrity": "sha512-j4SJniZ/qaZ5g8op+p1G9K1z22s/EYGg1UXIb3+Cg4nsxEpF5uSIGEE4mHUfA70L0BR9wKT2QF/zv3vkhfpX4g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-gnu": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.3.tgz",
|
||||
"integrity": "sha512-aKttAZnz8YB1VJwPQZtyU8Uk0BfMP63iDMkvjhJzRZVgySmqt/apWSdnoIcZlUoGheBrcqbMC17GGUmur7OT5A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-musl": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.3.tgz",
|
||||
"integrity": "sha512-oe8FctPu1gnUsdtGJRO2rvOUIkkIIaHqsO9xxN0bTR7dFTlPTGi2Fhk1tnvXeyAvCPxLIcwD8phzKg6wLv9yug==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-arm64-msvc": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.3.tgz",
|
||||
"integrity": "sha512-L9AjzP2ZQ/Xh58e0lTRMLvEDrcJpR7GwZqAtIeNLcTK7JVE+QineSyHp0kLkO1rttCHyCy0U74kDTj0dRz6raA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-ia32-msvc": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.3.tgz",
|
||||
"integrity": "sha512-B8UtogMzErUPDWUoKONSVBdsgKYd58rRyv2sHJWKOIMCHfZ22FVXICR4O/VwIYtlnZ7ahERcjayBHDlBZpR0aw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-x64-msvc": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.3.tgz",
|
||||
"integrity": "sha512-SpZKMR9QBTecHeqpzJdYEfgw30Oo8b/Xl6rjSzBt1g0ZsXyy60KLXrp6IagQyfTYqNYE/caDvwtF2FPn7pomog==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/counter": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@swc/types": {
|
||||
"version": "0.1.25",
|
||||
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz",
|
||||
"integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
@@ -842,6 +1067,44 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
|
||||
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "18.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
||||
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react-swc": {
|
||||
"version": "3.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.11.0.tgz",
|
||||
"integrity": "sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@rolldown/pluginutils": "1.0.0-beta.27",
|
||||
"@swc/core": "^1.12.11"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^4 || ^5 || ^6 || ^7"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||
@@ -1148,6 +1411,12 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
@@ -1499,6 +1768,11 @@
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||
@@ -1519,6 +1793,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
@@ -1912,6 +2197,29 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
@@ -2033,6 +2341,14 @@
|
||||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@@ -2300,6 +2616,19 @@
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
||||
|
||||
@@ -9,12 +9,18 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"gsap": "^3.12.5"
|
||||
"gsap": "^3.12.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"@vitejs/plugin-react-swc": "^3.7.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"postcss": "^8.4.38",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^5.4.0",
|
||||
"vite": "^5.2.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,975 @@
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import gsap from 'gsap'
|
||||
import { TopNav, SiteFooter } from './layout'
|
||||
|
||||
const API_BASE_URL = '/api'
|
||||
const FACR_API_URL = 'https://facr.tdvorak.dev'
|
||||
|
||||
type Club = {
|
||||
id: string
|
||||
name: string
|
||||
type?: string
|
||||
website?: string
|
||||
logo_url?: string
|
||||
}
|
||||
|
||||
type ExistingLogo = {
|
||||
id: string
|
||||
}
|
||||
|
||||
type ClubResult = Club & {
|
||||
existingLogo?: boolean
|
||||
logoUrl?: string
|
||||
}
|
||||
|
||||
type SelectedFile = {
|
||||
file: File
|
||||
ext: string
|
||||
name: string
|
||||
description: string
|
||||
}
|
||||
|
||||
type Notification = {
|
||||
message: string
|
||||
type: 'success' | 'error' | 'info'
|
||||
} | null
|
||||
|
||||
const ClubLogoImage: React.FC<{ src?: string; alt: string }> = ({ src, alt }) => {
|
||||
const [errored, setErrored] = useState(false)
|
||||
|
||||
if (!src || errored) {
|
||||
return (
|
||||
<svg
|
||||
className="w-8 h-8 text-gray-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
className="max-w-full max-h-full object-contain"
|
||||
onError={() => setErrored(true)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const AdminApp: React.FC = () => {
|
||||
const [clubSearchQuery, setClubSearchQuery] = useState('')
|
||||
const [clubs, setClubs] = useState<ClubResult[]>([])
|
||||
const [clubSearchLoading, setClubSearchLoading] = useState(false)
|
||||
const [searchError, setSearchError] = useState<string | null>(null)
|
||||
|
||||
const [clubUuid, setClubUuid] = useState('')
|
||||
const [clubName, setClubName] = useState('')
|
||||
const [clubType, setClubType] = useState<'football' | 'futsal'>('football')
|
||||
const [clubWebsite, setClubWebsite] = useState('')
|
||||
const [uploadVisible, setUploadVisible] = useState(false)
|
||||
|
||||
const [isEditMode, setIsEditMode] = useState(false)
|
||||
|
||||
const [websiteSearchLoading, setWebsiteSearchLoading] = useState(false)
|
||||
const [websiteSearchUrl, setWebsiteSearchUrl] = useState<string | null>(null)
|
||||
|
||||
const [logoUrlInput, setLogoUrlInput] = useState('')
|
||||
const [loadFromUrlLoading, setLoadFromUrlLoading] = useState(false)
|
||||
|
||||
const [selectedFiles, setSelectedFiles] = useState<SelectedFile[]>([])
|
||||
const [uploading, setUploading] = useState(false)
|
||||
const [uploadProgress, setUploadProgress] = useState<{ uploaded: number; total: number } | null>(
|
||||
null,
|
||||
)
|
||||
|
||||
const [dragOver, setDragOver] = useState(false)
|
||||
|
||||
const [notification, setNotification] = useState<Notification>(null)
|
||||
|
||||
const searchTimeoutRef = useRef<number | null>(null)
|
||||
const searchResultsRef = useRef<HTMLDivElement | null>(null)
|
||||
const uploadSectionRef = useRef<HTMLElement | null>(null)
|
||||
const filesPreviewAreaRef = useRef<HTMLDivElement | null>(null)
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null)
|
||||
|
||||
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
||||
|
||||
const showNotification = (message: string, type: 'success' | 'error' | 'info' = 'info') => {
|
||||
setNotification({ message, type })
|
||||
window.setTimeout(() => setNotification(null), 3000)
|
||||
}
|
||||
|
||||
const searchClubs = async (query: string) => {
|
||||
setClubSearchLoading(true)
|
||||
setSearchError(null)
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/clubs/search?q=${encodeURIComponent(query)}`)
|
||||
if (!response.ok) throw new Error('API nedostupné')
|
||||
|
||||
const contentType = response.headers.get('content-type') || ''
|
||||
if (!contentType.includes('application/json')) throw new Error('API vrátilo neplatnou odpověď')
|
||||
|
||||
const clubsData = (await response.json()) as Club[]
|
||||
|
||||
let existingLogos: ExistingLogo[] = []
|
||||
try {
|
||||
const logosResponse = await fetch(`${API_BASE_URL}/logos`)
|
||||
if (logosResponse.ok) {
|
||||
const logosContentType = logosResponse.headers.get('content-type') || ''
|
||||
if (logosContentType.includes('application/json')) {
|
||||
const logosData = (await logosResponse.json()) as ExistingLogo[]
|
||||
existingLogos = logosData || []
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// optional
|
||||
}
|
||||
|
||||
const enriched: ClubResult[] = clubsData.map((club) => {
|
||||
const existingLogo = existingLogos.find((l) => l.id === club.id)
|
||||
let logoUrl = ''
|
||||
if (existingLogo) logoUrl = `${API_BASE_URL}/logos/${club.id}`
|
||||
else if (club.logo_url) logoUrl = club.logo_url
|
||||
return { ...club, existingLogo: Boolean(existingLogo), logoUrl: logoUrl || undefined }
|
||||
})
|
||||
|
||||
setClubs(enriched)
|
||||
} catch (error: any) {
|
||||
if (!String(error?.message || '').includes('<!DOCTYPE')) {
|
||||
console.warn('Search failed:', error?.message || error)
|
||||
}
|
||||
setClubs([])
|
||||
setSearchError('Hledání dočasně nedostupné')
|
||||
} finally {
|
||||
setClubSearchLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelectClub = (club: ClubResult) => {
|
||||
setClubUuid(club.id)
|
||||
setClubName(club.name)
|
||||
setClubType((club.type as 'football' | 'futsal') || 'football')
|
||||
setClubWebsite(club.website || '')
|
||||
setUploadVisible(true)
|
||||
|
||||
window.setTimeout(() => {
|
||||
if (uploadSectionRef.current) {
|
||||
uploadSectionRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
gsap.from(uploadSectionRef.current, {
|
||||
duration: 0.5,
|
||||
opacity: 0,
|
||||
y: 20,
|
||||
ease: 'power2.out',
|
||||
})
|
||||
}
|
||||
}, 0)
|
||||
|
||||
showNotification(`Vybráno: ${club.name}`, 'success')
|
||||
}
|
||||
|
||||
const handleSearchWebsite = () => {
|
||||
const name = clubName.trim()
|
||||
if (!name) {
|
||||
showNotification('Nejprve zadejte název klubu', 'error')
|
||||
return
|
||||
}
|
||||
setWebsiteSearchLoading(true)
|
||||
try {
|
||||
const searchQuery = encodeURIComponent(`${name} český fotbal oficiální web`)
|
||||
const searchUrl = `https://www.google.com/search?q=${searchQuery}`
|
||||
setWebsiteSearchUrl(searchUrl)
|
||||
} catch (error) {
|
||||
console.error('Website search error:', error)
|
||||
} finally {
|
||||
setWebsiteSearchLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleFilesSelect = (files: File[]) => {
|
||||
const validFiles: SelectedFile[] = []
|
||||
for (const file of files) {
|
||||
const ext = file.name.split('.').pop()?.toLowerCase() || ''
|
||||
if (ext === 'svg' || ext === 'png' || ext === 'pdf') {
|
||||
validFiles.push({ file, ext, name: '', description: '' })
|
||||
}
|
||||
}
|
||||
if (validFiles.length === 0) {
|
||||
showNotification('Vyberte prosím SVG, PNG nebo PDF soubory', 'error')
|
||||
return
|
||||
}
|
||||
setSelectedFiles(validFiles)
|
||||
}
|
||||
|
||||
const handleFileMetadataChange = (
|
||||
index: number,
|
||||
field: 'name' | 'description',
|
||||
value: string,
|
||||
) => {
|
||||
setSelectedFiles((prev) => {
|
||||
const next = [...prev]
|
||||
if (!next[index]) return prev
|
||||
next[index] = { ...next[index], [field]: value }
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
const handleRemoveFile = (index: number) => {
|
||||
setSelectedFiles((prev) => {
|
||||
const next = [...prev]
|
||||
next.splice(index, 1)
|
||||
if (next.length === 0 && fileInputRef.current) fileInputRef.current.value = ''
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
const handleLoadFromUrl = async () => {
|
||||
const url = logoUrlInput.trim()
|
||||
if (!url) {
|
||||
showNotification('Zadejte prosím URL obrázku', 'error')
|
||||
return
|
||||
}
|
||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||
showNotification('URL musí začínat http:// nebo https://', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
setLoadFromUrlLoading(true)
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) throw new Error('Nelze načíst obrázek')
|
||||
const blob = await response.blob()
|
||||
|
||||
let ext = 'png'
|
||||
const contentType = response.headers.get('content-type') || ''
|
||||
if (contentType.includes('svg')) ext = 'svg'
|
||||
else if (contentType.includes('pdf')) ext = 'pdf'
|
||||
else if (contentType.includes('png')) ext = 'png'
|
||||
else {
|
||||
const urlExt = url.split('.').pop()?.toLowerCase().split('?')[0]
|
||||
if (urlExt && ['svg', 'png', 'pdf'].includes(urlExt)) ext = urlExt
|
||||
}
|
||||
|
||||
const filename = `logo-${Date.now()}.${ext}`
|
||||
const file = new File([blob], filename, { type: blob.type })
|
||||
handleFilesSelect([file])
|
||||
showNotification('Obrázek úspěšně načten z URL', 'success')
|
||||
} catch (error: any) {
|
||||
console.error('Load from URL error:', error)
|
||||
showNotification(`Chyba načítání: ${error?.message || 'Chyba'}`, 'error')
|
||||
} finally {
|
||||
setLoadFromUrlLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const resetFormAfterUpload = () => {
|
||||
setClubUuid('')
|
||||
setClubName('')
|
||||
setClubType('football')
|
||||
setClubWebsite('')
|
||||
setSelectedFiles([])
|
||||
setUploadVisible(false)
|
||||
setClubSearchQuery('')
|
||||
setClubs([])
|
||||
setWebsiteSearchUrl(null)
|
||||
setLogoUrlInput('')
|
||||
if (fileInputRef.current) fileInputRef.current.value = ''
|
||||
}
|
||||
|
||||
const uploadLogos = async (
|
||||
uuid: string,
|
||||
clubNameValue: string,
|
||||
clubTypeValue: string,
|
||||
clubWebsiteValue: string,
|
||||
filesData: SelectedFile[],
|
||||
) => {
|
||||
setUploading(true)
|
||||
setUploadProgress({ uploaded: 0, total: filesData.length })
|
||||
try {
|
||||
let uploadedCount = 0
|
||||
for (let i = 0; i < filesData.length; i++) {
|
||||
const fileData = filesData[i]
|
||||
const formData = new FormData()
|
||||
formData.append('file', fileData.file)
|
||||
formData.append('club_name', clubNameValue)
|
||||
if (clubTypeValue) formData.append('club_type', clubTypeValue)
|
||||
if (clubWebsiteValue) formData.append('club_website', clubWebsiteValue)
|
||||
|
||||
if (i > 0) {
|
||||
formData.append('variant', 'true')
|
||||
if (fileData.name) formData.append('variant_name', fileData.name)
|
||||
if (fileData.description) formData.append('variant_description', fileData.description)
|
||||
} else {
|
||||
if (fileData.name) formData.append('variant_name', fileData.name || 'Hlavní')
|
||||
if (fileData.description) formData.append('variant_description', fileData.description)
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}/logos/${uuid}`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
let message = 'Upload failed'
|
||||
try {
|
||||
const errorData = await response.json()
|
||||
if (errorData && errorData.error) message = errorData.error
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
throw new Error(message)
|
||||
}
|
||||
|
||||
uploadedCount++
|
||||
setUploadProgress({ uploaded: uploadedCount, total: filesData.length })
|
||||
}
|
||||
|
||||
showNotification(
|
||||
`${uploadedCount} ${uploadedCount === 1 ? 'logo' : 'loga'} úspěšně nahráno pro ${
|
||||
clubNameValue || uuid
|
||||
}! ✓`,
|
||||
'success',
|
||||
)
|
||||
|
||||
window.setTimeout(() => {
|
||||
resetFormAfterUpload()
|
||||
}, 2000)
|
||||
} catch (error: any) {
|
||||
console.error('Upload error:', error)
|
||||
showNotification(`Nahrání selhalo: ${error?.message || 'Chyba'}`, 'error')
|
||||
} finally {
|
||||
setUploading(false)
|
||||
setUploadProgress(null)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
const uuid = clubUuid.trim()
|
||||
const name = clubName.trim()
|
||||
const type = clubType
|
||||
const website = clubWebsite.trim()
|
||||
|
||||
if (!uuid) {
|
||||
showNotification('Nejprve vyberte klub', 'error')
|
||||
return
|
||||
}
|
||||
if (selectedFiles.length === 0) {
|
||||
showNotification('Vyberte prosím soubor loga', 'error')
|
||||
return
|
||||
}
|
||||
if (!uuidRegex.test(uuid)) {
|
||||
showNotification('Neplatný formát UUID', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
await uploadLogos(uuid, name, type, website, selectedFiles)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log('🇨🇿 České Kluby Loga API - Administrace')
|
||||
console.log('Backend API:', API_BASE_URL)
|
||||
console.log('FAČR API:', FACR_API_URL)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const editId = params.get('id')
|
||||
if (editId) {
|
||||
setIsEditMode(true)
|
||||
setClubUuid(editId)
|
||||
setUploadVisible(true)
|
||||
showNotification('Režim úprav pro existující logo', 'info')
|
||||
;(async () => {
|
||||
try {
|
||||
const resp = await fetch(`${API_BASE_URL}/logos/${editId}/json`)
|
||||
if (resp.ok) {
|
||||
const contentType = resp.headers.get('content-type') || ''
|
||||
if (contentType.includes('application/json')) {
|
||||
const data = await resp.json()
|
||||
if (data.club_name) setClubName(data.club_name)
|
||||
if (data.club_type) setClubType(data.club_type)
|
||||
if (data.club_website) setClubWebsite(data.club_website)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// non-fatal
|
||||
}
|
||||
})()
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const timer = window.setTimeout(() => {
|
||||
showNotification('Administrace: Vyhledejte kluby a nahrajte loga', 'info')
|
||||
}, 1000)
|
||||
return () => window.clearTimeout(timer)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (searchTimeoutRef.current) window.clearTimeout(searchTimeoutRef.current)
|
||||
const query = clubSearchQuery.trim()
|
||||
if (query.length < 2) {
|
||||
setClubs([])
|
||||
setSearchError(null)
|
||||
return
|
||||
}
|
||||
searchTimeoutRef.current = window.setTimeout(() => {
|
||||
searchClubs(query)
|
||||
}, 300)
|
||||
}, [clubSearchQuery])
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchResultsRef.current || clubs.length === 0) return
|
||||
const items = searchResultsRef.current.querySelectorAll('.club-result')
|
||||
if (!items.length) return
|
||||
gsap.from(items, {
|
||||
duration: 0.4,
|
||||
opacity: 0,
|
||||
y: 20,
|
||||
stagger: 0.08,
|
||||
ease: 'power2.out',
|
||||
})
|
||||
}, [clubs])
|
||||
|
||||
useEffect(() => {
|
||||
if (!filesPreviewAreaRef.current || selectedFiles.length === 0) return
|
||||
const items = filesPreviewAreaRef.current.querySelectorAll('[data-file-index]')
|
||||
if (!items.length) return
|
||||
gsap.from(items, {
|
||||
duration: 0.4,
|
||||
opacity: 0,
|
||||
y: 10,
|
||||
stagger: 0.05,
|
||||
ease: 'power2.out',
|
||||
})
|
||||
}, [selectedFiles])
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Navigation */}
|
||||
<TopNav active="admin" />
|
||||
|
||||
{/* Admin Header */}
|
||||
<header className="border-b border-dark-border bg-dark-card">
|
||||
<div className="container mx-auto px-6 py-8">
|
||||
<h1 className="text-3xl font-bold gradient-text mb-2">Administrace</h1>
|
||||
<p className="text-gray-400">Vyhledejte kluby a nahrajte jejich loga</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="container mx-auto px-6 py-12">
|
||||
{/* Club Search Section */}
|
||||
<section className="mb-12">
|
||||
<div className="bg-dark-card rounded-xl p-6 border border-dark-border">
|
||||
<h2 className="text-2xl font-bold mb-2">Krok 1: Vyhledat klub</h2>
|
||||
<p className="text-sm text-gray-400 mb-4">
|
||||
Začněte psát název klubu nebo města, poté vyberte správný klub ze seznamu.
|
||||
</p>
|
||||
|
||||
<div className="relative mb-6">
|
||||
<span className="pointer-events-none absolute inset-y-0 left-3 flex items-center text-gray-500">
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M21 21l-4.35-4.35M11 5a6 6 0 100 12 6 6 0 000-12z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
id="clubSearch"
|
||||
placeholder="Hledat české kluby (např. Sparta, Slavia)..."
|
||||
className="w-full bg-dark-bg border border-dark-border rounded-lg px-4 pl-9 py-3 text-sm text-white focus:outline-none focus:border-accent-blue transition-smooth"
|
||||
value={clubSearchQuery}
|
||||
onChange={(e) => setClubSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Search Results */}
|
||||
<div id="searchResults" className="space-y-3" ref={searchResultsRef}>
|
||||
{clubSearchLoading && (
|
||||
<div className="text-center py-4">
|
||||
<div className="spinner mx-auto" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!clubSearchLoading && searchError && (
|
||||
<div className="text-center py-4 text-yellow-400">
|
||||
<p className="mb-2">Hledání dočasně nedostupné</p>
|
||||
<p className="text-xs text-gray-400">Zkontrolujte, zda běží backend server</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!clubSearchLoading &&
|
||||
!searchError &&
|
||||
clubSearchQuery.trim().length >= 2 &&
|
||||
clubs.length === 0 && (
|
||||
<div className="text-center py-8 text-gray-400">
|
||||
<p>Žádné kluby nenalezeny</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!clubSearchLoading &&
|
||||
!searchError &&
|
||||
clubs.map((club) => (
|
||||
<div
|
||||
key={club.id}
|
||||
className="club-result bg-dark-bg rounded-lg p-4 border border-dark-border hover:border-accent-blue transition-smooth cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex-shrink-0 w-16 h-16 flex items-center justify-center bg-dark-border/30 rounded-lg p-2">
|
||||
<ClubLogoImage src={club.logoUrl} alt={club.name} />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-semibold text-lg truncate">{club.name}</h3>
|
||||
<p className="text-sm text-gray-400">{club.type || 'football'}</p>
|
||||
<p className="text-xs text-gray-500 font-mono mt-1 truncate">{club.id}</p>
|
||||
{club.website && (
|
||||
<p className="text-xs text-blue-400 mt-1 truncate">{club.website}</p>
|
||||
)}
|
||||
{club.existingLogo && (
|
||||
<p className="text-xs text-green-400 mt-1">Logo již nahráno</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 flex-shrink-0">
|
||||
{club.existingLogo && (
|
||||
<a
|
||||
href={`/logo.html?id=${club.id}`}
|
||||
className="px-4 py-2 bg-gray-700 rounded-lg hover:bg-gray-600 transition-smooth text-sm text-center"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
Detail
|
||||
</a>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="select-club px-4 py-2 bg-accent-blue rounded-lg hover:bg-blue-600 transition-smooth text-sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleSelectClub(club)
|
||||
}}
|
||||
>
|
||||
Vybrat
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Upload Section */}
|
||||
<section
|
||||
id="uploadSection"
|
||||
ref={uploadSectionRef}
|
||||
className={uploadVisible ? '' : 'hidden'}
|
||||
>
|
||||
<div className="bg-dark-card rounded-xl p-6 border border-dark-border">
|
||||
<h2 className="text-2xl font-bold mb-2">Krok 2: Nahrát logo</h2>
|
||||
<p className="text-sm text-gray-400 mb-4">
|
||||
Zkontrolujte údaje o klubu a nahrajte hlavní logo i případné varianty.
|
||||
</p>
|
||||
{clubUuid && (
|
||||
<div className="mb-4 rounded-lg border border-dark-border bg-dark-bg/60 px-4 py-3 text-sm md:flex md:items-center md:justify-between md:gap-4">
|
||||
<div className="min-w-0">
|
||||
{isEditMode && (
|
||||
<p className="mb-1 text-[11px] font-semibold uppercase tracking-wide text-accent-blue">
|
||||
Režim úprav existujícího loga
|
||||
</p>
|
||||
)}
|
||||
<p className="font-semibold truncate">
|
||||
{clubName || 'Vybraný klub'}
|
||||
</p>
|
||||
<p className="text-xs text-gray-400 truncate">
|
||||
{clubType === 'futsal' ? 'Futsal' : 'Fotbal'}
|
||||
{clubWebsite ? ` • ${clubWebsite}` : ''}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-3 flex flex-wrap gap-2 md:mt-0 md:ml-4">
|
||||
<span className="inline-flex items-center rounded-full border border-dark-border px-3 py-1 text-xs text-gray-400 font-mono max-w-full truncate">
|
||||
{clubUuid}
|
||||
</span>
|
||||
<a
|
||||
href={`/logo.html?id=${clubUuid}`}
|
||||
className="inline-flex items-center rounded-full border border-dark-border px-3 py-1 text-xs text-accent-blue hover:border-accent-blue transition-smooth"
|
||||
>
|
||||
Detail loga
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<form id="uploadForm" className="space-y-6" onSubmit={handleSubmit}>
|
||||
{/* Club UUID (Read-only) */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-400 mb-2">
|
||||
UUID Klubu <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="clubUuid"
|
||||
readOnly
|
||||
className="w-full bg-dark-bg/50 border border-dark-border rounded-lg px-4 py-3 text-gray-400 cursor-not-allowed"
|
||||
value={clubUuid}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Club Name (Optional) */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-400 mb-2">
|
||||
Název Klubu{' '}
|
||||
<span className="text-gray-500 text-xs">(volitelné)</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="clubName"
|
||||
placeholder="AC Sparta Praha"
|
||||
className="w-full bg-dark-bg border border-dark-border rounded-lg px-4 py-3 text-white focus:outline-none focus:border-accent-blue transition-smooth"
|
||||
value={clubName}
|
||||
onChange={(e) => setClubName(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Volitelné: Pokud název neuvedete, doplníme jej automaticky dle
|
||||
FAČR (podle UUID)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Club Type */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-400 mb-2">
|
||||
Typ Klubu
|
||||
</label>
|
||||
<select
|
||||
id="clubType"
|
||||
className="w-full bg-dark-bg border border-dark-border rounded-lg px-4 py-3 text-white focus:outline-none focus:border-accent-blue transition-smooth"
|
||||
value={clubType}
|
||||
onChange={(e) => setClubType(e.target.value as 'football' | 'futsal')}
|
||||
>
|
||||
<option value="football">Fotbal</option>
|
||||
<option value="futsal">Futsal</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Club Website with Search */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-400 mb-2">
|
||||
Web Klubu
|
||||
<button
|
||||
type="button"
|
||||
id="searchWebsite"
|
||||
className="ml-2 text-accent-blue hover:text-blue-400 text-xs disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onClick={handleSearchWebsite}
|
||||
disabled={websiteSearchLoading}
|
||||
>
|
||||
{websiteSearchLoading ? (
|
||||
<span className="inline-flex items-center">
|
||||
<div className="spinner inline-block w-4 h-4" />
|
||||
</span>
|
||||
) : (
|
||||
'Hledat online'
|
||||
)}
|
||||
</button>
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
id="clubWebsite"
|
||||
placeholder="https://www.sparta.cz"
|
||||
className="w-full bg-dark-bg border border-dark-border rounded-lg px-4 py-3 text-white focus:outline-none focus:border-accent-blue transition-smooth"
|
||||
value={clubWebsite}
|
||||
onChange={(e) => setClubWebsite(e.target.value)}
|
||||
/>
|
||||
<div
|
||||
id="websiteSearchResults"
|
||||
className={`mt-2 ${websiteSearchUrl ? '' : 'hidden'}`}
|
||||
>
|
||||
{websiteSearchUrl && (
|
||||
<div className="bg-dark-bg rounded-lg p-3 border border-dark-border">
|
||||
<p className="text-sm text-gray-400 mb-2">Vyhledat web klubu:</p>
|
||||
<a
|
||||
href={websiteSearchUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-accent-blue hover:text-blue-400 text-sm"
|
||||
>
|
||||
Hledat "{clubName || 'klub'}" na Google
|
||||
</a>
|
||||
<p className="text-xs text-gray-500 mt-2">
|
||||
Zkopírujte URL oficiálního webu a vložte jej výše
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* File Upload Area */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-400 mb-2">
|
||||
Soubor Loga <span className="text-red-500">*</span>
|
||||
</label>
|
||||
|
||||
{/* URL Upload */}
|
||||
<div className="mb-3">
|
||||
<input
|
||||
type="url"
|
||||
id="logoUrl"
|
||||
placeholder="Nebo vložte URL obrázku (https://...)"
|
||||
className="w-full bg-dark-bg border border-dark-border rounded-lg px-4 py-3 text-white focus:outline-none focus:border-accent-blue transition-smooth"
|
||||
value={logoUrlInput}
|
||||
onChange={(e) => setLogoUrlInput(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
id="loadFromUrl"
|
||||
className="mt-2 px-4 py-2 bg-blue-600 rounded-lg hover:bg-blue-700 transition-smooth text-sm disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onClick={handleLoadFromUrl}
|
||||
disabled={loadFromUrlLoading}
|
||||
>
|
||||
{loadFromUrlLoading ? (
|
||||
<span className="inline-flex items-center">
|
||||
<div className="spinner inline-block w-4 h-4" />
|
||||
</span>
|
||||
) : (
|
||||
'Načíst z URL'
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-dark-border" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-dark-card text-gray-400">nebo</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="uploadArea"
|
||||
className={`upload-area rounded-lg p-12 text-center cursor-pointer border-2 border-dashed border-dark-border hover:border-accent-blue transition-smooth mt-3 ${
|
||||
dragOver ? 'dragover border-accent-blue' : ''
|
||||
}`}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault()
|
||||
setDragOver(true)
|
||||
}}
|
||||
onDragLeave={(e) => {
|
||||
e.preventDefault()
|
||||
setDragOver(false)
|
||||
}}
|
||||
onDrop={(e) => {
|
||||
e.preventDefault()
|
||||
setDragOver(false)
|
||||
const files = Array.from(e.dataTransfer.files || [])
|
||||
if (files.length > 0) {
|
||||
handleFilesSelect(files)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
style={{ width: 75, paddingTop: 20 }}
|
||||
className="mx-auto h-12 w-12 text-gray-400 mb-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
|
||||
/>
|
||||
</svg>
|
||||
<p className="text-lg mb-2">
|
||||
Přetáhněte logo sem nebo{' '}
|
||||
<span className="text-accent-blue font-semibold">
|
||||
procházet
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
SVG, PNG nebo PDF • Preferováno průhledné pozadí
|
||||
</p>
|
||||
<p className="text-xs text-gray-600 mt-2">
|
||||
SVG a PDF soubory budou automaticky převedeny na PNG
|
||||
</p>
|
||||
<input
|
||||
type="file"
|
||||
id="fileInput"
|
||||
accept=".svg,.png,.pdf"
|
||||
className="hidden"
|
||||
multiple
|
||||
ref={fileInputRef}
|
||||
onChange={(e) => {
|
||||
const files = Array.from(e.target.files || [])
|
||||
if (files.length > 0) {
|
||||
handleFilesSelect(files)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-gray-500 mt-2">
|
||||
Můžete vybrat více souborů najednou pro nahrání variant
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Files Preview */}
|
||||
<div
|
||||
id="filesPreviewArea"
|
||||
ref={filesPreviewAreaRef}
|
||||
className={selectedFiles.length > 0 ? '' : 'hidden'}
|
||||
>
|
||||
<h3 className="text-lg font-semibold mb-3">Vybrané soubory</h3>
|
||||
<div id="filesPreviewList" className="space-y-3">
|
||||
{selectedFiles.map((fileObj, index) => {
|
||||
const sizeKB = (fileObj.file.size / 1024).toFixed(2)
|
||||
const isPrimary = index === 0
|
||||
const icon =
|
||||
fileObj.ext === 'svg' ? 'SVG' : fileObj.ext === 'pdf' ? 'PDF' : 'PNG'
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-dark-bg rounded-lg p-4 border border-dark-border"
|
||||
data-file-index={index}
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex-shrink-0 w-16 h-16 bg-dark-border/30 rounded flex items-center justify-center">
|
||||
<span className="text-2xl">{icon}</span>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<h4 className="font-semibold">{fileObj.file.name}</h4>
|
||||
{isPrimary && (
|
||||
<span className="px-2 py-0.5 bg-accent-blue rounded text-xs">
|
||||
Hlavní
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-400 mb-3">
|
||||
{fileObj.ext.toUpperCase()} • {sizeKB} KB
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Název varianty (volitelné)"
|
||||
value={fileObj.name}
|
||||
onChange={(e) =>
|
||||
handleFileMetadataChange(index, 'name', e.target.value)
|
||||
}
|
||||
className="w-full bg-dark-card border border-dark-border rounded px-3 py-2 text-sm text-white focus:outline-none focus:border-accent-blue transition-smooth"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Popis (volitelné)"
|
||||
value={fileObj.description}
|
||||
onChange={(e) =>
|
||||
handleFileMetadataChange(index, 'description', e.target.value)
|
||||
}
|
||||
className="w-full bg-dark-card border border-dark-border rounded px-3 py-2 text-sm text-white focus:outline-none focus:border-accent-blue transition-smooth"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveFile(index)}
|
||||
className="flex-shrink-0 p-2 text-red-400 hover:text-red-300 transition-smooth"
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upload Button */}
|
||||
<button
|
||||
type="submit"
|
||||
id="uploadSubmit"
|
||||
className="w-full px-6 py-4 bg-accent-green rounded-lg font-semibold hover:bg-green-600 transition-smooth disabled:opacity-50 disabled:cursor-not-allowed text-lg"
|
||||
disabled={uploading}
|
||||
>
|
||||
{uploading && uploadProgress ? (
|
||||
<span className="inline-flex items-center justify-center gap-2">
|
||||
<div className="spinner mx-auto" />
|
||||
<span>
|
||||
{uploadProgress.uploaded}/{uploadProgress.total}
|
||||
</span>
|
||||
</span>
|
||||
) : (
|
||||
isEditMode ? 'Aktualizovat logo' : 'Nahrát logo'
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Requirements Notice */}
|
||||
<div className="bg-red-900/20 border border-red-800 rounded-lg p-4 text-sm">
|
||||
<p className="font-semibold text-red-400 mb-2">
|
||||
⚠️ Požadavky na nahrání:
|
||||
</p>
|
||||
<ul className="list-disc list-inside space-y-1 text-red-300/80">
|
||||
<li>
|
||||
Název klubu je volitelný (doplníme dle FAČR podle UUID)
|
||||
</li>
|
||||
<li>UUID klubu musí být platné</li>
|
||||
<li>Akceptovány pouze SVG, PNG a PDF soubory</li>
|
||||
<li>Doporučeno průhledné pozadí</li>
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<SiteFooter caption="České Kluby Loga API" />
|
||||
{notification && (
|
||||
<div
|
||||
className={`fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 text-white font-medium ${
|
||||
notification.type === 'success'
|
||||
? 'bg-accent-green'
|
||||
: notification.type === 'error'
|
||||
? 'bg-red-500'
|
||||
: 'bg-accent-blue'
|
||||
}`}
|
||||
>
|
||||
{notification.message}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AdminApp
|
||||
@@ -0,0 +1,348 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import gsap from 'gsap'
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
||||
import { TopNav, SiteFooter } from './layout'
|
||||
|
||||
const API_BASE_URL = '/api'
|
||||
|
||||
type Logo = {
|
||||
id: string
|
||||
club_name: string
|
||||
club_city?: string
|
||||
club_type?: string
|
||||
has_svg?: boolean
|
||||
has_png?: boolean
|
||||
}
|
||||
|
||||
gsap.registerPlugin(ScrollTrigger)
|
||||
|
||||
function useHomeAnimations() {
|
||||
useEffect(() => {
|
||||
gsap.from('.hero-content', {
|
||||
duration: 1,
|
||||
opacity: 0,
|
||||
y: 50,
|
||||
ease: 'power3.out',
|
||||
delay: 0.2,
|
||||
})
|
||||
|
||||
gsap.utils.toArray<HTMLElement>('.feature-card').forEach((card, index) => {
|
||||
gsap.from(card, {
|
||||
scrollTrigger: {
|
||||
trigger: card,
|
||||
start: 'top 80%',
|
||||
toggleActions: 'play none none reverse',
|
||||
},
|
||||
duration: 0.6,
|
||||
opacity: 0,
|
||||
y: 30,
|
||||
delay: index * 0.1,
|
||||
ease: 'power2.out',
|
||||
})
|
||||
})
|
||||
}, [])
|
||||
}
|
||||
|
||||
function useRecentLogos() {
|
||||
const [logos, setLogos] = useState<Logo[]>([])
|
||||
const [filtered, setFiltered] = useState<Logo[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
try {
|
||||
const resp = await fetch(`${API_BASE_URL}/logos?sort=recent&limit=8`)
|
||||
if (!resp.ok) throw new Error('Failed to fetch recent logos')
|
||||
const data: Logo[] = await resp.json()
|
||||
setLogos(data)
|
||||
setFiltered(data)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
setError('Načtení log selhalo')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
load()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const q = search.trim().toLowerCase()
|
||||
if (!q) {
|
||||
setFiltered(logos)
|
||||
return
|
||||
}
|
||||
setFiltered(
|
||||
logos.filter((logo) => logo.club_name.toLowerCase().includes(q))
|
||||
)
|
||||
}, [search, logos])
|
||||
|
||||
return {
|
||||
logos,
|
||||
filtered,
|
||||
loading,
|
||||
error,
|
||||
search,
|
||||
setSearch,
|
||||
}
|
||||
}
|
||||
|
||||
const App: React.FC = () => {
|
||||
useHomeAnimations()
|
||||
const { filtered, loading, error, search, setSearch } = useRecentLogos()
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopNav active="home" />
|
||||
|
||||
<header className="relative overflow-hidden border-b border-dark-border">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-blue-600/10 to-green-600/10" />
|
||||
<div className="container mx-auto px-6 py-20 relative z-10">
|
||||
<div className="text-center hero-content max-w-4xl mx-auto">
|
||||
<h1 className="text-5xl md:text-7xl font-bold mb-6">
|
||||
<span className="gradient-text">České Kluby Loga CDN</span>
|
||||
</h1>
|
||||
<p className="text-xl text-gray-400 mb-8">
|
||||
Vysoce kvalitní loga českých fotbalových a futsalových klubů s
|
||||
průhledným pozadím. Založeno na UUID, API-first, připraveno pro
|
||||
produkci.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-4 justify-center">
|
||||
<button
|
||||
onClick={() => (window.location.href = '/logos.html')}
|
||||
className="px-8 py-4 bg-accent-blue rounded-lg font-semibold hover:bg-blue-600 transition-smooth text-lg"
|
||||
>
|
||||
Procházet loga
|
||||
</button>
|
||||
<a
|
||||
href="/admin.html"
|
||||
className="px-8 py-4 bg-accent-green rounded-lg font-semibold hover:bg-green-600 transition-smooth text-lg"
|
||||
>
|
||||
Nahrát logo
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section className="container mx-auto px-6 py-16" id="logoGallery">
|
||||
<div className="bg-dark-card border border-dark-border rounded-3xl px-6 py-6 md:px-8 md:py-8 shadow-sm">
|
||||
<div className="mb-8 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold mb-1">Dostupná loga klubů</h2>
|
||||
<p className="text-sm text-gray-500">
|
||||
Nejnovější nahraná loga z registru, připravená pro vaše aplikace.
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full md:w-auto flex flex-col items-stretch gap-3 md:flex-row md:items-center md:gap-4">
|
||||
<input
|
||||
type="text"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder="Filtrovat podle názvu klubu..."
|
||||
className="w-full md:w-64 bg-dark-bg border border-dark-border rounded-lg px-4 py-2.5 text-sm text-white focus:outline-none focus:border-accent-blue transition-smooth"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => (window.location.href = '/logos.html')}
|
||||
className="inline-flex items-center justify-center px-4 py-2.5 text-sm rounded-lg border border-dark-border bg-dark-bg hover:border-accent-blue transition-smooth"
|
||||
>
|
||||
Zobrazit všechna loga
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loading && (
|
||||
<div className="text-center py-12">
|
||||
<div className="spinner mx-auto" />
|
||||
<p className="mt-4 text-gray-400">Načítání log klubů...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && error && (
|
||||
<div className="text-center py-12 text-red-400">{error}</div>
|
||||
)}
|
||||
|
||||
{!loading && !error && filtered.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-lg text-gray-400 mb-4">
|
||||
Zatím nebyla nahrána žádná loga
|
||||
</p>
|
||||
<a
|
||||
href="/admin.html"
|
||||
className="px-6 py-3 bg-accent-green rounded-lg font-semibold hover:bg-green-600 transition-smooth inline-block text-sm"
|
||||
>
|
||||
Nahrát první logo
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && !error && filtered.length > 0 && (
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-4 md:gap-6">
|
||||
{filtered.map((logo) => {
|
||||
const logoUrl = `${API_BASE_URL}/logos/${logo.id}`
|
||||
return (
|
||||
<button
|
||||
key={logo.id}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
(window.location.href = `/logo.html?id=${logo.id}`)
|
||||
}
|
||||
className="logo-card bg-dark-card rounded-xl p-4 border border-dark-border hover:border-accent-blue transition-smooth cursor-pointer group text-left"
|
||||
>
|
||||
<div className="aspect-square bg-dark-bg rounded-lg flex items-center justify-center mb-3 overflow-hidden">
|
||||
<img
|
||||
src={logoUrl}
|
||||
alt={logo.club_name}
|
||||
className="max-w-full max-h-full object-contain p-2 group-hover:scale-110 transition-transform duration-300"
|
||||
loading="lazy"
|
||||
onError={(e) => {
|
||||
const el = e.currentTarget.parentElement
|
||||
if (!el) return
|
||||
el.innerHTML =
|
||||
"<svg class='w-8 h-8 text-gray-500' fill='none' stroke='currentColor' viewBox='0 0 24 24'><path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z'></path></svg>"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<h3 className="font-semibold text-sm truncate mb-1">
|
||||
{logo.club_name}
|
||||
</h3>
|
||||
<p className="text-xs text-gray-400 truncate">
|
||||
{logo.club_city
|
||||
? `${logo.club_city} | ||||