mirror of
https://github.com/maaslalani/slides.git
synced 2026-01-09 14:28:05 -05:00
194 lines
4.0 KiB
Go
194 lines
4.0 KiB
Go
package model
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/charmbracelet/bubbles/viewport"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/glamour"
|
|
"github.com/maaslalani/slides/internal/code"
|
|
"github.com/maaslalani/slides/internal/meta"
|
|
"github.com/maaslalani/slides/internal/process"
|
|
"github.com/maaslalani/slides/styles"
|
|
)
|
|
|
|
const (
|
|
delimiter = "\n---\n"
|
|
)
|
|
|
|
type Model struct {
|
|
Slides []string
|
|
Page int
|
|
Author string
|
|
Date string
|
|
Theme glamour.TermRendererOption
|
|
FileName string
|
|
viewport viewport.Model
|
|
// VirtualText is used for additional information that is not part of the
|
|
// original slides, it will be displayed on a slide and reset on page change
|
|
VirtualText string
|
|
}
|
|
|
|
type fileWatchMsg struct{}
|
|
|
|
var fileInfo os.FileInfo
|
|
|
|
func (m Model) Init() tea.Cmd {
|
|
if m.FileName == "" {
|
|
return nil
|
|
}
|
|
fileInfo, _ = os.Stat(m.FileName)
|
|
return fileWatchCmd()
|
|
}
|
|
|
|
func fileWatchCmd() tea.Cmd {
|
|
return tea.Every(time.Second, func(t time.Time) tea.Msg {
|
|
return fileWatchMsg{}
|
|
})
|
|
}
|
|
|
|
func (m *Model) Load() error {
|
|
var content string
|
|
var err error
|
|
|
|
if m.FileName != "" {
|
|
content, err = readFile(m.FileName)
|
|
} else {
|
|
content, err = readStdin()
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
content = process.Pre(content)
|
|
|
|
slides := strings.Split(content, delimiter)
|
|
|
|
metaData, exists := meta.New().ParseHeader(slides[0])
|
|
// If the user specifies a custom configuration options
|
|
// skip the first "slide" since this is all configuration
|
|
if exists {
|
|
slides = slides[1:]
|
|
}
|
|
|
|
m.Slides = slides
|
|
if m.Theme == nil {
|
|
m.Theme = styles.SelectTheme(metaData.Theme)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
switch msg := msg.(type) {
|
|
case tea.WindowSizeMsg:
|
|
m.viewport.Width = msg.Width
|
|
m.viewport.Height = msg.Height
|
|
return m, nil
|
|
|
|
case tea.KeyMsg:
|
|
switch msg.String() {
|
|
case "ctrl+c", "q":
|
|
return m, tea.Quit
|
|
case " ", "down", "j", "right", "l", "enter", "n":
|
|
if m.Page < len(m.Slides)-1 {
|
|
m.Page++
|
|
}
|
|
m.VirtualText = ""
|
|
case "up", "k", "left", "h", "p":
|
|
if m.Page > 0 {
|
|
m.Page--
|
|
}
|
|
m.VirtualText = ""
|
|
case "ctrl+e":
|
|
// Run code block
|
|
block, err := code.Parse(m.Slides[m.Page])
|
|
if err != nil {
|
|
// We couldn't parse the code block on the screen
|
|
m.VirtualText = "\n" + err.Error()
|
|
return m, nil
|
|
}
|
|
res := code.Execute(block)
|
|
m.VirtualText = res.Out
|
|
}
|
|
|
|
case fileWatchMsg:
|
|
newFileInfo, err := os.Stat(m.FileName)
|
|
if err == nil && newFileInfo.ModTime() != fileInfo.ModTime() {
|
|
fileInfo = newFileInfo
|
|
_ = m.Load()
|
|
if m.Page >= len(m.Slides) {
|
|
m.Page = len(m.Slides) - 1
|
|
}
|
|
}
|
|
return m, fileWatchCmd()
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
func (m Model) View() string {
|
|
r, _ := glamour.NewTermRenderer(m.Theme, glamour.WithWordWrap(0))
|
|
slide := m.Slides[m.Page]
|
|
slide, err := r.Render(slide)
|
|
slide += m.VirtualText
|
|
if err != nil {
|
|
slide = fmt.Sprintf("Error: Could not render markdown! (%v)", err)
|
|
}
|
|
slide = styles.Slide.Render(slide)
|
|
|
|
left := styles.Author.Render(m.Author) + styles.Date.Render(m.Date)
|
|
right := styles.Page.Render(fmt.Sprintf("Slide %d / %d", m.Page+1, len(m.Slides)))
|
|
status := styles.Status.Render(styles.JoinHorizontal(left, right, m.viewport.Width))
|
|
return styles.JoinVertical(slide, status, m.viewport.Height)
|
|
}
|
|
|
|
func readFile(path string) (string, error) {
|
|
s, err := os.Stat(path)
|
|
if err != nil {
|
|
return "", errors.New("could not read file")
|
|
}
|
|
if s.IsDir() {
|
|
return "", errors.New("can not read directory")
|
|
}
|
|
b, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(b), err
|
|
}
|
|
|
|
func readStdin() (string, error) {
|
|
stat, err := os.Stdin.Stat()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if stat.Mode()&os.ModeNamedPipe == 0 && stat.Size() == 0 {
|
|
return "", errors.New("no slides provided")
|
|
}
|
|
|
|
reader := bufio.NewReader(os.Stdin)
|
|
var b strings.Builder
|
|
|
|
for {
|
|
r, _, err := reader.ReadRune()
|
|
if err != nil && err == io.EOF {
|
|
break
|
|
}
|
|
_, err = b.WriteRune(r)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
return b.String(), nil
|
|
}
|