gxg-web-client/gxg/web/gxg_applications.rb
2021-11-16 08:58:51 -08:00

519 lines
No EOL
20 KiB
Ruby

module GxG
# ----------------------------------------------------------------------------------------------------
class Application
def initialize(settings={})
# @process = server-side process uuid
@process = settings[:application]
# receives a :source PersistedHash defining the application internals.
@definition = settings[:source]
@title = "Untitled"
# @state is persisted as state-data on the server when altered.
@state = (settings[:state] || {})
# Gui component if any:
@viewports = {}
if settings[:viewport].is_a?(::GxG::Gui::ApplicationViewport)
@viewports[(settings[:viewport].uuid.to_s.to_sym)] = settings[:viewport]
end
if settings[:viewports].is_a?(::Array)
settings[:viewports].each do |the_viewport|
if the_viewport.is_a?(::GxG::Gui::ApplicationViewport)
@viewports[(the_viewport.uuid.to_s.to_sym)] = the_viewport
end
end
end
@windows = {}
if settings[:window].is_a?(::GxG::Gui::Window)
@windows[(settings[:window].uuid.to_s.to_sym)] = settings[:window]
end
if settings[:windows].is_a?(::Array)
settings[:windows].each do |the_window|
if the_window.is_a?(::GxG::Gui::Window)
@windows[(the_window.uuid.to_s.to_sym)] = the_window
end
end
end
@libraries = {}
# Event Supports:
@event_handlers = {}
@event_listeners = {}
# Runtime flags:
@credentialed = (settings[:credentialed] || false)
@unique = (settings[:unique] || true)
# Application location:
@location = settings[:location]
# Internal Heap:
@heap = {}
# ### Compile Source
if @definition.is_a?(::String)
if @definition.base64?
@definition = @definition.decode64
end
if @definition.json?
begin
@definition = ::JSON::parse(@definition,{:symbolize_names => true})
@definition = ::GxG::Database::process_import(@definition)
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:definition => @definition}})
end
else
# error - malformed source.
end
end
if @definition.is_a?(::GxG::Database::DetachedHash)
@title = @definition.title
# Question: should I overwrite the potentially passed settings options?
the_opts = @definition[:options].unpersist
if the_opts[:credentialed] == true
@credentialed = true
else
@credentialed = false
end
if the_opts[:unique] == true
@unique = true
else
@unique = false
end
end
# Load Libraries
# You can use ruby or javascript for the :native code portion which will be added to the page body.
# you can then also set a wrapper library script on the library for smooth integration.
# use get_library(uuid/title) to access the library in order to call methods.
if @definition.is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedHash)
# libarary requirement format: {:library => "", :type => "", :minimum => 0.0, :maximum => nil}
load_list = []
if @definition[:requirements].is_any?(::GxG::Database::DetachedArray, ::GxG::Database::PersistedArray)
@definition[:requirements].each do |the_requirement|
unless GxG::DISPLAY_DETAILS[:object].library_loaded?(the_requirement)
load_list << the_requirement
end
end
end
GxG::DISPLAY_DETAILS[:object].load_libraries(load_list) do |all_loaded|
if all_loaded == true
# Link to libraries required:
if @definition[:requirements].is_a?(::GxG::Database::PersistedArray)
@definition[:requirements].each do |the_requirement|
the_library = GxG::DISPLAY_DETAILS[:object].get_library(the_requirement)
if the_library
@libraries[(the_library.uuid)] = the_library
end
end
end
# Set Script:
if @definition[:script].size > 0
if @definition[:script].base64?
instance_eval(@definition[:script].to_s.decode64)
else
instance_eval(@definition[:script].to_s)
end
end
else
log_warn("Library load error - not processing main application script for #{@definition.title}.")
end
end
end
#
self
end
#
def get_resource(the_reference=nil)
result = nil
if @definition
@definition[:content].each do |the_object|
if (the_object.title == the_reference || the_object.uuid == the_refrence.to_s.to_sym)
result = the_object
break
end
end
end
result
end
#
def require_resource(the_reference=nil)
# call self.require_resource("script-name") to load content.script-name
result = false
if @definition
# content source record format: {:component => "script", :type => "text/ruby", :script => ""}
the_script = self.get_resource(the_reference)
if the_script
if the_script[:component].to_s == "script" && the_script[:type].to_s == "text/ruby"
if the_script[:script].size > 0
if the_script[:script].base64?
instance_eval(the_script[:script].to_s.decode64)
else
instance_eval(the_script[:script].to_s)
end
result = true
end
end
end
end
result
end
#
def title()
@title
end
#
def credentialed()
@credentialed
end
#
def unique()
@unique
end
#
def location()
@location
end
# Libraries:
def libraries()
@libraries
end
#
def state_data()
@state
end
#
def state_pull()
GxG::CONNECTION.app_state_pull({:application => @process.to_s}) do |response|
if response.is_a?(::Hash)
if response[:result].is_a?(::Hash)
@state = response[:result]
true
else
false
end
else
false
end
end
end
#
def state_push()
GxG::CONNECTION.app_state_push({:application => @process.to_s, :data => @state}) do |response|
if response.is_a?(::Hash)
if response[:result] == true
true
else
false
end
else
false
end
end
end
#
def get_library(the_reference=nil)
result = nil
if the_reference.is_any?(::String, ::Symbol)
result = @libraries[(the_reference.to_s.to_sym)]
unless result
@libraries.values.each do |the_library|
if the_library.title == the_reference
result = the_library
break
end
end
end
end
result
end
#
# Viewport Supports:
def link_viewport(the_viewport=nil)
if the_viewport.is_a?(::GxG::Gui::ApplicationViewport)
@viewports[(the_viewport.uuid.to_s.to_sym)] = the_viewport
true
else
false
end
end
#
def unlink_viewport(the_reference=nil)
if the_reference.is_any?(::String, ::Symbol)
if @viewports[(the_reference.to_s.to_sym)]
@viewports.delete((the_reference.to_s.to_sym))
else
@viewports.values.each do |the_viewport|
if the_viewport.title == the_reference
@viewports.delete(the_viewport.uuid)
break
end
end
end
true
else
false
end
end
#
def viewports()
@viewports
end
#
def get_viewport(the_reference=nil)
result = nil
if the_reference.is_any?(::String, ::Symbol)
result = @viewports[(the_reference.to_s.to_sym)]
unless result
@viewports.values.each do |the_viewport|
if the_viewport.title == the_reference
result = the_viewport
break
end
end
end
end
result
end
#
# Window Supports:
def link_window(the_window=nil)
if the_window.is_a?(::GxG::Gui::Window)
@windows[(the_window.uuid.to_s.to_sym)] = the_window
# Review: add link to window object to application ??
true
else
false
end
end
#
def unlink_window(the_reference=nil)
if the_reference.is_any?(::String, ::Symbol)
if @windows[(the_reference.to_s.to_sym)]
@windows.delete((the_reference.to_s.to_sym))
else
@windows.values.each do |the_window|
if the_window.title == the_reference
@windows.delete(the_window.uuid)
break
end
end
end
true
else
false
end
end
#
def windows()
@windows
end
#
def get_window(the_reference=nil)
result = nil
if the_reference.is_any?(::String, ::Symbol)
result = @windows[(the_reference.to_s.to_sym)]
unless result
@windows.values.each do |the_window|
if the_window.title == the_reference
result = the_window
break
end
end
end
end
result
end
#
def process_uuid()
@process
end
#
def source_uuid()
if @definition.is_a?(::GxG::Database::DetachedHash)
@definition.uuid()
else
nil
end
end
#
def search_resources(the_reference=nil)
result = nil
if the_reference.is_any?(::String, ::Symbol)
if @definition
@definition[:content].search do |item,selector,container|
if item.is_a?(::GxG::Database::DetachedHash)
if the_reference.to_s.to_sym == item.uuid().to_s.to_sym || the_reference.to_s == item.title().to_s
result = item
break
end
end
end
end
end
result
end
# Event Supports:
## Internal method on
def on(the_event_type=nil, captured=false, &block)
if the_event_type.is_any?(::String, ::Symbol)
if block.respond_to?(:call)
@event_handlers[(the_event_type.to_s.downcase.to_sym)] = block
@event_listeners[(the_event_type.to_s.downcase.to_sym)] = `#{self.method(:event_handler).to_proc}`
`#{self.element}.addEventListener(#{the_event_type.to_s.downcase}, #{@event_listeners[(the_event_type.to_s.downcase.to_sym)]}, #{captured})`
end
end
end
#
def remove_listener(the_event_type=nil)
if @event_handlers[(the_event_type.to_s.downcase.to_sym)] && @event_listeners[(the_event_type.to_s.downcase.to_sym)]
`#{self.element}.removeEventListener(#{the_event_type.to_s.downcase},#{@event_listeners[(the_event_type.to_s.downcase.to_sym)]});`
@event_handlers.delete(the_event_type.to_s.downcase.to_sym)
@event_listeners.delete(the_event_type.to_s.downcase.to_sym)
end
true
end
#
def event_handler(the_event)
# format the event record for travel in the system.
# See: https://www.w3schools.com/jsref/dom_obj_event.asp
# Setup event type groupings to glean appropriate details from the event.
the_type = `#{the_event}.type`.to_s.downcase.to_sym
event_record = {}
if @event_handlers[(the_type)]
genre = (GxG::Gui::EventHandlers::EVENT_GENRES[(the_type)] || :event)
if genre
fields = GxG::Gui::EventHandlers::EVENT_FIELDS[(genre)]
if fields
#
%x{
serializer = function thisOne (e) {
if (e) {
var o = {
eventName: e.toString(),
altKey: e.altKey,
bubbles: e.bubbles,
button: e.button,
buttons: e.buttons,
cancelBubble: e.cancelBubble,
cancelable: e.cancelable,
clientX: e.clientX,
clientY: e.clientY,
composed: e.composed,
ctrlKey: e.ctrlKey,
currentTarget: e.currentTarget ? e.currentTarget.outerHTML : null,
defaultPrevented: e.defaultPrevented,
detail: e.detail,
eventPhase: e.eventPhase,
fromElement: e.fromElement ? e.fromElement.outerHTML : null,
isTrusted: e.isTrusted,
layerX: e.layerX,
layerY: e.layerY,
metaKey: e.metaKey,
movementX: e.movementX,
movementY: e.movementY,
offsetX: e.offsetX,
offsetY: e.offsetY,
pageX: e.pageX,
pageY: e.pageY,
relatedTarget: e.relatedTarget ? e.relatedTarget.outerHTML : null,
returnValue: e.returnValue,
screenX: e.screenX,
screenY: e.screenY,
shiftKey: e.shiftKey,
sourceCapabilities: e.sourceCapabilities ? e.sourceCapabilities.toString() : null,
target: e.target ? e.target.outerHTML : null,
timeStamp: e.timeStamp,
toElement: e.toElement ? e.toElement.outerHTML : null,
type: e.type,
view: e.view ? e.view.toString() : null,
which: e.which,
x: e.x,
y: e.y
};
return o;
};
};
event_record = JSON.stringify(serializer(#{the_event}));
}
event_record = ::JSON::parse(event_record.to_s, {:symbolize_names => true})
# puts "Got: #{event_record.inspect}"
end
end
#
@event_handlers[(the_type)].call(event_record)
#
end
end
#
def run(data={})
# override this in your object script.
if data.is_a?(::Hash)
if data[:restore] == true
self.state_pull
end
end
end
#
def exitready?(data={})
# override this in your object script.
:ready
end
#
def before_exit(data={})
# override this in your object script.
true
end
def exit(data={})
# override this in your object script.
# GxG::APPLICATIONS[:processes]
GxG::CONNECTION.application_close({:application => @process}) do |response|
if response.is_a?(::Hash)
if response[:result] == true
GxG::APPLICATIONS[:processes].delete(@process)
else
log_warn("Could not close application #{@process.inspect} : #{response.inspect}")
end
else
log_warn("Malformed response: #{response.inspect}")
end
end
true
end
#
def window_close(details={})
# override this in your object script.
if details.is_a?(::Hash)
if GxG::valid_uuid?(details[:window])
the_window = GxG::DISPLAY_DETAILS[:object].get_window(details[:window])
if the_window
# TODO: re-think documents and such.
# Document data needs saving first ??
@viewports.keys.each do |the_reference|
the_viewport = the_window.find_child(the_reference)
if the_viewport
the_viewport.set_application(nil)
self.unlink_viewport(the_reference)
end
end
self.unlink_window(the_window.uuid)
GxG::DISPLAY_DETAILS[:object].window_close(the_window.uuid)
unless @windows.size > 0
self.exit()
end
end
else
log_warn("Invalid Argument passed: #{details.inspect}")
end
end
end
#
# Viewport Management:
def viewport_clear(the_reference=nil)
viewport = self.get_viewport(the_reference)
if viewport
viewport.gxg_each_child do |the_item|
unless the_item == viewport
the_item.destroy
end
end
true
else
false
end
end
#
end
end