Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 243a41fa7a | |||
| 7cce11dc66 | |||
| fc808a6c28 | |||
| c6b3f65b35 | |||
| 427a872811 | |||
| 1e5527f629 | |||
| 6d5404337e | |||
| 8c06248c32 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,4 @@
|
|||||||
### Above combination will ignore all files without extension ###
|
### Above combination will ignore all files without extension ###
|
||||||
src/resources/pixctrl.js
|
|
||||||
bin/
|
bin/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
pixctrl.js
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Package
|
# Package
|
||||||
|
|
||||||
version = "1.0.0"
|
version = "1.1.0"
|
||||||
author = "luxick"
|
author = "luxick"
|
||||||
description = "Play an image slide show from different sources"
|
description = "Play an image slide show from different sources"
|
||||||
license = "GPL-2.0"
|
license = "GPL-2.0"
|
||||||
@@ -10,10 +10,12 @@ bin = @["randopix", "pixctrl"]
|
|||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
requires "nim >= 1.0.0", "gintro", "argparse", "jester", "ajax"
|
requires "nim >= 1.0.0", "gintro", "argparse", "jester", "ajax"
|
||||||
|
# Not on nimble yet
|
||||||
|
requires "https://github.com/luxick/op.git >= 1.0.0"
|
||||||
|
|
||||||
proc genJS =
|
proc genJS =
|
||||||
echo "Generating JS Client"
|
echo "Generating JS Client"
|
||||||
exec("nim js -o:src/resources/pixctrl.js src/pixctrl.nim")
|
exec("nim js -o:src/resources/www/pixctrl.js src/pixctrl.nim")
|
||||||
|
|
||||||
task genJS, "Generate the Javascript client":
|
task genJS, "Generate the Javascript client":
|
||||||
genJS()
|
genJS()
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import os, sets, random, httpClient, json, strutils, strformat, options, deques, times
|
import os, sets, random, httpClient, json, strutils, strformat, options, deques, times
|
||||||
import gintro/[gdkpixbuf, gobject]
|
from lenientops import `*`
|
||||||
|
import op, gintro/[gdkpixbuf, gobject]
|
||||||
import common
|
import common
|
||||||
|
|
||||||
const
|
const
|
||||||
supportedExts = @[".png", ".jpg", ".jpeg"]
|
supportedExts = @[".png", ".jpg", ".jpeg"]
|
||||||
|
placeholderImg = slurp("resources/blank.png")
|
||||||
foxesUrl = "https://randomfox.ca/floof/"
|
foxesUrl = "https://randomfox.ca/floof/"
|
||||||
inspiroUrl = "http://inspirobot.me/api?generate=true"
|
inspiroUrl = "http://inspirobot.me/api?generate=true"
|
||||||
|
|
||||||
type
|
type
|
||||||
FileOpResult* = object of OpResult
|
|
||||||
file*: string
|
|
||||||
|
|
||||||
ImageProvider* = ref object of RootObj
|
ImageProvider* = ref object of RootObj
|
||||||
## Manages images that should be displayed
|
## Manages images that should be displayed
|
||||||
verbose: bool ## Additional logging for the image provider
|
verbose: bool ## Additional logging for the image provider
|
||||||
@@ -43,12 +42,6 @@ proc newImageProvider*(verbose: bool, mode: Mode): ImageProvider =
|
|||||||
proc newImageProvider*(verbose: bool, mode: Mode, path: string): ImageProvider =
|
proc newImageProvider*(verbose: bool, mode: Mode, path: string): ImageProvider =
|
||||||
newImageProvider(verbose, mode, some(path))
|
newImageProvider(verbose, mode, some(path))
|
||||||
|
|
||||||
proc newFileOpResultError(msg: string): FileOpResult =
|
|
||||||
FileOpResult(success: false, errorMsg: msg)
|
|
||||||
|
|
||||||
proc newFileOpResult(file: string): FileOpResult =
|
|
||||||
FileOpResult(success: true, file: file)
|
|
||||||
|
|
||||||
########################
|
########################
|
||||||
# Utilities
|
# Utilities
|
||||||
########################
|
########################
|
||||||
@@ -57,11 +50,31 @@ proc log(ip: ImageProvider, things: varargs[string, `$`]) =
|
|||||||
if ip.verbose:
|
if ip.verbose:
|
||||||
echo things.join()
|
echo things.join()
|
||||||
|
|
||||||
|
func calcImageSize(maxWidth, maxHeight, imgWidth, imgHeight: int): tuple[width: int, height: int] =
|
||||||
|
## Calculate the best fit for an image on the give screen size.
|
||||||
|
## This should keep the image aspect ratio
|
||||||
|
let
|
||||||
|
ratioMax = maxWidth / maxHeight
|
||||||
|
ratioImg = imgWidth / imgHeight
|
||||||
|
if (ratioMax > ratioImg):
|
||||||
|
result.width = (imgWidth * (maxHeight / imgHeight)).toInt
|
||||||
|
result.height = maxHeight
|
||||||
|
else:
|
||||||
|
result.width = maxWidth
|
||||||
|
result.height = (imgHeight * (maxWidth / imgWidth)).toInt
|
||||||
|
|
||||||
########################
|
########################
|
||||||
# Image Provider procs
|
# Image Provider procs
|
||||||
########################
|
########################
|
||||||
|
|
||||||
proc getFox(ip: ImageProvider): FileOpResult =
|
proc getPlaceHolder(ip: ImageProvider): OP[string] =
|
||||||
|
## Provide the placeholder image.
|
||||||
|
## This is used when no mode is active
|
||||||
|
let f = fmt"{tmpFile}.blank"
|
||||||
|
writeFile(f, placeholderImg)
|
||||||
|
ok f
|
||||||
|
|
||||||
|
proc getFox(ip: ImageProvider): OP[string] =
|
||||||
## Download image from the fox API
|
## Download image from the fox API
|
||||||
try:
|
try:
|
||||||
let urlData = client.getContent(foxesUrl)
|
let urlData = client.getContent(foxesUrl)
|
||||||
@@ -69,15 +82,15 @@ proc getFox(ip: ImageProvider): FileOpResult =
|
|||||||
let imageData = client.getContent(info["image"].getStr)
|
let imageData = client.getContent(info["image"].getStr)
|
||||||
let dlFile = fmt"{tmpFile}.download"
|
let dlFile = fmt"{tmpFile}.download"
|
||||||
writeFile(dlFile, imageData)
|
writeFile(dlFile, imageData)
|
||||||
return newFileOpResult(dlFile)
|
ok dlFile
|
||||||
except JsonParsingError:
|
except JsonParsingError:
|
||||||
ip.log fmt"Error while fetching from fox API: {getCurrentExceptionMsg()}"
|
ip.log fmt"Error while fetching from fox API: {getCurrentExceptionMsg()}"
|
||||||
return newFileOpResultError("Json parsing error")
|
fail[string] "Json parsing error"
|
||||||
except KeyError:
|
except KeyError:
|
||||||
ip.log fmt"No image in downloaded data: {getCurrentExceptionMsg()}"
|
ip.log fmt"No image in downloaded data: {getCurrentExceptionMsg()}"
|
||||||
return newFileOpResultError("No image from API")
|
fail[string] "No image from API"
|
||||||
|
|
||||||
proc getInspiro(ip: ImageProvider): FileOpResult =
|
proc getInspiro(ip: ImageProvider): OP[string] =
|
||||||
## Download and save image from the inspiro API
|
## Download and save image from the inspiro API
|
||||||
try:
|
try:
|
||||||
let imageUrl = client.getContent(inspiroUrl)
|
let imageUrl = client.getContent(inspiroUrl)
|
||||||
@@ -85,12 +98,12 @@ proc getInspiro(ip: ImageProvider): FileOpResult =
|
|||||||
let imageData = client.getContent(imageUrl)
|
let imageData = client.getContent(imageUrl)
|
||||||
let dlFile = fmt"{tmpFile}.download"
|
let dlFile = fmt"{tmpFile}.download"
|
||||||
writeFile(dlFile,imageData)
|
writeFile(dlFile,imageData)
|
||||||
return newFileOpResult(dlFile)
|
ok dlFile
|
||||||
except:
|
except:
|
||||||
ip.log fmt"Unexpected error while downloading: {getCurrentExceptionMsg()}"
|
ip.log fmt"Unexpected error while downloading: {getCurrentExceptionMsg()}"
|
||||||
return newFileOpResultError(getCurrentExceptionMsg())
|
fail[string] getCurrentExceptionMsg()
|
||||||
|
|
||||||
proc getLocalFile(ip: var ImageProvider): FileOpResult =
|
proc getLocalFile(ip: var ImageProvider): OP[string] =
|
||||||
## Provide an image from a local folder
|
## Provide an image from a local folder
|
||||||
|
|
||||||
# First, check if there are still images left to be loaded.
|
# First, check if there are still images left to be loaded.
|
||||||
@@ -108,61 +121,54 @@ proc getLocalFile(ip: var ImageProvider): FileOpResult =
|
|||||||
for file in tmp:
|
for file in tmp:
|
||||||
fileList.addLast(file)
|
fileList.addLast(file)
|
||||||
if fileList.len == 0:
|
if fileList.len == 0:
|
||||||
return newFileOpResultError("No files found")
|
return fail[string] "No files found"
|
||||||
|
|
||||||
let next = fileList.popFirst()
|
let next = fileList.popFirst()
|
||||||
# Remove the current file after
|
# Remove the current file after
|
||||||
result = newFileOpResult(next)
|
ok next
|
||||||
|
|
||||||
proc getFileName(ip: var ImageProvider): FileOpResult =
|
proc getFileName(ip: var ImageProvider): OP[string] =
|
||||||
## Get the temporary file name of the next file to display
|
## Get the temporary file name of the next file to display
|
||||||
case ip.mode
|
case ip.mode
|
||||||
|
of Mode.None:
|
||||||
|
return ip.getPlaceHolder()
|
||||||
of Mode.File:
|
of Mode.File:
|
||||||
return ip.getLocalFile()
|
return ip.getLocalFile()
|
||||||
of Mode.Foxes:
|
of Mode.Foxes:
|
||||||
return ip.getFox()
|
return ip.getFox()
|
||||||
of Mode.Inspiro:
|
of Mode.Inspiro:
|
||||||
return ip.getInspiro()
|
return ip.getInspiro()
|
||||||
else:
|
|
||||||
return newFileOpResultError("Not implemented")
|
|
||||||
|
|
||||||
########################
|
########################
|
||||||
# Exported procs
|
# Exported procs
|
||||||
########################
|
########################
|
||||||
|
|
||||||
proc next*(ip: var ImageProvider, width, height: int): FileOpResult =
|
proc next*(ip: var ImageProvider, maxWidth, maxHeight: int): OP[string] =
|
||||||
## Uses the image provider to get a new image ready to display.
|
## Uses the image provider to get a new image ready to display.
|
||||||
## `width` and `height` should be the size of the window.
|
## `width` and `height` should be the size of the window.
|
||||||
if ip.mode == Mode.None:
|
|
||||||
return newFileOpResultError("No mode active")
|
|
||||||
|
|
||||||
let op = ip.getFileName()
|
let r = ip.getFileName()
|
||||||
if not op.success: return op
|
if not r.isOk: return r
|
||||||
|
|
||||||
var rawPixbuf = newPixbufFromFile(op.file)
|
var rawPixbuf = newPixbufFromFile(r.val)
|
||||||
# resize the pixbuf to best fit on screen
|
# Resize the pixbuf to best fit on screen
|
||||||
var w, h: int
|
let size = calcImageSize(maxWidth, maxHeight, rawPixbuf.width, rawPixbuf.height)
|
||||||
if (width > height):
|
ip.log "Scale image to: ", size
|
||||||
h = height
|
|
||||||
w = ((rawPixbuf.width * h) / rawPixbuf.height).toInt
|
|
||||||
else:
|
|
||||||
w = width
|
|
||||||
h = ((rawPixbuf.height * w) / rawPixbuf.width).toInt
|
|
||||||
let then = now()
|
let then = now()
|
||||||
var pixbuf = rawPixbuf.scaleSimple(w, h, InterpType.nearest)
|
var pixbuf = rawPixbuf.scaleSimple(size.width, size.height, InterpType.nearest)
|
||||||
let now = now()
|
let now = now()
|
||||||
ip.log "Image scaled. Time: ", (now - then).inMilliseconds, "ms"
|
ip.log "Image scaled. Time: ", (now - then).inMilliseconds, "ms"
|
||||||
# The pixbuf is written to disk and loaded again once because
|
# The pixbuf is written to disk and loaded again once because
|
||||||
# directly setting the image from a pixbuf will leak memory
|
# directly setting the image from a pixbuf will leak memory
|
||||||
let saved = pixbuf.savev(tmpFile, "png", @[])
|
let saved = pixbuf.savev(tmpFile, "png", @[])
|
||||||
if not saved:
|
if not saved:
|
||||||
return newFileOpResultError("Error while saving temporary image")
|
return result.fail "Error while saving temporary image"
|
||||||
|
|
||||||
# GTK pixbuf leaks memory when not manually decreasing reference count
|
# GTK pixbuf leaks memory when not manually decreasing reference count
|
||||||
pixbuf.unref()
|
pixbuf.unref()
|
||||||
rawPixbuf.unref()
|
rawPixbuf.unref()
|
||||||
|
|
||||||
newFileOpResult(tmpFile)
|
ok tmpFile
|
||||||
|
|
||||||
createDir(tmpDir)
|
createDir(tmpDir)
|
||||||
randomize()
|
randomize()
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import os, options, strformat
|
import os, options, strformat
|
||||||
|
import op
|
||||||
import gintro/[glib, gobject, gtk, gio]
|
import gintro/[glib, gobject, gtk, gio]
|
||||||
import gintro/gdk except Window
|
import gintro/gdk except Window
|
||||||
import argparse except run
|
import argparse except run
|
||||||
@@ -24,7 +25,7 @@ type
|
|||||||
var
|
var
|
||||||
imageProvider: ImageProvider ## Gets images from the chosen source
|
imageProvider: ImageProvider ## Gets images from the chosen source
|
||||||
args: Args ## The parsed command line args
|
args: Args ## The parsed command line args
|
||||||
updateTimeout: int ## ID of the timeout that updates the images
|
updateTimeout, serverTimeout: int ## ID of the timeouts for image updating and server checking
|
||||||
# Widgets
|
# Widgets
|
||||||
window: ApplicationWindow
|
window: ApplicationWindow
|
||||||
label: Label
|
label: Label
|
||||||
@@ -98,18 +99,18 @@ proc updateImage(image: Image): bool =
|
|||||||
if imageProvider.mode == Mode.None:
|
if imageProvider.mode == Mode.None:
|
||||||
log "No display mode"
|
log "No display mode"
|
||||||
label.notify "No mode selected"
|
label.notify "No mode selected"
|
||||||
return true
|
|
||||||
|
|
||||||
var wWidth, wHeight: int
|
var wWidth, wHeight: int
|
||||||
window.getSize(wWidth, wHeight)
|
window.getSize(wWidth, wHeight)
|
||||||
|
|
||||||
let op = imageProvider.next(wWidth, wHeight)
|
let r = imageProvider.next(wWidth, wHeight)
|
||||||
result = op.success
|
result = r.isOk
|
||||||
if not op.success:
|
if not r.isOk:
|
||||||
label.notify op.errorMsg
|
label.notify r.error
|
||||||
return
|
return
|
||||||
|
|
||||||
image.setFromFile(op.file)
|
image.setFromFile(r.val)
|
||||||
|
if imageProvider.mode != Mode.None:
|
||||||
label.notify
|
label.notify
|
||||||
except:
|
except:
|
||||||
let
|
let
|
||||||
@@ -165,8 +166,6 @@ proc checkServerChannel(image: Image): bool =
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
log "Command ignored: ", msg.command
|
log "Command ignored: ", msg.command
|
||||||
|
|
||||||
sleep(100)
|
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
proc toggleFullscreen(action: SimpleAction; parameter: Variant; window: ApplicationWindow) =
|
proc toggleFullscreen(action: SimpleAction; parameter: Variant; window: ApplicationWindow) =
|
||||||
@@ -193,12 +192,18 @@ proc quit(action: SimpleAction; parameter: Variant; app: Application) =
|
|||||||
## Application quit event
|
## Application quit event
|
||||||
cleanUp(window, app)
|
cleanUp(window, app)
|
||||||
|
|
||||||
proc hidePointer(window: ApplicationWindow): void =
|
proc realizeWindow(window: ApplicationWindow, image: Image): void =
|
||||||
## Hides the mouse pointer for the application.
|
## Hides the mouse pointer for the application.
|
||||||
|
if args.fullscreen:
|
||||||
|
window.fullscreen
|
||||||
let cur = window.getDisplay().newCursorForDisplay(CursorType.blankCursor)
|
let cur = window.getDisplay().newCursorForDisplay(CursorType.blankCursor)
|
||||||
let win = window.getWindow()
|
let win = window.getWindow()
|
||||||
win.setCursor(cur)
|
win.setCursor(cur)
|
||||||
|
|
||||||
|
# Setting the inital image
|
||||||
|
# Fix 1 second timeout to make sure all other initialization has finished
|
||||||
|
updateTimeout = int(timeoutAdd(1000, timedUpdate, image))
|
||||||
|
|
||||||
proc appActivate(app: Application) =
|
proc appActivate(app: Application) =
|
||||||
# Parse arguments from the command line
|
# Parse arguments from the command line
|
||||||
let parsed = newArgs()
|
let parsed = newArgs()
|
||||||
@@ -243,9 +248,6 @@ proc appActivate(app: Application) =
|
|||||||
let image = newImage()
|
let image = newImage()
|
||||||
container.add(image)
|
container.add(image)
|
||||||
|
|
||||||
if args.fullscreen:
|
|
||||||
window.fullscreen
|
|
||||||
|
|
||||||
## Connect the GTK signals to the procs
|
## Connect the GTK signals to the procs
|
||||||
var action: SimpleAction
|
var action: SimpleAction
|
||||||
|
|
||||||
@@ -270,23 +272,19 @@ proc appActivate(app: Application) =
|
|||||||
window.actionMap.addAction(action)
|
window.actionMap.addAction(action)
|
||||||
|
|
||||||
window.connect("destroy", cleanUp, app)
|
window.connect("destroy", cleanUp, app)
|
||||||
window.connect("realize", hidePointer)
|
window.connect("realize", realizeWindow, image)
|
||||||
|
|
||||||
window.showAll
|
window.showAll
|
||||||
# Help is only shown on demand
|
# Help is only shown on demand
|
||||||
helpBox.hide
|
helpBox.hide
|
||||||
|
|
||||||
# Setting the inital image
|
|
||||||
# Fix 1 second timeout to make sure all other initialization has finished
|
|
||||||
updateTimeout = int(timeoutAdd(1000, timedUpdate, image))
|
|
||||||
|
|
||||||
## open communication channel from the control server
|
## open communication channel from the control server
|
||||||
chan.open()
|
chan.open()
|
||||||
|
|
||||||
## Start the server for handling incoming commands
|
## Start the server for handling incoming commands
|
||||||
let serverArgs = ServerArgs(verbose: args.verbose, port: args.port)
|
let serverArgs = ServerArgs(verbose: args.verbose, port: args.port)
|
||||||
createThread(serverWorker, runServer, serverArgs)
|
createThread(serverWorker, runServer, serverArgs)
|
||||||
discard idleAdd(checkServerChannel, image)
|
serverTimeout = int(timeoutAdd(100, checkServerChannel, image))
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
let app = newApplication("org.luxick.randopix")
|
let app = newApplication("org.luxick.randopix")
|
||||||
|
|||||||
BIN
src/resources/blank.png
Normal file
BIN
src/resources/blank.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
File diff suppressed because one or more lines are too long
BIN
src/resources/www/favicon.ico
Normal file
BIN
src/resources/www/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/resources/www/github.png
Normal file
BIN
src/resources/www/github.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -17,6 +17,8 @@
|
|||||||
<main>
|
<main>
|
||||||
<h1 class="center">Randopix Remote</h1>
|
<h1 class="center">Randopix Remote</h1>
|
||||||
|
|
||||||
|
<p>Control the image that is shown on this randopix.</p>
|
||||||
|
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<div class="control-info">Load the next image</div>
|
<div class="control-info">Load the next image</div>
|
||||||
<div class="control-body">
|
<div class="control-body">
|
||||||
@@ -40,6 +42,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="social">
|
||||||
|
<a class="btn btn-mini" href="https://github.com/luxick/randopix">
|
||||||
|
<img src="/github.png"></img>
|
||||||
|
Source code and usage
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
BIN
src/resources/www/microfab.gif
Normal file
BIN
src/resources/www/microfab.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
src/resources/www/not-comic-sans.woff
Normal file
BIN
src/resources/www/not-comic-sans.woff
Normal file
Binary file not shown.
157
src/resources/www/site.css
Normal file
157
src/resources/www/site.css
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
@charset "utf-8";
|
||||||
|
/* CSS Document */
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: notcomicsans;
|
||||||
|
src: url("/not-comic-sans.woff") format('truetype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.star-bg {
|
||||||
|
background-color: #1c1d1b;
|
||||||
|
background-image: url("/stars.gif")
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chrome, Safari, Opera */
|
||||||
|
@-webkit-keyframes rainbow {
|
||||||
|
0%{color: orange;}
|
||||||
|
10%{color: purple;}
|
||||||
|
20%{color: red;}
|
||||||
|
30%{color: CadetBlue;}
|
||||||
|
40%{color: yellow;}
|
||||||
|
50%{color: coral;}
|
||||||
|
60%{color: green;}
|
||||||
|
70%{color: cyan;}
|
||||||
|
80%{color: DeepPink;}
|
||||||
|
90%{color: DodgerBlue;}
|
||||||
|
100%{color: orange;}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Internet Explorer */
|
||||||
|
@-ms-keyframes rainbow {
|
||||||
|
0%{color: orange;}
|
||||||
|
10%{color: purple;}
|
||||||
|
20%{color: red;}
|
||||||
|
30%{color: CadetBlue;}
|
||||||
|
40%{color: yellow;}
|
||||||
|
50%{color: coral;}
|
||||||
|
60%{color: green;}
|
||||||
|
70%{color: cyan;}
|
||||||
|
80%{color: DeepPink;}
|
||||||
|
90%{color: DodgerBlue;}
|
||||||
|
100%{color: orange;}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Standard Syntax */
|
||||||
|
@keyframes rainbow {
|
||||||
|
0%{color: orange;}
|
||||||
|
10%{color: purple;}
|
||||||
|
20%{color: red;}
|
||||||
|
30%{color: CadetBlue;}
|
||||||
|
40%{color: yellow;}
|
||||||
|
50%{color: coral;}
|
||||||
|
60%{color: green;}
|
||||||
|
70%{color: cyan;}
|
||||||
|
80%{color: DeepPink;}
|
||||||
|
90%{color: DodgerBlue;}
|
||||||
|
100%{color: orange;}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: notcomicsans;
|
||||||
|
color:#FFFF00;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
max-width: 900px;
|
||||||
|
align-self: center;
|
||||||
|
border: 1px solid gray;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-image: url("/microfab.gif");
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control {
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-info {
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.control-body > * {
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
/* Chrome, Safari, Opera */
|
||||||
|
-webkit-animation: rainbow 5s infinite;
|
||||||
|
/* Internet Explorer */
|
||||||
|
-ms-animation: rainbow 5s infinite;
|
||||||
|
/* Standar Syntax */
|
||||||
|
animation: rainbow 5s infinite;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
background-color: transparent;
|
||||||
|
/* show a hand cursor on hover; some argue that we
|
||||||
|
should keep the default arrow cursor for buttons */
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
color: #ff0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
border: 2px solid darkgray;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0px 10px;
|
||||||
|
background-color: #1c1d1b;
|
||||||
|
}
|
||||||
|
.btn:hover {
|
||||||
|
color: #ff0;
|
||||||
|
fill: #ff0;
|
||||||
|
background-color: #0b0c0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-mini {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
border: 1px solid darkgray;
|
||||||
|
padding: 3px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.btn-mini > img {
|
||||||
|
width: 0.75rem;
|
||||||
|
height: 0.75rem;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
BIN
src/resources/www/stars.gif
Normal file
BIN
src/resources/www/stars.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -1,12 +1,22 @@
|
|||||||
import asyncdispatch, strutils, json, logging
|
import asyncdispatch, strutils, json, logging, os
|
||||||
import jester
|
import jester
|
||||||
import common
|
import common
|
||||||
|
|
||||||
|
when defined(release):
|
||||||
|
proc slurpResources(): Table[string, string] {.compileTime.} =
|
||||||
|
## Include everything from the www dir into the binary.
|
||||||
|
## This way the final executable will not need an static web folder.
|
||||||
|
for item in walkDir("src/resources/www/", true):
|
||||||
|
if item.kind == pcFile:
|
||||||
|
result[item.path] = slurp("resources/www/" & item.path)
|
||||||
|
|
||||||
|
const resources = slurpResources()
|
||||||
|
|
||||||
const
|
const
|
||||||
index = slurp("resources/index.html")
|
contentTypes = {
|
||||||
style = slurp("resources/site.css")
|
".js": "text/javascript",
|
||||||
pixctrlJs = slurp("resources/pixctrl.js")
|
".css": "text/css"
|
||||||
script = slurp("resources/script.js")
|
}.toTable
|
||||||
|
|
||||||
type
|
type
|
||||||
ServerArgs* = object of RootObj
|
ServerArgs* = object of RootObj
|
||||||
@@ -21,19 +31,37 @@ proc log(things: varargs[string, `$`]) =
|
|||||||
if verbose:
|
if verbose:
|
||||||
echo things.join()
|
echo things.join()
|
||||||
|
|
||||||
router randopixRouter:
|
when defined(release):
|
||||||
|
## When in release mode, use resources includes in the binary.
|
||||||
|
## When developing use the files directly.
|
||||||
|
router getRouter:
|
||||||
get "/":
|
get "/":
|
||||||
resp index
|
resp resources["index.html"]
|
||||||
|
|
||||||
get "/site.css":
|
get "/@resource":
|
||||||
resp(style, contentType="text/css")
|
try:
|
||||||
|
var cType: string
|
||||||
get "/pixctrl.js":
|
if contentTypes.hasKey(@"resource".splitFile.ext):
|
||||||
resp(pixctrlJs, contentType="text/javascript")
|
cType = contentTypes[@"resource".splitFile.ext]
|
||||||
|
resp resources[@"resource"], contentType=cType
|
||||||
get "/script.js":
|
except KeyError:
|
||||||
resp(script, contentType="text/javascript")
|
log "Resource not found: ", @"resource"
|
||||||
|
resp Http404
|
||||||
|
else:
|
||||||
|
router getRouter:
|
||||||
|
get "/":
|
||||||
|
resp readFile("src/resources/www/index.html")
|
||||||
|
get "/@resource":
|
||||||
|
try:
|
||||||
|
var cType: string
|
||||||
|
if contentTypes.hasKey(@"resource".splitFile.ext):
|
||||||
|
cType = contentTypes[@"resource".splitFile.ext]
|
||||||
|
resp readFile("src/resources/www/" & @"resource"), contentType=cType
|
||||||
|
except KeyError:
|
||||||
|
log "Resource not found: ", @"resource"
|
||||||
|
resp Http404
|
||||||
|
|
||||||
|
router postRouter:
|
||||||
post "/":
|
post "/":
|
||||||
try:
|
try:
|
||||||
log "Command from ", request.ip
|
log "Command from ", request.ip
|
||||||
@@ -46,6 +74,10 @@ router randopixRouter:
|
|||||||
except:
|
except:
|
||||||
log "Error: ", getCurrentExceptionMsg()
|
log "Error: ", getCurrentExceptionMsg()
|
||||||
|
|
||||||
|
router randopixRouter:
|
||||||
|
extend postRouter, ""
|
||||||
|
extend getRouter, ""
|
||||||
|
|
||||||
proc runServer*[ServerArgs](arg: ServerArgs) {.thread, nimcall.} =
|
proc runServer*[ServerArgs](arg: ServerArgs) {.thread, nimcall.} =
|
||||||
verbose = arg.verbose
|
verbose = arg.verbose
|
||||||
logging.setLogFilter(lvlInfo)
|
logging.setLogFilter(lvlInfo)
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.0.0
|
1.1.0
|
||||||
Reference in New Issue
Block a user