initial backend setup w/braille_xr page

This commit is contained in:
ethan merchant 2024-02-27 18:57:13 -05:00
commit 24109aa8d8
32 changed files with 685 additions and 0 deletions

6
.gitattributes vendored Normal file
View file

@ -0,0 +1,6 @@
*.svg filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.webp filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.webm filter=lfs diff=lfs merge=lfs -text

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
dofdev
*.json

1
data/data.go Normal file
View file

@ -0,0 +1 @@
package data

5
go.mod Normal file
View file

@ -0,0 +1,5 @@
module github.com/spatialfree/dofdev
go 1.20
require github.com/gorilla/mux v1.8.1 // indirect

2
go.sum Normal file
View file

@ -0,0 +1,2 @@
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=

22
readme.md Normal file
View file

@ -0,0 +1,22 @@
```shell
# build and run
go build && ./dofdev
#deploy
rsync -avz --progress ~/go/src/dofdev/ root@dofdev:/root/dofdev/
```
server starting at: http://localhost:3000
i actually want to do this in a different way
instead of a server that can be attacked
or taken down/compromised by me
we instead have a distributed application that receives updates
that way he has his copy and i have mine
and they are on local secure devices
it can just be console application for now

61
server.go Normal file
View file

@ -0,0 +1,61 @@
package main
import (
"fmt"
"log"
"net/http"
"github.com/spatialfree/dofdev/src/router"
"github.com/spatialfree/dofdev/tem"
)
func init() {
fmt.Println("init()")
tem.Load()
}
func main() {
r := router.New()
// Start the web server
log.Println("server starting on port 3000...")
addr := ":3210"
// addr := "192.168.0.21:3000"
if err := http.ListenAndServe(addr, r); err != nil {
log.Fatalf("error starting server: %v", err)
}
// fs := http.FileServer(http.Dir("./res"))
// http.Handle("/res/", http.StripPrefix("/res/", fs))
// http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// fmt.Println("request: ", r.URL.Path)
// http.ServeFile(w, r, "./index.html")
// })
// http.HandleFunc("/pass", func(w http.ResponseWriter, r *http.Request) {
// fmt.Println("request: ", r.URL.Path)
// // fmt.Println("request: ", r.Header)
// fmt.Println("request: ", r.FormValue("pass"))
// fmt.Println("request: ", hash(r.FormValue("pass")))
// if hash(r.FormValue("pass")) == 3556498 {
// http.ServeFile(w, r, "./biz.html")
// } else {
// http.Error(w, "Forbidden", http.StatusForbidden)
// }
// })
// fmt.Println("server starting @ http://localhost:3210 ...")
// if err := http.ListenAndServe(":3210", nil); err != nil {
// panic(err)
// }
}
// hash function
func hash(s string) uint32 {
h := uint32(0)
for i := 0; i < len(s); i++ {
h = 31*h + uint32(s[i])
}
return h
}

19
src/router/router.go Normal file
View file

@ -0,0 +1,19 @@
package router
import (
"net/http"
"github.com/gorilla/mux"
braillexr "github.com/spatialfree/dofdev/web/pages/braille_xr"
"github.com/spatialfree/dofdev/web/pages/mono"
)
func New() *mux.Router {
r := mux.NewRouter() // .StrictSlash(false)
r.PathPrefix(("/public/")).Handler(http.StripPrefix("/public/", http.FileServer(http.Dir("web/public/"))))
r.HandleFunc("/", mono.Handler)
r.HandleFunc("/braille_xr", braillexr.Handler)
return r
}

49
tem/html/biz copy.html Normal file
View file

@ -0,0 +1,49 @@
<h2>shared accounts:</h2>
<!-- there might be sections for personal accounts later ~ -->
<div id='accounts'></div>
<script>
accounts = []
el = document.getElementById('accounts')
for (i = 0; i < accounts.length; i++) {
a = accounts[i]
el.innerHTML += `<div class='account ${(a.dead ? 'dead' : '')}'>
<div>
<b>${a.name}</b>
<a href='${a.link}'>&RightArrow;</a>
</div>
<div>${a.user}</div>
<div class='pass'>
<div onclick='
this.innerHTML="&checkmark;&nbsp;";
navigator.clipboard.writeText("${a.pass}")
'>&boxbox;&nbsp;</div>
<div onclick='
this.innerHTML === "${a.pass}" ?
window.getSelection().selectAllChildren(this) :
this.innerHTML = "${a.pass}"
'>${'*'.repeat(a.pass.length)}</div>
</div>
</div>`
}
</script>
<style>
.account {
margin-bottom: 32px;
}
.dead {
opacity: 0.666;
}
.pass {
cursor: pointer;
display: flex;
font-family: 'Courier Prime', monospace;
}
a {
text-decoration: none;
}
</style>

99
tem/html/biz.html Normal file
View file

@ -0,0 +1,99 @@
<h2>shared accounts:</h2>
<!-- there might be sections for personal accounts later ~ -->
<div id='accounts'></div>
<script>
// const generateKey = async (password, salt) => {
// const encoder = new TextEncoder()
// const baseKey = await window.crypto.subtle.importKey('raw', encoder.encode(password), { name: 'PBKDF2' }, false, ['deriveKey'])
// return window.crypto.subtle.deriveKey({
// name: 'PBKDF2',
// salt: salt,
// iterations: 1000,
// hash: 'SHA-256'
// }, baseKey, { name: 'AES-CBC', length: 256 }, true, ['encrypt', 'decrypt'])
// }
// const encrypt = async (data, password) => {
// const salt = window.crypto.getRandomValues(new Uint8Array(16))
// const encoder = new TextEncoder()
// const key = await generateKey(password, salt)
// const encryptedContent = await window.crypto.subtle.encrypt({
// name: 'AES-CBC',
// iv: window.crypto.getRandomValues(new Uint8Array(16))
// }, key, encoder.encode(data))
// return {
// encryptedContent,
// salt
// }
// }
// const decrypt = async (data, password, salt, iv) => {
// const decoder = new TextDecoder()
// const key = await generateKey(password, salt)
// const decryptedContent = await window.crypto.subtle.decrypt({
// name: 'AES-CBC',
// iv
// }, key, data)
// return decoder.decode(decryptedContent)
// }
el = document.getElementById('accounts')
for (i = 0; i < accounts.length; i++) {
a = accounts[i]
el.innerHTML += `<div class='account ${(a.dead ? 'dead' : '')}'>
<div class='name'>
<a href='${a.link}'>${a.name}</a>
</div>
<div class='info'>
<div onclick='
window.getSelection().selectAllChildren(this)
'>${a.user}</div>
</div>
<div class='info'>
<div onclick='
this.innerHTML === "${a.pass}" ?
window.getSelection().selectAllChildren(this) :
this.innerHTML = "${a.pass}"
'>${'*'.repeat(a.pass.length)}</div>
</div>
</div>`
}
</script>
<!--
<div onclick='
this.innerHTML="&checkmark;&nbsp;";
navigator.clipboard.writeText("${a.pass}")
'>&boxbox;&nbsp;</div>
-->
<style>
.account {
margin-bottom: 32px;
padding-left: 16px;
display: flex;
flex-direction: column;
gap: 8px;
border-left: 2px solid #0000003a;
> .name {
font-size: 1.5em;
}
}
.info {
cursor: pointer;
/* display: flex;
gap: 8px;
font-family: 'Courier Prime', monospace; */
}
a {
text-decoration: none;
}
</style>

34
tem/html/braille_xr.html Normal file
View file

@ -0,0 +1,34 @@
{{ template "head.html" .}}
<main>
<h1>braille_xr</h1>
<h2>problem</h2>
<ul>
<li>How can we help make XR (VR/AR/MR) accessible to people with visual impairments?</li>
<li>Current refreshable braille devices have ~40x6x2 actuators with carefully tuned long levers making them very expensive~$3,500</li>
<li>Not a very portable device</li>
<li>1 interaction method</li>
<li>Other interactions of input for people with low vision require voice input which is not always comfortable.</li>
</ul>
<img src='/public/img/brailliant_bi_40x.webp' width='60%'>
<h2>solution</h2>
<ul>
<li>Reduce the number of actuators</li>
<li>use spatial hand tracking for accessible and extensible interactions</li>
<li>create a comfortable and portable way to input braille</li>
</ul>
<h2>progress</h2>
<ul>
<li>easy to manufacture braille display</li>
<li>small enough to leverage openxr hand tracking</li>
<li>software demo braille keyboard w/custom layouts</li>
</ul>
<img src='/public/img/hack.webp' width='60%'>
<div style='height: 100px;'></div>
</main>

8
tem/html/head.html Normal file
View file

@ -0,0 +1,8 @@
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<script src='https://unpkg.com/htmx.org@1.9.5'></script>
<link rel="stylesheet" href="/public/style.css?v19">
</head>
<body>

99
tem/html/mono.html Normal file
View file

@ -0,0 +1,99 @@
{{ template "head.html" .}}
<main>
<p style='
width: 100%;
padding-right: 20px;
font-size: 18px;
line-height: 2.0;
text-align: right;
color: white;
'>
An effort to open higher degrees of<br>
spatial interaction free<br>
to the world.
</p>
<img src='/public/img/dofdev_logo.svg?v22' width='100%'>
<p>degrees of freedom development</p>
<div class='shuffle' style='
display: flex;
flex-flow: column;
align-items: center;
'>
<div style='color: white; font-size: 20px;'>Ethan Merchant
<a href='https://ethanmerchant.com' style='color: var(--light);'>@spatialfree</a>
</div>
<div style='color: white; font-size: 20px;'>Niko Ryan
<a href='https://twitter.com/opendegree' style='color: var(--light);'>@opendegree</a>
</div>
</div>
<div class='t_symbol' style='
font-size: 32px;
'>&times;</div>
<div id='x'>
<a href='https://stereokit.net' target='_blank'>
<div style='
background-image: url("https://stereokit.net/img/StereoKitLogoLight.svg");
'></div>
</a>
<a href='https://www.projectnorthstar.org/' target='_blank'>
<div style='
background-image: url("/public/img/icons/northstar.svg");
'></div>
</a>
<a href='https://stardustxr.org' target='_blank'>
<div style='
background-image: url("https://stardustxr.org/img/icon.gif");
'></div>
</a>
</div>
<div class='t_symbol' style='
margin-top: 4rem;
'>&conint;</div>
<!-- <div style='height: 32px;'></div> -->
<!-- <div id='fdeg'>&plus;</div> -->
<h1>projects</h1>
<p class='subtitle'>xr based interfaces</p>
<a href='/braille_xr'>braille_xr</a>
<div style='height: 32px;'></div>
<img src='/public/img/logo.svg' width='64px'>
<div style='height: 24px;'></div>
<input type='password' autofocus
name='pass' placeholder='pass'
hx-post='/pass'
hx-vals='{"pass": this.value}'
hx-swap='outerHTML'
>
<!-- <button hx-post='/clicked' hx-swap='outerHTML'>
Click Me
</button> -->
<footer>&copy; 2018-2024 dofdev</footer>
<style>
#x {
display: flex;
flex-flow: wrap;
justify-content: center;
gap: 32px;
> a > div {
min-width: 64px;
min-height: 64px;
background-size: contain;
}
}
</style>

67
tem/tem.go Normal file
View file

@ -0,0 +1,67 @@
package tem
import (
"fmt"
"html/template"
"net/http"
)
var (
Templates *template.Template
)
func Load() {
Templates = template.Must(template.ParseGlob("tem/html/*.html"))
fmt.Println("templates loaded")
}
func Render(
w http.ResponseWriter,
r *http.Request,
template string,
page string,
title string,
desc string,
page_data interface{},
) {
w.WriteHeader(http.StatusOK)
data := struct {
Host string
Page string
Title string
Desc string
Local bool
Root bool
Data interface{}
}{
Host: r.Host,
Page: page,
Title: title,
Desc: desc,
Local: true, // (os.Getenv("LOCAL") == "true"), // toggle here for flag testing
Root: true, // (os.Getenv("HOST_IDEABRELLA") == strings.ReplaceAll(r.Host, ":3000", "")),
Data: page_data,
}
err := Templates.ExecuteTemplate(w, template, data)
if err != nil {
fmt.Println(err)
}
}
// for small refreshable templates within pages
// so no need for page things like title, desc, etc.
func RenderSub(
w http.ResponseWriter,
r *http.Request,
template string,
page_data interface{},
) {
w.WriteHeader(http.StatusOK)
err := Templates.ExecuteTemplate(w, template, page_data)
if err != nil {
fmt.Println(err)
}
}

View file

@ -0,0 +1,21 @@
package braillexr
import (
"net/http"
"github.com/spatialfree/dofdev/tem"
)
// func Handler(ctx *gin.Context) {
func Handler(w http.ResponseWriter, r *http.Request) {
data := struct {
}{}
tem.Render(
w, r,
"braille_xr.html",
"braille_xr",
"braille_xr",
"refreshable braille display for rendering and input with handtracking",
data,
)
}

50
web/pages/mono/mono.go Normal file
View file

@ -0,0 +1,50 @@
package mono
import (
"net/http"
"github.com/spatialfree/dofdev/tem"
)
// func Handler(ctx *gin.Context) {
func Handler(w http.ResponseWriter, r *http.Request) {
data := struct {
Projects []Project
}{
Projects,
}
tem.Render(
w, r,
"mono.html",
"",
"dofdev",
"an effort to open higher degrees of spatial interaction free to the world.",
data,
)
}
type Project struct {
ID int
Title string
Description string
Art string
}
// This would ideally come from a database
var Projects = []Project{
{ID: 1,
Title: "Prompt-Train",
Description: "PromptTrain, the PromptWriting Excellence Engine, is here to help you enhance your writing skills and unleash your creativity. Receive valuable feedback on your prompts, compete in creative challenges, connect with a supportive community, and embark on your journey towards writing excellence.",
Art: "/res/projects/prompt-train.png",
},
{ID: 2,
Title: "Echo AI",
Description: "With EchoAI, creating AI personas is a breeze. Define your character's name, appearance, traits, speech patterns, emotional range, and background story. Submit website and training reference documents, and specify preferred topics and custom responses. EchoAI offers a user-friendly, step-by-step process to bring your Echo AI persona to life.",
Art: "/res/projects/echo-ai.png",
},
{ID: 3,
Title: "PromptScript",
Description: "PromptScript is a web based ai prompting language that allows you to orchestrate complex systematic patterns with large language models.",
Art: "/res/projects/promptscript.png",
},
}

BIN
web/public/img/brailliant_bi_40x.webp (Stored with Git LFS) Normal file

Binary file not shown.

BIN
web/public/img/dofdev_logo.svg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
web/public/img/hack.jpg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
web/public/img/hack.webp (Stored with Git LFS) Normal file

Binary file not shown.

BIN
web/public/img/icons/art.svg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
web/public/img/icons/com.svg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
web/public/img/icons/degree.svg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
web/public/img/icons/kofi.svg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
web/public/img/icons/merch.svg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
web/public/img/icons/net.svg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
web/public/img/icons/northstar.svg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
web/public/img/icons/org.svg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
web/public/img/icons/patreon.svg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
web/public/img/icons/scroll.svg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
web/public/img/logo.svg (Stored with Git LFS) Normal file

Binary file not shown.

94
web/public/style.css Normal file
View file

@ -0,0 +1,94 @@
@import url('https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Courier+Prime&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Nanum+Myeongjo&display=swap');
html {
height: 100dvh;
margin: 0 auto;
max-width: 600px;
}
* {
display: block;
box-sizing: border-box;
flex-shrink: 0;
text-wrap: balance;
font-family: 'Atkinson Hyperlegible', sans-serif;
text-rendering: optimizeLegibility;
}
title, style, script {
display: none;
}
body {
height: 100dvh;
margin: 0 auto; padding: 0;
line-height: 1.5;
background-color: #808080;
color: #000;
}
main {
height: 100dvh;
margin: 0 auto;
/* padding-top: 64px; */
display: flex;
flex-flow: column;
align-items: center;
}
h1, h2 {
letter-spacing: 2px;
}
h1 {
color: #fff;
}
a, b, i, code, span {
display: inline;
font-family: inherit;
font-size: inherit;
color: inherit;
}
a {
cursor: pointer;
text-decoration: underline;
}
a:focus {
filter: brightness(1.333);
}
b { font-weight: bold; }
i { font-style: italic; }
code {
font-family: 'DM Mono', monospace;
font-weight: 500;
color: #0000FF;
}
img, video, canvas {
overflow: hidden;
}
img {
all: revert-layer;
display: block;
margin: 0 auto;
border-radius: 1px;
}
.t_symbol {
margin: 2rem auto;
font-family: "Nanum Myeongjo", serif;
text-align: center;
font-size: 64px;
/* font-weight: bold; */
color: #666;
}
footer {
margin-top: 128px;
height: 64px;
line-height: 64px;
letter-spacing: 0.5px;
font-size: 0.75em;
}
input {
outline: none;
border: none;
padding: 8px 16px;
border-radius: 32px;
letter-spacing: 1px;
font-size: 16px;
}