# 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 = <