Update
This commit is contained in:
parent
0224abf215
commit
1b1598e668
12
Gemfile
12
Gemfile
@ -1,6 +1,8 @@
|
||||
source "https://rubygems.org"
|
||||
# frozen_string_literal: true
|
||||
|
||||
gem "pry"
|
||||
gem "web_gui", :path => "."
|
||||
gem "dry-struct"
|
||||
gem "dry-events"
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'dry-events'
|
||||
gem 'dry-struct'
|
||||
gem 'pry'
|
||||
gem 'web_gui', path: '.'
|
||||
|
6
Rakefile
6
Rakefile
@ -1,5 +1,7 @@
|
||||
require "bundler/gem_tasks"
|
||||
task :default => :build
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'bundler/gem_tasks'
|
||||
task default: :build
|
||||
task :geminabox do
|
||||
`gem inabox -o pkg/web_gui-*.gem`
|
||||
`rm pkg/web_gui-*.gem`
|
||||
|
@ -1,11 +1,12 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/setup"
|
||||
require "web_gui"
|
||||
require 'bundler/setup'
|
||||
require 'web_gui'
|
||||
|
||||
# You can add fixtures and/or initialization code here to make experimenting
|
||||
# with your gem easier. You can also use a different console, if you like.
|
||||
|
||||
# (If you use this, don't forget to add pry to your Gemfile!)
|
||||
require "pry"
|
||||
require 'pry'
|
||||
Pry.start
|
||||
|
40
calc.rb
40
calc.rb
@ -1,28 +1,30 @@
|
||||
require "bundler/setup"
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'bundler/setup'
|
||||
Bundler.setup
|
||||
require "web_gui"
|
||||
$op=nil
|
||||
$num1=nil
|
||||
$num2=nil
|
||||
app=WebGui::App.new("Calculator") {
|
||||
opthash={:add=>"Add",:sub=>"Subtract",:mult=>"Multiply",:div=>"Divide"}
|
||||
add_element(WebGui::TextField.new {|val| $num1=val.to_f})
|
||||
add_element(WebGui::Menu.new(opthash) {|val| $op=val})
|
||||
add_element(WebGui::TextField.new {|val| $num2=val.to_f})
|
||||
res=nil
|
||||
add_element(WebGui::ActionButton.new("Calculate") {
|
||||
require 'web_gui'
|
||||
$op = nil
|
||||
$num1 = nil
|
||||
$num2 = nil
|
||||
app = WebGui::App.new('Calculator') do
|
||||
opthash = { add: 'Add', sub: 'Subtract', mult: 'Multiply', div: 'Divide' }
|
||||
add_element(WebGui::TextField.new { |val| $num1 = val.to_f })
|
||||
add_element(WebGui::Menu.new(opthash) { |val| $op = val })
|
||||
add_element(WebGui::TextField.new { |val| $num2 = val.to_f })
|
||||
res = nil
|
||||
add_element(WebGui::ActionButton.new('Calculate') do
|
||||
case $op
|
||||
when :add
|
||||
result=$num1+$num2
|
||||
result = $num1 + $num2
|
||||
when :sub
|
||||
result=$num1-$num2
|
||||
result = $num1 - $num2
|
||||
when :mult
|
||||
result=$num1*$num2
|
||||
result = $num1 * $num2
|
||||
when :div
|
||||
result=$num1.to_f/$num2
|
||||
result = $num1.to_f / $num2
|
||||
end
|
||||
res.settext("Result:#{result}")
|
||||
})
|
||||
res=add_element(WebGui::Text.new("Result:"))
|
||||
}
|
||||
end)
|
||||
res = add_element(WebGui::Text.new('Result:'))
|
||||
end
|
||||
app.run
|
||||
|
115
lib/web_gui.rb
115
lib/web_gui.rb
@ -1,69 +1,71 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Object
|
||||
def descendants
|
||||
ObjectSpace.each_object(::Class).select {|klass| klass < self }
|
||||
ObjectSpace.each_object(::Class).select { |klass| klass < self }
|
||||
end
|
||||
end
|
||||
|
||||
module WebGui; end
|
||||
require "dry-struct"
|
||||
require 'dry-struct'
|
||||
module Types
|
||||
include Dry::Types.module
|
||||
end
|
||||
require "dry-events"
|
||||
require_relative "web_gui/element"
|
||||
require_relative "web_gui/event_manager"
|
||||
require_relative "web_gui/app"
|
||||
require_relative "web_gui/window"
|
||||
require_relative "web_gui/serv"
|
||||
require_relative "web_gui/wsserv"
|
||||
require 'dry-events'
|
||||
require_relative 'web_gui/element'
|
||||
require_relative 'web_gui/event_manager'
|
||||
require_relative 'web_gui/app'
|
||||
require_relative 'web_gui/window'
|
||||
require_relative 'web_gui/serv'
|
||||
require_relative 'web_gui/wsserv'
|
||||
module WebGui
|
||||
class Text < Element
|
||||
attribute :text, Types::Coercible::String
|
||||
|
||||
def render()
|
||||
return "<p id=#{id}>#{text}</p>"
|
||||
def render
|
||||
"<p id=#{id}>#{text}</p>"
|
||||
end
|
||||
|
||||
def settext(text)
|
||||
$app.update("paragraph",id,text)
|
||||
$app.update('paragraph', id, text)
|
||||
end
|
||||
end
|
||||
|
||||
class Link < Element
|
||||
attribute :page, Types::Symbol
|
||||
attribute :text, Types::Coercible::String.optional
|
||||
def render()
|
||||
def render
|
||||
if text
|
||||
return "<a href=\"#{page}\">#{text}</a>"
|
||||
"<a href=\"#{page}\">#{text}</a>"
|
||||
else
|
||||
return "<a href=\"#{page}\">#{page.to_s}</a>"
|
||||
"<a href=\"#{page}\">#{page}</a>"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class Button < Link
|
||||
@css="a.button{-webkit-appearance: button;-moz-appearance: button;appearance: button;text-decoration: none;color: initial;padding: 0px 10px;}"
|
||||
def render()
|
||||
return "<a href=\"#{page}\" class=\"button\">#{text}</a>"
|
||||
@css = 'a.button{-webkit-appearance: button;-moz-appearance: button;appearance: button;text-decoration: none;color: initial;padding: 0px 10px;}'
|
||||
def render
|
||||
"<a href=\"#{page}\" class=\"button\">#{text}</a>"
|
||||
end
|
||||
|
||||
def self.css()
|
||||
return @css
|
||||
class << self
|
||||
attr_reader :css
|
||||
end
|
||||
end
|
||||
|
||||
class Image < Element
|
||||
attribute :name, Types::Coercible::String
|
||||
def render()
|
||||
return "<img src=\"#{name}.jpg\"></img>"
|
||||
def render
|
||||
"<img src=\"#{name}.jpg\"></img>"
|
||||
end
|
||||
end
|
||||
|
||||
class Video < Element
|
||||
attribute :name, Types::Coercible::String
|
||||
|
||||
def render()
|
||||
return "<video controls width=320 height=300><source src=\"#{name}.webm\" type=\"video/webm\"><source src=\"#{name}.mp4\" type=\"video/mp4\"></video>"
|
||||
def render
|
||||
"<video controls width=320 height=300><source src=\"#{name}.webm\" type=\"video/webm\"><source src=\"#{name}.mp4\" type=\"video/mp4\"></video>"
|
||||
end
|
||||
end
|
||||
|
||||
@ -71,53 +73,51 @@ module WebGui
|
||||
attribute :text, Types::Coercible::String
|
||||
def initialize(opthash, &block)
|
||||
super(opthash)
|
||||
@block=block
|
||||
@block = block
|
||||
end
|
||||
|
||||
def render()
|
||||
return "<button id=#{id}>#{text}</button>"
|
||||
def render
|
||||
"<button id=#{id}>#{text}</button>"
|
||||
end
|
||||
|
||||
def on_button_pushed(event)
|
||||
@block.call if event[:id]==id
|
||||
@block.call if event[:id] == id
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class Menu < Element
|
||||
attribute :opts, Types::Hash
|
||||
def initialize(opthash, &block)
|
||||
super(opthash)
|
||||
@block=block
|
||||
@block = block
|
||||
end
|
||||
|
||||
def render()
|
||||
html="<select id=#{id}>"
|
||||
opts.each do |val,text|
|
||||
html+="<option value=\"#{val.to_s}\">#{text}</option>"
|
||||
def render
|
||||
html = "<select id=#{id}>"
|
||||
opts.each do |val, text|
|
||||
html += "<option value=\"#{val}\">#{text}</option>"
|
||||
end
|
||||
html+="</select>"
|
||||
return html
|
||||
html += '</select>'
|
||||
html
|
||||
end
|
||||
|
||||
def on_menu_updated(event)
|
||||
@block.call(event[:val]) if event[:id]==id
|
||||
@block.call(event[:val]) if event[:id] == id
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class TextField < Element
|
||||
def initialize(opthash, &block)
|
||||
super(opthash)
|
||||
@block=block
|
||||
@block = block
|
||||
end
|
||||
|
||||
def render()
|
||||
html="<input id=#{id}>"
|
||||
def render
|
||||
html = "<input id=#{id}>"
|
||||
end
|
||||
|
||||
def on_textfield_updated(event)
|
||||
@block.call(event[:val]) if event[:id]==id
|
||||
@block.call(event[:val]) if event[:id] == id
|
||||
end
|
||||
end
|
||||
|
||||
@ -125,40 +125,39 @@ module WebGui
|
||||
attribute :buttons, Types::Coercible::Hash
|
||||
def initialize(opthash, &block)
|
||||
super(opthash)
|
||||
@block=block
|
||||
@block = block
|
||||
end
|
||||
|
||||
def render()
|
||||
html=""
|
||||
buttons.each do |value,text|
|
||||
html+="<input type=\"radio\" id=#{id} name=\"#{id}\" value=\"#{value.to_s}\">#{text}<br>"
|
||||
def render
|
||||
html = ''
|
||||
buttons.each do |value, text|
|
||||
html += "<input type=\"radio\" id=#{id} name=\"#{id}\" value=\"#{value}\">#{text}<br>"
|
||||
end
|
||||
return html
|
||||
html
|
||||
end
|
||||
|
||||
def on_radiobutton_updated(event)
|
||||
@block.call(event[:val]) if event[:id]==id
|
||||
@block.call(event[:val]) if event[:id] == id
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class CheckBox < Element
|
||||
attribute :boxes, Types::Coercible::Hash
|
||||
def initialize(opthash, &block)
|
||||
super(opthash)
|
||||
@block=block
|
||||
@block = block
|
||||
end
|
||||
|
||||
def render()
|
||||
html=""
|
||||
boxes.each do |value,text|
|
||||
html+="<input type=\"checkbox\" id=#{id} name=\"#{id}\" value=\"#{value.to_s}\">#{text}<br>"
|
||||
def render
|
||||
html = ''
|
||||
boxes.each do |value, text|
|
||||
html += "<input type=\"checkbox\" id=#{id} name=\"#{id}\" value=\"#{value}\">#{text}<br>"
|
||||
end
|
||||
return html
|
||||
html
|
||||
end
|
||||
|
||||
def on_checkbox_updated(event)
|
||||
@block.call(event[:val]) if event[:id]==id
|
||||
@block.call(event[:val]) if event[:id] == id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,110 +1,110 @@
|
||||
$forcenochrome=false
|
||||
$stdout.sync=true
|
||||
$eventmanager=EventManager.new
|
||||
class WebGui::App
|
||||
attr_reader :windows
|
||||
def initialize(title,&block)
|
||||
$app=self
|
||||
if ARGV.length>0
|
||||
if ARGV[0]=="--forcenochrome"
|
||||
$forcenochrome=true
|
||||
# frozen_string_literal: true
|
||||
|
||||
$forcenochrome = false
|
||||
$stdout.sync = true
|
||||
$eventmanager = EventManager.new
|
||||
module WebGui
|
||||
class App
|
||||
attr_reader :windows
|
||||
|
||||
def initialize(title, &block)
|
||||
$app = self
|
||||
$forcenochrome = true if ARGV.length.positive? && (ARGV[0] == '--forcenochrome')
|
||||
@windows = {}
|
||||
@windows[:main] = WebGui::Window.new(name: :main, title: title)
|
||||
@windows[:main].instance_eval(&block)
|
||||
end
|
||||
|
||||
def render_document(wname)
|
||||
window = windows[wname]
|
||||
html = window.render
|
||||
css = window.render_css
|
||||
<<~ENDDOC
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||
<script src=\"main.js\"></script>
|
||||
<style>
|
||||
#{css}
|
||||
</style>
|
||||
<title>#{window.title}</title>
|
||||
</head>
|
||||
<body>
|
||||
#{html}
|
||||
</body>
|
||||
</html>
|
||||
ENDDOC
|
||||
end
|
||||
|
||||
def add_window(wname, title, &block)
|
||||
@windows[wname] = WebGui::Window.new(name: wname, title: title)
|
||||
@windows[wname].instance_eval(&block)
|
||||
end
|
||||
|
||||
def run
|
||||
servthread = Thread.new do
|
||||
WebGui::Server.server(self)
|
||||
end
|
||||
startwsserv
|
||||
if File.exist?("/Applications/Google\ Chrome.app") && !$forcenochrome
|
||||
`"/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome" --app="http://localhost:2000"`
|
||||
else
|
||||
puts 'Chrome is not on your system.'
|
||||
puts 'Please install Chrome to use this framework properly.'
|
||||
puts 'If Chrome is installed, please make sure it is called Google Chrome.app and is in the root Applications folder.'
|
||||
puts 'The app will open in your default browser as a regular webpage instead.'
|
||||
sleep(5)
|
||||
`open http://localhost:2000`
|
||||
servthread.join
|
||||
end
|
||||
end
|
||||
@windows={}
|
||||
@windows[:main]=WebGui::Window.new(name: :main,title: title)
|
||||
@windows[:main].instance_eval(&block)
|
||||
end
|
||||
|
||||
def render_document(wname)
|
||||
|
||||
window=windows[wname]
|
||||
html=window.render
|
||||
css=window.render_css
|
||||
doc=<<-ENDDOC
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||
<script src=\"main.js\"></script>
|
||||
<style>
|
||||
#{css}
|
||||
</style>
|
||||
<title>#{window.title}</title>
|
||||
</head>
|
||||
<body>
|
||||
#{html}
|
||||
</body>
|
||||
</html>
|
||||
ENDDOC
|
||||
return doc
|
||||
end
|
||||
|
||||
def add_window(wname,title,&block)
|
||||
@windows[wname]=WebGui::Window.new(name: wname,title: title)
|
||||
@windows[wname].instance_eval(&block)
|
||||
end
|
||||
def run()
|
||||
servthread=Thread.new do
|
||||
WebGui::Server.server(self)
|
||||
end
|
||||
startwsserv()
|
||||
if File.exists? "/Applications/Google\ Chrome.app" and !$forcenochrome
|
||||
`"/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome" --app="http://localhost:2000"`
|
||||
else
|
||||
puts "Chrome is not on your system."
|
||||
puts "Please install Chrome to use this framework properly."
|
||||
puts "If Chrome is installed, please make sure it is called Google Chrome.app and is in the root Applications folder."
|
||||
puts "The app will open in your default browser as a regular webpage instead."
|
||||
sleep(5)
|
||||
`open http://localhost:2000`
|
||||
servthread.join
|
||||
end
|
||||
end
|
||||
|
||||
def startwsserv()
|
||||
@serv=WebGui::WebSocketServer.new
|
||||
Thread.new(self,@serv) do |parent,server|
|
||||
server.accept
|
||||
while true
|
||||
message=server.recv
|
||||
if message==false
|
||||
server.accept
|
||||
next
|
||||
def startwsserv
|
||||
@serv = WebGui::WebSocketServer.new
|
||||
Thread.new(self, @serv) do |parent, server|
|
||||
server.accept
|
||||
loop do
|
||||
message = server.recv
|
||||
if message == false
|
||||
server.accept
|
||||
next
|
||||
end
|
||||
parent.handlemessage(message)
|
||||
end
|
||||
parent.handlemessage(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def handlemessage(message)
|
||||
puts "Got message #{message}"
|
||||
part1,val=message.split("=")
|
||||
type=part1.match /[a-zA-Z]+/.to_s
|
||||
type=type[0]
|
||||
id=part1.match /\d+/
|
||||
id=id[0].to_i
|
||||
if val
|
||||
case type
|
||||
when "menu"
|
||||
val=val.to_sym
|
||||
when "radiobutton"
|
||||
val=val.to_sym
|
||||
when "checkbox"
|
||||
vals=val.split("&")
|
||||
temp=[]
|
||||
vals.each do |val|
|
||||
temp.push(val.to_sym)
|
||||
def handlemessage(message)
|
||||
puts "Got message #{message}"
|
||||
part1, val = message.split('=')
|
||||
type = part1.match(/[a-zA-Z]+/.to_s)
|
||||
type = type[0]
|
||||
id = part1.match(/\d+/)
|
||||
id = id[0].to_i
|
||||
if val
|
||||
case type
|
||||
when 'menu'
|
||||
val = val.to_sym
|
||||
when 'radiobutton'
|
||||
val = val.to_sym
|
||||
when 'checkbox'
|
||||
vals = val.split('&')
|
||||
temp = []
|
||||
vals.each do |val|
|
||||
temp.push(val.to_sym)
|
||||
end
|
||||
val = temp
|
||||
end
|
||||
val=temp
|
||||
$eventmanager.publish("#{type}.updated", val: val, id: id)
|
||||
else
|
||||
$eventmanager.publish("#{type}.pushed", id: id)
|
||||
end
|
||||
$eventmanager.publish("#{type}.updated",val: val,id: id)
|
||||
else
|
||||
$eventmanager.publish("#{type}.pushed",id: id)
|
||||
end
|
||||
end
|
||||
|
||||
def update(type,id,val)
|
||||
puts "Sent message #{type}#{id}=#{val}"
|
||||
@serv.send("#{type}#{id}=#{val}")
|
||||
def update(type, id, val)
|
||||
puts "Sent message #{type}#{id}=#{val}"
|
||||
@serv.send("#{type}#{id}=#{val}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,24 +1,31 @@
|
||||
$id=0
|
||||
$idtoel={}
|
||||
def get_id()
|
||||
id=$id
|
||||
$id+=1
|
||||
return id
|
||||
# frozen_string_literal: true
|
||||
|
||||
$id = 0
|
||||
$idtoel = {}
|
||||
def get_id
|
||||
id = $id
|
||||
$id += 1
|
||||
id
|
||||
end
|
||||
class WebGui::Element < Dry::Struct
|
||||
constructor_type :strict_with_defaults
|
||||
attribute :id, Types::Coercible::Int.default { get_id() }
|
||||
def initialize(opthash=nil)
|
||||
if opthash
|
||||
super(opthash)
|
||||
@block=nil
|
||||
|
||||
module WebGui
|
||||
class Element < Dry::Struct
|
||||
constructor_type :strict_with_defaults
|
||||
attribute :id, Types::Coercible::Int.default { get_id }
|
||||
def initialize(opthash = nil)
|
||||
if opthash
|
||||
super(opthash)
|
||||
@block = nil
|
||||
end
|
||||
$idtoel[id] = self
|
||||
end
|
||||
|
||||
def render
|
||||
''
|
||||
end
|
||||
|
||||
def self.css
|
||||
@ccs
|
||||
end
|
||||
$idtoel[id]=self
|
||||
end
|
||||
def render()
|
||||
return ""
|
||||
end
|
||||
def self.css()
|
||||
return @ccs
|
||||
end
|
||||
end
|
||||
|
@ -1,8 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class EventManager
|
||||
include Dry::Events::Publisher[:event_manager]
|
||||
register_event("button.pushed")
|
||||
register_event("menu.updated")
|
||||
register_event("textfield.updated")
|
||||
register_event("checkbox.updated")
|
||||
register_event("radiobutton.updated")
|
||||
register_event('button.pushed')
|
||||
register_event('menu.updated')
|
||||
register_event('textfield.updated')
|
||||
register_event('checkbox.updated')
|
||||
register_event('radiobutton.updated')
|
||||
end
|
||||
|
@ -1,136 +1,144 @@
|
||||
require "socket"
|
||||
class WebGui::Server
|
||||
def self.sfile(file,headers,type,client,url)
|
||||
total=file.length
|
||||
range=headers["Range"]
|
||||
if range!=nil
|
||||
positions=range.split("=")[1].split("-")
|
||||
start=positions[0].to_i(10)
|
||||
m_end=positions[1] ? positions[1].to_i(10) : total - 1;
|
||||
chunksize=(m_end-start)+1
|
||||
chunk=file[start, m_end+1]
|
||||
if type=="mp4"
|
||||
r_headers={"Content-Range"=>"bytes #{start}-#{m_end}/#{total}","Accept-Ranges"=>"bytes","Content-Length"=>chunksize,"Content-Type"=>"video/mp4"}
|
||||
elsif type=="webm"
|
||||
r_headers={"Content-Range"=>"bytes #{start}-#{m_end}/#{total}","Accept-Ranges"=>"bytes","Content-Length"=>chunksize,"Content-Type"=>"video/webm"}
|
||||
elsif type=="mpeg"
|
||||
r_headers={"Content-Range"=>"bytes #{start}-#{m_end}/#{total}","Accept-Ranges"=>"bytes","Content-Length"=>chunksize,"Content-Type"=>"audio/mpeg"}
|
||||
elsif type=="ogg"
|
||||
r_headers={"Content-Range"=>"bytes #{start}-#{m_end}/#{total}","Accept-Ranges"=>"bytes","Content-Length"=>chunksize,"Content-Type"=>"audio/ogg"}
|
||||
end
|
||||
header=""
|
||||
r_headers.each do |key,value|
|
||||
header+="#{key}: #{value}\n"
|
||||
end
|
||||
client.puts "HTTP/1.1 206 Partial Content"
|
||||
client.print "#{header}"
|
||||
client.print "\n"
|
||||
client.print "#{chunk}"
|
||||
else
|
||||
if type=="mp4"
|
||||
r_headers={"Content-Type"=>"video/mp4"}
|
||||
elsif type=="webm"
|
||||
r_headers={"Content-Type"=>"video/webm"}
|
||||
elsif type=="mpeg"
|
||||
r_headers={"Content-Type"=>"audio/mpeg"}
|
||||
elsif type=="ogg"
|
||||
r_headers={"Content-Type"=>"audio/ogg"}
|
||||
end
|
||||
header=""
|
||||
r_headers.each do |key,value|
|
||||
header+="#{key}: #{value}\n"
|
||||
end
|
||||
client.puts "HTTP/1.1 200 OK"
|
||||
client.print "#{header}"
|
||||
client.print "\n"
|
||||
client.print "#{file}"
|
||||
end
|
||||
client.close
|
||||
end
|
||||
# frozen_string_literal: true
|
||||
|
||||
def self.server(app)
|
||||
Thread::abort_on_exception=true
|
||||
server = TCPServer.new(2000)
|
||||
$debug = true
|
||||
igfiles=["favicon.ico"]
|
||||
loop do
|
||||
require 'socket'
|
||||
module WebGui
|
||||
class Server
|
||||
def self.sfile(file, headers, type, client, _url)
|
||||
total = file.length
|
||||
range = headers['Range']
|
||||
if !range.nil?
|
||||
positions = range.split('=')[1].split('-')
|
||||
start = positions[0].to_i(10)
|
||||
m_end = positions[1] ? positions[1].to_i(10) : total - 1
|
||||
chunksize = (m_end - start) + 1
|
||||
chunk = file[start, m_end + 1]
|
||||
case type
|
||||
when 'mp4'
|
||||
r_headers = { 'Content-Range' => "bytes #{start}-#{m_end}/#{total}", 'Accept-Ranges' => 'bytes',
|
||||
'Content-Length' => chunksize, 'Content-Type' => 'video/mp4' }
|
||||
when 'webm'
|
||||
r_headers = { 'Content-Range' => "bytes #{start}-#{m_end}/#{total}", 'Accept-Ranges' => 'bytes',
|
||||
'Content-Length' => chunksize, 'Content-Type' => 'video/webm' }
|
||||
when 'mpeg'
|
||||
r_headers = { 'Content-Range' => "bytes #{start}-#{m_end}/#{total}", 'Accept-Ranges' => 'bytes',
|
||||
'Content-Length' => chunksize, 'Content-Type' => 'audio/mpeg' }
|
||||
when 'ogg'
|
||||
r_headers = { 'Content-Range' => "bytes #{start}-#{m_end}/#{total}", 'Accept-Ranges' => 'bytes',
|
||||
'Content-Length' => chunksize, 'Content-Type' => 'audio/ogg' }
|
||||
end
|
||||
header = ''
|
||||
r_headers.each do |key, value|
|
||||
header += "#{key}: #{value}\n"
|
||||
end
|
||||
client.puts 'HTTP/1.1 206 Partial Content'
|
||||
client.print header.to_s
|
||||
client.print "\n"
|
||||
client.print chunk.to_s
|
||||
else
|
||||
case type
|
||||
when 'mp4'
|
||||
r_headers = { 'Content-Type' => 'video/mp4' }
|
||||
when 'webm'
|
||||
r_headers = { 'Content-Type' => 'video/webm' }
|
||||
when 'mpeg'
|
||||
r_headers = { 'Content-Type' => 'audio/mpeg' }
|
||||
when 'ogg'
|
||||
r_headers = { 'Content-Type' => 'audio/ogg' }
|
||||
end
|
||||
header = ''
|
||||
r_headers.each do |key, value|
|
||||
header += "#{key}: #{value}\n"
|
||||
end
|
||||
client.puts 'HTTP/1.1 200 OK'
|
||||
client.print header.to_s
|
||||
client.print "\n"
|
||||
client.print file.to_s
|
||||
end
|
||||
client.close
|
||||
end
|
||||
|
||||
def self.server(app)
|
||||
Thread.abort_on_exception = true
|
||||
server = TCPServer.new(2000)
|
||||
$debug = true
|
||||
igfiles = ['favicon.ico']
|
||||
loop do
|
||||
Thread.start(server.accept) do |client|
|
||||
begin
|
||||
lines=[]
|
||||
line=client.gets
|
||||
while line != "\r\n"
|
||||
if line==nil
|
||||
lines=[""]
|
||||
runreq=false
|
||||
break
|
||||
begin
|
||||
lines = []
|
||||
line = client.gets
|
||||
while line != "\r\n"
|
||||
if line.nil?
|
||||
lines = ['']
|
||||
runreq = false
|
||||
break
|
||||
end
|
||||
lines << line
|
||||
line = client.gets
|
||||
end
|
||||
lines<<line
|
||||
line=client.gets
|
||||
end
|
||||
i=0
|
||||
lines.each do |value|
|
||||
lines[i]=value.chomp
|
||||
i=i+1
|
||||
end
|
||||
temp=lines.shift
|
||||
method=temp.split(" ")[0]
|
||||
url=temp.split(" ")[1]
|
||||
headers={}
|
||||
lines.each do |value|
|
||||
temp=value.split(": ")
|
||||
headers[temp[0]]=temp[1]
|
||||
end
|
||||
wname=url.gsub("/","")
|
||||
if igfiles.include? wname
|
||||
client.puts "HTTP/1.1 404 Not Found"
|
||||
client.puts "Content-Type:text/html"
|
||||
client.puts
|
||||
client.print ""
|
||||
else
|
||||
if wname.include? ".jpg"
|
||||
img=File.read(wname)
|
||||
client.puts "HTTP/1.1 200 OK"
|
||||
client.puts "Content-Type:image/jpeg"
|
||||
i = 0
|
||||
lines.each do |value|
|
||||
lines[i] = value.chomp
|
||||
i += 1
|
||||
end
|
||||
temp = lines.shift
|
||||
method = temp.split(' ')[0]
|
||||
url = temp.split(' ')[1]
|
||||
headers = {}
|
||||
lines.each do |value|
|
||||
temp = value.split(': ')
|
||||
headers[temp[0]] = temp[1]
|
||||
end
|
||||
wname = url.gsub('/', '')
|
||||
if igfiles.include? wname
|
||||
client.puts 'HTTP/1.1 404 Not Found'
|
||||
client.puts 'Content-Type:text/html'
|
||||
client.puts
|
||||
client.print ''
|
||||
elsif wname.include? '.jpg'
|
||||
img = File.read(wname)
|
||||
client.puts 'HTTP/1.1 200 OK'
|
||||
client.puts 'Content-Type:image/jpeg'
|
||||
client.puts
|
||||
client.print img
|
||||
elsif wname.include? ".mp4"
|
||||
sfile(File.open(wname, "rb") {|io| io.read},headers,"mp4",client,url)
|
||||
elsif wname.include? ".webm"
|
||||
sfile(File.open(wname, "rb") {|io| io.read},headers,"webm",client,url)
|
||||
elsif wname.include? ".js"
|
||||
if wname=="main.js"
|
||||
jspath=`gem which web_gui`.split("/")
|
||||
elsif wname.include? '.mp4'
|
||||
sfile(File.open(wname, 'rb', &:read), headers, 'mp4', client, url)
|
||||
elsif wname.include? '.webm'
|
||||
sfile(File.open(wname, 'rb', &:read), headers, 'webm', client, url)
|
||||
elsif wname.include? '.js'
|
||||
if wname == 'main.js'
|
||||
jspath = `gem which web_gui`.split('/')
|
||||
jspath.pop
|
||||
jspath.push "main.js"
|
||||
jspath=jspath.join("/")
|
||||
script=File.read(jspath)
|
||||
jspath.push 'main.js'
|
||||
jspath = jspath.join('/')
|
||||
script = File.read(jspath)
|
||||
else
|
||||
script=File.read(wname)
|
||||
script = File.read(wname)
|
||||
end
|
||||
client.puts "HTTP/1.1 200 OK"
|
||||
client.puts "Content-Type:application/javascript"
|
||||
client.puts 'HTTP/1.1 200 OK'
|
||||
client.puts 'Content-Type:application/javascript'
|
||||
client.puts
|
||||
client.print script
|
||||
else
|
||||
wname="main" if wname==""
|
||||
wname=wname.to_sym
|
||||
doc=app.render_document(wname)
|
||||
client.puts "HTTP/1.1 200 OK"
|
||||
client.puts "Content-Type:text/html"
|
||||
wname = 'main' if wname == ''
|
||||
wname = wname.to_sym
|
||||
doc = app.render_document(wname)
|
||||
client.puts 'HTTP/1.1 200 OK'
|
||||
client.puts 'Content-Type:text/html'
|
||||
client.puts
|
||||
client.print doc
|
||||
end
|
||||
client.close
|
||||
rescue StandardError => e
|
||||
puts e
|
||||
puts e.backtrace
|
||||
client.puts 'HTTP/1.1 500 Internal Server Error'
|
||||
client.puts 'Content-Type:text/plain'
|
||||
client.puts
|
||||
client.puts "Server error: #{e}"
|
||||
client.print e.backtrace.join("\n")
|
||||
client.close
|
||||
end
|
||||
client.close
|
||||
rescue StandardError=>e
|
||||
puts e
|
||||
puts e.backtrace
|
||||
client.puts "HTTP/1.1 500 Internal Server Error"
|
||||
client.puts "Content-Type:text/plain"
|
||||
client.puts
|
||||
client.puts "Server error: #{e}"
|
||||
client.print e.backtrace.join("\n")
|
||||
client.close
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WebGui
|
||||
VERSION = "1.0.0"
|
||||
VERSION = '1.0.0'
|
||||
end
|
||||
|
@ -1,34 +1,37 @@
|
||||
class WebGui::Window < Dry::Struct
|
||||
attribute :title, Types::Coercible::String.default("")
|
||||
attribute :name, Types::Coercible::String
|
||||
def initialize(opthash)
|
||||
super(opthash)
|
||||
@elements=[]
|
||||
end
|
||||
# frozen_string_literal: true
|
||||
|
||||
def add_element(element)
|
||||
raise ArgumentError, "The element must be of type Element." unless element.is_a? WebGui::Element
|
||||
@elements.push element
|
||||
$eventmanager.subscribe(element)
|
||||
return element
|
||||
end
|
||||
|
||||
def render()
|
||||
html=""
|
||||
@elements.each do |el|
|
||||
html+="<p>#{el.render()}</p>"
|
||||
module WebGui
|
||||
class Window < Dry::Struct
|
||||
attribute :title, Types::Coercible::String.default('')
|
||||
attribute :name, Types::Coercible::String
|
||||
def initialize(opthash)
|
||||
super(opthash)
|
||||
@elements = []
|
||||
end
|
||||
return html
|
||||
end
|
||||
|
||||
def render_css()
|
||||
css=""
|
||||
WebGui::Element.descendants.each do |el|
|
||||
elcss=el.css
|
||||
if elcss!=nil
|
||||
css+=elcss
|
||||
def add_element(element)
|
||||
raise ArgumentError, 'The element must be of type Element.' unless element.is_a? WebGui::Element
|
||||
|
||||
@elements.push element
|
||||
$eventmanager.subscribe(element)
|
||||
element
|
||||
end
|
||||
|
||||
def render
|
||||
html = ''
|
||||
@elements.each do |el|
|
||||
html += "<p>#{el.render}</p>"
|
||||
end
|
||||
html
|
||||
end
|
||||
|
||||
def render_css
|
||||
css = ''
|
||||
WebGui::Element.descendants.each do |el|
|
||||
elcss = el.css
|
||||
css += elcss unless elcss.nil?
|
||||
end
|
||||
css
|
||||
end
|
||||
return css
|
||||
end
|
||||
end
|
||||
|
@ -1,76 +1,82 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'socket' # Provides TCPServer and TCPSocket classes
|
||||
require 'digest/sha1'
|
||||
|
||||
class WebGui::WebSocketServer
|
||||
def initialize(host="localhost",port=2345)
|
||||
@server=TCPServer.new(host,port)
|
||||
end
|
||||
|
||||
def accept()
|
||||
|
||||
# Wait for a connection
|
||||
@socket = @server.accept
|
||||
|
||||
# Read the HTTP request. We know it's finished when we see a line with nothing but \r\n
|
||||
http_request = ""
|
||||
while (line = @socket.gets) && (line != "\r\n")
|
||||
http_request += line
|
||||
module WebGui
|
||||
class WebSocketServer
|
||||
def initialize(host = 'localhost', port = 2345)
|
||||
@server = TCPServer.new(host, port)
|
||||
end
|
||||
|
||||
# Grab the security key from the headers. If one isn't present, close the connection.
|
||||
if matches = http_request.match(/^Sec-WebSocket-Key: (\S+)/)
|
||||
websocket_key = matches[1]
|
||||
else
|
||||
socket.close
|
||||
return
|
||||
def accept
|
||||
# Wait for a connection
|
||||
@socket = @server.accept
|
||||
|
||||
# Read the HTTP request. We know it's finished when we see a line with nothing but \r\n
|
||||
http_request = ''
|
||||
while (line = @socket.gets) && (line != "\r\n")
|
||||
http_request += line
|
||||
end
|
||||
|
||||
# Grab the security key from the headers. If one isn't present, close the connection.
|
||||
if matches = http_request.match(/^Sec-WebSocket-Key: (\S+)/)
|
||||
websocket_key = matches[1]
|
||||
else
|
||||
socket.close
|
||||
return
|
||||
end
|
||||
response_key = Digest::SHA1.base64digest([websocket_key, '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'].join)
|
||||
@socket.write <<~EOS
|
||||
HTTP/1.1 101 Switching Protocols
|
||||
Upgrade: websocket
|
||||
Connection: Upgrade
|
||||
Sec-WebSocket-Accept: #{response_key}
|
||||
#{' '}
|
||||
EOS
|
||||
end
|
||||
response_key = Digest::SHA1.base64digest([websocket_key, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"].join)
|
||||
@socket.write <<-eos
|
||||
HTTP/1.1 101 Switching Protocols
|
||||
Upgrade: websocket
|
||||
Connection: Upgrade
|
||||
Sec-WebSocket-Accept: #{response_key}
|
||||
|
||||
eos
|
||||
end
|
||||
def recv
|
||||
first_byte = @socket.getbyte
|
||||
fin = first_byte & 0b10000000
|
||||
opcode = first_byte & 0b00001111
|
||||
fin = fin >> 7
|
||||
fin = (fin ? true : false)
|
||||
raise "We don't support continuations" unless fin
|
||||
|
||||
def recv()
|
||||
first_byte = @socket.getbyte
|
||||
fin = first_byte & 0b10000000
|
||||
opcode = first_byte & 0b00001111
|
||||
fin = fin >> 7
|
||||
fin = (fin ? true : false)
|
||||
raise "We don't support continuations" unless fin
|
||||
unless opcode == 1 or opcode == 8
|
||||
raise "We only support text data and close frame"
|
||||
send("",3)
|
||||
unless (opcode == 1) || (opcode == 8)
|
||||
raise 'We only support text data and close frame'
|
||||
send('', 3)
|
||||
end
|
||||
case opcode
|
||||
when 1
|
||||
second_byte = @socket.getbyte
|
||||
is_masked = second_byte & 0b10000000
|
||||
payload_size = second_byte & 0b01111111
|
||||
|
||||
raise 'All incoming frames should be masked according to the websocket spec' unless is_masked
|
||||
raise 'We only support payloads < 126 bytes in length' unless payload_size < 126
|
||||
|
||||
mask = 4.times.map { @socket.getbyte }
|
||||
data = payload_size.times.map { @socket.getbyte }
|
||||
unmasked_data = data.each_with_index.map { |byte, i| byte ^ mask[i % 4] }
|
||||
unmasked_data.pack('C*').force_encoding('utf-8')
|
||||
|
||||
when 8
|
||||
send('', 0)
|
||||
false
|
||||
end
|
||||
end
|
||||
if opcode == 1
|
||||
second_byte = @socket.getbyte
|
||||
is_masked = second_byte & 0b10000000
|
||||
payload_size = second_byte & 0b01111111
|
||||
|
||||
raise "All incoming frames should be masked according to the websocket spec" unless is_masked
|
||||
raise "We only support payloads < 126 bytes in length" unless payload_size < 126
|
||||
mask = 4.times.map {@socket.getbyte}
|
||||
data = payload_size.times.map {@socket.getbyte}
|
||||
unmasked_data = data.each_with_index.map { |byte, i| byte ^ mask[i % 4] }
|
||||
string=unmasked_data.pack('C*').force_encoding('utf-8')
|
||||
return string
|
||||
elsif opcode == 8
|
||||
send("",0)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def send(message,closetype=false)
|
||||
if closetype
|
||||
output=[0x88,2,3,0xe8+closetype]
|
||||
@socket.write output.pack("C*")
|
||||
@socket.close
|
||||
else
|
||||
output = [0x81, message.size, message]
|
||||
@socket.write output.pack("CCA#{message.size}")
|
||||
def send(message, closetype = false)
|
||||
if closetype
|
||||
output = [0x88, 2, 3, 0xe8 + closetype]
|
||||
@socket.write output.pack('C*')
|
||||
@socket.close
|
||||
else
|
||||
output = [0x81, message.size, message]
|
||||
@socket.write output.pack("CCA#{message.size}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,17 +1,18 @@
|
||||
# coding: utf-8
|
||||
lib = File.expand_path("../lib", __FILE__)
|
||||
# frozen_string_literal: true
|
||||
|
||||
lib = File.expand_path('lib', __dir__)
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
require "web_gui/version"
|
||||
require 'web_gui/version'
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name="web_gui"
|
||||
spec.version=WebGui::VERSION
|
||||
spec.authors=["pjht"]
|
||||
spec.email=["pjht@users.noreply.github.com"]
|
||||
spec.summary= "A GUI framework for ruby based around the web browser"
|
||||
spec.homepage="https://github.com/pjht/web_gui"
|
||||
spec.files=["lib/web_gui.rb","lib/main.js"]+Dir.glob("lib/web_gui/*.rb")
|
||||
spec.bindir="exe"
|
||||
spec.executables=spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||
spec.require_paths=["lib"]
|
||||
spec.name = 'web_gui'
|
||||
spec.version = WebGui::VERSION
|
||||
spec.authors = ['pjht']
|
||||
spec.email = ['pjht@users.noreply.github.com']
|
||||
spec.summary = 'A GUI framework for ruby based around the web browser'
|
||||
spec.homepage = 'https://github.com/pjht/web_gui'
|
||||
spec.files = ['lib/web_gui.rb', 'lib/main.js'] + Dir.glob('lib/web_gui/*.rb')
|
||||
spec.bindir = 'exe'
|
||||
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||
spec.require_paths = ['lib']
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user