add intro page and storage initialization

This commit is contained in:
sqshq 2019-05-20 00:12:40 -04:00
parent 8fa382e37d
commit 911be1ba00
15 changed files with 366 additions and 60 deletions

8
client/backend.go Normal file
View File

@ -0,0 +1,8 @@
package client
import "github.com/sqshq/sampler/storage"
// TODO
func loadLicense(key string) *storage.License {
return nil
}

View File

@ -44,7 +44,7 @@ func RenderAlert(alert *data.Alert, area image.Rectangle, buffer *ui.Buffer) {
}
block := *ui.NewBlock()
block.SetRect(getRectCoordinates(area, width, len(lines)))
block.SetRect(util.GetRectCoordinates(area, width, len(lines)))
block.BorderStyle = ui.Style{Fg: color, Bg: ui.ColorClear}
block.Draw(buffer)
@ -52,13 +52,6 @@ func RenderAlert(alert *data.Alert, area image.Rectangle, buffer *ui.Buffer) {
for i := 0; i < len(lines); i++ {
buffer.SetString(lines[i],
ui.NewStyle(color), getMiddlePoint(block.Inner, lines[i], i-1))
ui.NewStyle(color), util.GetMiddlePoint(block.Inner, lines[i], i-1))
}
}
// TODO move to utils
func getRectCoordinates(area image.Rectangle, width int, height int) (int, int, int, int) {
x1 := area.Min.X + area.Dx()/2 - width/2
y1 := area.Min.Y + area.Dy()/2 - height
return x1, y1, x1 + width, y1 + height + 2
}

139
component/intro.go Normal file
View File

@ -0,0 +1,139 @@
package component
import (
ui "github.com/gizak/termui/v3"
"github.com/sqshq/sampler/component/util"
"github.com/sqshq/sampler/console"
)
type Intro struct {
*ui.Block
page IntroPage
option introOption
palette console.Palette
}
type IntroPage rune
const (
IntroPageWelcome IntroPage = 0
IntroPageCommercial IntroPage = 1
IntroPagePersonal IntroPage = 2
)
type introOption rune
const (
introOptionCommercial introOption = 0
introOptionPersonal introOption = 1
)
const (
buttonCommercial string = " COMMERCIAL USE "
buttonPersonal string = " PERSONAL USE "
buttonOk string = " OK "
)
func (intro *Intro) Up() {
intro.option = introOptionCommercial
}
func (intro *Intro) Down() {
intro.option = introOptionPersonal
}
func (intro *Intro) NextPage() {
if intro.option == introOptionCommercial {
intro.page = IntroPageCommercial
} else {
intro.page = IntroPagePersonal
}
}
func (intro *Intro) GetSelectedPage() IntroPage {
return intro.page
}
func NewIntro(palette console.Palette) *Intro {
return &Intro{
Block: NewBlock("", false, palette),
palette: palette,
}
}
func (intro *Intro) Draw(buffer *ui.Buffer) {
logo := []string{
" __ ",
" _________ ____ ___ ____ / /__ _____",
" / ___/ __ `/ __ `__ \\/ __ \\/ / _ \\/ ___/",
" (__ ) /_/ / / / / / / /_/ / / __/ / ",
"/____/\\__,_/_/ /_/ /_/ .___/_/\\___/_/ ",
" /_/ ",
}
introText := append(logo, []string{
"", "", "",
"Welcome.",
"Sampler is free of charge for personal use, but license must be purchased to use it for business purposes.",
"Clicking below indicates you agree to the terms of the license agreement and privacy policy: www.sampler.dev/license",
"", "", "",
"How do you plan to use Sampler?",
}...)
commericalText := append(logo, []string{
"", "", "", "",
"Please visit www.sampler.dev to purchase a license and then start Sampler with --license flag",
}...)
personalText := append(logo, []string{
"", "", "", "",
"Sampler is always free for non-commercial use, but you can support the project and buy a personal license:",
"www.sampler.dev",
}...)
text := introText
switch intro.page {
case IntroPageWelcome:
text = introText
case IntroPageCommercial:
text = commericalText
case IntroPagePersonal:
text = personalText
}
for i, a := range text {
util.PrintString(
a,
ui.NewStyle(intro.palette.BaseColor),
util.GetMiddlePoint(intro.Block.Rectangle, a, i-15),
buffer)
}
highlightedStyle := ui.NewStyle(intro.palette.ReverseColor, intro.palette.BaseColor)
regularStyle := ui.NewStyle(intro.palette.BaseColor, intro.palette.ReverseColor)
if intro.page == IntroPageWelcome {
commercialButtonStyle := highlightedStyle
if intro.option == introOptionPersonal {
commercialButtonStyle = regularStyle
}
personalButtonStyle := highlightedStyle
if intro.option == introOptionCommercial {
personalButtonStyle = regularStyle
}
buffer.SetString(string(buttonCommercial), commercialButtonStyle,
util.GetMiddlePoint(intro.Block.Rectangle, string(buttonCommercial), 6))
buffer.SetString(string(buttonPersonal), personalButtonStyle,
util.GetMiddlePoint(intro.Block.Rectangle, string(buttonPersonal), 8))
} else {
buffer.SetString(string(buttonOk), highlightedStyle,
util.GetMiddlePoint(intro.Block.Rectangle, string(buttonOk), 4))
}
intro.Block.Draw(buffer)
}

View File

@ -4,6 +4,7 @@ import (
ui "github.com/gizak/termui/v3"
"github.com/sqshq/sampler/component"
"github.com/sqshq/sampler/component/runchart"
"github.com/sqshq/sampler/component/util"
"github.com/sqshq/sampler/config"
"github.com/sqshq/sampler/console"
"github.com/sqshq/sampler/data"
@ -16,6 +17,7 @@ type Layout struct {
Components []*component.Component
statusbar *component.StatusBar
menu *component.Menu
intro *component.Intro
ChangeModeEvents chan Mode
mode Mode
selection int
@ -25,12 +27,13 @@ type Mode rune
const (
ModeDefault Mode = 0
ModePause Mode = 1
ModeComponentSelect Mode = 2
ModeMenuOptionSelect Mode = 3
ModeComponentMove Mode = 4
ModeComponentResize Mode = 5
ModeChartPinpoint Mode = 6
ModeIntro Mode = 1
ModePause Mode = 2
ModeComponentSelect Mode = 3
ModeMenuOptionSelect Mode = 4
ModeComponentMove Mode = 5
ModeComponentResize Mode = 6
ModeChartPinpoint Mode = 7
)
const (
@ -38,10 +41,11 @@ const (
statusbarHeight = 1
)
func NewLayout(width, height int, statusline *component.StatusBar, menu *component.Menu) *Layout {
func NewLayout(width, height int, statusline *component.StatusBar, menu *component.Menu, intro *component.Intro) *Layout {
block := *ui.NewBlock()
block.SetRect(0, 0, width, height)
intro.SetRect(0, 0, width, height)
statusline.SetRect(0, height-statusbarHeight, width, height)
return &Layout{
@ -49,6 +53,7 @@ func NewLayout(width, height int, statusline *component.StatusBar, menu *compone
Components: make([]*component.Component, 0),
statusbar: statusline,
menu: menu,
intro: intro,
mode: ModeDefault,
selection: 0,
ChangeModeEvents: make(chan Mode, 10),
@ -59,12 +64,19 @@ func (l *Layout) AddComponent(cpt *component.Component) {
l.Components = append(l.Components, cpt)
}
func (l *Layout) RunIntro() {
l.mode = ModeIntro
}
func (l *Layout) changeMode(m Mode) {
l.mode = m
l.ChangeModeEvents <- m
}
func (l *Layout) HandleMouseClick(x int, y int) {
if l.mode == ModeIntro {
return
}
l.menu.Idle()
selected, i := l.findComponentAtPoint(image.Point{X: x, Y: y})
if selected == nil {
@ -118,6 +130,14 @@ func (l *Layout) HandleKeyboardEvent(e string) {
case ModeComponentResize:
l.menu.Idle()
l.changeMode(ModeDefault)
break
case ModeIntro:
page := l.intro.GetSelectedPage()
if page == component.IntroPageWelcome {
l.intro.NextPage()
} else {
l.changeMode(ModeDefault)
}
}
case console.KeyEsc:
l.resetAlerts()
@ -180,6 +200,8 @@ func (l *Layout) HandleKeyboardEvent(e string) {
selected.Move(0, -1)
case ModeComponentResize:
selected.Resize(0, -1)
case ModeIntro:
l.intro.Up()
}
case console.KeyDown:
switch l.mode {
@ -195,6 +217,8 @@ func (l *Layout) HandleKeyboardEvent(e string) {
selected.Move(0, 1)
case ModeComponentResize:
selected.Resize(0, 1)
case ModeIntro:
l.intro.Down()
}
}
}
@ -232,21 +256,21 @@ func (l *Layout) moveSelection(direction string) {
switch direction {
case console.KeyLeft:
previouslySelectedCornerPoint = component.GetRectLeftSideCenter(previouslySelected.GetRect())
newlySelectedCornerPoint = component.GetRectRightSideCenter(l.getComponent(newlySelectedIndex).GetRect())
currentCornerPoint = component.GetRectRightSideCenter(current.GetRect())
previouslySelectedCornerPoint = util.GetRectLeftSideCenter(previouslySelected.GetRect())
newlySelectedCornerPoint = util.GetRectRightSideCenter(l.getComponent(newlySelectedIndex).GetRect())
currentCornerPoint = util.GetRectRightSideCenter(current.GetRect())
case console.KeyRight:
previouslySelectedCornerPoint = component.GetRectRightSideCenter(previouslySelected.GetRect())
newlySelectedCornerPoint = component.GetRectLeftSideCenter(l.getComponent(newlySelectedIndex).GetRect())
currentCornerPoint = component.GetRectLeftSideCenter(current.GetRect())
previouslySelectedCornerPoint = util.GetRectRightSideCenter(previouslySelected.GetRect())
newlySelectedCornerPoint = util.GetRectLeftSideCenter(l.getComponent(newlySelectedIndex).GetRect())
currentCornerPoint = util.GetRectLeftSideCenter(current.GetRect())
case console.KeyUp:
previouslySelectedCornerPoint = component.GetRectTopSideCenter(previouslySelected.GetRect())
newlySelectedCornerPoint = component.GetRectBottomSideCenter(l.getComponent(newlySelectedIndex).GetRect())
currentCornerPoint = component.GetRectBottomSideCenter(current.GetRect())
previouslySelectedCornerPoint = util.GetRectTopSideCenter(previouslySelected.GetRect())
newlySelectedCornerPoint = util.GetRectBottomSideCenter(l.getComponent(newlySelectedIndex).GetRect())
currentCornerPoint = util.GetRectBottomSideCenter(current.GetRect())
case console.KeyDown:
previouslySelectedCornerPoint = component.GetRectBottomSideCenter(previouslySelected.GetRect())
newlySelectedCornerPoint = component.GetRectTopSideCenter(l.getComponent(newlySelectedIndex).GetRect())
currentCornerPoint = component.GetRectTopSideCenter(current.GetRect())
previouslySelectedCornerPoint = util.GetRectBottomSideCenter(previouslySelected.GetRect())
newlySelectedCornerPoint = util.GetRectTopSideCenter(l.getComponent(newlySelectedIndex).GetRect())
currentCornerPoint = util.GetRectTopSideCenter(current.GetRect())
}
switch direction {
@ -279,6 +303,12 @@ func (l *Layout) Draw(buffer *ui.Buffer) {
columnWidth := float64(l.GetRect().Dx()) / float64(console.ColumnsCount)
rowHeight := float64(l.GetRect().Dy()-statusbarHeight) / float64(console.RowsCount)
if l.mode == ModeIntro {
l.intro.SetRect(l.Min.X, l.Min.Y, l.Max.X, l.Max.Y)
l.intro.Draw(buffer)
return
}
for _, c := range l.Components {
rectangle := calculateComponentCoordinates(c, columnWidth, rowHeight)
c.SetRect(rectangle.Min.X, rectangle.Min.Y, rectangle.Max.X, rectangle.Max.Y)

View File

@ -2,6 +2,7 @@ package component
import (
ui "github.com/gizak/termui/v3"
"github.com/sqshq/sampler/component/util"
"github.com/sqshq/sampler/config"
"github.com/sqshq/sampler/console"
"image"
@ -130,14 +131,14 @@ func (m *Menu) renderHighlight(buffer *ui.Buffer) {
buffer.SetString(
optionsText,
ui.NewStyle(console.ColorDarkGrey),
getMiddlePoint(m.Block.Rectangle, optionsText, -1),
util.GetMiddlePoint(m.Block.Rectangle, optionsText, -1),
)
return
}
m.printAllDirectionsArrowSign(buffer, -2)
arrowsTextPoint := getMiddlePoint(m.Block.Rectangle, arrowsText, 2)
arrowsTextPoint := util.GetMiddlePoint(m.Block.Rectangle, arrowsText, 2)
if arrowsTextPoint.Y+1 < m.Inner.Max.Y {
buffer.SetString(
arrowsText,
@ -146,16 +147,16 @@ func (m *Menu) renderHighlight(buffer *ui.Buffer) {
)
}
optionsTextPoint := getMiddlePoint(m.Block.Rectangle, optionsText, 3)
optionsTextPoint := util.GetMiddlePoint(m.Block.Rectangle, optionsText, 3)
if optionsTextPoint.Y+1 < m.Inner.Max.Y {
buffer.SetString(
optionsText,
ui.NewStyle(console.ColorDarkGrey),
getMiddlePoint(m.Block.Rectangle, optionsText, 3),
util.GetMiddlePoint(m.Block.Rectangle, optionsText, 3),
)
}
resumeTextPoint := getMiddlePoint(m.Block.Rectangle, resumeText, 4)
resumeTextPoint := util.GetMiddlePoint(m.Block.Rectangle, resumeText, 4)
if resumeTextPoint.Y+1 < m.Inner.Max.Y {
buffer.SetString(
resumeText,
@ -170,12 +171,12 @@ func (m *Menu) renderMoveAndResize(buffer *ui.Buffer) {
saveText := "<ENTER> to save changes"
if m.Dy() <= minimalMenuHeight {
buffer.SetString(saveText, ui.NewStyle(console.ColorDarkGrey), getMiddlePoint(m.Block.Rectangle, saveText, -1))
buffer.SetString(saveText, ui.NewStyle(console.ColorDarkGrey), util.GetMiddlePoint(m.Block.Rectangle, saveText, -1))
return
}
m.printAllDirectionsArrowSign(buffer, -1)
buffer.SetString(saveText, ui.NewStyle(console.ColorDarkGrey), getMiddlePoint(m.Block.Rectangle, saveText, 3))
buffer.SetString(saveText, ui.NewStyle(console.ColorDarkGrey), util.GetMiddlePoint(m.Block.Rectangle, saveText, 3))
}
func (m *Menu) printAllDirectionsArrowSign(buffer *ui.Buffer, y int) {
@ -187,10 +188,10 @@ func (m *Menu) printAllDirectionsArrowSign(buffer *ui.Buffer, y int) {
}
for i, a := range arrows {
printString(
util.PrintString(
a,
ui.NewStyle(console.ColorOlive),
getMiddlePoint(m.Block.Rectangle, a, i+y),
util.GetMiddlePoint(m.Block.Rectangle, a, i+y),
buffer,
)
}
@ -211,7 +212,7 @@ func (m *Menu) renderOptions(buffer *ui.Buffer) {
if option != MenuOptionPinpoint || m.component.Type == config.TypeRunChart {
offset += 2
point := getMiddlePoint(m.Block.Rectangle, string(option), offset-6)
point := util.GetMiddlePoint(m.Block.Rectangle, string(option), offset-6)
buffer.SetString(string(option), style, point)
}
}
@ -239,15 +240,3 @@ func (m *Menu) drawInnerBorder(buffer *ui.Buffer) {
buffer.SetCell(ui.Cell{ui.BOTTOM_LEFT, m.BorderStyle}, image.Pt(m.Min.X+2, m.Max.Y-2))
buffer.SetCell(ui.Cell{ui.BOTTOM_RIGHT, m.BorderStyle}, image.Pt(m.Max.X-3, m.Max.Y-2))
}
// TODO move to utils
func getMiddlePoint(rectangle image.Rectangle, text string, offset int) image.Point {
return image.Pt(rectangle.Min.X+rectangle.Dx()/2-len(text)/2, rectangle.Max.Y-rectangle.Dy()/2+offset)
}
// TODO move to utils
func printString(s string, style ui.Style, p image.Point, buffer *ui.Buffer) {
for i, char := range s {
buffer.SetCell(ui.Cell{Rune: char, Style: style}, image.Pt(p.X+i, p.Y))
}
}

View File

@ -15,6 +15,7 @@ type TextBox struct {
alert *data.Alert
text string
border bool
style ui.Style
}
func NewTextBox(c config.TextBoxConfig, palette console.Palette) *TextBox {
@ -22,6 +23,7 @@ func NewTextBox(c config.TextBoxConfig, palette console.Palette) *TextBox {
box := TextBox{
Block: component.NewBlock(c.Title, *c.Border, palette),
Consumer: data.NewConsumer(),
style: ui.NewStyle(palette.BaseColor),
}
go func() {
@ -54,6 +56,7 @@ func (t *TextBox) Draw(buffer *ui.Buffer) {
row = ui.TrimCells(row, t.Inner.Dx()-2)
for _, cx := range ui.BuildCellWithXArray(row) {
x, cell := cx.X, cx.Cell
cell.Style = t.style
buffer.SetCell(cell, image.Pt(x+1, y+1).Add(t.Inner.Min))
}
}

View File

@ -2,6 +2,8 @@ package util
import (
"bytes"
ui "github.com/gizak/termui/v3"
"image"
"math"
"strconv"
"strings"
@ -74,3 +76,13 @@ func formatTrailingDigits(value string, scale int) string {
return value
}
func GetMiddlePoint(rectangle image.Rectangle, text string, offset int) image.Point {
return image.Pt(rectangle.Min.X+rectangle.Dx()/2-len(text)/2, rectangle.Max.Y-rectangle.Dy()/2+offset)
}
func PrintString(s string, style ui.Style, p image.Point, buffer *ui.Buffer) {
for i, char := range s {
buffer.SetCell(ui.Cell{Rune: char, Style: style}, image.Pt(p.X+i, p.Y))
}
}

View File

@ -1,4 +1,4 @@
package component
package util
import (
"image"
@ -38,3 +38,9 @@ func GetDistance(p1 image.Point, p2 image.Point) float64 {
y := math.Abs(float64(p1.Y - p2.Y))
return math.Sqrt(x*x + y*y)
}
func GetRectCoordinates(area image.Rectangle, width int, height int) (int, int, int, int) {
x1 := area.Min.X + area.Dx()/2 - width/2
y1 := area.Min.Y + area.Dy()/2 - height
return x1, y1, x1 + width, y1 + height + 2
}

View File

@ -21,7 +21,7 @@ type Config struct {
AsciiBoxes []AsciiBoxConfig `yaml:"asciiboxes,omitempty"`
}
func Load() (Config, Options) {
func LoadConfig() (Config, Options) {
var opt Options
_, err := flags.Parse(&opt)
@ -114,7 +114,7 @@ func saveFile(config *Config, fileName string) {
log.Fatalf("Can't marshal config file: %v", err)
}
err = ioutil.WriteFile(fileName, file, 0644)
err = ioutil.WriteFile(fileName, file, os.ModePerm)
if err != nil {
log.Fatalf("Can't save config file: %v", err)
}

View File

@ -14,7 +14,7 @@ import (
"time"
)
const interactiveShellStartupTimeout = time.Second
const interactiveShellStartupTimeout = 100 * time.Millisecond
type Item struct {
Label string

View File

@ -29,7 +29,7 @@ func NewSampler(consumer *Consumer, items []*Item, triggers []*Trigger, options
go func() {
for ; true; <-ticker.C {
for _, item := range sampler.items {
go sampler.sample(item, options)
sampler.sample(item, options)
}
}
}()
@ -39,7 +39,7 @@ func NewSampler(consumer *Consumer, items []*Item, triggers []*Trigger, options
select {
case sample := <-sampler.triggersChannel:
for _, t := range sampler.triggers {
go t.Execute(sample)
t.Execute(sample)
}
}
}

19
main.go
View File

@ -15,6 +15,7 @@ import (
"github.com/sqshq/sampler/console"
"github.com/sqshq/sampler/data"
"github.com/sqshq/sampler/event"
"github.com/sqshq/sampler/storage"
"time"
)
@ -31,12 +32,12 @@ func (s *Starter) start(drawable ui.Drawable, consumer *data.Consumer, component
items := data.NewItems(itemsConfig, *componentConfig.RateMs)
data.NewSampler(consumer, items, triggers, s.opt, s.cfg.Variables, *componentConfig.RateMs)
s.lout.AddComponent(cpt)
time.Sleep(100 * time.Millisecond) // desync coroutines
time.Sleep(10 * time.Millisecond) // desync coroutines
}
func main() {
cfg, opt := config.Load()
cfg, opt := config.LoadConfig()
console.Init()
defer console.Close()
@ -47,10 +48,11 @@ func main() {
palette := console.GetPalette(*cfg.Theme)
width, height := ui.TerminalDimensions()
lout := layout.NewLayout(width, height, component.NewStatusLine(opt.ConfigFile, palette), component.NewMenu(palette))
lout := layout.NewLayout(width, height, component.NewStatusLine(opt.ConfigFile, palette), component.NewMenu(palette), component.NewIntro(palette))
starter := &Starter{lout, player, opt, cfg}
license := storage.GetLicense()
for _, c := range cfg.RunCharts {
cpt := runchart.NewRunChart(c, palette)
starter.start(cpt, cpt.Consumer, c.ComponentConfig, c.Items, c.Triggers)
@ -81,6 +83,15 @@ func main() {
starter.start(cpt, cpt.Consumer, c.ComponentConfig, []config.Item{c.Item}, c.Triggers)
}
if license == nil {
lout.RunIntro()
storage.InitLicense()
} else if !license.Purchased /* && random */ {
// TODO lout.showNagWindow() with timeout and OK button
// TODO verify license
// TODO send stats
}
handler := event.NewHandler(lout, opt)
handler.HandleEvents()
}

53
storage/license.go Normal file
View File

@ -0,0 +1,53 @@
package storage
import (
"gopkg.in/yaml.v2"
"log"
)
type License struct {
Purchased bool
Valid bool
Key *string
Username *string
Company *string
}
const licenseFileName = "license.yml"
func GetLicense() *License {
if !fileExists(licenseFileName) {
return nil
} else {
file := readStorageFile(getPlatformStoragePath(licenseFileName))
license := new(License)
err := yaml.Unmarshal(file, license)
if err != nil {
log.Fatalf("Can't read license file: %v", err)
}
return license
}
}
func InitLicense() {
license := License{
Purchased: false,
Valid: false,
}
file, err := yaml.Marshal(license)
if err != nil {
log.Fatalf("Can't marshal config file: %v", err)
}
initStorage()
saveStorageFile(file, getPlatformStoragePath(licenseFileName))
}
func SaveLicense() {
// TODO
}

6
storage/stats.go Normal file
View File

@ -0,0 +1,6 @@
package storage
// TODO
// version
// launch count
// components count by type

56
storage/storage.go Normal file
View File

@ -0,0 +1,56 @@
package storage
import (
"io/ioutil"
"log"
"os"
"path/filepath"
"runtime"
)
const (
macOSDir = "/Library/Application Support/Sampler"
linuxDir = "/.config/Sampler"
windowsDir = "%LOCALAPPDATA%\\Sampler"
)
func fileExists(filename string) bool {
_, err := os.Stat(getPlatformStoragePath(filename))
return !os.IsNotExist(err)
}
func getPlatformStoragePath(filename string) string {
home, _ := os.UserHomeDir()
switch runtime.GOOS {
case "darwin":
return filepath.Join(home, macOSDir, filename)
case "windows":
return filepath.Join(home, windowsDir, filename)
default:
return filepath.Join(linuxDir, filename)
}
}
func initStorage() {
err := os.MkdirAll(getPlatformStoragePath(""), os.ModePerm)
if err != nil {
log.Fatalf("Failed to init storage: %v", err)
}
}
func readStorageFile(path string) []byte {
file, err := ioutil.ReadFile(path)
if err != nil {
log.Fatalf("Failed to the read storage file: %s", path)
}
return file
}
func saveStorageFile(file []byte, fileName string) {
err := ioutil.WriteFile(fileName, file, os.ModePerm)
if err != nil {
log.Fatalf("Failed to save the storage file: %v", err)
}
}