diff --git a/app.go b/app.go index f43b217..8bc8d53 100644 --- a/app.go +++ b/app.go @@ -4,8 +4,8 @@ import ( "context" "desktop/internal" "desktop/internal/api" + "errors" "fmt" - "log" "os" "os/exec" "strings" @@ -64,11 +64,11 @@ func (a *App) startup(ctx context.Context) { // startSchedulerWorker() } -func (a *App) GetDownloadedImages() []string { +func (a *App) GetDownloadedImages() ([]string, error) { selectedPath, err := appConf.Get("image.selected_abs_path") if err != nil { - println(err) + return nil, err } path := selectedPath.(string) var fp string = path @@ -78,10 +78,10 @@ func (a *App) GetDownloadedImages() []string { } img, err := internal.GetAllFilesInDir(fp) if err != nil { - println(err.Error()) + return nil, err } - return img + return img, nil } func (a *App) SelectImageDir() []string { @@ -104,14 +104,14 @@ func (a *App) SelectImageDir() []string { return imgs } -func (a *App) DownloadImages() { +func (a *App) DownloadImages() error { apikey, _ := appConf.Get("api.unsplash_apikey") dp, _ := appConf.Get("image.selected_abs_path") tot, _ := appConf.Get("api.download_limit") cat, _ := appConf.Get("api.image_category") if apikey == nil || dp == nil { - log.Fatal("Image path not set") + return fmt.Errorf("Image path not set") } var ct int @@ -145,9 +145,13 @@ func (a *App) DownloadImages() { err := deleteFilesWithPrefix(imagePath, "picasa_") if err != nil { - log.Fatal("Error deleting images ", err.Error()) + return fmt.Errorf("Error deleting images: %v ", err.Error()) + } + if err := internal.FetchImages(c); err != nil { + return err } - internal.FetchImages(c) + + return nil } func (a *App) SetWallpaper(path string) { @@ -243,8 +247,12 @@ func deleteFilesWithPrefix(dir, prf string) error { return nil } -func (a *App) GetGradient() error { - err := internal.GenerateGradientImage() +func (a *App) GetGradientImage(c []api.RGBA) error { + if len(c) == 0 { + return errors.New("no colors added") + } + + err := internal.GenerateGradientImage(c[0], c[1]) if err != nil { return err } @@ -252,5 +260,36 @@ func (a *App) GetGradient() error { return nil } +func (a *App) GetHexToRGBA(color string) (api.RGBA, error) { + fmt.Println(color) + r, _ := internal.HexToRGBA(color) + fmt.Println(r) + return api.RGBA{}, nil +} + +func (a *App) Testament() error { + return fmt.Errorf("Testament...") +} + +// func (ax *App) GGradient(color string) interface{} { +// +// r, g, b, a, err := internal.HexToRGBA(color) +// if err != nil { +// log.Fatal(err) +// } +// res := struct { +// R int `json:"r"` +// G int `json:"g"` +// B int `json:"b"` +// A int `json:"a"` +// }{ +// R: r, +// G: g, +// B: b, +// A: a, +// } +// return res +// } + // https://gist.github.com/stupidbodo/0db61fa874213a31dc57 - replacement for cronjob // https://gist.github.com/harubaru/f727cedacae336d1f7877c4bbe2196e1#model-overview diff --git a/frontend/dist/assets/no-image.7f4e8366.svg b/frontend/dist/assets/no-image.7f4e8366.svg deleted file mode 100644 index 692f588..0000000 --- a/frontend/dist/assets/no-image.7f4e8366.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/dist/index.html b/frontend/dist/index.html index 2e511b6..5b69032 100644 --- a/frontend/dist/index.html +++ b/frontend/dist/index.html @@ -10,8 +10,8 @@ --main-color: orange; } - - + +
diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index d451b7e..ae20826 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -3,14 +3,25 @@ import Setting from './pages/Setting.svelte'; import PlainArt from './pages/PlainArt.svelte'; - import { Router, Route } from 'svelte-routing'; + import type { SvelteComponentTyped } from 'svelte'; + import { + Router as RouterComponent, + Route as RouteComponent, + } from 'svelte-routing'; + + const Router: typeof SvelteComponentTyped = RouterComponent; + const Route: typeof SvelteComponentTyped = RouteComponent; + import AbstractArt from './pages/AbstractArt.svelte'; + import Ai from './pages/AI.svelte'; export let url = '';
-
- + + + +
diff --git a/frontend/src/components/CloseIcon.svelte b/frontend/src/components/CloseIcon.svelte new file mode 100644 index 0000000..fc982cd --- /dev/null +++ b/frontend/src/components/CloseIcon.svelte @@ -0,0 +1,18 @@ + + + diff --git a/frontend/src/components/ErrorIcon.svelte b/frontend/src/components/ErrorIcon.svelte new file mode 100644 index 0000000..6ac54f8 --- /dev/null +++ b/frontend/src/components/ErrorIcon.svelte @@ -0,0 +1,19 @@ + + + diff --git a/frontend/src/components/InfoIcon.svelte b/frontend/src/components/InfoIcon.svelte new file mode 100644 index 0000000..4422eb1 --- /dev/null +++ b/frontend/src/components/InfoIcon.svelte @@ -0,0 +1,18 @@ + + + diff --git a/frontend/src/components/ListImages.svelte b/frontend/src/components/ListImages.svelte index 2418ff9..edf7fac 100644 --- a/frontend/src/components/ListImages.svelte +++ b/frontend/src/components/ListImages.svelte @@ -1,73 +1,31 @@ diff --git a/frontend/src/components/Navigation.svelte b/frontend/src/components/Navigation.svelte index 79da662..2a52844 100644 --- a/frontend/src/components/Navigation.svelte +++ b/frontend/src/components/Navigation.svelte @@ -10,26 +10,14 @@ import DownloadImage from '../../src/assets/images/download.svg'; import ConfigImage from '../../src/assets/images/config.svg'; - import PlainArtImage from '../../src/assets/images/plain.svg'; - - import AbstractArt from '../../src/assets/images/art.svg'; - - import AIArt from '../../src/assets/images/ai.svg'; + import { dispatcher } from '../../src/utilities/util'; let isLoading = true; let images: string[] = []; async function downloadImages() { - try { - isLoading = true; - const res = await DownloadImages(); - } catch (e) { - } finally { - const result = await GetDownloadedImages(); - images = result ?? []; - isLoading = false; - } + dispatcher('downloading'); } @@ -50,7 +38,7 @@ class="mr-1 dark:brightness-0 dark:invert-[1]" /> Images - +
+ export let width = '1em'; + + + diff --git a/frontend/src/components/Toast.svelte b/frontend/src/components/Toast.svelte new file mode 100644 index 0000000..af8317d --- /dev/null +++ b/frontend/src/components/Toast.svelte @@ -0,0 +1,66 @@ + + + + + diff --git a/frontend/src/components/Toasts.svelte b/frontend/src/components/Toasts.svelte new file mode 100644 index 0000000..02395d7 --- /dev/null +++ b/frontend/src/components/Toasts.svelte @@ -0,0 +1,32 @@ + + +{#if $toasts} +
+ {#each $toasts as toast (toast.id)} + dismissToast(toast.id)}>{toast.message} + {/each} +
+{/if} + + diff --git a/frontend/src/pages/AI.svelte b/frontend/src/pages/AI.svelte new file mode 100644 index 0000000..06fe6f8 --- /dev/null +++ b/frontend/src/pages/AI.svelte @@ -0,0 +1,13 @@ + + +
+ +
+

Welcome to the AI Page

+

This is a template for your AI page.

+
+
\ No newline at end of file diff --git a/frontend/src/pages/AbstractArt.svelte b/frontend/src/pages/AbstractArt.svelte new file mode 100644 index 0000000..9cea05d --- /dev/null +++ b/frontend/src/pages/AbstractArt.svelte @@ -0,0 +1,13 @@ + + diff --git a/frontend/src/pages/Main.svelte b/frontend/src/pages/Main.svelte index 08b53fd..ca78ead 100644 --- a/frontend/src/pages/Main.svelte +++ b/frontend/src/pages/Main.svelte @@ -1,45 +1,98 @@ - +
+ + {#if !isLoading && images.length === 0} +
+ +

+ No Images found in your library +

+

To get started click on the download button.

+
+ {/if} + {#if isLoading} +
+ +

Processing...

+
+ {:else} + + {/if} +
diff --git a/frontend/src/pages/PlainArt.svelte b/frontend/src/pages/PlainArt.svelte index 1a0fe9f..2a18b40 100644 --- a/frontend/src/pages/PlainArt.svelte +++ b/frontend/src/pages/PlainArt.svelte @@ -1,21 +1,22 @@ - + +
+ {#each commonColors as c} +
handleSelectColor(c)} + on:keydown={() => handleSelectColor(c)} + class="mt-5 cursor-default border-2 transition-all hover:opacity-70 rounded-full pt-9 text-center w-[100px] h-[100px] ml-5" + >
+ {/each} +
+ + +
+
+ +
+ {#each colors as color} +
(colors = colors.filter((c) => c !== color))} + on:keydown={() => (colors = colors.filter((c) => c !== color))} + style="background-color: {color}" + class="mt-5 cursor-default border-2 transition-all hover:opacity-70 rounded-full pt-9 text-center w-[100px] h-[100px] ml-5" + >
+ {/each} +
+ +
+ +
+
+ diff --git a/frontend/src/pages/Setting.svelte b/frontend/src/pages/Setting.svelte index a141b7e..80310de 100644 --- a/frontend/src/pages/Setting.svelte +++ b/frontend/src/pages/Setting.svelte @@ -79,6 +79,15 @@ message = error; } }; + + function setImageCategory(query: string) { + imageCategory = query; + } + + function handleRemoveWhiteSpace(event) { + const value = event.target.value; + event.target.value = value.replace(/\s/g, ''); + } @@ -212,4 +270,10 @@ .selection label { display: block; } + + select, + input { + appearance: none; + -webkit-appearance: none; + } diff --git a/frontend/src/store/app.ts b/frontend/src/store/app.ts index 46facf4..6534c1c 100644 --- a/frontend/src/store/app.ts +++ b/frontend/src/store/app.ts @@ -1,3 +1,6 @@ import { writable } from 'svelte/store'; export const imagePathStore = writable(''); + + + diff --git a/frontend/src/utilities/util.ts b/frontend/src/utilities/util.ts new file mode 100644 index 0000000..685cca9 --- /dev/null +++ b/frontend/src/utilities/util.ts @@ -0,0 +1,6 @@ +import { imagePathStore } from "../store/app"; + +export const dispatcher = (value: string) => { + imagePathStore.set(value); +} + diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index f27dc47..588cc3a 100755 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -1,3 +1,20 @@ +export namespace api { + + export class RGBA { + + + static createFrom(source: any = {}) { + return new RGBA(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + + } + } + +} + export namespace main { export class Conf { diff --git a/internal/api/unsplash.go b/internal/api/unsplash.go index 33522c8..677a0fb 100644 --- a/internal/api/unsplash.go +++ b/internal/api/unsplash.go @@ -1,6 +1,7 @@ package api import ( + "context" "encoding/json" "fmt" "io" @@ -10,8 +11,7 @@ import ( "strconv" "strings" "sync" - - log "github.com/sirupsen/logrus" + "time" ) type UnleaseService struct { @@ -41,6 +41,15 @@ type ImageConfig struct { Path string } +type RGBA struct { + R int + G int + B int + A int +} + +const timeout = 60 * time.Second + func NewUnsplashService(apikey string, path string) *UnleaseService { return &UnleaseService{ apikey: apikey, @@ -54,6 +63,8 @@ func (u *UnleaseService) GetImages(imgConf ImageConfig) error { imgCount := imgConf.TotalDownloadImage maxImage := strconv.Itoa(imgCount) accessKey := u.apikey + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() var imagePath string = u.path @@ -61,35 +72,62 @@ func (u *UnleaseService) GetImages(imgConf ImageConfig) error { fmt.Println("Download...") fmt.Println(url) - result, err := getImage(url) + result, err := getImage(ctx, url) if err != nil { return err } var wg sync.WaitGroup + + errCh := make(chan error, len(result)) + for key, v := range result { wg.Add(1) struri := v.User.Links.Html parsedURL, err := neturl.Parse(struri) if err != nil { - fmt.Println("Error parsing URL:", err) + return fmt.Errorf("Error parsing URL: %v", err) } path := parsedURL.Path parts := strings.Split(path, "/") username := parts[len(parts)-1] - go download(v.Urls.Full, key, &wg, imagePath, username) + go (func() { + defer wg.Done() + if err := download(ctx, v.Urls.Full, key, imagePath, username); err != nil { + errCh <- err + } + })() } wg.Wait() - return nil + close(errCh) + + select { + case err := <-errCh: + return err + default: + return nil + } } -func getImage(url string) ([]Image, error) { - resp, err := http.Get(url) +func getImage(ctx context.Context, url string) ([]Image, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, err } + + client := http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("[GetImage]: request failed: %w", err) + } + defer resp.Body.Close() + // Check if the response status is OK + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + var p []Image if err := json.NewDecoder(resp.Body).Decode(&p); err != nil { return nil, err @@ -97,27 +135,43 @@ func getImage(url string) ([]Image, error) { return p, nil } -func download(image string, index int, wg *sync.WaitGroup, IMAGE_DIR string, upr string) { - defer wg.Done() - resp, err := http.Get(image) +func download(ctx context.Context, image string, index int, IMAGE_DIR string, upr string) error { + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, image, nil) if err != nil { - log.Fatal(err) + return err + } + + client := http.Client{} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("[Download]: request failed: %v", err) } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + fn := fmt.Sprintf("%s/picasa_%v_%s.jpg", IMAGE_DIR, index, upr) info := fmt.Sprintf("Downloading: %s", fn) fmt.Println(info) - defer resp.Body.Close() f, err := os.Create(fn) // fmt.Sprintf("%s/%v.jpg", IMAGE_DIR, index)) if err != nil { - log.Fatal(err) + return err } + defer f.Close() _, err = io.Copy(f, resp.Body) + if err != nil { - log.Fatal(err) + return err } + fmt.Println("Downloaded to: ", f.Name()) + + return nil } diff --git a/internal/service.go b/internal/service.go index 35ab6f6..c0997ce 100644 --- a/internal/service.go +++ b/internal/service.go @@ -3,17 +3,15 @@ package internal import ( "desktop/internal/api" "fmt" + "github.com/fogleman/gg" "image/color" "image/png" - "log" "os" "os/exec" "path" "path/filepath" "strconv" "strings" - - "github.com/fogleman/gg" ) func GetImagesFromDir() []string { @@ -40,14 +38,15 @@ func GetImagesFromDir() []string { } -func FetchImages(conf api.ImageConfig) { +func FetchImages(conf api.ImageConfig) error { apikey := conf.Apikey sp := conf.Path svc := api.NewImageDownload("unsplash", sp, apikey) err := svc.GetImages(conf) if err != nil { - log.Fatal(err.Error()) + return err } + return nil } func WallpaperEvent(path string) { @@ -68,7 +67,7 @@ func GetAllFilesInDir(dir string) ([]string, error) { return err } - if !info.IsDir() && isImageFile(path) { + if !info.IsDir() && isImageFile(path) && info.Size() > 0 { if isImageFile(info.Name()) { images = append(images, path) } @@ -92,11 +91,15 @@ func isImageFile(file string) bool { return false } -func GenerateGradientImage() error { +func GenerateGradientImage(c1 api.RGBA, c2 api.RGBA) error { + w, h := 1900, 1200 dc := gg.NewContext(w, h) - stc := color.RGBA{255, 0, 0, 255} - endc := color.RGBA{0, 0, 255, 255} + // stc := color.RGBA{255, 0, 0, 255} + // endc := color.RGBA{0, 0, 255, 255} + + stc := color.RGBA{uint8(c1.R), uint8(c1.G), uint8(c1.B), uint8(c1.A)} + endc := color.RGBA{uint8(c2.R), uint8(c2.G), uint8(c2.B), uint8(c2.A)} for y := 0; y < h; y++ { t := float64(y) / float64(h) @@ -104,7 +107,7 @@ func GenerateGradientImage() error { g := uint8((1-t)*float64(stc.G) + t*float64(endc.G)) b := uint8((1-t)*float64(stc.B) + t*float64(endc.B)) // a := uint8((1-t)*float64(stc.A) + t*float64(endc.A)) - + // dc.SetRGB(float64(r)/255, float64(g)/255, float64(b)/255) dc.DrawLine(0, float64(y), float64(w), float64(y)) dc.Stroke() @@ -119,23 +122,64 @@ func GenerateGradientImage() error { return png.Encode(f, dc.Image()) } -func HexToRGB(hex string) (int, int, int, error) { - // Remove the '#' if present - hex = strings.TrimPrefix(hex, "#") - - // Parse hex values - r, err := strconv.ParseInt(hex[0:2], 16, 32) - if err != nil { - return 0, 0, 0, err +func HexToRGBA(hex string) (api.RGBA, error) { + if len(hex) > 0 && hex[0] == '#' { + hex = hex[1:] } - g, err := strconv.ParseInt(hex[2:4], 16, 32) - if err != nil { - return 0, 0, 0, err + + var r, g, b, a int + + switch len(hex) { + case 6: // #RRGGBB + r64, err := strconv.ParseInt(hex[0:2], 16, 8) + if err != nil { + return api.RGBA{}, err + } + g64, err := strconv.ParseInt(hex[2:4], 16, 8) + if err != nil { + return api.RGBA{}, err + } + b64, err := strconv.ParseInt(hex[4:6], 16, 8) + if err != nil { + return api.RGBA{}, err + } + + r, g, b, a = int(r64), int(g64), int(b64), 255 // Default alpha to 255 + + fmt.Println(r, g, b, a) + case 8: // #RRGGBBAA + r64, err := strconv.ParseInt(hex[0:2], 16, 8) + if err != nil { + return api.RGBA{}, err + } + g64, err := strconv.ParseInt(hex[2:4], 16, 8) + if err != nil { + return api.RGBA{}, err + } + b64, err := strconv.ParseInt(hex[4:6], 16, 8) + if err != nil { + return api.RGBA{}, err + } + a64, err := strconv.ParseInt(hex[6:8], 16, 8) + if err != nil { + return api.RGBA{}, err + } + + r, g, b, a = int(r64), int(g64), int(b64), int(a64) + fmt.Println(r, g, b, a) + + default: + return api.RGBA{}, fmt.Errorf("invalid hex color format") } - b, err := strconv.ParseInt(hex[4:6], 16, 32) - if err != nil { - return 0, 0, 0, err + + fmt.Println(r, g, b, a) + + res := api.RGBA{ + R: r, + G: g, + B: b, + A: a, } - return int(r), int(g), int(b), nil + return res, nil }