Browse Source
Merge branch 'asmodeus' of git.aiterp.net:lucifer/new-server into asmodeus
pull/1/head
Merge branch 'asmodeus' of git.aiterp.net:lucifer/new-server into asmodeus
pull/1/head
Gisle Aune
4 years ago
21 changed files with 366 additions and 12 deletions
-
4app/api/bridges.go
-
83app/api/presets.go
-
15app/config/driver.go
-
9app/config/repo.go
-
1app/server.go
-
13cmd/bridgetest/main.go
-
2cmd/goose/main.go
-
14internal/drivers/provider.go
-
2internal/mysql/bridgerepo.go
-
99internal/mysql/presetrepo.go
-
2models/bridge.go
-
26models/colorpreset.go
-
1models/errors.go
-
17scripts/20210523175111_color_preset.sql
-
11scripts/20210523175425_color_preset_name.sql
-
2webui/package.json
-
7webui/src/App.tsx
-
43webui/src/primitives/Forms.tsx
-
2webui/src/primitives/Layout.tsx
-
2webui/src/res/colors.sass
-
23webui/yarn.lock
@ -0,0 +1,83 @@ |
|||||
|
package api |
||||
|
|
||||
|
import ( |
||||
|
"git.aiterp.net/lucifer/new-server/app/config" |
||||
|
"git.aiterp.net/lucifer/new-server/models" |
||||
|
"github.com/gin-gonic/gin" |
||||
|
) |
||||
|
|
||||
|
func ColorPresets(r gin.IRoutes) { |
||||
|
r.GET("", handler(func(c *gin.Context) (interface{}, error) { |
||||
|
return config.ColorPresetRepository().FetchAll(ctxOf(c)) |
||||
|
})) |
||||
|
|
||||
|
r.GET("/:id", handler(func(c *gin.Context) (interface{}, error) { |
||||
|
return config.ColorPresetRepository().Find(ctxOf(c), intParam(c, "id")) |
||||
|
})) |
||||
|
|
||||
|
r.POST("", handler(func(c *gin.Context) (interface{}, error) { |
||||
|
var body struct { |
||||
|
Name string `json:"name"` |
||||
|
ColorString string `json:"colorString"` |
||||
|
} |
||||
|
err := parseBody(c, &body) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
newColor, err := models.ParseColorValue(body.ColorString) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
preset := models.ColorPreset{ |
||||
|
Name: body.Name, |
||||
|
Value: newColor, |
||||
|
} |
||||
|
|
||||
|
preset.Validate() |
||||
|
err = config.ColorPresetRepository().Save(ctxOf(c), &preset) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
return preset, nil |
||||
|
})) |
||||
|
|
||||
|
r.PUT("/:id", handler(func(c *gin.Context) (interface{}, error) { |
||||
|
var body struct { |
||||
|
Name *string `json:"name"` |
||||
|
ColorString *string `json:"colorString"` |
||||
|
} |
||||
|
err := parseBody(c, &body) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
preset, err := config.ColorPresetRepository().Find(ctxOf(c), intParam(c, "id")) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
if body.Name != nil { |
||||
|
preset.Name = *body.Name |
||||
|
} |
||||
|
|
||||
|
if body.ColorString != nil { |
||||
|
newColor, err := models.ParseColorValue(*body.ColorString) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
preset.Value = newColor |
||||
|
} |
||||
|
|
||||
|
preset.Validate() |
||||
|
err = config.ColorPresetRepository().Save(ctxOf(c), &preset) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
return preset, nil |
||||
|
})) |
||||
|
} |
@ -1,12 +1,23 @@ |
|||||
package config |
package config |
||||
|
|
||||
import "git.aiterp.net/lucifer/new-server/models" |
|
||||
|
import ( |
||||
|
"git.aiterp.net/lucifer/new-server/internal/drivers" |
||||
|
"git.aiterp.net/lucifer/new-server/internal/drivers/nanoleaf" |
||||
|
"git.aiterp.net/lucifer/new-server/models" |
||||
|
"sync" |
||||
|
) |
||||
|
|
||||
var dp models.DriverProvider |
var dp models.DriverProvider |
||||
|
var dpMutex sync.Mutex |
||||
|
|
||||
func DriverProvider() models.DriverProvider { |
func DriverProvider() models.DriverProvider { |
||||
|
dpMutex.Lock() |
||||
|
defer dpMutex.Unlock() |
||||
|
|
||||
if dp == nil { |
if dp == nil { |
||||
panic("not implemented yet") |
|
||||
|
dp = drivers.DriverMap{ |
||||
|
models.DTNanoLeaf: &nanoleaf.Driver{}, |
||||
|
} |
||||
} |
} |
||||
|
|
||||
return dp |
return dp |
||||
|
@ -0,0 +1,14 @@ |
|||||
|
package drivers |
||||
|
|
||||
|
import "git.aiterp.net/lucifer/new-server/models" |
||||
|
|
||||
|
type DriverMap map[models.DriverKind]models.Driver |
||||
|
|
||||
|
func (m DriverMap) Provide(kind models.DriverKind) (models.Driver, error) { |
||||
|
if m[kind] == nil { |
||||
|
return nil, models.ErrUnknownDriver |
||||
|
} |
||||
|
|
||||
|
return m[kind], nil |
||||
|
} |
||||
|
|
@ -0,0 +1,99 @@ |
|||||
|
package mysql |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"git.aiterp.net/lucifer/new-server/models" |
||||
|
"github.com/jmoiron/sqlx" |
||||
|
) |
||||
|
|
||||
|
type presetRecord struct { |
||||
|
ID int `db:"id"` |
||||
|
Name string `db:"name"` |
||||
|
Hue float64 `db:"hue"` |
||||
|
Saturation float64 `db:"saturation"` |
||||
|
Kelvin int `db:"kelvin"` |
||||
|
} |
||||
|
|
||||
|
type ColorPresetRepo struct { |
||||
|
DBX *sqlx.DB |
||||
|
} |
||||
|
|
||||
|
func (c *ColorPresetRepo) Find(ctx context.Context, id int) (models.ColorPreset, error) { |
||||
|
var record presetRecord |
||||
|
err := c.DBX.GetContext(ctx, &record, "SELECT * FROM color_preset WHERE id = ?", id) |
||||
|
if err != nil { |
||||
|
return models.ColorPreset{}, dbErr(err) |
||||
|
} |
||||
|
|
||||
|
return c.fromRecords(record)[0], nil |
||||
|
} |
||||
|
|
||||
|
func (c *ColorPresetRepo) FetchAll(ctx context.Context) ([]models.ColorPreset, error) { |
||||
|
records := make([]presetRecord, 0, 16) |
||||
|
err := c.DBX.SelectContext(ctx, &records, "SELECT * FROM color_preset") |
||||
|
if err != nil { |
||||
|
return nil, dbErr(err) |
||||
|
} |
||||
|
|
||||
|
return c.fromRecords(records...), nil |
||||
|
} |
||||
|
|
||||
|
func (c *ColorPresetRepo) Save(ctx context.Context, preset *models.ColorPreset) error { |
||||
|
if preset.ID > 0 { |
||||
|
_, err := c.DBX.ExecContext( |
||||
|
ctx, |
||||
|
"UPDATE color_preset SET name = ?, hue = ?, saturation = ?, kelvin = ? WHERE id = ?", |
||||
|
preset.Name, preset.Value.Hue, preset.Value.Saturation, preset.Value.Kelvin, preset.ID, |
||||
|
) |
||||
|
|
||||
|
if err != nil { |
||||
|
return dbErr(err) |
||||
|
} |
||||
|
} else { |
||||
|
rs, err := c.DBX.ExecContext( |
||||
|
ctx, |
||||
|
"INSERT INTO color_preset (name, hue, saturation, kelvin) VALUES (?, ?, ?, ?)", |
||||
|
preset.Name, preset.Value.Hue, preset.Value.Saturation, preset.Value.Kelvin, |
||||
|
) |
||||
|
|
||||
|
if err != nil { |
||||
|
return dbErr(err) |
||||
|
} |
||||
|
|
||||
|
id, err := rs.LastInsertId() |
||||
|
if err != nil { |
||||
|
return dbErr(err) |
||||
|
} |
||||
|
|
||||
|
preset.ID = int(id) |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (c *ColorPresetRepo) Delete(ctx context.Context, preset *models.ColorPreset) error { |
||||
|
_, err := c.DBX.ExecContext(ctx, "DELETE FROM color_preset WHERE id = ?", preset.ID) |
||||
|
if err != nil { |
||||
|
return dbErr(err) |
||||
|
} |
||||
|
|
||||
|
preset.ID = 0 |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (c *ColorPresetRepo) fromRecords(records ...presetRecord) []models.ColorPreset { |
||||
|
newList := make([]models.ColorPreset, len(records), len(records)) |
||||
|
for i, record := range records { |
||||
|
newList[i] = models.ColorPreset{ |
||||
|
ID: record.ID, |
||||
|
Name: record.Name, |
||||
|
Value: models.ColorValue{ |
||||
|
Hue: record.Hue, |
||||
|
Saturation: record.Saturation, |
||||
|
Kelvin: record.Kelvin, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return newList |
||||
|
} |
@ -0,0 +1,26 @@ |
|||||
|
package models |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"strings" |
||||
|
) |
||||
|
|
||||
|
type ColorPreset struct { |
||||
|
ID int `json:"id"` |
||||
|
Name string `json:"name"` |
||||
|
Value ColorValue `json:"value"` |
||||
|
} |
||||
|
|
||||
|
type ColorPresetRepository interface { |
||||
|
Find(ctx context.Context, id int) (ColorPreset, error) |
||||
|
FetchAll(ctx context.Context) ([]ColorPreset, error) |
||||
|
Save(ctx context.Context, preset *ColorPreset) error |
||||
|
Delete(ctx context.Context, preset *ColorPreset) error |
||||
|
} |
||||
|
|
||||
|
func (c *ColorPreset) Validate() { |
||||
|
c.Name = strings.Trim(c.Name, " \t\n") |
||||
|
if len(c.Name) == 0 { |
||||
|
c.Name = c.Value.String() |
||||
|
} |
||||
|
} |
@ -0,0 +1,17 @@ |
|||||
|
-- +goose Up |
||||
|
-- +goose StatementBegin |
||||
|
CREATE TABLE color_preset |
||||
|
( |
||||
|
id INT NOT NULL AUTO_INCREMENT, |
||||
|
hue DOUBLE NOT NULL, |
||||
|
saturation DOUBLE NOT NULL, |
||||
|
kelvin INT NOT NULL, |
||||
|
|
||||
|
PRIMARY KEY (id) |
||||
|
); |
||||
|
-- +goose StatementEnd |
||||
|
|
||||
|
-- +goose Down |
||||
|
-- +goose StatementBegin |
||||
|
DROP TABLE color_preset; |
||||
|
-- +goose StatementEnd |
@ -0,0 +1,11 @@ |
|||||
|
-- +goose Up |
||||
|
-- +goose StatementBegin |
||||
|
ALTER TABLE color_preset |
||||
|
ADD COLUMN name VARCHAR(255) NOT NULL DEFAULT 'Unnamed'; |
||||
|
-- +goose StatementEnd |
||||
|
|
||||
|
-- +goose Down |
||||
|
-- +goose StatementBegin |
||||
|
ALTER TABLE color_preset |
||||
|
DROP COLUMN name; |
||||
|
-- +goose StatementEnd |
@ -0,0 +1,43 @@ |
|||||
|
import React, {useLayoutEffect, useState} from 'react'; |
||||
|
// @ts-ignore
|
||||
|
import iro from "@jaames/iro"; |
||||
|
|
||||
|
interface ColorPickerProps { |
||||
|
h: number |
||||
|
s: number |
||||
|
onChange: (h: number, v: number) => void |
||||
|
} |
||||
|
|
||||
|
const randomId = () => Math.floor(Math.random() * 100000); |
||||
|
|
||||
|
export const HSColorPicker: React.FC<ColorPickerProps> = ({h, s, onChange}) => { |
||||
|
const [random] = useState(() => `color-picker-${randomId()}`); |
||||
|
|
||||
|
useLayoutEffect(() => { |
||||
|
// @ts-ignore
|
||||
|
const colorPicker = new iro.ColorPicker(`#${random}`, { |
||||
|
color: {h, s: s * 100, v: 255}, |
||||
|
layout: [ |
||||
|
{ |
||||
|
component: iro.ui.Wheel, |
||||
|
options: {} |
||||
|
} |
||||
|
], |
||||
|
}); |
||||
|
|
||||
|
colorPicker.on("input:end", (color: { hsv: { h: number, s: number } }) => { |
||||
|
onChange(color.hsv.h || 0, (color.hsv.s || 0) / 100); |
||||
|
}); |
||||
|
|
||||
|
return () => { |
||||
|
const elem = document.getElementById(`color-picker-${random}`); |
||||
|
if (elem === null) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
elem.innerHTML = ""; |
||||
|
}; |
||||
|
}, [h, s, onChange, random]); |
||||
|
|
||||
|
return <div id={random} style={{margin: "0 auto"}}/>; |
||||
|
}; |
Write
Preview
Loading…
Cancel
Save
Reference in new issue