introduce turbo_stream view_component abstraction

This commit is contained in:
Jonas Jabari
2023-09-19 16:45:19 +02:00
committed by Oliver Günther
parent d22f50a93b
commit 5fa2eafc0a
11 changed files with 589 additions and 0 deletions
@@ -0,0 +1,124 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpTurbo
module Streamable
extend ActiveSupport::Concern
class_methods do
def wrapper_key
name.underscore.gsub("/", "-").gsub("_", "-")
end
end
included do
def render_as_turbo_stream(view_context:, action: :update)
case action
when :update
@inner_html_only = true
template = render_in(view_context)
when :replace
template = render_in(view_context)
when :remove
template = nil
else
raise "Unsupported action #{action}"
end
unless @component_wrapper_used
raise "You need to wrap your component in a `component_wrapper` block in order to use the turbo-stream methods"
end
OpTurbo::StreamWrapperComponent.new(
action:,
target: wrapper_key,
template:
).render_in(view_context)
end
def insert_as_turbo_stream(component:, view_context:, action: :append)
template = component.render_in(view_context)
unless @component_wrapper_used
raise "You need to wrap your component in a `component_wrapper` block in order to use the turbo-stream methods"
end
OpTurbo::StreamWrapperComponent.new(
action:,
target: insert_target_modified? ? insert_target_modifier_id : wrapper_key,
template:
).render_in(view_context)
end
def component_wrapper(tag: "div", class: nil, data: nil, style: nil, &block)
@component_wrapper_used = true
if inner_html_only?
capture(&block)
else
content_tag(tag, id: wrapper_key, class:, data:, style:, &block)
end
end
def inner_html_only?
@inner_html_only == true
end
def wrapper_key
if wrapper_uniq_by.nil?
self.class.wrapper_key
else
"#{self.class.wrapper_key}-#{wrapper_uniq_by}"
end
end
def wrapper_uniq_by
# optionally implemented in subclass in order to make the wrapper key unique
end
def insert_target_modified?
# optionally overriden (returning true) in subclass in order to indicate thate the insert target
# is modified and should not be the root inner html element
# insert_target_container needs to be present on component's erb template then
false
end
def insert_target_container(tag: "div", class: nil, data: nil, style: nil, &block)
unless insert_target_modified?
raise "`insert_target_modified?` needs to be implemented and return true if `insert_target_container` is " \
"used in this component"
end
content_tag(tag, id: insert_target_modifier_id, class:, data:, style:, &block)
end
def insert_target_modifier_id
"#{wrapper_key}-insert-target-modifier"
end
end
end
end
@@ -0,0 +1,33 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) 2012-2023 the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<% if boxes.any? %>
<% boxes.each do |box| %>
<%= box %>
<% end %>
<% end %>
@@ -0,0 +1,41 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpPrimer
class BoxCollectionComponent < Primer::Component
def initialize(**)
super
@system_arguments = deny_tag_argument(**) || {}
end
renders_many :boxes, lambda { |**system_arguments|
Primer::Box.new(**system_arguments || {})
}
end
end
@@ -0,0 +1,33 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) 2012-2023 the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<% if components.any? %>
<% components.each do |component| %>
<%= component %>
<% end %>
<% end %>
@@ -0,0 +1,41 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpPrimer
class ComponentCollectionComponent < Primer::Component
def initialize(**)
super
@system_arguments = deny_tag_argument(**) || {}
end
renders_many :components, lambda { |component_instance|
component_instance
}
end
end
@@ -0,0 +1,43 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpPrimer
module ComponentHelpers
def flex_layout(**, &)
render(OpPrimer::FlexLayoutComponent.new(**), &)
end
def box_collection(**, &)
render(OpPrimer::BoxCollectionComponent.new(**), &)
end
def component_collection(**, &)
render(OpPrimer::ComponentCollectionComponent.new(**), &)
end
end
end
@@ -0,0 +1,52 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) 2012-2023 the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<% if rows.any? %>
<%= render(Primer::Box.new(**@system_arguments.merge(direction: :column))) do %>
<% rows.each do |row| %>
<%= row %>
<% end %>
<% end %>
<% end %>
<% if columns.any? %>
<%= render(Primer::Box.new(**@system_arguments.merge(direction: :row))) do %>
<% columns.each do |column| %>
<%= column %>
<% end %>
<% end %>
<% end %>
<% if boxes.any? %>
<%= render(Primer::Box.new(**@system_arguments)) do %>
<% boxes.each do |box| %>
<%= box %>
<% end %>
<% end %>
<% end %>
@@ -0,0 +1,72 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpPrimer
class FlexLayoutComponent < Primer::Component
def initialize(**)
super
@system_arguments = deny_tag_argument(**) || {}
@system_arguments[:display] = :flex
end
renders_many :rows, lambda { |**system_arguments, &block|
child_component(system_arguments, &block)
}
renders_many :columns, lambda { |**system_arguments, &block|
child_component(system_arguments, &block)
}
# boxes are used when direction is set to row or column based on responsive breakpoints
renders_many :boxes, lambda { |**system_arguments, &block|
child_component(system_arguments, &block)
}
private
def render?
if rows.empty? && columns.empty? && boxes.empty?
# no slot provided
raise ArgumentError, "You have to provide either rows, columns or boxes as a slot"
elsif [rows, columns, boxes].count { |arr| !arr.empty? } == 1
# only rows or columns or boxes are used
true
else
# rows, columns and boxes are used together, which is not allowed
raise ArgumentError, "You can't mix row, column and box slots"
end
end
def child_component(system_arguments, &)
if system_arguments[:flex_layout] == true
OpPrimer::FlexLayoutComponent.new(**system_arguments.except(:flex_layout), &)
else
Primer::Box.new(**system_arguments || {})
end
end
end
end
@@ -0,0 +1,36 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) 2012-2023 the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<turbo-stream action="<%=@action%>" target="<%=@target%>">
<% if @template %>
<template>
<%= @template %>
</template>
<% end %>
</turbo-stream>
@@ -0,0 +1,39 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpTurbo
class StreamWrapperComponent < ApplicationComponent
def initialize(template:, action:, target:)
super()
@template = template
@action = action
@target = target
end
end
end
@@ -0,0 +1,75 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpTurbo
module ComponentStream
extend ActiveSupport::Concern
def respond_to_with_turbo_streams(&format_block)
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_streams
end
format_block.call(format) if block_given?
end
end
alias_method :respond_with_turbo_streams, :respond_to_with_turbo_streams
def update_via_turbo_stream(component:)
modify_via_turbo_stream(component:, action: :update)
end
def replace_via_turbo_stream(component:)
modify_via_turbo_stream(component:, action: :replace)
end
def remove_via_turbo_stream(component:)
modify_via_turbo_stream(component:, action: :remove)
end
def modify_via_turbo_stream(component:, action:)
turbo_streams << component.render_as_turbo_stream(
view_context:,
action:
)
end
def append_via_turbo_stream(component:, target_component:)
turbo_streams << target_component.insert_as_turbo_stream(component:, view_context:, action: :append)
end
def prepend_via_turbo_stream(component:, target_component:)
turbo_streams << target_component.insert_as_turbo_stream(component:, view_context:, action: :prepend)
end
def turbo_streams
@turbo_streams ||= []
end
end
end