From 4532b7a87a57e51e78b1d3756e82cb2fb7f02a57 Mon Sep 17 00:00:00 2001 From: Khairul Hidayat Date: Sun, 25 Aug 2024 00:15:23 +0000 Subject: [PATCH] feat: initial commit --- .air.toml | 51 + .gitignore | 5 + Makefile | 10 + go.mod | 22 + go.sum | 73 + lib/tasks.go | 85 + lib/yt2mp3.go | 213 +++ main.go | 144 ++ rest.http | 17 + types/api.go | 19 + ui/.gitignore | 24 + ui/README.md | 50 + ui/eslint.config.js | 28 + ui/index.html | 17 + ui/package.json | 35 + ui/pnpm-lock.yaml | 2433 ++++++++++++++++++++++++++ ui/postcss.config.js | 6 + ui/public/android-chrome-192x192.png | Bin 0 -> 9773 bytes ui/public/android-chrome-512x512.png | Bin 0 -> 22407 bytes ui/public/apple-touch-icon.png | Bin 0 -> 8827 bytes ui/public/favicon-16x16.png | Bin 0 -> 483 bytes ui/public/favicon-32x32.png | Bin 0 -> 1091 bytes ui/public/favicon.ico | Bin 0 -> 15406 bytes ui/public/site.webmanifest | 1 + ui/public/vite.svg | 1 + ui/src/App.tsx | 347 ++++ ui/src/api.ts | 18 + ui/src/assets/react.svg | 1 + ui/src/hooks/useFetch.ts | 48 + ui/src/index.css | 3 + ui/src/main.tsx | 10 + ui/src/vite-env.d.ts | 1 + ui/tailwind.config.js | 15 + ui/tsconfig.app.json | 24 + ui/tsconfig.json | 7 + ui/tsconfig.node.json | 22 + ui/ui.go | 10 + ui/ui_prod.go | 34 + ui/vite.config.ts | 12 + utils/utils.go | 10 + 40 files changed, 3796 insertions(+) create mode 100644 .air.toml create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 go.mod create mode 100644 go.sum create mode 100644 lib/tasks.go create mode 100644 lib/yt2mp3.go create mode 100644 main.go create mode 100644 rest.http create mode 100644 types/api.go create mode 100644 ui/.gitignore create mode 100644 ui/README.md create mode 100644 ui/eslint.config.js create mode 100644 ui/index.html create mode 100644 ui/package.json create mode 100644 ui/pnpm-lock.yaml create mode 100644 ui/postcss.config.js create mode 100755 ui/public/android-chrome-192x192.png create mode 100755 ui/public/android-chrome-512x512.png create mode 100755 ui/public/apple-touch-icon.png create mode 100755 ui/public/favicon-16x16.png create mode 100755 ui/public/favicon-32x32.png create mode 100755 ui/public/favicon.ico create mode 100755 ui/public/site.webmanifest create mode 100644 ui/public/vite.svg create mode 100644 ui/src/App.tsx create mode 100644 ui/src/api.ts create mode 100644 ui/src/assets/react.svg create mode 100644 ui/src/hooks/useFetch.ts create mode 100644 ui/src/index.css create mode 100644 ui/src/main.tsx create mode 100644 ui/src/vite-env.d.ts create mode 100644 ui/tailwind.config.js create mode 100644 ui/tsconfig.app.json create mode 100644 ui/tsconfig.json create mode 100644 ui/tsconfig.node.json create mode 100644 ui/ui.go create mode 100644 ui/ui_prod.go create mode 100644 ui/vite.config.ts create mode 100644 utils/utils.go diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..58fff2a --- /dev/null +++ b/.air.toml @@ -0,0 +1,51 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./tmp/main" + cmd = "go build -o ./tmp/main ." + delay = 1000 + exclude_dir = ["assets", "tmp", "vendor", "testdata"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + post_cmd = [] + pre_cmd = [] + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + time = false + +[misc] + clean_on_exit = false + +[proxy] + app_port = 0 + enabled = false + proxy_port = 0 + +[screen] + clear_on_rebuild = false + keep_scroll = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aad5b19 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +out/ +tmp/ +main +._.DS_Store +.DS_Store diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e5aeb08 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +all: build-ui backend + +build-ui: + cd ui && npm run build + +backend: + CGO_ENABLED=0 go build -o main -tags="prod" main.go + +clean: + rm -f main && rm -rf ui/dist diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..27b3d91 --- /dev/null +++ b/go.mod @@ -0,0 +1,22 @@ +module rul.sh/go-ytmp3 + +go 1.23.0 + +require github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 + +require ( + github.com/disintegration/imaging v1.6.2 // indirect + github.com/joho/godotenv v1.5.1 // indirect +) + +require ( + github.com/anthonynsimon/bild v0.14.0 + github.com/aws/aws-sdk-go v1.55.5 // indirect + github.com/gosimple/slug v1.14.0 // indirect + github.com/gosimple/unidecode v1.0.1 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/u2takey/ffmpeg-go v0.5.0 // indirect + github.com/u2takey/go-utils v0.3.1 // indirect + github.com/wader/goutubedl v0.0.0-20240818101919-a623bde37ba9 // indirect + golang.org/x/image v0.19.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..eb5ab1e --- /dev/null +++ b/go.sum @@ -0,0 +1,73 @@ +github.com/anthonynsimon/bild v0.14.0 h1:IFRkmKdNdqmexXHfEU7rPlAmdUZ8BDZEGtGHDnGWync= +github.com/anthonynsimon/bild v0.14.0/go.mod h1:hcvEAyBjTW69qkKJTfpcDQ83sSZHxwOunsseDfeQhUs= +github.com/aws/aws-sdk-go v1.38.20/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gosimple/slug v1.14.0 h1:RtTL/71mJNDfpUbCOmnf/XFkzKRtD6wL6Uy+3akm4Es= +github.com/gosimple/slug v1.14.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= +github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= +github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/u2takey/ffmpeg-go v0.5.0 h1:r7d86XuL7uLWJ5mzSeQ03uvjfIhiJYvsRAJFCW4uklU= +github.com/u2takey/ffmpeg-go v0.5.0/go.mod h1:ruZWkvC1FEiUNjmROowOAps3ZcWxEiOpFoHCvk97kGc= +github.com/u2takey/go-utils v0.3.1 h1:TaQTgmEZZeDHQFYfd+AdUT1cT4QJgJn/XVPELhHw4ys= +github.com/u2takey/go-utils v0.3.1/go.mod h1:6e+v5vEZ/6gu12w/DC2ixZdZtCrNokVxD0JUklcqdCs= +github.com/wader/goutubedl v0.0.0-20240818101919-a623bde37ba9 h1:RJcftQ9fAsNachIVAxtZw1nF2Tu8bIYNjY0ui4Ku1Ts= +github.com/wader/goutubedl v0.0.0-20240818101919-a623bde37ba9/go.mod h1:5KXd5tImdbmz4JoVhePtbIokCwAfEhUVVx3WLHmjYuw= +github.com/wader/osleaktest v0.0.0-20191111175233-f643b0fed071/go.mod h1:XD6emOFPHVzb0+qQpiNOdPL2XZ0SRUM0N5JHuq6OmXo= +gocv.io/x/gocv v0.25.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ= +golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/lib/tasks.go b/lib/tasks.go new file mode 100644 index 0000000..7605261 --- /dev/null +++ b/lib/tasks.go @@ -0,0 +1,85 @@ +package lib + +import "rul.sh/go-ytmp3/utils" + +type Task struct { + Url string `json:"url"` + Slug string `json:"slug"` + Thumbnail string `json:"thumbnail"` + Title string `json:"title"` + Artist string `json:"artist"` + Album string `json:"album"` + IsPending bool `json:"is_pending"` + Result string `json:"result"` + Error error `json:"error"` +} + +var tasks = []*Task{} +var queue = []*Task{} + +func NewTask(task Task) *Task { + task.IsPending = true + + queue = append(queue, &task) + tasks = append(tasks, &task) + if len(tasks) > 20 { + tasks = tasks[1:] + } + + return &task +} + +func GetTasks() []*Task { + return tasks +} + +type TaskScheduler struct { + Ch chan bool +} + +func InitTaskScheduler() *TaskScheduler { + ch := make(chan bool) + outDir := utils.GetEnv("OUT_DIR", "/tmp") + + go func() { + for { + select { + case <-ch: + return + default: + if len(queue) == 0 { + continue + } + + task := queue[0] + queue = queue[1:] + + video, err := YtGetVideo(task.Url) + if err != nil { + task.Error = err + task.IsPending = false + continue + } + + result, err := Yt2Mp3(&video, Yt2Mp3Options{ + OutDir: outDir, + Slug: task.Slug, + Thumbnail: task.Thumbnail, + Title: task.Title, + Artist: task.Artist, + Album: task.Album, + }) + + task.IsPending = false + task.Error = err + task.Result = result + } + } + }() + + return &TaskScheduler{Ch: ch} +} + +func (s *TaskScheduler) Stop() { + s.Ch <- true +} diff --git a/lib/yt2mp3.go b/lib/yt2mp3.go new file mode 100644 index 0000000..91a9f59 --- /dev/null +++ b/lib/yt2mp3.go @@ -0,0 +1,213 @@ +package lib + +import ( + "context" + "fmt" + "image" + "image/jpeg" + "io" + "net/http" + "os" + "strings" + + "github.com/disintegration/imaging" + "github.com/gosimple/slug" + ffmpeg "github.com/u2takey/ffmpeg-go" + "github.com/wader/goutubedl" + "golang.org/x/image/webp" + "rul.sh/go-ytmp3/utils" +) + +func fetchVideo(video *goutubedl.Result, out string, ch chan error) { + dl, err := video.Download(context.Background(), "best") + if err != nil { + ch <- err + return + } + defer dl.Close() + + f, err := os.Create(out) + if err != nil { + ch <- err + return + } + + defer f.Close() + + fmt.Println("Downloading...") + if _, err := io.Copy(f, dl); err != nil { + ch <- err + return + } + + ch <- nil +} + +func resizeImage(imgName string, src io.Reader, dst *os.File, width int, height int) error { + var img image.Image + var err error + + ext := imgName[strings.LastIndex(imgName, "."):] + + switch ext { + case "webp": + img, err = webp.Decode(src) + default: + img, _, err = image.Decode(src) + } + + if err != nil { + fmt.Println(err) + return err + } + + img = imaging.Fill(img, width, height, imaging.Center, imaging.Lanczos) + + return jpeg.Encode(dst, img, nil) +} + +func fetchThumbnail(video *goutubedl.Result, thumbnail string, ch chan error) { + if video.Info.Thumbnail == "" { + ch <- fmt.Errorf("no thumbnail found") + return + } + + fmt.Println("Downloading thumbnail...") + + f, err := os.Create(thumbnail) + if err != nil { + ch <- err + return + } + + defer f.Close() + + resp, err := http.Get(video.Info.Thumbnail) + if err != nil { + ch <- err + return + } + + defer resp.Body.Close() + + if err := resizeImage(video.Info.Thumbnail, resp.Body, f, 512, 512); err != nil { + ch <- err + return + } + + ch <- nil +} + +type ConvertOptions struct { + Video string + Thumbnail string + Title string + Artist string + Album string + Output string +} + +func convertToMp3(data ConvertOptions, ch chan error) { + fmt.Println("Converting...") + + input := []*ffmpeg.Stream{ffmpeg.Input(data.Video).Audio()} + args := ffmpeg.KwArgs{ + "format": "mp3", + "id3v2_version": "3", + "write_id3v1": "1", + "metadata": []string{ + fmt.Sprintf("title=%s", data.Title), + fmt.Sprintf("artist=%s", data.Artist), + fmt.Sprintf("album=%s", data.Album), + }, + } + + if data.Thumbnail != "" { + input = append(input, ffmpeg.Input(data.Thumbnail).Video()) + } + + if err := ffmpeg.Output(input, data.Output, args).OverWriteOutput().Run(); err != nil { + ch <- err + return + } + + ch <- nil +} + +func YtGetVideo(url string) (goutubedl.Result, error) { + return goutubedl.New(context.Background(), url, goutubedl.Options{}) +} + +type Yt2Mp3Options struct { + OutDir string + Slug string + Thumbnail string + Title string + Artist string + Album string +} + +func Yt2Mp3(video *goutubedl.Result, options Yt2Mp3Options) (string, error) { + if video == nil { + return "", fmt.Errorf("no video found") + } + + tmpDir := utils.GetEnv("TMP_DIR", "/tmp") + + title := video.Info.Title + artist := video.Info.Channel + album := video.Info.Album + + if video.Info.Artist != "" { + artist = video.Info.Artist + } + + videoSlug := options.Slug + if len(options.Slug) == 0 { + videoSlug = slug.Make(title) + } + + videoSrc := fmt.Sprintf("%s/%s.mp4", tmpDir, videoSlug) + thumbnail := fmt.Sprintf("%s/%s.jpg", tmpDir, videoSlug) + out := fmt.Sprintf("%s/%s.mp3", options.OutDir, videoSlug) + + if err := os.MkdirAll(options.OutDir, os.ModePerm); err != nil { + return "", err + } + + videoCh := make(chan error) + thumbCh := make(chan error) + + go fetchVideo(video, videoSrc, videoCh) + go fetchThumbnail(video, thumbnail, thumbCh) + + err := <-videoCh + if err != nil { + return "", err + } + + err = <-thumbCh + if err != nil { + thumbnail = "" + } + + fmt.Println(artist, album) + + convertCh := make(chan error) + + go convertToMp3(ConvertOptions{ + Video: videoSrc, + Thumbnail: thumbnail, + Title: title, + Artist: artist, + Album: album, + Output: out, + }, convertCh) + + err = <-convertCh + if err != nil { + return "", err + } + + return out, nil +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..b372c14 --- /dev/null +++ b/main.go @@ -0,0 +1,144 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/gosimple/slug" + "github.com/joho/godotenv" + "rul.sh/go-ytmp3/lib" + "rul.sh/go-ytmp3/types" + "rul.sh/go-ytmp3/ui" + "rul.sh/go-ytmp3/utils" +) + +func main() { + godotenv.Load() + outDir := utils.GetEnv("OUT_DIR", "/tmp") + + app := http.NewServeMux() + + app.HandleFunc("GET /api/info/", func(w http.ResponseWriter, r *http.Request) { + url := r.URL.Query().Get("url") + if len(url) == 0 { + http.Error(w, "No video url provided", http.StatusBadRequest) + return + } + + video, err := lib.YtGetVideo(url) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + data := types.GetVideoInfoRes{ + Url: url, + Slug: slug.Make(video.Info.Title), + Thumbnail: video.Info.Thumbnail, + Title: video.Info.Title, + Artist: video.Info.Channel, + Album: video.Info.Album, + } + + if video.Info.Artist != "" { + data.Artist = video.Info.Artist + } + + json, err := json.Marshal(data) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(json) + }) + + app.HandleFunc("POST /api/tasks/", func(w http.ResponseWriter, r *http.Request) { + var data types.CreateTaskBody + + err := json.NewDecoder(r.Body).Decode(&data) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if len(data.Url) == 0 { + http.Error(w, "No video url provided", http.StatusBadRequest) + return + } + + task := lib.NewTask(lib.Task{ + Url: data.Url, + Slug: data.Slug, + Thumbnail: data.Thumbnail, + Title: data.Title, + Artist: data.Artist, + Album: data.Album, + }) + + json, err := json.Marshal(task) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(json) + }) + + app.HandleFunc("GET /api/tasks/", func(w http.ResponseWriter, r *http.Request) { + json, err := json.Marshal(lib.GetTasks()) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Write(json) + }) + + app.HandleFunc("GET /api/get/", func(w http.ResponseWriter, r *http.Request) { + filename := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:] + isDownload := r.URL.Query().Get("dl") == "true" + + if len(filename) == 0 { + http.Error(w, "No filename provided", http.StatusBadRequest) + return + } + + file, err := os.Open(filepath.Join(outDir, filename)) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + + defer file.Close() + + w.Header().Set("Content-Type", "audio/mpeg") + if isDownload { + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename)) + } + + if _, err := io.Copy(w, file); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) + + ui.ServeUI(app) + + scheduler := lib.InitTaskScheduler() + defer scheduler.Stop() + + port := utils.GetEnv("PORT", "8080") + fmt.Printf("Listening on http://localhost:%s\n", port) + + if err := http.ListenAndServe(fmt.Sprintf(":%s", port), app); err != nil { + panic(err) + } +} diff --git a/rest.http b/rest.http new file mode 100644 index 0000000..7acd13f --- /dev/null +++ b/rest.http @@ -0,0 +1,17 @@ + +GET http://localhost:8080/tasks/ HTTP/1.1 +Accept: : application/json + +### + +GET http://localhost:8080/info?url=https://www.youtube.com/watch?v=lCok_iWOIXw HTTP/1.1 +Accept: : application/json + +### + +POST http://localhost:8080/tasks/ HTTP/1.1 +Content-Type: application/json + +{ + "url": "https://www.youtube.com/watch?v=lCok_iWOIXw" +} diff --git a/types/api.go b/types/api.go new file mode 100644 index 0000000..64ac830 --- /dev/null +++ b/types/api.go @@ -0,0 +1,19 @@ +package types + +type GetVideoInfoRes struct { + Url string `json:"url"` + Slug string `json:"slug"` + Thumbnail string `json:"thumbnail"` + Title string `json:"title"` + Artist string `json:"artist"` + Album string `json:"album"` +} + +type CreateTaskBody struct { + Url string `json:"url"` + Slug string `json:"slug"` + Thumbnail string `json:"thumbnail"` + Title string `json:"title"` + Artist string `json:"artist"` + Album string `json:"album"` +} diff --git a/ui/.gitignore b/ui/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/ui/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/ui/README.md b/ui/README.md new file mode 100644 index 0000000..74872fd --- /dev/null +++ b/ui/README.md @@ -0,0 +1,50 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default tseslint.config({ + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) +``` + +- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` +- Optionally add `...tseslint.configs.stylisticTypeChecked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: + +```js +// eslint.config.js +import react from 'eslint-plugin-react' + +export default tseslint.config({ + // Set the react version + settings: { react: { version: '18.3' } }, + plugins: { + // Add the react plugin + react, + }, + rules: { + // other rules... + // Enable its recommended rules + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + }, +}) +``` diff --git a/ui/eslint.config.js b/ui/eslint.config.js new file mode 100644 index 0000000..092408a --- /dev/null +++ b/ui/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/ui/index.html b/ui/index.html new file mode 100644 index 0000000..dd825ce --- /dev/null +++ b/ui/index.html @@ -0,0 +1,17 @@ + + + + + + + + + + + YouTube To MP3 + + +
+ + + diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 0000000..4183510 --- /dev/null +++ b/ui/package.json @@ -0,0 +1,35 @@ +{ + "name": "ui", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "lucide-react": "^0.435.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "slugify": "^1.6.6" + }, + "devDependencies": { + "@eslint/js": "^9.9.0", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react-swc": "^3.5.0", + "autoprefixer": "^10.4.20", + "daisyui": "^4.12.10", + "eslint": "^9.9.0", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.9", + "globals": "^15.9.0", + "postcss": "^8.4.41", + "tailwindcss": "^3.4.10", + "typescript": "^5.5.3", + "typescript-eslint": "^8.0.1", + "vite": "^5.4.1" + } +} diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml new file mode 100644 index 0000000..ea81d53 --- /dev/null +++ b/ui/pnpm-lock.yaml @@ -0,0 +1,2433 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + lucide-react: + specifier: ^0.435.0 + version: 0.435.0(react@18.3.1) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + slugify: + specifier: ^1.6.6 + version: 1.6.6 + devDependencies: + '@eslint/js': + specifier: ^9.9.0 + version: 9.9.1 + '@types/react': + specifier: ^18.3.3 + version: 18.3.4 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.0 + '@vitejs/plugin-react-swc': + specifier: ^3.5.0 + version: 3.7.0(vite@5.4.2) + autoprefixer: + specifier: ^10.4.20 + version: 10.4.20(postcss@8.4.41) + daisyui: + specifier: ^4.12.10 + version: 4.12.10(postcss@8.4.41) + eslint: + specifier: ^9.9.0 + version: 9.9.1(jiti@1.21.6) + eslint-plugin-react-hooks: + specifier: ^5.1.0-rc.0 + version: 5.1.0-rc-fb9a90fa48-20240614(eslint@9.9.1(jiti@1.21.6)) + eslint-plugin-react-refresh: + specifier: ^0.4.9 + version: 0.4.11(eslint@9.9.1(jiti@1.21.6)) + globals: + specifier: ^15.9.0 + version: 15.9.0 + postcss: + specifier: ^8.4.41 + version: 8.4.41 + tailwindcss: + specifier: ^3.4.10 + version: 3.4.10 + typescript: + specifier: ^5.5.3 + version: 5.5.4 + typescript-eslint: + specifier: ^8.0.1 + version: 8.2.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4) + vite: + specifier: ^5.4.1 + version: 5.4.2 + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.4.0': + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.11.0': + resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.18.0': + resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.1.0': + resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.9.1': + resolution: {integrity: sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.4': + resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.0': + resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} + engines: {node: '>=18.18'} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@rollup/rollup-android-arm-eabi@4.21.0': + resolution: {integrity: sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.21.0': + resolution: {integrity: sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.21.0': + resolution: {integrity: sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.21.0': + resolution: {integrity: sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-linux-arm-gnueabihf@4.21.0': + resolution: {integrity: sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.21.0': + resolution: {integrity: sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.21.0': + resolution: {integrity: sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.21.0': + resolution: {integrity: sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.21.0': + resolution: {integrity: sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.21.0': + resolution: {integrity: sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.21.0': + resolution: {integrity: sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.21.0': + resolution: {integrity: sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.21.0': + resolution: {integrity: sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.21.0': + resolution: {integrity: sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.21.0': + resolution: {integrity: sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.21.0': + resolution: {integrity: sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==} + cpu: [x64] + os: [win32] + + '@swc/core-darwin-arm64@1.7.18': + resolution: {integrity: sha512-MwLc5U+VGPMZm8MjlFBjEB2wyT1EK0NNJ3tn+ps9fmxdFP+PL8EpMiY1O1F2t1ydy2OzBtZz81sycjM9RieFBg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.7.18': + resolution: {integrity: sha512-IkukOQUw7/14VkHp446OkYGCZEHqZg9pTmTdBawlUyz2JwZMSn2VodCl7aFSdGCsU4Cwni8zKA8CCgkCCAELhw==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.7.18': + resolution: {integrity: sha512-ATnb6jJaBeXCqrTUawWdoOy7eP9SCI7UMcfXlYIMxX4otKKspLPAEuGA5RaNxlCcj9ObyO0J3YGbtZ6hhD2pjg==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.7.18': + resolution: {integrity: sha512-poHtH7zL7lEp9K2inY90lGHJABWxURAOgWNeZqrcR5+jwIe7q5KBisysH09Zf/JNF9+6iNns+U0xgWTNJzBuGA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.7.18': + resolution: {integrity: sha512-qnNI1WmcOV7Wz1ZDyK6WrOlzLvJ01rnni8ec950mMHWkLRMP53QvCvhF3S+7gFplWBwWJTOOPPUqJp/PlSxWyQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.7.18': + resolution: {integrity: sha512-x9SCqCLzwtlqtD5At3I1a7Gco+EuXnzrJGoucmkpeQohshHuwa+cskqsXO6u1Dz0jXJEuHbBZB9va1wYYfjgFg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.7.18': + resolution: {integrity: sha512-qtj8iOpMMgKjzxTv+islmEY0JBsbd93nka0gzcTTmGZxKtL5jSUsYQvkxwNPZr5M9NU1fgaR3n1vE6lFmtY0IQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.7.18': + resolution: {integrity: sha512-ltX/Ol9+Qu4SXmISCeuwVgAjSa8nzHTymknpozzVMgjXUoZMoz6lcynfKL1nCh5XLgqh0XNHUKLti5YFF8LrrA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.7.18': + resolution: {integrity: sha512-RgTcFP3wgyxnQbTCJrlgBJmgpeTXo8t807GU9GxApAXfpLZJ3swJ2GgFUmIJVdLWyffSHF5BEkF3FmF6mtH5AQ==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.7.18': + resolution: {integrity: sha512-XbZ0wAgzR757+DhQcnv60Y/bK9yuWPhDNRQVFFQVRsowvK3+c6EblyfUSytIidpXgyYFzlprq/9A9ZlO/wvDWw==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.7.18': + resolution: {integrity: sha512-qL9v5N5S38ijmqiQRvCFUUx2vmxWT/JJ2rswElnyaHkOHuVoAFhBB90Ywj4RKjh3R0zOjhEcemENTyF3q3G6WQ==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '*' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/types@0.1.12': + resolution: {integrity: sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA==} + + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + + '@types/prop-types@15.7.12': + resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + + '@types/react-dom@18.3.0': + resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + + '@types/react@18.3.4': + resolution: {integrity: sha512-J7W30FTdfCxDDjmfRM+/JqLHBIyl7xUIp9kwK637FGmY7+mkSFSe6L4jpZzhj5QMfLssSDP4/i75AKkrdC7/Jw==} + + '@typescript-eslint/eslint-plugin@8.2.0': + resolution: {integrity: sha512-02tJIs655em7fvt9gps/+4k4OsKULYGtLBPJfOsmOq1+3cdClYiF0+d6mHu6qDnTcg88wJBkcPLpQhq7FyDz0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@8.2.0': + resolution: {integrity: sha512-j3Di+o0lHgPrb7FxL3fdEy6LJ/j2NE8u+AP/5cQ9SKb+JLH6V6UHDqJ+e0hXBkHP1wn1YDFjYCS9LBQsZDlDEg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@8.2.0': + resolution: {integrity: sha512-OFn80B38yD6WwpoHU2Tz/fTz7CgFqInllBoC3WP+/jLbTb4gGPTy9HBSTsbDWkMdN55XlVU0mMDYAtgvlUspGw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.2.0': + resolution: {integrity: sha512-g1CfXGFMQdT5S+0PSO0fvGXUaiSkl73U1n9LTK5aRAFnPlJ8dLKkXr4AaLFvPedW8lVDoMgLLE3JN98ZZfsj0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@8.2.0': + resolution: {integrity: sha512-6a9QSK396YqmiBKPkJtxsgZZZVjYQ6wQ/TlI0C65z7vInaETuC6HAHD98AGLC8DyIPqHytvNuS8bBVvNLKyqvQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.2.0': + resolution: {integrity: sha512-kiG4EDUT4dImplOsbh47B1QnNmXSoUqOjWDvCJw/o8LgfD0yr7k2uy54D5Wm0j4t71Ge1NkynGhpWdS0dEIAUA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@8.2.0': + resolution: {integrity: sha512-O46eaYKDlV3TvAVDNcoDzd5N550ckSe8G4phko++OCSC1dYIb9LTc3HDGYdWqWIAT5qDUKphO6sd9RrpIJJPfg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + + '@typescript-eslint/visitor-keys@8.2.0': + resolution: {integrity: sha512-sbgsPMW9yLvS7IhCi8IpuK1oBmtbWUNP+hBdwl/I9nzqVsszGnNGti5r9dUtF5RLivHUFFIdRvLiTsPhzSyJ3Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react-swc@3.7.0': + resolution: {integrity: sha512-yrknSb3Dci6svCd/qhHqhFPDSw0QtjumcqdKMoNNzmOl5lMXTTiqzjWtG4Qask2HdvvzaNgSunbQGet8/GrKdA==} + peerDependencies: + vite: ^4 || ^5 + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + autoprefixer@10.4.20: + resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.23.3: + resolution: {integrity: sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001651: + resolution: {integrity: sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + css-selector-tokenizer@0.8.0: + resolution: {integrity: sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + culori@3.3.0: + resolution: {integrity: sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + daisyui@4.12.10: + resolution: {integrity: sha512-jp1RAuzbHhGdXmn957Z2XsTZStXGHzFfF0FgIOZj3Wv9sH7OZgLfXTRZNfKVYxltGUOBsG1kbWAdF5SrqjebvA==} + engines: {node: '>=16.9.0'} + + debug@4.3.6: + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.5.13: + resolution: {integrity: sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@5.1.0-rc-fb9a90fa48-20240614: + resolution: {integrity: sha512-xsiRwaDNF5wWNC4ZHLut+x/YcAxksUd9Rizt7LaEn3bV8VyYRpXnRJQlLOfYaVy9esk4DFP4zPPnoNVjq5Gc0w==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.11: + resolution: {integrity: sha512-wrAKxMbVr8qhXTtIKfXqAn5SAtRZt0aXxe5P23Fh4pUAdC6XEsybGLB8P0PI4j1yYqOgUEUlzKAGDfo7rJOjcw==} + peerDependencies: + eslint: '>=7' + + eslint-scope@8.0.2: + resolution: {integrity: sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.0.0: + resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.9.1: + resolution: {integrity: sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.1.0: + resolution: {integrity: sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastparse@1.1.2: + resolution: {integrity: sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@15.9.0: + resolution: {integrity: sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==} + engines: {node: '>=18'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@1.21.6: + resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + lilconfig@3.1.2: + resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lucide-react@0.435.0: + resolution: {integrity: sha512-we5GKfzjMDw9m9SsyZJvWim9qaT+Ya5kaRS+OGFqgLqXUrPM1h+7CiMw5pKdEIoaBqfXz2pyv9TASAdpIAJs0Q==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.18: + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.0: + resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.0.1: + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@4.0.2: + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.41: + resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.21.0: + resolution: {integrity: sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slugify@1.6.6: + resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==} + engines: {node: '>=8.0.0'} + + source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tailwindcss@3.4.10: + resolution: {integrity: sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==} + engines: {node: '>=14.0.0'} + hasBin: true + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-api-utils@1.3.0: + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.2.0: + resolution: {integrity: sha512-DmnqaPcML0xYwUzgNbM1XaKXpEb7BShYf2P1tkUmmcl8hyeG7Pj08Er7R9bNy6AufabywzJcOybQAtnD/c9DGw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} + engines: {node: '>=14.17'} + hasBin: true + + update-browserslist-db@1.1.0: + resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite@5.4.2: + resolution: {integrity: sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + yaml@2.5.0: + resolution: {integrity: sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==} + engines: {node: '>= 14'} + hasBin: true + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@eslint-community/eslint-utils@4.4.0(eslint@9.9.1(jiti@1.21.6))': + dependencies: + eslint: 9.9.1(jiti@1.21.6) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.11.0': {} + + '@eslint/config-array@0.18.0': + dependencies: + '@eslint/object-schema': 2.1.4 + debug: 4.3.6 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/eslintrc@3.1.0': + dependencies: + ajv: 6.12.6 + debug: 4.3.6 + espree: 10.1.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.9.1': {} + + '@eslint/object-schema@2.1.4': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.0': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rollup/rollup-android-arm-eabi@4.21.0': + optional: true + + '@rollup/rollup-android-arm64@4.21.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.21.0': + optional: true + + '@rollup/rollup-darwin-x64@4.21.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.21.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.21.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.21.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.21.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.21.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.21.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.21.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.21.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.21.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.21.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.21.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.21.0': + optional: true + + '@swc/core-darwin-arm64@1.7.18': + optional: true + + '@swc/core-darwin-x64@1.7.18': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.7.18': + optional: true + + '@swc/core-linux-arm64-gnu@1.7.18': + optional: true + + '@swc/core-linux-arm64-musl@1.7.18': + optional: true + + '@swc/core-linux-x64-gnu@1.7.18': + optional: true + + '@swc/core-linux-x64-musl@1.7.18': + optional: true + + '@swc/core-win32-arm64-msvc@1.7.18': + optional: true + + '@swc/core-win32-ia32-msvc@1.7.18': + optional: true + + '@swc/core-win32-x64-msvc@1.7.18': + optional: true + + '@swc/core@1.7.18': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.12 + optionalDependencies: + '@swc/core-darwin-arm64': 1.7.18 + '@swc/core-darwin-x64': 1.7.18 + '@swc/core-linux-arm-gnueabihf': 1.7.18 + '@swc/core-linux-arm64-gnu': 1.7.18 + '@swc/core-linux-arm64-musl': 1.7.18 + '@swc/core-linux-x64-gnu': 1.7.18 + '@swc/core-linux-x64-musl': 1.7.18 + '@swc/core-win32-arm64-msvc': 1.7.18 + '@swc/core-win32-ia32-msvc': 1.7.18 + '@swc/core-win32-x64-msvc': 1.7.18 + + '@swc/counter@0.1.3': {} + + '@swc/types@0.1.12': + dependencies: + '@swc/counter': 0.1.3 + + '@types/estree@1.0.5': {} + + '@types/prop-types@15.7.12': {} + + '@types/react-dom@18.3.0': + dependencies: + '@types/react': 18.3.4 + + '@types/react@18.3.4': + dependencies: + '@types/prop-types': 15.7.12 + csstype: 3.1.3 + + '@typescript-eslint/eslint-plugin@8.2.0(@typescript-eslint/parser@8.2.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4)': + dependencies: + '@eslint-community/regexpp': 4.11.0 + '@typescript-eslint/parser': 8.2.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.2.0 + '@typescript-eslint/type-utils': 8.2.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4) + '@typescript-eslint/utils': 8.2.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 8.2.0 + eslint: 9.9.1(jiti@1.21.6) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.2.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4)': + dependencies: + '@typescript-eslint/scope-manager': 8.2.0 + '@typescript-eslint/types': 8.2.0 + '@typescript-eslint/typescript-estree': 8.2.0(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 8.2.0 + debug: 4.3.6 + eslint: 9.9.1(jiti@1.21.6) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.2.0': + dependencies: + '@typescript-eslint/types': 8.2.0 + '@typescript-eslint/visitor-keys': 8.2.0 + + '@typescript-eslint/type-utils@8.2.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4)': + dependencies: + '@typescript-eslint/typescript-estree': 8.2.0(typescript@5.5.4) + '@typescript-eslint/utils': 8.2.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4) + debug: 4.3.6 + ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - eslint + - supports-color + + '@typescript-eslint/types@8.2.0': {} + + '@typescript-eslint/typescript-estree@8.2.0(typescript@5.5.4)': + dependencies: + '@typescript-eslint/types': 8.2.0 + '@typescript-eslint/visitor-keys': 8.2.0 + debug: 4.3.6 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.2.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1(jiti@1.21.6)) + '@typescript-eslint/scope-manager': 8.2.0 + '@typescript-eslint/types': 8.2.0 + '@typescript-eslint/typescript-estree': 8.2.0(typescript@5.5.4) + eslint: 9.9.1(jiti@1.21.6) + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@8.2.0': + dependencies: + '@typescript-eslint/types': 8.2.0 + eslint-visitor-keys: 3.4.3 + + '@vitejs/plugin-react-swc@3.7.0(vite@5.4.2)': + dependencies: + '@swc/core': 1.7.18 + vite: 5.4.2 + transitivePeerDependencies: + - '@swc/helpers' + + acorn-jsx@5.3.2(acorn@8.12.1): + dependencies: + acorn: 8.12.1 + + acorn@8.12.1: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-regex@6.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + array-union@2.1.0: {} + + autoprefixer@10.4.20(postcss@8.4.41): + dependencies: + browserslist: 4.23.3 + caniuse-lite: 1.0.30001651 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.0.1 + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + balanced-match@1.0.2: {} + + binary-extensions@2.3.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.23.3: + dependencies: + caniuse-lite: 1.0.30001651 + electron-to-chromium: 1.5.13 + node-releases: 2.0.18 + update-browserslist-db: 1.1.0(browserslist@4.23.3) + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001651: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@4.1.1: {} + + concat-map@0.0.1: {} + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-selector-tokenizer@0.8.0: + dependencies: + cssesc: 3.0.0 + fastparse: 1.1.2 + + cssesc@3.0.0: {} + + csstype@3.1.3: {} + + culori@3.3.0: {} + + daisyui@4.12.10(postcss@8.4.41): + dependencies: + css-selector-tokenizer: 0.8.0 + culori: 3.3.0 + picocolors: 1.0.1 + postcss-js: 4.0.1(postcss@8.4.41) + transitivePeerDependencies: + - postcss + + debug@4.3.6: + dependencies: + ms: 2.1.2 + + deep-is@0.1.4: {} + + didyoumean@1.2.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dlv@1.1.3: {} + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.5.13: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.1.2: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@5.1.0-rc-fb9a90fa48-20240614(eslint@9.9.1(jiti@1.21.6)): + dependencies: + eslint: 9.9.1(jiti@1.21.6) + + eslint-plugin-react-refresh@0.4.11(eslint@9.9.1(jiti@1.21.6)): + dependencies: + eslint: 9.9.1(jiti@1.21.6) + + eslint-scope@8.0.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.0.0: {} + + eslint@9.9.1(jiti@1.21.6): + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1(jiti@1.21.6)) + '@eslint-community/regexpp': 4.11.0 + '@eslint/config-array': 0.18.0 + '@eslint/eslintrc': 3.1.0 + '@eslint/js': 9.9.1 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.3.0 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.6 + escape-string-regexp: 4.0.0 + eslint-scope: 8.0.2 + eslint-visitor-keys: 4.0.0 + espree: 10.1.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + optionalDependencies: + jiti: 1.21.6 + transitivePeerDependencies: + - supports-color + + espree@10.1.0: + dependencies: + acorn: 8.12.1 + acorn-jsx: 5.3.2(acorn@8.12.1) + eslint-visitor-keys: 4.0.0 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastparse@1.1.2: {} + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + + flatted@3.3.1: {} + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + + fraction.js@4.3.7: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.0 + path-scurry: 1.11.1 + + globals@14.0.0: {} + + globals@15.9.0: {} + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + ignore@5.3.2: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.15.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@1.21.6: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@2.1.0: {} + + lilconfig@3.1.2: {} + + lines-and-columns@1.2.4: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@10.4.3: {} + + lucide-react@0.435.0(react@18.3.1): + dependencies: + react: 18.3.1 + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minipass@7.1.2: {} + + ms@2.1.2: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.7: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.18: {} + + normalize-path@3.0.0: {} + + normalize-range@0.1.2: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-type@4.0.0: {} + + picocolors@1.0.1: {} + + picomatch@2.3.1: {} + + pify@2.3.0: {} + + pirates@4.0.6: {} + + postcss-import@15.1.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.8 + + postcss-js@4.0.1(postcss@8.4.41): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.41 + + postcss-load-config@4.0.2(postcss@8.4.41): + dependencies: + lilconfig: 3.1.2 + yaml: 2.5.0 + optionalDependencies: + postcss: 8.4.41 + + postcss-nested@6.2.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.41: + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.1 + source-map-js: 1.2.0 + + prelude-ls@1.2.1: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + resolve-from@4.0.0: {} + + resolve@1.22.8: + dependencies: + is-core-module: 2.15.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.0.4: {} + + rollup@4.21.0: + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.21.0 + '@rollup/rollup-android-arm64': 4.21.0 + '@rollup/rollup-darwin-arm64': 4.21.0 + '@rollup/rollup-darwin-x64': 4.21.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.21.0 + '@rollup/rollup-linux-arm-musleabihf': 4.21.0 + '@rollup/rollup-linux-arm64-gnu': 4.21.0 + '@rollup/rollup-linux-arm64-musl': 4.21.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.21.0 + '@rollup/rollup-linux-riscv64-gnu': 4.21.0 + '@rollup/rollup-linux-s390x-gnu': 4.21.0 + '@rollup/rollup-linux-x64-gnu': 4.21.0 + '@rollup/rollup-linux-x64-musl': 4.21.0 + '@rollup/rollup-win32-arm64-msvc': 4.21.0 + '@rollup/rollup-win32-ia32-msvc': 4.21.0 + '@rollup/rollup-win32-x64-msvc': 4.21.0 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver@7.6.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + slugify@1.6.6: {} + + source-map-js@1.2.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.0.1 + + strip-json-comments@3.1.1: {} + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tailwindcss@3.4.10: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.2 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.6 + lilconfig: 2.1.0 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.1 + postcss: 8.4.41 + postcss-import: 15.1.0(postcss@8.4.41) + postcss-js: 4.0.1(postcss@8.4.41) + postcss-load-config: 4.0.2(postcss@8.4.41) + postcss-nested: 6.2.0(postcss@8.4.41) + postcss-selector-parser: 6.1.2 + resolve: 1.22.8 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + + text-table@0.2.0: {} + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@1.3.0(typescript@5.5.4): + dependencies: + typescript: 5.5.4 + + ts-interface-checker@0.1.13: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.2.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4): + dependencies: + '@typescript-eslint/eslint-plugin': 8.2.0(@typescript-eslint/parser@8.2.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4) + '@typescript-eslint/parser': 8.2.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4) + '@typescript-eslint/utils': 8.2.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - eslint + - supports-color + + typescript@5.5.4: {} + + update-browserslist-db@1.1.0(browserslist@4.23.3): + dependencies: + browserslist: 4.23.3 + escalade: 3.1.2 + picocolors: 1.0.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + vite@5.4.2: + dependencies: + esbuild: 0.21.5 + postcss: 8.4.41 + rollup: 4.21.0 + optionalDependencies: + fsevents: 2.3.3 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + yaml@2.5.0: {} + + yocto-queue@0.1.0: {} diff --git a/ui/postcss.config.js b/ui/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/ui/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/ui/public/android-chrome-192x192.png b/ui/public/android-chrome-192x192.png new file mode 100755 index 0000000000000000000000000000000000000000..4aed069d826b607955bb5c69aac57fa414ecb717 GIT binary patch literal 9773 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 literal 0 HcmV?d00001 diff --git a/ui/public/apple-touch-icon.png b/ui/public/apple-touch-icon.png new file mode 100755 index 0000000000000000000000000000000000000000..b8d616b404c0dac943c714f305c2fd2f0ec1fc0c GIT binary patch literal 8827 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 literal 0 HcmV?d00001 diff --git a/ui/public/favicon-16x16.png b/ui/public/favicon-16x16.png new file mode 100755 index 0000000000000000000000000000000000000000..f7ea10a19b869f91067711f224cc7127c8c84762 GIT binary patch literal 483 zcmV<90UZ8`P)Px$ok>JNR5(wilRZm`CO5}I0-?wz8-fZ6^2Y7)NhPGBJ3J5fp8xrsbMCoE=q}~)*bv!3WC^8a zff-=5YiP&FEIw$P=#TPxgTMtaZUTS!KLe{$DCDFYx&8-dll_cRPElU( zF0l6%adHv@0dP2=)q;E;?(c^s6cy$3l~8K=Q=y$=F({Ye>I&?32#4YH6spzl5RtOZ z;8weST91w(ogNh1Y;bS@XJ^o8K(pBoy3>FcU<|7jPEH^a`5wvdhq*aeSb$6hvRPB} zssZo72-DLLkHhBX@CHmwKr#vI>(JJnrPBlGZ6X23$8ddZK!UovKrYvVrc$u81CNhi z0HXxG=I3E;%@9~D5DFQNI_0-F(51NDu(dT%LhlmIW2Y0gx1m-uP8Jtoe;=l%pk9Zk zCsTPh*v9_qS5}NjB5-&JrP9zg4nCxQnagDWDwSau8~y)N*64o?lbPGE$GOwF1XAWW Ze*me?oZs1J6IK8K002ovPDHLkV1m+})$afR literal 0 HcmV?d00001 diff --git a/ui/public/favicon-32x32.png b/ui/public/favicon-32x32.png new file mode 100755 index 0000000000000000000000000000000000000000..0c1a9a3c57d92e0831d8fb262c503e7c29bae5cb GIT binary patch literal 1091 zcmV-J1ibr+P)Px&{YgYYR9HvFmtAO7M-+g+F}oTcsvwPG;}4i1BAOqpf|eRJW-&haptRsq#QgY| z`UB!W{-L0VicRn#TBtq<7JN~(x+cM5#RrQf`k=Ons9=;tK^wui+1)AUPPXj5d+&Dd z676}q+&O2?H)qZ{Gowf&dclGM0)aB11SkaZfiHnC&^i|g446kzst@P|UIQ#jK6)05?6sH69G1iXOjr8~btZv+hCfnHnbb0w?a&mqdM8{4> z0r^f{zy4QZ;9r0N=f4AaK30~EY7bg}lYp=QtgsIq`o||+d1@pgk*{St48X#LwG0{3 zl+ALowu#5JO?S_;F4 zK~E3t*<&K&PpSprN8krnyDeLwyd1i^;Mg&E@W8G%Vg%IILMR09-a&mmba(qfXb^xa zKxK;6?Afq)FT8pM8#ch(x84)4S_Mm&!ks&?YnLB@Uj?8YC`z$9ejKJwh3(tn?%nj0 z=Hx(A6O0=Nw{AgSA3S>oEiLd`IW3joe(AITMyGGbOukAKbp}Hczk8 z6%`Sb3fe}Cir~l*2#4Y5(aZqcv}VnM{rgQMtE)k4yO!4d0uV4w;|Zws}Kt+60Rh!Pc$NCJi@9 zw6*|Jw?K4c|LW>Y1?=1j@p!6}3m42k(e;uOCt%7Hlg!SASXF)t4e9)!n_&0Y>44r9l{lP5;Q838#fUcQ76A7IWL z)08B=1mfO3Hvp>)-5Zgc`ug>-c(JMNix=$x(g)VAh0B+rp#h|mu3inNPs7cdUK?aJ zBKKD8W-@&`95`SOAc3f=GFnnGYt}#{V)Dg#W_V|a)r!3jBR9k1VpH1!ku2hT_wLk8 z_jWEL)?ws*9D5ljAf)4oNXzx_Tr8`VuBusH#DJoZd(4hW~t=I@4>QL_g{C{2y)Fgt%?Fo3j7_002ov JPDHLkV1kR%^@jie literal 0 HcmV?d00001 diff --git a/ui/public/favicon.ico b/ui/public/favicon.ico new file mode 100755 index 0000000000000000000000000000000000000000..94c3cf2976a41eacec462ec52d6a23b15fbef6e9 GIT binary patch literal 15406 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 literal 0 HcmV?d00001 diff --git a/ui/public/site.webmanifest b/ui/public/site.webmanifest new file mode 100755 index 0000000..45dc8a2 --- /dev/null +++ b/ui/public/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/ui/public/vite.svg b/ui/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/ui/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/src/App.tsx b/ui/src/App.tsx new file mode 100644 index 0000000..f25e402 --- /dev/null +++ b/ui/src/App.tsx @@ -0,0 +1,347 @@ +import { DownloadIcon, LinkIcon, Loader2, SearchIcon } from "lucide-react"; +import { useEffect, useMemo, useRef, useState } from "react"; +import { useFetch } from "./hooks/useFetch"; +import { API_BASE_URL, fetchAPI } from "./api"; +import slugify from "slugify"; + +const App = () => { + const formRef = useRef(null); + const [errors, setErrors] = useState>({}); + const [video, setVideo] = useState(null); + const [isLoading, setLoading] = useState(false); + const tasks = useFetch("tasks", () => fetchAPI("/tasks/")); + + const onFindVideo = async () => { + if (!formRef.current || isLoading) { + return; + } + setErrors({}); + setVideo(null); + setLoading(true); + + const formData = new FormData(formRef.current); + const url = (formData.get("url") as string).trim(); + + if (!url?.length || !url.startsWith("http")) { + setErrors({ url: "URL is invalid" }); + setLoading(false); + return; + } + + try { + const data = await fetchAPI("/info?url=" + encodeURI(url)); + setVideo(data); + } catch (err) { + setErrors({ url: (err as Error)?.message || "Unknown error" }); + } + + setLoading(false); + }; + + const onSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (isLoading) { + return; + } + + setErrors({}); + + if (!video?.url) { + setErrors({ url: "URL is invalid" }); + return; + } + + const formData = new FormData(e.target as HTMLFormElement); + const artist = (formData.get("artist") as string).trim(); + const slug = (formData.get("slug") as string).trim(); + const album = (formData.get("album") as string).trim(); + const title = (formData.get("title") as string).trim(); + + const data = { + url: video.url, + thumbnail: video.thumbnail, + title, + slug, + artist, + album, + }; + + try { + await fetchAPI("/tasks/", { + method: "POST", + body: JSON.stringify(data), + headers: { + "Content-Type": "application/json", + }, + }); + + // reset form + formRef.current?.reset(); + setVideo(null); + tasks.refetch(); + } catch (err) { + setErrors({ result: (err as Error)?.message || "Unknown error" }); + } + }; + + useEffect(() => { + const hasPendingTask = tasks.data?.find((task: any) => task.is_pending); + if (hasPendingTask) { + const timeout = setTimeout(() => tasks.refetch(), 1000); + return () => clearTimeout(timeout); + } + }, [tasks.data]); + + return ( +

+
+
+

+ YouTube To MP3 +

+

+ Download and convert YouTube videos to MP3 +

+ +
+ + {errors.url && ( +

{errors.url}

+ )} + + {video != null && ( +
+ thumbnail + +
+ + + + + + + + + +
+
+ )} +
+
+ + +
+
+ ); +}; + +type TaskListProps = { + data?: any[]; +}; + +const TaskList = ({ data }: TaskListProps) => { + const items = useMemo(() => { + return data?.reverse() ?? []; + }, [data]); + + if (!items?.length) { + return null; + } + + return ( +
+ + + + + + + + + + + {items.map((task, idx) => { + let downloadUrl = ""; + + if (!task.is_pending && task.result) { + const filename = task.result.split("/").pop(); + downloadUrl = API_BASE_URL + "/get/" + filename; + } + + return ( + + + + + + + ); + })} + +
TitleArtistAlbumStatus
+
+ thumbnail + {downloadUrl ? ( + + {task.title} + + ) : ( + + {task.title} + + )} +
+
+ + {task.artist || "-"} + + {task.album || "-"} +
+ + {downloadUrl ? ( + + + + ) : null} +
+
+
+ ); +}; + +type TaskStatusProps = { + isPending: boolean; + error?: string | null; +}; + +const TaskStatus = ({ isPending, error }: TaskStatusProps) => { + if (isPending) { + return ; + } + + return error ? ( +

+ Error! +

+ ) : ( +

Done

+ ); +}; + +export default App; diff --git a/ui/src/api.ts b/ui/src/api.ts new file mode 100644 index 0000000..c808f35 --- /dev/null +++ b/ui/src/api.ts @@ -0,0 +1,18 @@ +// + +export const API_BASE_URL = "/api"; + +export const fetchAPI = async ( + url: string, + init: RequestInit = {} +) => { + const res = await fetch(API_BASE_URL + url, init); + if (!res.ok) { + const data = await res.json().catch(() => null); + const message = data.message ?? res.statusText; + + throw new Error(message); + } + + return (await res.json()) as T; +}; diff --git a/ui/src/assets/react.svg b/ui/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/ui/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/src/hooks/useFetch.ts b/ui/src/hooks/useFetch.ts new file mode 100644 index 0000000..7323f2b --- /dev/null +++ b/ui/src/hooks/useFetch.ts @@ -0,0 +1,48 @@ +import { useCallback, useEffect, useRef, useState } from "react"; + +const cacheStore = new Map(); + +type UseFetchOptions = { + enabled: boolean; +}; + +export const useFetch = ( + fetchKey: any, + fetchFn: () => Promise, + options?: Partial +) => { + const key = JSON.stringify(fetchKey); + const loadingRef = useRef(false); + const [isLoading, setIsLoading] = useState(false); + const [data, setData] = useState(cacheStore.get(key)); + const [error, setError] = useState(); + + const fetchData = useCallback(async () => { + if (loadingRef.current) { + return; + } + + try { + loadingRef.current = true; + setIsLoading(true); + + const res = await fetchFn(); + setData(res); + cacheStore.set(key, res); + } catch (err) { + setError(err instanceof Error ? err : new Error("Unknown error")); + setData(undefined); + } finally { + loadingRef.current = false; + setIsLoading(false); + } + }, [key]); + + useEffect(() => { + if (options?.enabled !== false) { + fetchData(); + } + }, [fetchData, options?.enabled]); + + return { data, isLoading, error, refetch: fetchData }; +}; diff --git a/ui/src/index.css b/ui/src/index.css new file mode 100644 index 0000000..bd6213e --- /dev/null +++ b/ui/src/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/ui/src/main.tsx b/ui/src/main.tsx new file mode 100644 index 0000000..0ebf5a7 --- /dev/null +++ b/ui/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App.tsx"; +import "./index.css"; + +createRoot(document.getElementById("root")!).render( + + + +); diff --git a/ui/src/vite-env.d.ts b/ui/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/ui/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/ui/tailwind.config.js b/ui/tailwind.config.js new file mode 100644 index 0000000..0bc30de --- /dev/null +++ b/ui/tailwind.config.js @@ -0,0 +1,15 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [require('daisyui')], + daisyui: { + themes: ['dracula'], + }, +} + diff --git a/ui/tsconfig.app.json b/ui/tsconfig.app.json new file mode 100644 index 0000000..f0a2350 --- /dev/null +++ b/ui/tsconfig.app.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/ui/tsconfig.json b/ui/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/ui/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/ui/tsconfig.node.json b/ui/tsconfig.node.json new file mode 100644 index 0000000..0d3d714 --- /dev/null +++ b/ui/tsconfig.node.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/ui/ui.go b/ui/ui.go new file mode 100644 index 0000000..7021697 --- /dev/null +++ b/ui/ui.go @@ -0,0 +1,10 @@ +//go:build !prod +// +build !prod + +package ui + +import "net/http" + +func ServeUI(app *http.ServeMux) { + // +} diff --git a/ui/ui_prod.go b/ui/ui_prod.go new file mode 100644 index 0000000..8b49b3a --- /dev/null +++ b/ui/ui_prod.go @@ -0,0 +1,34 @@ +//go:build prod +// +build prod + +package ui + +import ( + "embed" + "io/fs" + "net/http" + "path" +) + +//go:embed dist +var embeddedFs embed.FS + +func ServeUI(app *http.ServeMux) { + distFs, _ := fs.Sub(embeddedFs, "dist") + fileServer := http.FileServer(http.FS(distFs)) + + app.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _path := path.Clean(r.URL.Path)[1:] + + // Rewrite non-existing paths to index.html + if _, err := fs.Stat(distFs, _path); err != nil { + index, _ := fs.ReadFile(distFs, "index.html") + w.Header().Add("Content-Type", "text/html") + w.WriteHeader(http.StatusOK) + w.Write(index) + return + } + + fileServer.ServeHTTP(w, r) + })) +} diff --git a/ui/vite.config.ts b/ui/vite.config.ts new file mode 100644 index 0000000..f905164 --- /dev/null +++ b/ui/vite.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react-swc' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + proxy: { + '/api': 'http://localhost:8080' + } + } +}) diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000..d061775 --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,10 @@ +package utils + +import "os" + +func GetEnv(key string, fallback string) string { + if value, ok := os.LookupEnv(key); ok { + return value + } + return fallback +}