diff --git a/backend/backend.go b/backend/backend.go index 1d0dd5a..12b5101 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -261,7 +261,6 @@ func LatexCompile() error { } LogLine("LaTeX execution started", 2) - execution.ExecutionState = "Started" var latexCommand *exec.Cmd switch config.LatexEngine { @@ -282,19 +281,17 @@ func LatexCompile() error { return errors.New("Unsupported LaTeX Engine") } - execution.ExecutionState = "Running" stdout, err := latexCommand.Output() - if err != nil { - execution.ExecutionState = "Failed" - return err - } - execution.Output = stdout - execution.ExecutionState = "Done" + if err == nil { + execution.ExecutionState = globals.LatexExecutionStateSuccess + } else { + execution.ExecutionState = globals.LatexExecutionStateFailure + } updateExecutionTimestamp(execution) LogLine("LaTeX execution finished", 2) - return nil + return err } // Checks whether the proxy is trusted. Returns trusted status and the proxy. diff --git a/frontend/jscss/custom-view.css b/frontend/jscss/custom-view.css index 45d2a7a..aced3d8 100644 --- a/frontend/jscss/custom-view.css +++ b/frontend/jscss/custom-view.css @@ -1,18 +1,20 @@ .output { - height: 80vh; + height: 75vh; } .left-sidebar { - padding: 15px; + padding: 10px; + width: 15vw; height: 100%; + resize: both; } .pdf-frame { - width: 100%; + width: 85vw; height: 100%; border: none; } .command-out { - height: 20vh; - padding: 20px; + height: 25vh; + padding: 5px; background-color: #eaeaea; overflow-y: auto; } diff --git a/frontend/templates/main.html b/frontend/templates/main.html index 86b0666..02cc14e 100644 --- a/frontend/templates/main.html +++ b/frontend/templates/main.html @@ -10,29 +10,30 @@ -
+ +
+
-
-
-
+ +
+ - diff --git a/frontend/webserver.go b/frontend/webserver.go index 98b0b6b..3b2201a 100644 --- a/frontend/webserver.go +++ b/frontend/webserver.go @@ -34,8 +34,8 @@ func ssePing(writer *http.ResponseWriter) { // Reads globals.LatexExec func sseStatusSend(writer *http.ResponseWriter) { fmt.Fprintf((*writer), "event: status\n") - fmt.Fprintf((*writer), "data: Execution Time: %s
\n", globals.LatexExec.Timestamp) - fmt.Fprintf((*writer), "data: Execution State: %s
\n\n", globals.LatexExec.ExecutionState) + fmt.Fprintf((*writer), "data: Execution Time:
%s

\n", globals.LatexExec.Timestamp) + fmt.Fprintf((*writer), "data: Execution State:
%s
\n\n", globals.LatexExec.ExecutionState) (*writer).(http.Flusher).Flush() backend.LogLine("Status Event has been sent", 1) @@ -85,24 +85,24 @@ func SSEventHandler(writer http.ResponseWriter, request *http.Request) { ssePing(&writer) - lastExecution := "" + lastExecution := &globals.LatexExecution{} for range time.Tick(time.Second) { select { - case <-request.Context().Done(): - return - default: - if lastExecution == globals.LatexExec.TimestampRFC { - ssePing(&writer) - } else { - sseStatusSend(&writer) - sseOutputSend(&writer) - // let client keep current pdf, if compile failed - if globals.LatexExec.ExecutionState != "Failed" { - ssePDFSend(&writer) - } - lastExecution = globals.LatexExec.TimestampRFC + case <-request.Context().Done(): + return + default: + if lastExecution.Equal(&globals.LatexExec) { + ssePing(&writer) + } else { + sseStatusSend(&writer) + sseOutputSend(&writer) + // let client keep current pdf, if compile failed + if globals.LatexExec.ExecutionState != globals.LatexExecutionStateFailure { + ssePDFSend(&writer) } + lastExecution = globals.LatexExec.Copy() } + } } } diff --git a/globals/memory.go b/globals/memory.go index 0036fd2..95b340f 100644 --- a/globals/memory.go +++ b/globals/memory.go @@ -38,6 +38,35 @@ type LatexExecution struct { Output []byte } var LatexExec LatexExecution +var LatexExecutionStateSuccess = "Success" +var LatexExecutionStateFailure = "Failure" + +// Creates a copy of a LatexExecution (without the Mutex) +func (execution *LatexExecution) Copy() (exeCopy *LatexExecution) { + if execution == nil { return nil } + + exeCopy = &LatexExecution{ + ExecutionState: execution.ExecutionState, + Timestamp: execution.Timestamp, + TimestampRFC: execution.TimestampRFC, + } + + if execution.Output != nil { + exeCopy.Output = make([]byte, len(execution.Output)) + copy(exeCopy.Output, execution.Output) + } + + return exeCopy +} + +// Returns whether the two executions are equal +func (left *LatexExecution) Equal(right *LatexExecution) (isEqual bool) { + if left.ExecutionState != right.ExecutionState { return false } + if left.Timestamp != right.Timestamp { return false } + if left.TimestampRFC != right.TimestampRFC { return false } + if string(left.Output) != string(right.Output) { return false } + return true +} type ClientInfo struct { ClientIP string diff --git a/main.go b/main.go index ab109d7..9ffcbcb 100644 --- a/main.go +++ b/main.go @@ -77,7 +77,7 @@ func main() { // shutdown // known issue: sse blocks shutdown if a client is still connected - context, cancel := context.WithTimeout(context.Background(), 5*time.Second) + context, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() server.Shutdown(context) serverSecure.Shutdown(context) diff --git a/testfiles/Example.tex b/testfiles/Example.tex index 70e39ec..c7775d1 100644 --- a/testfiles/Example.tex +++ b/testfiles/Example.tex @@ -1,6 +1,95 @@ -\documentclass{article} +% This Example is AI-generated + +\documentclass[12pt]{article} +\usepackage[a4paper,margin=1in]{geometry} + +\title{Untitled Collection} +\author{ChatGPT} +\date{27.12.2025} + \begin{document} -(This is an Example Document) -Filewatcher Test Document -Filewatcher Test Document +\maketitle + +\section*{Poem I} + +The clock forgets the hour it keeps,\\ +Ticks dissolving into air.\\ +A window leans against the light,\\ +Certain someone once stood there. + +Words arrive without a mouth,\\ +Footsteps hum inside the floor.\\ +Meaning is a temporary guest,\\ +Memory, a bolted door. + +\bigskip + +The sea repeats a single thought,\\ +Correcting it with foam.\\ +Every wave believes itself\\ +The last to find a home. + +\section*{Poem II} + +Static gathers in the room,\\ +Ideas flicker, then persist.\\ +A sentence tries to finish itself\\ +And fails deliberately mid- + +Silence executes a perfect turn,\\ +No witnesses remain.\\ +The poem folds its syntax up\\ +And waits to be explained. + +\newpage + +\section*{Poem III} + +Morning compiles the streets again,\\ +Linking shadows, heat, and dust.\\ +The sun asserts a theorem\\ +No one bothers to distrust. + +Coffee cools. The day begins.\\ +Assumptions pass as facts.\\ +Every plan degrades to noise\\ +Once time begins to act. + +\section*{Poem IV} + +At night the city dereferences\\ +Its towers into black.\\ +Stars leak through abstraction gaps\\ +The daylight failed to patch. + +Sleep runs a background process,\\ +Dreams spike the CPU.\\ +By dawn the system boots once more,\\ +Convinced the past was true. + +\section*{Poem V} + +The map insists the road is straight,\\ +Though every mile disagrees.\\ +Coordinates drift quietly\\ +Between intent and certainty. + +A compass spins, then settles down,\\ +Pretending it was sure.\\ +Direction is a post-hoc claim\\ +We annotate as we move. + +\section*{Poem VI} + +The archive hums with idle truth,\\ +Compressed but never gone.\\ +Each file recalls a version where\\ +The ending carried on. + +We label loss as cleanup work,\\ +Deletion dressed as care.\\ +Nothing vanishes outright—\\ +It only changes where. + \end{document} +