# frozen_string_literal: true
# toolbox method here
require 'json'
# def is_descendant(ancestor, descendant)
# JS.eval("return isDescendant('#{ancestor}', '#{descendant}')")
# end
class Atome
class << self
attr_accessor :initialized
def sanitize_data_for_json(data)
data.gsub('"', '\\"')
end
def send_localstorage_content
storage = JS.global[:localStorage]
storage_array = storage.to_a
storage_array.each_with_index do |_i, index|
key = JS.global[:localStorage].key(index)
sanitize_data_for_json(storage.getItem(key))
end
end
def server_receiver(params)
callback_found = Universe.messages[params[:message_id]]
callback_found.call(params) if callback_found.is_a? Proc
end
def file_handler(parent, content, bloc)
grab(parent).instance_exec(content, &bloc)
end
def controller_sender(message)
return if $host == :html
json_msg = message.to_json
js_json_msg = json_msg.inspect
js_command = "atomeJS.controller_sender(#{js_json_msg})"
JS.eval(js_command)
end
# atome builder
def preset_builder(preset_name, &bloc)
# Important : previously def box , def circle
Universe.atome_preset << preset_name
Object.define_method preset_name do |params = {}, &proc|
grab(:view).send(preset_name, params, &proc)
end
define_method preset_name do |params|
preset_to_add = instance_exec(params, &bloc) if bloc.is_a? Proc
if Essentials.default_params[preset_name]
Essentials.default_params[preset_name].merge(preset_to_add) if preset_to_add
else
Essentials.default_params[preset_name] = preset_to_add if preset_to_add
end
params = atome_common(preset_name, params)
preset_common(params, &bloc)
end
end
# monitoring system
def monitoring(atomes_to_monitor, particles_to_monitor, &bloc)
atomes_to_monitor.each do |atome_to_monitor|
particles_to_monitor.each do |monitored_particle|
# Store original method
original_method = atome_to_monitor.method(monitored_particle)
# redefine method
atome_to_monitor.define_singleton_method(monitored_particle) do |*args, &proc|
# Monitor before calling original method
value_before = atome_to_monitor.instance_variable_get("@#{monitored_particle}")
if args.empty?
args = nil
else
if monitored_particle == :touch
instance_variable_set("@#{monitored_particle}", { tap: args[0] })
instance_variable_set("@#{monitored_particle}_code", { touch: proc })
args = { tap: args[0] }
else
instance_variable_set("@#{monitored_particle}", args[0])
end
args = args[0]
end
if bloc.is_a?(Proc)
instance_exec({ original: value_before, altered: args, particle: monitored_particle }, &bloc)
end
original_method.call(*args)
end
end
end
end
def controller_listener
return if $host == :html
atome_js.JS.controller_listener() # js folder atome/helipers/atome/communication
end
def handleSVGContent(svg_content, target)
puts svg_content
atome_content = A.vectorizer(svg_content)
target_vector = grab(target)
target_vector.data(atome_content)
end
end
@initialized = {}
def grip(role_wanted)
gripped_atome = []
fasten.each do |child_id|
child_found = grab(child_id)
gripped_atome << child_id if child_found.role && child_found.role.include?(role_wanted)
end
gripped_atome
end
def recursive(_val)
# dummy method
end
def retrieve(params = {}, &block)
closest_first = true, include_self = false
if params[:ascending] == false
closest_first = :inverted
end
if params[:self] == true
include_self = true
end
# this method allow to retrieve all children of an atome recursively, beginning from the closet child or inverted
all_children = []
fetch_children_recursively = lambda do |parent, depth|
children_ids = parent.fasten
if children_ids.any?
children_ids.each do |child_id|
child = grab(child_id)
fetch_children_recursively.call(child, depth + 1)
end
end
if include_self
all_children << { depth: depth, child: parent }
else
all_children << { depth: depth, child: parent } unless parent == self
end
end
fetch_children_recursively.call(self, 0)
sorted_children = if closest_first != :inverted
all_children.sort_by { |entry| entry[:depth] }
else
all_children.sort_by { |entry| -entry[:depth] }
end
sorted_children.each do |entry|
block.call(entry[:child])
end
end
def found_spacing_in_percent(parent_width, child_width, nb_of_children)
total_child_width = child_width * nb_of_children
remaining_width = parent_width - total_child_width
spacing = remaining_width.to_f / (nb_of_children + 1)
spacing_percentage = (spacing / parent_width) * 100
spacing_percentage.round(2)
end
def block(params)
direction = params.delete(:direction) || :vertical
spacing = params.delete(:spacing) || 3
width_found = params.delete(:width) || '100%'
height_found = params.delete(:height) || '100%'
bloc_params = params.delete(:data) || {}
last_id_found = grip(:block).last
if last_id_found
last_found = grab(last_id_found)
case direction
when :vertical
box({ top: below(last_found, spacing), role: :block, width: width_found }.merge(params).merge(bloc_params))
when :horizontal
width_found = to_px(:width)
block_left = after(last_found, spacing)
left_in_percent = (block_left / width_found) * 100
box({ left: "#{left_in_percent}%", role: :block, height: height_found }.merge(params).merge(bloc_params))
else
#
end
else
case direction
when :vertical
box({ top: spacing, role: :block, width: width_found }.merge(params).merge(bloc_params))
when :horizontal
box({ left: spacing, role: :block, height: height_found }.merge(params).merge(bloc_params))
else
#
end
end
end
def blocks(params)
# alert 'blocks case'
blocks = params.delete(:blocks)
distribute = params.delete(:distribute)
if distribute && params[:direction] == :horizontal
width_found = to_px(:width)
params[:spacing] = "#{found_spacing_in_percent(width_found, params[:width], blocks.length)}%"
elsif distribute
height_found = to_px(:height)
params[:spacing] = found_spacing_in_percent(height_found, params[:height], blocks.length)
end
blocks.each do |bloc_id, block_to_create|
sanitized_bloc_data = params.merge(block_to_create)
block({ data: sanitized_bloc_data }.merge({ id: bloc_id }).merge(params))
end
end
def sub_block(sub_params, spacing_found = 3)
num_blocks = sub_params.size
parent_width = to_px(:width)
total_ratios = sub_params.values.sum { |sub_content| sub_content[:width] }
total_spacing = (num_blocks + 1) * spacing_found
available_width = parent_width - total_spacing
left_offset = spacing_found
sub_params.each do |sub_id, sub_content|
ratio = sub_content[:width]
block_width = (available_width * ratio) / total_ratios
sub_created = box({ id: sub_id, height: '100%', left: left_offset, role: :sub })
sub_content["width"] = block_width
sub_created.set(sub_content)
sub_created.width(block_width)
left_offset += block_width + spacing_found
sub_created.width(sub_created.to_percent(:width))
sub_created.left(sub_created.to_percent(:left))
end
end
def help(particle, &doc)
if doc
Universe.set_help(particle, &doc)
else
doc_found = Universe.get_help(particle)
instance_exec(&doc_found) if doc_found.is_a?(Proc)
end
end
def example(particle, &example)
if example
Universe.set_example(particle, &example)
else
example_found = Universe.get_example(particle)
instance_exec(&example_found) if example_found.is_a?(Proc)
end
end
# local server messaging
def file_for_opal(parent, bloc)
JS.eval("fileForOpal('#{parent}', #{bloc})")
end
def response_listener(hashed_msg)
js_action = hashed_msg.JS[:action]
js_body = hashed_msg.JS[:body]
send(js_action, js_body)
end
def collapse(new_atome)
initialized_procs = []
initialized = Atome.initialized
new_atome.each do |element, value|
send(element, value)
initialized_proc = initialized[element]
initialized_procs << { value => initialized_proc } if initialized_proc.is_a?(Proc)
end
initialized_procs.each do |value|
value.each do |val, proc|
instance_exec(val, &proc)
end
end
end
def add_text_visual(params)
html.add_font_to_css(params)
end
def particle_main(element, params, &user_proc)
# TODO : optimise below removing all conditions if possible
if Atome.instance_variable_get("@main_#{element}").is_a?(Proc) # post is before rendering and broadcasting
result = instance_exec(params, user_proc, self, &Atome.instance_variable_get("@main_#{element}"))
params = result if result && !result.instance_of?(Atome)
end
params
end
def particle_read(element, params, &user_proc)
if Atome.instance_variable_get("@read_#{element}").is_a?(Proc) # post is before rendering and broadcasting
params = instance_exec(params, user_proc, self, &Atome.instance_variable_get("@read_#{element}"))
end
params
end
def particle_sanitizer(element, params, &user_proc)
bloc_found = Universe.get_sanitizer_method(element)
# sanitizer occurs before any treatment
# it's call at the very start when a new atome is created : in genesis.rb /new_atome
# it's also call when creating a new particle in genesis/ new_particle befre creating the particle
# and also before creating additional method in genesis/ additional_particle_methods
params = instance_exec(params, user_proc, &bloc_found) if bloc_found.is_a?(Proc)
params
end
def particle_pre(element, params, &user_proc)
if Atome.instance_variable_get("@pre_#{element}").is_a?(Proc) # post is before rendering and broadcasting
params = instance_exec(params, user_proc, self, &Atome.instance_variable_get("@pre_#{element}"))
end
params
end
def particle_post(element, params, &user_proc)
if Atome.instance_variable_get("@post_#{element}").is_a?(Proc) # post is after rendering and broadcasting
params = instance_exec(params, user_proc, self, &Atome.instance_variable_get("@post_#{element}"))
end
params
end
def particle_after(element, params, &user_proc)
if Atome.instance_variable_get("@after_#{element}").is_a?(Proc) # after is post saving
params = instance_exec(params, user_proc, self, &Atome.instance_variable_get("@after_#{element}"))
end
params
end
def atome_pre_process(element, params, &user_proc)
if Atome.instance_variable_get("@pre_#{element}").is_a?(Proc)
params = instance_exec(params, self, user_proc, &Atome.instance_variable_get("@pre_#{element}"))
end
params
end
def atome_sanitizer(element, params, &user_proc)
# Attention: the method is the same as the one used for the particle
particle_sanitizer(element, params)
end
def atome_post_process(element, params, new_atome, &user_proc)
return unless Atome.instance_variable_get("@post_#{element}").is_a?(Proc)
new_atome.instance_exec(params, user_proc, &Atome.instance_variable_get("@post_#{element}"))
end
def atome_processor(element, params, &user_proc)
# TODO: replace with the line below but need extensive testing as it crash some demos ex: animation
params = atome_common(element, params)
atome_pre_process(element, params, &user_proc)
new_atome = send("set_#{element}", params, &user_proc) # it call Atome.define_method "set_#{element}" in new_atome method
# TODO : check if we don't have a security issue allowing atome modification after creation
# if we have one find another solution the keep this facility
atome_post_process(element, params, new_atome, &user_proc)
new_atome
end
# def store(params)
# params.each do |particle_to_save, data|
# # @!atome[particle_to_save]=data
# # instance_variable_set(particle_to_save,data)
# end
#
# end
# def history(filter = {})
#
# filter[:id] = @id
# Universe.story(filter)
# end
def history
Universe.story
end
# def broadcasting(element)
# params = instance_variable_get("@#{element}")
# @broadcast.each_value do |particle_monitored|
# if particle_monitored[:particles].include?(element)
# code_found = particle_monitored[:code]
# instance_exec(self, element, params, &code_found) if code_found.is_a?(Proc)
# end
# end
# end
def store_proc(element, params = true, &user_proc)
instance_variable_set("@#{element}_code", {}) unless instance_variable_get("@#{element}_code")
# TODO : we may have to change this code if we need multiple proc for an particle
# FIXME : find a better algorithm can be found to avoid test if option is a Hash
Object.attr_accessor "#{element}_code"
elem_code = "@#{element}_code"
# if params.instance_of? Hash
# option_found = params.values[0]
# instance_variable_get(elem_code)["#{option_found}_code"] = user_proc
# else
instance_variable_get(elem_code)[element] = user_proc
# end
end
# ###################### new 1
# def store_proc(element, params = true, &user_proc)
# instance_variable_set("@#{element}_code", {}) unless instance_variable_get("@#{element}_code")
# # TODO : we may have to change this code if we need multiple proc for an particle
# # FIXME : find a better algorithm can be found to avoid test if option is a Hash
# Object.attr_accessor "#{element}_code"
# elem_code = "@#{element}_code"
# if params.instance_of? Hash
# option_found = params.values[0]
# puts "#{instance_variable_get(elem_code)["#{option_found}_code"]}"
# proc_stored= instance_variable_get(elem_code)["#{option_found}_code"] = []
# # proc_stored=[]
# else
# proc_stored= instance_variable_get(elem_code)[element] = []
# end
# proc_stored << user_proc
# # the commented line below will automatically execute the callback method
# # we keep it commented because sometime the execution is conditioned, ex : run callbck on a touch
# # send("#{element}_callback")
# end
# ##################### new 1
# This method is used to automatically create a callback method suffixed by '_callback'. For example: shell => shell_callback.
# it can be override if you create a method like:
# new({callback: :shell}) do |params, bloc|
# # write what you want …
# end
def particle_callback(element)
Atome.define_method("#{element}_callback") do |return_params|
# we test if instance_variable_get("@#{element}_code") is a hash for the can se the particle value is a hash
proc_found = if instance_variable_get("@#{element}_code").instance_of? Hash
# Then we get the first item of the hash because the proc is fasten to it
instance_variable_get("@#{element}_code").values.first
# instance_exec(@callback[element], proc_found)if proc_found.is_a? Proc
else
instance_variable_get("@#{element}_code")[element]
# instance_exec(@callback[element], proc_found)if proc_found.is_a? Proc
end
# array_of_proc_found.each do |proc_found|
proc_found.call(return_params) if proc_found.is_a? Proc
# end if array_of_proc_found
# if array_of_proc_found
# proc_found= array_of_proc_found.shift
# proc_found.call(return_params) if proc_found.is_a? Proc
# end
end
end
# this method generate the method accessible for end developers
# it's the send the method define in "particle_callback"
def callback(element, return_params = nil)
send("#{element}_callback", return_params)
end
def js_callback(id, particle, value, sub = nil)
current_atome = grab(id)
proc_found = current_atome.instance_variable_get("@#{particle}_code")[particle.to_sym]
instance_exec(value, &proc_found) if proc_found.is_a?(Proc)
end
# def callback(data)
# @callback[data.keys[0]] = data[data.keys[0]]
# end
def particles(particles_found = nil)
if particles_found
particles_found.each do |particle_found, value_found|
atome[particle_found] = value_found
end
else
atome
end
end
def atome
# allow to get all atomes instance variables available as a Hash
instance_variables.each_with_object({}) do |var, hash|
hash[var[1..-1].to_sym] = instance_variable_get(var) # var[1..-1] enlève le '@' au début
end
end
def particles_to_hash
hash = {}
instance_variables.each do |var|
next if %i[@selection_style @html_object @history @initialized @tick @controller_proc].include?(var)
hash[var.to_s.delete('@').to_sym] = instance_variable_get(var)
end
hash
end
# def refresh
#
# # we get the current color because they will be removed
# particles_found = particles_to_hash.dup
# # id_found=id
# data_found=particles_found.delete(:data)
# attach_found=particles_found.delete(:attach)
# apply_found=particles_found.delete(:apply)
# particles_found.each do |particle_found, value_found|
# send(particle_found, value_found)
# end
# # Universe.applicable_atomes.each do |atome_type|
# #
# # send(atome_type).each do |col|
# # apply(col)
# # end
# # end
# # alert id_found
# # grab(attach_found).fasten(id_found)
# data(data_found)
#
# apply_found.delete(:text_color) #TODO : patch here : the array is not correctly ordered so default color are apply over the next
# apply_found.delete(:box_color) ##TODO : patch here : the array is not correctly ordered so default color are apply over the next
# apply(apply_found)
# # attach(attach_found)
# end
def refresh_atome
id_found = id.dup
id(:temporary)
fasten_atomes = []
fasten_found = fasten.dup
fasten_found.each do |child_id_found|
child_found = grab(child_id_found)
if child_found
new_child = child_found.duplicate({})
fasten_atomes << new_child.id
end
end
infos_found = infos.dup
data_found = infos_found.delete(:data)
keys_to_delete = %i[history callback duplicate copy paste touch_code html fasten aid]
keys_to_delete.each { |key| infos_found.delete(key) }
new_atome_id = id_found
infos_found[:id] = new_atome_id
new_atome = Atome.new(infos_found)
@duplicate ||= {}
@duplicate[new_atome_id] = new_atome
new_atome.data(data_found) # needed because if atome type is a text we need add type at the end
new_atome
end
def refresh(&bloc)
retrieve({ self: true }) do |child|
child.refresh_atome
end
end
def <<(item)
collect << item
end
def include?(value)
include?(value)
end
def set(params)
params.each do |particle, value|
send(particle, value)
end
end
# def detach_atome(atome_id_to_detach)
# alert :uuu
# atome_to_detach = grab(atome_id_to_detach)
# # TODO: remove the condition below and find why it try to detach an atome that doesn't exist
# nil unless atome_to_detach
# end
def debug(msg)
puts msg
end
def set_current_user(user_id)
if Universe.users[user_id]
Universe.current_user = user_id
else
debug "#{user_id} not found"
end
end
def remove_layout
display(:default)
# we get the current parent (the previous layout)
parent_found = grab(attach)
# we get the parent of the parent
grand_parent = parent_found.attach
# and attach the item to the grand parent
# we remove the parent category and restore atome category
remove({ category: attach })
category(:atome)
attach(grand_parent)
# we delete the parent (the layout) if it no more children fasten
parent_found.delete(true) if parent_found.fasten.length == 0
end
def server(server_params = nil)
if server_params
@current_server = server_params
else
@current_server
end
end
def init_websocket
connection(@current_server)
end
def encrypt(string)
# if RUBY_ENGINE.downcase == 'opal' || 'wasm32-wasi'
# `sha256(#{string})`
js_code = "sha256('#{string}')"
JS.eval(js_code)
# else
# Digest::SHA256.hexdigest(string)
# end
end
def get_localstorage_content
storage = JS.global[:localStorage]
storage_array = storage.to_a
storage_items = {}
storage_array.each_with_index do |_i, index|
key = JS.global[:localStorage].key(index)
value = JS.global[:localStorage].getItem(key)
storage_items[key] = value
end
storage_items
end
# def to_sym
# puts "sanitizer temp patch when an atome is passed instead of an id"
# @id
# end
# def transform_to_string_keys_and_values(hash)
# hash.transform_keys(&:to_s).transform_values do |value|
# if value.is_a?(Hash)
# transform_to_string_keys_and_values(value)
# else
# value.to_s
# end
# end
# end
def sync(params, &bloc)
params = { data: params } unless params.instance_of? Hash
message_id = "msg_#{Universe.messages.length}"
params[:message_id] = message_id
Universe.store_messages({ msg_nb: message_id, proc: bloc })
# params = transform_to_string_keys_and_values(params)
html.send_message(params)
end
def alternate(*states)
@alternate ||= { state: 0 }
@alternate[:data] = states
if @alternate[:state] < states.length - 1
@alternate[:state] += 1
else
@alternate[:state] = 0
end
current_state = @alternate[:data][@alternate[:state] - 1]
if current_state.instance_of?(Hash)
current_state.each do |state, value|
send(state, value)
end
end
current_state
end
def vectorizer(svg_content)
atome_content = []
# circle_regex = //
circle_regex = //
svg_content.scan(circle_regex) do |id, stroke, stroke_width, fill, cx, cy, r|
stroke = stroke || 'none'
stroke_width = stroke_width || '0'
fill = fill || 'none'
circle_def = { circle: { cx: cx, cy: cy, r: r, id: id, stroke: stroke, "stroke-width" => stroke_width, fill: fill } }
atome_content << circle_def
end
# path_regex = //
path_regex = //
svg_content.scan(path_regex) do |d, id, stroke, stroke_width, fill|
id = id || 'path_id'
stroke = stroke || 'none'
stroke_width = stroke_width || '0'
fill = fill || 'none'
path_def = { path: { d: d, id: id, stroke: stroke, 'stroke-width' => stroke_width, fill: fill } }
atome_content << path_def
end
# rect_regex = //
rect_regex = //
svg_content.scan(rect_regex) do |id, stroke, stroke_width, fill, x, y, width, height|
id = id || 'rect_id'
stroke = stroke || 'none'
stroke_width = stroke_width || '0'
fill = fill || 'none'
rect_def = { rect: { x: x, y: y, width: width, height: height, id: id, stroke: stroke, 'stroke-width' => stroke_width, fill: fill } }
atome_content << rect_def
end
# line_regex = //
line_regex = //
svg_content.scan(line_regex) do |id, stroke, stroke_width, x1, y1, x2, y2|
id = id || 'line_id'
stroke = stroke || 'none'
stroke_width = stroke_width || '0'
line_def = { line: { x1: x1, y1: y1, x2: x2, y2: y2, id: id, stroke: stroke, 'stroke-width' => stroke_width } }
atome_content << line_def
end
# ellipse_regex = //
ellipse_regex = //
svg_content.scan(ellipse_regex) do |id, stroke, stroke_width, fill, cx, cy, rx, ry|
id = id || 'ellipse_id'
stroke = stroke || 'none'
stroke_width = stroke_width || '0'
fill = fill || 'none'
ellipse_def = { ellipse: { cx: cx, cy: cy, rx: rx, ry: ry, id: id, stroke: stroke, 'stroke-width' => stroke_width, fill: fill } }
atome_content << ellipse_def
end
# polygon_regex = //
polygon_regex = //
svg_content.scan(polygon_regex) do |id, stroke, stroke_width, fill, points|
id ||= 'polygon_id'
stroke ||= 'none'
stroke_width ||= '0'
fill ||= 'none'
polygon_def = { polygon: { points: points, id: id, stroke: stroke, 'stroke-width' => stroke_width, fill: fill } }
atome_content << polygon_def
end
polyline_regex = //
svg_content.scan(polyline_regex) do |points, id, stroke, stroke_width, fill|
id ||= 'polyline_id'
stroke ||= 'none'
stroke_width ||= '0'
fill ||= 'none'
polyline_def = { polyline: { points: points, id: id, stroke: stroke, 'stroke-width' => stroke_width, fill: fill } }
atome_content << polyline_def
end
atome_content
end
def b64_to_tag(params)
unless params[:target]
new_img = image({ left: 0, top: 0 })
params[:target] = new_img.id
end
new_tag = <