studio-engine 0.82.1 → 0.83.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/studio.rb CHANGED
@@ -13,6 +13,8 @@ require "studio/js_literal"
13
13
  require "studio/js_identifier"
14
14
  require "studio/partial_block"
15
15
  require "studio/sidebar_sections"
16
+ require "studio/site_footer"
17
+ require "studio/booking"
16
18
  require "studio/navbar_links"
17
19
  require "studio/navbar_identity"
18
20
  require "studio/profile_sections"
@@ -558,6 +560,80 @@ module Studio
558
560
  # config.draw_public_user_routes = true
559
561
  mattr_accessor :draw_public_user_routes, default: false
560
562
 
563
+ # ---- Site footer and booking (Studio::SiteFooter, Studio::Booking) -------
564
+ #
565
+ # The public site's footer: brand and social profiles, link columns, an
566
+ # optional Location band with a live map, and a legal line. And the booking
567
+ # primitives: Google Calendar's appointment page inline, in a popup, and on a
568
+ # page. See docs/SITE_FOOTER.md.
569
+
570
+ # The footer's facts: a Hash, or a callable receiving the view. nil (the
571
+ # default) means this app has no footer, and `studio_site_footer` renders
572
+ # nothing, so a layout can call it before the app declares one. Every key is
573
+ # optional; shape and rules: lib/studio/site_footer.rb.
574
+ #
575
+ # config.site_footer = ->(view) {
576
+ # { tagline: "Everything, by example",
577
+ # address: { street: "123 Example St", city_line: "Washington, DC 20024",
578
+ # lat: 38.8894, lng: -77.0352 },
579
+ # columns: [ [ "Company", [ [ "Home", view.root_path ], [ "Career", nil ] ] ] ],
580
+ # legal: [ [ "Privacy Policy", view.privacy_path ] ] }
581
+ # }
582
+ mattr_reader :site_footer, default: nil
583
+
584
+ def self.site_footer=(declared)
585
+ SiteFooter.validate!(declared)
586
+ @@site_footer = declared
587
+ end
588
+
589
+ # Controllers whose pages keep the footer for a SIGNED-IN viewer, by
590
+ # controller name ("landing") or path ("admin/reports"). A visitor gets the
591
+ # footer on every page; a signed-in viewer gets it only here, because every
592
+ # other signed-in page is a working surface (a board, a queue, an editor) and
593
+ # stays full height. The engine's own booking page is always included.
594
+ #
595
+ # config.site_footer_controllers = %w[landing packages contact_submissions]
596
+ mattr_accessor :site_footer_controllers, default: []
597
+
598
+ # THE WHOLE RULE for where the footer shows, as one callable receiving the
599
+ # view. The default is the rule above: every page for a visitor, the listed
600
+ # controllers for a signed-in viewer. That default shows the footer on ANY page
601
+ # a signed-out visitor can reach, an app page included, so an app with public
602
+ # working pages narrows it here. Compose with the default rather than
603
+ # restating it:
604
+ #
605
+ # config.site_footer_visible = ->(view) {
606
+ # Studio::SiteFooter.default_visible?(view) && !view.controller_path.start_with?("games/")
607
+ # }
608
+ mattr_reader :site_footer_visible, default: ->(view) { Studio::SiteFooter.default_visible?(view) }
609
+
610
+ def self.site_footer_visible=(rule)
611
+ SiteFooter.validate_visible!(rule)
612
+ @@site_footer_visible = rule
613
+ end
614
+
615
+ # The app's Google Calendar appointment schedule: the public link Google gives
616
+ # you to share, WITHOUT `?gv=true` (the frame and the popup add it). nil (the
617
+ # default) means no booking: `studio_booking_frame` and `studio_booking_popup`
618
+ # render nothing and no link opens a popup.
619
+ #
620
+ # config.booking_url = "https://calendar.google.com/calendar/appointments/schedules/EXAMPLE-SCHEDULE-ID"
621
+ mattr_reader :booking_url, default: nil
622
+
623
+ def self.booking_url=(url)
624
+ Booking.validate!(url)
625
+ @@booking_url = url.to_s.strip.empty? ? nil : Booking.page_url(url)
626
+ end
627
+
628
+ # Draw the booking page, GET /schedule (Studio::BookingsController, route
629
+ # helper studio_booking_path). OFF by default, like every route surface a
630
+ # consumer might already own: mcritchie-studio draws its own /schedule today,
631
+ # and its adoption turns this on as it deletes that route. With the flag on and
632
+ # no booking_url the page answers 404.
633
+ #
634
+ # config.draw_booking_routes = true
635
+ mattr_accessor :draw_booking_routes, default: false
636
+
561
637
  # THE APP'S IDENTITY COPY, resolved: { title:, description:, image_url: }.
562
638
  # The operator's saved value (Studio::SiteIdentity, edited at
563
639
  # /admin/link_preview) wins, then the drafted Studio.site_title /
@@ -1057,6 +1133,18 @@ module Studio
1057
1133
  SidebarSections.resolve(sidebar_sections, view)
1058
1134
  end
1059
1135
 
1136
+ # The site footer's facts resolved for a view (lib/studio/site_footer.rb), or
1137
+ # nil when the app declared none. The name defaults to the site identity's
1138
+ # title and the logo to the navbar logo, so an app that has set those up says
1139
+ # them once.
1140
+ def self.site_footer_for(view)
1141
+ return nil if site_footer.nil?
1142
+
1143
+ booking_path = draw_booking_routes && view.respond_to?(:studio_booking_path) ? view.studio_booking_path : nil
1144
+ SiteFooter.resolve(site_footer, view,
1145
+ name: site_identity[:title], logo: logo_for("Footer Logo"), booking_path: booking_path)
1146
+ end
1147
+
1060
1148
  # Navbar links resolved for a view context (lib/studio/navbar_links.rb).
1061
1149
  def self.navbar_links_for(view)
1062
1150
  NavbarLinks.resolve(navbar_links, view)
@@ -1261,6 +1349,12 @@ module Studio
1261
1349
  format: false, constraints: { username: %r{[^/]+} }
1262
1350
  end
1263
1351
 
1352
+ # The booking page (/schedule): Google Calendar's appointment page inline,
1353
+ # with the site footer. OPT-IN — see Studio.draw_booking_routes.
1354
+ if Studio.draw_booking_routes
1355
+ get "schedule", to: "studio/bookings#show", as: :studio_booking
1356
+ end
1357
+
1264
1358
  # The living style guide. Canonical at /admin/style (StyleController#index);
1265
1359
  # /admin/design_system redirects here but KEEPS its admin_design_system_path
1266
1360
  # helper so a shipped host sidebar link on the old helper still resolves.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: studio-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.82.1
4
+ version: 0.83.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie
@@ -309,10 +309,12 @@ files:
309
309
  - app/assets/images/state-flags/wy.svg
310
310
  - app/assets/javascripts/studio/alpine.js
311
311
  - app/assets/javascripts/studio/canvas_confetti.js
312
+ - app/assets/javascripts/studio/leaflet.js
312
313
  - app/assets/javascripts/studio/session.js
313
314
  - app/assets/javascripts/studio/sortable.js
314
315
  - app/assets/javascripts/studio/sticky_table_header.js
315
316
  - app/assets/javascripts/studio/studio_confetti.js
317
+ - app/assets/stylesheets/studio/leaflet.css
316
318
  - app/assets/stylesheets/studio/sticky_table_header.css
317
319
  - app/assets/tailwind/studio_engine/engine-motion.css
318
320
  - app/assets/tailwind/studio_engine/engine.css
@@ -334,6 +336,7 @@ files:
334
336
  - app/controllers/schema_controller.rb
335
337
  - app/controllers/sessions_controller.rb
336
338
  - app/controllers/solana_sessions_controller.rb
339
+ - app/controllers/studio/bookings_controller.rb
337
340
  - app/controllers/studio/email_images_controller.rb
338
341
  - app/controllers/studio/emails_controller.rb
339
342
  - app/controllers/studio/geo_settings_controller.rb
@@ -355,6 +358,7 @@ files:
355
358
  - app/helpers/studio/geo_helper.rb
356
359
  - app/helpers/studio/link_preview_helper.rb
357
360
  - app/helpers/studio/public_user_helper.rb
361
+ - app/helpers/studio/site_footer_helper.rb
358
362
  - app/helpers/studio_email_delivery_helper.rb
359
363
  - app/helpers/studio_navbar_helper.rb
360
364
  - app/helpers/studio_sidebar_helper.rb
@@ -445,6 +449,10 @@ files:
445
449
  - app/views/studio/board/_board.html.erb
446
450
  - app/views/studio/board/_card_shell.html.erb
447
451
  - app/views/studio/board/_column.html.erb
452
+ - app/views/studio/booking/_assets.html.erb
453
+ - app/views/studio/booking/_frame.html.erb
454
+ - app/views/studio/booking/_popup.html.erb
455
+ - app/views/studio/bookings/show.html.erb
448
456
  - app/views/studio/email_images/index.html.erb
449
457
  - app/views/studio/emails/_banner_editor.html.erb
450
458
  - app/views/studio/emails/_banner_preview.html.erb
@@ -529,10 +537,16 @@ files:
529
537
  - app/views/studio/profiles/show.html.erb
530
538
  - app/views/studio/public_users/not_found.html.erb
531
539
  - app/views/studio/public_users/show.html.erb
540
+ - app/views/studio/site_footer/_assets.html.erb
541
+ - app/views/studio/site_footer/_footer.html.erb
542
+ - app/views/studio/site_footer/_link.html.erb
543
+ - app/views/studio/site_footer/_map.html.erb
544
+ - app/views/studio/site_footer/_social_icon.html.erb
532
545
  - app/views/studio/site_identities/edit.html.erb
533
546
  - app/views/style/_host.html.erb
534
547
  - app/views/style/_modal_specimen.html.erb
535
548
  - app/views/style/_modals.html.erb
549
+ - app/views/style/_site_footer.html.erb
536
550
  - app/views/style/_specimen.html.erb
537
551
  - app/views/style/_tasks.html.erb
538
552
  - app/views/style/_theme.html.erb
@@ -573,6 +587,7 @@ files:
573
587
  - lib/generators/studio/site_identity/site_identity_generator.rb
574
588
  - lib/studio-engine.rb
575
589
  - lib/studio.rb
590
+ - lib/studio/booking.rb
576
591
  - lib/studio/cable.rb
577
592
  - lib/studio/color_scale.rb
578
593
  - lib/studio/email.rb
@@ -605,6 +620,7 @@ files:
605
620
  - lib/studio/session_fingerprint.rb
606
621
  - lib/studio/session_state.rb
607
622
  - lib/studio/sidebar_sections.rb
623
+ - lib/studio/site_footer.rb
608
624
  - lib/studio/theme_resolver.rb
609
625
  - lib/studio/ui_primitives.rb
610
626
  - lib/studio/username_generator.rb