neon_sakura 0.1.4
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.
- checksums.yaml +7 -0
- data/.ai-reviewer/README.md +182 -0
- data/.ai-reviewer/ai-reviewer.sh +56 -0
- data/.ai-reviewer/build-system-prompt.sh +136 -0
- data/.ai-reviewer/extract-claude-sections.sh +32 -0
- data/.ai-reviewer/test-ai-reviewer.sh +40 -0
- data/.ai-reviewer-config.yml +190 -0
- data/.github/dependabot.yml +12 -0
- data/.github/settings.yml +70 -0
- data/.github/workflows/ai-pr-review-on-comment.yml +384 -0
- data/.github/workflows/ai-pr-review.yml +328 -0
- data/.github/workflows/license-check.yml +78 -0
- data/.github/workflows/lint.yml +79 -0
- data/.github/workflows/security.yml +131 -0
- data/.github/workflows/semgrep.yml +26 -0
- data/.github/workflows/test.yml +44 -0
- data/.gitignore +75 -0
- data/.rubocop.yml +33 -0
- data/.ruby-version +1 -0
- data/.simplecov +14 -0
- data/.stylelintignore +10 -0
- data/.stylelintrc.json +37 -0
- data/AGENTS.md +51 -0
- data/CHANGELOG.md +568 -0
- data/CLAUDE.md +632 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +327 -0
- data/LICENSE +21 -0
- data/README.md +1209 -0
- data/Rakefile +25 -0
- data/app/assets/images/cherry_blossom.svg +1525 -0
- data/app/assets/images/cherry_blossom_tree.png +0 -0
- data/app/assets/images/prysm-icon.png +0 -0
- data/app/assets/stylesheets/base.css +29 -0
- data/app/assets/stylesheets/components.css +1652 -0
- data/app/assets/stylesheets/forms.css +152 -0
- data/app/assets/stylesheets/loading.css +145 -0
- data/app/assets/stylesheets/neon_sakura.css +40 -0
- data/app/assets/stylesheets/pagy-tailwind.css +120 -0
- data/app/assets/stylesheets/theme-default.css +40 -0
- data/app/assets/stylesheets/theme-green.css +84 -0
- data/app/assets/stylesheets/theme-purple.css +94 -0
- data/app/assets/stylesheets/theme-red.css +84 -0
- data/app/assets/stylesheets/utility-borders.css +29 -0
- data/app/assets/stylesheets/utility-colors.css +185 -0
- data/app/assets/stylesheets/utility-effects.css +123 -0
- data/app/assets/stylesheets/utility-gradients.css +158 -0
- data/app/assets/stylesheets/utility-layout.css +132 -0
- data/app/assets/stylesheets/utility-reset.css +13 -0
- data/app/assets/stylesheets/utility-responsive.css +145 -0
- data/app/assets/stylesheets/utility-sizing.css +99 -0
- data/app/assets/stylesheets/utility-spacing.css +174 -0
- data/app/assets/stylesheets/utility-typography.css +97 -0
- data/app/controllers/errors_controller.rb +120 -0
- data/app/controllers/style_guide_controller.rb +117 -0
- data/app/helpers/errors_helper.rb +12 -0
- data/app/helpers/neon_sakura/navbar_helper.rb +43 -0
- data/app/helpers/style_guide_helper.rb +36 -0
- data/app/javascript/neon_sakura/dropdown.js +22 -0
- data/app/javascript/neon_sakura/navbar.js +71 -0
- data/app/javascript/neon_sakura/theme_switcher.js +187 -0
- data/app/views/errors/show.html.erb +105 -0
- data/app/views/layouts/error.html.erb +19 -0
- data/app/views/layouts/mission_control/jobs/_application_selection.html.erb +14 -0
- data/app/views/layouts/mission_control/jobs/_navigation.html.erb +21 -0
- data/app/views/layouts/mission_control/jobs/application.html.erb +453 -0
- data/app/views/layouts/style_guide.html.erb +416 -0
- data/app/views/shared/_file_upload.html.erb +184 -0
- data/app/views/shared/_footer.html.erb +23 -0
- data/app/views/shared/_header.html.erb +42 -0
- data/app/views/shared/_navbar.html.erb +306 -0
- data/app/views/shared/_profile_image_selector.html.erb +165 -0
- data/app/views/shared/_theme_switcher.html.erb +64 -0
- data/app/views/shared/icons/_adjustments.html.erb +10 -0
- data/app/views/shared/icons/_alert_circle.html.erb +3 -0
- data/app/views/shared/icons/_alert_triangle.html.erb +3 -0
- data/app/views/shared/icons/_archive.html.erb +3 -0
- data/app/views/shared/icons/_arrow_down.html.erb +3 -0
- data/app/views/shared/icons/_arrow_left.html.erb +3 -0
- data/app/views/shared/icons/_arrow_up.html.erb +3 -0
- data/app/views/shared/icons/_arrows_pointing_in.html.erb +10 -0
- data/app/views/shared/icons/_arrows_pointing_out.html.erb +10 -0
- data/app/views/shared/icons/_artemis_logo.html.erb +26 -0
- data/app/views/shared/icons/_auth_banner.html.erb +1 -0
- data/app/views/shared/icons/_bars.html.erb +10 -0
- data/app/views/shared/icons/_bell.html.erb +3 -0
- data/app/views/shared/icons/_book.html.erb +3 -0
- data/app/views/shared/icons/_bookmark.html.erb +3 -0
- data/app/views/shared/icons/_box.html.erb +3 -0
- data/app/views/shared/icons/_brain.html.erb +3 -0
- data/app/views/shared/icons/_briefcase.html.erb +3 -0
- data/app/views/shared/icons/_calendar.html.erb +3 -0
- data/app/views/shared/icons/_camera.html.erb +4 -0
- data/app/views/shared/icons/_chart_bar.html.erb +3 -0
- data/app/views/shared/icons/_chart_line.html.erb +10 -0
- data/app/views/shared/icons/_chart_pie.html.erb +11 -0
- data/app/views/shared/icons/_chat.html.erb +3 -0
- data/app/views/shared/icons/_check.html.erb +3 -0
- data/app/views/shared/icons/_check_circle.html.erb +3 -0
- data/app/views/shared/icons/_cherry_blossom.html.erb +1516 -0
- data/app/views/shared/icons/_cherry_blossom_silhouette.html.erb +1016 -0
- data/app/views/shared/icons/_cherry_blossom_single_flower.html.erb +1125 -0
- data/app/views/shared/icons/_cherry_blossom_tree.html.erb +159 -0
- data/app/views/shared/icons/_chevron_down.html.erb +3 -0
- data/app/views/shared/icons/_chevron_right.html.erb +9 -0
- data/app/views/shared/icons/_clipboard.html.erb +3 -0
- data/app/views/shared/icons/_clock.html.erb +3 -0
- data/app/views/shared/icons/_close.html.erb +3 -0
- data/app/views/shared/icons/_cog.html.erb +4 -0
- data/app/views/shared/icons/_crop.html.erb +10 -0
- data/app/views/shared/icons/_crown.html.erb +3 -0
- data/app/views/shared/icons/_disc.html.erb +3 -0
- data/app/views/shared/icons/_download.html.erb +3 -0
- data/app/views/shared/icons/_dragonfly.html.erb +58 -0
- data/app/views/shared/icons/_duplicate.html.erb +4 -0
- data/app/views/shared/icons/_edit.html.erb +3 -0
- data/app/views/shared/icons/_envelope.html.erb +3 -0
- data/app/views/shared/icons/_eraser.html.erb +10 -0
- data/app/views/shared/icons/_external_link.html.erb +3 -0
- data/app/views/shared/icons/_eye.html.erb +4 -0
- data/app/views/shared/icons/_file_csv.html.erb +10 -0
- data/app/views/shared/icons/_file_export.html.erb +10 -0
- data/app/views/shared/icons/_file_image.html.erb +10 -0
- data/app/views/shared/icons/_file_import.html.erb +10 -0
- data/app/views/shared/icons/_file_question.html.erb +6 -0
- data/app/views/shared/icons/_film.html.erb +3 -0
- data/app/views/shared/icons/_filter.html.erb +3 -0
- data/app/views/shared/icons/_folder.html.erb +3 -0
- data/app/views/shared/icons/_folder_open.html.erb +3 -0
- data/app/views/shared/icons/_folder_plus.html.erb +3 -0
- data/app/views/shared/icons/_globe.html.erb +3 -0
- data/app/views/shared/icons/_google.html.erb +11 -0
- data/app/views/shared/icons/_heart.html.erb +3 -0
- data/app/views/shared/icons/_heart_broken.html.erb +11 -0
- data/app/views/shared/icons/_heart_pulse.html.erb +4 -0
- data/app/views/shared/icons/_history.html.erb +11 -0
- data/app/views/shared/icons/_home.html.erb +10 -0
- data/app/views/shared/icons/_image.html.erb +3 -0
- data/app/views/shared/icons/_inbox.html.erb +3 -0
- data/app/views/shared/icons/_info_circle.html.erb +10 -0
- data/app/views/shared/icons/_key.html.erb +3 -0
- data/app/views/shared/icons/_layers.html.erb +10 -0
- data/app/views/shared/icons/_lightbulb.html.erb +10 -0
- data/app/views/shared/icons/_lightning.html.erb +3 -0
- data/app/views/shared/icons/_list.html.erb +3 -0
- data/app/views/shared/icons/_lock.html.erb +3 -0
- data/app/views/shared/icons/_logout.html.erb +3 -0
- data/app/views/shared/icons/_magazine.html.erb +3 -0
- data/app/views/shared/icons/_magic.html.erb +3 -0
- data/app/views/shared/icons/_minus.html.erb +10 -0
- data/app/views/shared/icons/_mobile.html.erb +10 -0
- data/app/views/shared/icons/_moon.html.erb +3 -0
- data/app/views/shared/icons/_network.html.erb +10 -0
- data/app/views/shared/icons/_new_item_banner.html.erb +1 -0
- data/app/views/shared/icons/_ouroboros.html.erb +24 -0
- data/app/views/shared/icons/_package.html.erb +3 -0
- data/app/views/shared/icons/_palette.html.erb +3 -0
- data/app/views/shared/icons/_paper_plane.html.erb +10 -0
- data/app/views/shared/icons/_photo.html.erb +10 -0
- data/app/views/shared/icons/_play.html.erb +4 -0
- data/app/views/shared/icons/_plus.html.erb +3 -0
- data/app/views/shared/icons/_pocket.html.erb +11 -0
- data/app/views/shared/icons/_prysm-icon.html.erb +34 -0
- data/app/views/shared/icons/_prysm.html.erb +13 -0
- data/app/views/shared/icons/_pushbullet-1.html.erb +29 -0
- data/app/views/shared/icons/_pushbullet-2.html.erb +2 -0
- data/app/views/shared/icons/_puzzle.html.erb +10 -0
- data/app/views/shared/icons/_qrcode.html.erb +3 -0
- data/app/views/shared/icons/_question.html.erb +3 -0
- data/app/views/shared/icons/_receipt.html.erb +10 -0
- data/app/views/shared/icons/_redo.html.erb +3 -0
- data/app/views/shared/icons/_refresh.html.erb +3 -0
- data/app/views/shared/icons/_rocket.html.erb +10 -0
- data/app/views/shared/icons/_rss.html.erb +3 -0
- data/app/views/shared/icons/_save.html.erb +3 -0
- data/app/views/shared/icons/_search.html.erb +3 -0
- data/app/views/shared/icons/_search_minus.html.erb +10 -0
- data/app/views/shared/icons/_search_plus.html.erb +10 -0
- data/app/views/shared/icons/_server_error.html.erb +6 -0
- data/app/views/shared/icons/_share.html.erb +3 -0
- data/app/views/shared/icons/_shield_check.html.erb +3 -0
- data/app/views/shared/icons/_sign_in.html.erb +3 -0
- data/app/views/shared/icons/_spinner.html.erb +4 -0
- data/app/views/shared/icons/_star.html.erb +3 -0
- data/app/views/shared/icons/_store.html.erb +10 -0
- data/app/views/shared/icons/_sun.html.erb +3 -0
- data/app/views/shared/icons/_sync.html.erb +3 -0
- data/app/views/shared/icons/_table.html.erb +3 -0
- data/app/views/shared/icons/_tag.html.erb +3 -0
- data/app/views/shared/icons/_tags.html.erb +11 -0
- data/app/views/shared/icons/_tools.html.erb +4 -0
- data/app/views/shared/icons/_trash.html.erb +3 -0
- data/app/views/shared/icons/_undo.html.erb +3 -0
- data/app/views/shared/icons/_unlock.html.erb +3 -0
- data/app/views/shared/icons/_upload.html.erb +3 -0
- data/app/views/shared/icons/_user.html.erb +3 -0
- data/app/views/shared/icons/_user_circle.html.erb +10 -0
- data/app/views/shared/icons/_user_plus.html.erb +10 -0
- data/app/views/shared/icons/_video.html.erb +3 -0
- data/app/views/shared/icons/_wrench.html.erb +11 -0
- data/app/views/style_guide/index.html.erb +77 -0
- data/app/views/style_guide/sections/_alerts.html.erb +114 -0
- data/app/views/style_guide/sections/_badges.html.erb +78 -0
- data/app/views/style_guide/sections/_buttons.html.erb +130 -0
- data/app/views/style_guide/sections/_cards.html.erb +84 -0
- data/app/views/style_guide/sections/_colors.html.erb +106 -0
- data/app/views/style_guide/sections/_file_upload.html.erb +135 -0
- data/app/views/style_guide/sections/_forms.html.erb +129 -0
- data/app/views/style_guide/sections/_gradients.html.erb +253 -0
- data/app/views/style_guide/sections/_header.html.erb +12 -0
- data/app/views/style_guide/sections/_icons.html.erb +55 -0
- data/app/views/style_guide/sections/_images.html.erb +40 -0
- data/app/views/style_guide/sections/_loading.html.erb +242 -0
- data/app/views/style_guide/sections/_pagination.html.erb +212 -0
- data/app/views/style_guide/sections/_profile_components.html.erb +203 -0
- data/app/views/style_guide/sections/_theme_switcher.html.erb +72 -0
- data/app/views/style_guide/sections/_typography.html.erb +65 -0
- data/bin/ai-optimize-claude-md +540 -0
- data/bin/ai-review-local +345 -0
- data/bin/ai-security-review +585 -0
- data/bin/brakeman +9 -0
- data/bin/install-hooks +57 -0
- data/bin/rake +7 -0
- data/bin/rubocop +10 -0
- data/bin/verify_setup.rb +31 -0
- data/config/brakeman.ignore +28 -0
- data/config/initializers/neon_sakura.rb +15 -0
- data/config/license_overrides.yml +13 -0
- data/config/routes.rb +21 -0
- data/config/theme_mappings.yml +61 -0
- data/docs/PRYSM_ASSETS.md +210 -0
- data/docs/plans/extract_ai_reviewer_plan.md +151 -0
- data/docs/plans/neon_sakura_gem_plan.md +138 -0
- data/lib/neon_sakura/configuration.rb +94 -0
- data/lib/neon_sakura/engine.rb +48 -0
- data/lib/neon_sakura/icon_helper.rb +54 -0
- data/lib/neon_sakura/profile_helper.rb +24 -0
- data/lib/neon_sakura/stylesheet_helper.rb +40 -0
- data/lib/neon_sakura/theme_helper.rb +63 -0
- data/lib/neon_sakura/theme_importer.rb +112 -0
- data/lib/neon_sakura/version.rb +5 -0
- data/lib/neon_sakura.rb +13 -0
- data/neon_sakura.gemspec +50 -0
- data/package.json +18 -0
- data/scripts/git-hooks/post-merge +132 -0
- data/scripts/git-hooks/pre-commit +123 -0
- data/scripts/git-hooks/pre-push +127 -0
- data/scripts/license-check.rb +587 -0
- data/settings.local.json +12 -0
- data/yarn.lock +778 -0
- metadata +503 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Controller for the style guide - development/test only
|
|
4
|
+
# Displays all UI components, icons, themes, and code examples
|
|
5
|
+
class StyleGuideController < ActionController::Base
|
|
6
|
+
# Include Pagy method for pagination support (if available)
|
|
7
|
+
# Pagy 43+ uses Pagy::Method instead of Pagy::Backend
|
|
8
|
+
include Pagy::Method if defined?(Pagy::Method)
|
|
9
|
+
include Pagy::Backend if defined?(Pagy::Backend) && !defined?(Pagy::Method)
|
|
10
|
+
|
|
11
|
+
# Include helpers in controller for use in actions
|
|
12
|
+
include NeonSakura::IconHelper
|
|
13
|
+
include NeonSakura::ThemeHelper
|
|
14
|
+
|
|
15
|
+
# Make helpers available to views
|
|
16
|
+
helper Pagy::Frontend if defined?(Pagy::Frontend)
|
|
17
|
+
helper NeonSakura::IconHelper
|
|
18
|
+
helper NeonSakura::ThemeHelper
|
|
19
|
+
helper NeonSakura::StylesheetHelper
|
|
20
|
+
|
|
21
|
+
# Use null_session for CSRF - appropriate for read-only pages with no forms
|
|
22
|
+
# This resets the session instead of raising an exception for missing/invalid tokens
|
|
23
|
+
protect_from_forgery with: :exception
|
|
24
|
+
|
|
25
|
+
layout "style_guide"
|
|
26
|
+
|
|
27
|
+
# Before actions to enforce environment restriction
|
|
28
|
+
before_action :ensure_dev_or_test_environment
|
|
29
|
+
before_action :ensure_style_guide_enabled
|
|
30
|
+
|
|
31
|
+
def index
|
|
32
|
+
@page_title = "Neon Sakura Style Guide"
|
|
33
|
+
|
|
34
|
+
# Collect all icons dynamically
|
|
35
|
+
@icons = available_icons
|
|
36
|
+
|
|
37
|
+
# Collect all images dynamically
|
|
38
|
+
@images = available_images
|
|
39
|
+
|
|
40
|
+
# Set up Pagy pagination examples if enabled
|
|
41
|
+
@pagy_enabled = NeonSakura.configuration.style_guide_pagy_examples
|
|
42
|
+
@pagy_available = false
|
|
43
|
+
@pagy_error = nil
|
|
44
|
+
|
|
45
|
+
if @pagy_enabled
|
|
46
|
+
# Generate dummy data for Pagy pagination examples
|
|
47
|
+
@pagy_items = generate_pagy_dummy_data
|
|
48
|
+
|
|
49
|
+
# Try to set up Pagy if available
|
|
50
|
+
if defined?(Pagy)
|
|
51
|
+
begin
|
|
52
|
+
# Pagy 43+ uses pagy(:offset, collection, **options)
|
|
53
|
+
# Older Pagy versions use pagy_array(collection, **options)
|
|
54
|
+
if defined?(Pagy::Method)
|
|
55
|
+
# Pagy 43+ API
|
|
56
|
+
@pagy, @page_items = pagy(:offset, @pagy_items, limit: 10)
|
|
57
|
+
elsif respond_to?(:pagy_array)
|
|
58
|
+
# Older Pagy API with array extra
|
|
59
|
+
@pagy, @page_items = pagy_array(@pagy_items, limit: 10)
|
|
60
|
+
else
|
|
61
|
+
raise NoMethodError, "No compatible Pagy pagination method found"
|
|
62
|
+
end
|
|
63
|
+
@pagy_available = true
|
|
64
|
+
rescue StandardError => e
|
|
65
|
+
# Pagy initialization failed - provide helpful error message
|
|
66
|
+
@pagy_error = "Pagy pagination examples failed to initialize. " \
|
|
67
|
+
"Error: #{e.message}. " \
|
|
68
|
+
"To disable these examples, set config.style_guide_pagy_examples = false in your " \
|
|
69
|
+
"NeonSakura configuration."
|
|
70
|
+
end
|
|
71
|
+
else
|
|
72
|
+
# Pagy not installed
|
|
73
|
+
@pagy_error = "Pagy gem is not installed. Add 'gem \"pagy\"' to your Gemfile, " \
|
|
74
|
+
"or disable Pagy examples by setting config.style_guide_pagy_examples = false"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Theme information
|
|
79
|
+
@available_themes = NeonSakura.config.available_themes
|
|
80
|
+
@current_theme = current_theme
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def ensure_dev_or_test_environment
|
|
86
|
+
unless Rails.env.development? || Rails.env.test?
|
|
87
|
+
render plain: "Style guide only available in development and test environments", status: :not_found
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def ensure_style_guide_enabled
|
|
92
|
+
unless NeonSakura.configuration.enable_style_guide
|
|
93
|
+
render plain: "Style guide is disabled", status: :not_found
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Get list of available images
|
|
98
|
+
def available_images
|
|
99
|
+
image_dir = File.join(NeonSakura::Engine.root, "app", "assets", "images")
|
|
100
|
+
return [] unless Dir.exist?(image_dir)
|
|
101
|
+
|
|
102
|
+
Dir.glob(File.join(image_dir, "*.{png,jpg,jpeg,svg,gif}")).map do |file|
|
|
103
|
+
File.basename(file)
|
|
104
|
+
end.sort
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Generate dummy data for Pagy examples
|
|
108
|
+
def generate_pagy_dummy_data
|
|
109
|
+
(1..100).map do |i|
|
|
110
|
+
{
|
|
111
|
+
id: i,
|
|
112
|
+
title: "Item #{i}",
|
|
113
|
+
description: "This is a sample item for pagination demo"
|
|
114
|
+
}
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Helper methods for error pages
|
|
4
|
+
module ErrorsHelper
|
|
5
|
+
# Include all route helpers for error pages
|
|
6
|
+
include Rails.application.routes.url_helpers
|
|
7
|
+
|
|
8
|
+
# Provide default_url_options for url_helpers
|
|
9
|
+
def default_url_options
|
|
10
|
+
Rails.application.config.action_mailer.default_url_options || {}
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NeonSakura
|
|
4
|
+
module NavbarHelper
|
|
5
|
+
# Check if a navigation link is currently active
|
|
6
|
+
# @param link [Hash] The link configuration
|
|
7
|
+
# @return [Boolean] true if the link is active
|
|
8
|
+
def nav_link_active?(link)
|
|
9
|
+
return false unless link[:path]
|
|
10
|
+
|
|
11
|
+
is_active = current_page?(link[:path]) rescue false
|
|
12
|
+
return true if is_active
|
|
13
|
+
|
|
14
|
+
# Check additional active_paths if present
|
|
15
|
+
if link[:active_paths]
|
|
16
|
+
link[:active_paths].any? { |path| current_page?(path) rescue false }
|
|
17
|
+
else
|
|
18
|
+
false
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Safely evaluate a condition string in the current context
|
|
23
|
+
# @param condition [String] The Ruby code to evaluate
|
|
24
|
+
# @return [Boolean] The result of the evaluation
|
|
25
|
+
def eval_nav_condition(condition)
|
|
26
|
+
return true if condition.nil? || condition.empty?
|
|
27
|
+
|
|
28
|
+
instance_eval(condition.to_s)
|
|
29
|
+
rescue StandardError => e
|
|
30
|
+
Rails.logger.warn("Failed to evaluate navbar condition '#{condition}': #{e.message}")
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Check if an icon exists before rendering
|
|
35
|
+
# @param icon_name [String] The icon partial name
|
|
36
|
+
# @return [Boolean] true if the icon partial exists
|
|
37
|
+
def nav_icon_exists?(icon_name)
|
|
38
|
+
return false if icon_name.blank?
|
|
39
|
+
|
|
40
|
+
lookup_context.exists?("shared/icons/#{icon_name}", [], true)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Helper methods for the style guide
|
|
4
|
+
module StyleGuideHelper
|
|
5
|
+
# Render a component example with preview and code
|
|
6
|
+
# @param title [String] The example title
|
|
7
|
+
# @param code [String] The ERB code to display
|
|
8
|
+
# @param options [Hash] Additional options
|
|
9
|
+
# @yield Block containing the component to render
|
|
10
|
+
# @return [String] HTML for the example with code
|
|
11
|
+
def example_with_code(title, code = nil, options = {}, &block)
|
|
12
|
+
content_tag :div, class: "code-example-wrapper #{options[:wrapper_class]}" do
|
|
13
|
+
preview_section(title, &block) + code_section(code)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def preview_section(title, &block)
|
|
20
|
+
content_tag :div, class: "code-example-preview" do
|
|
21
|
+
title_tag = content_tag :h3, title, class: "text-xl font-semibold mb-4"
|
|
22
|
+
preview_content = capture(&block)
|
|
23
|
+
title_tag + preview_content
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def code_section(code)
|
|
28
|
+
return "".html_safe if code.blank?
|
|
29
|
+
|
|
30
|
+
content_tag :div, class: "code-example-code" do
|
|
31
|
+
content_tag :pre do
|
|
32
|
+
content_tag :code, code
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Dropdown functionality for Mission Control
|
|
2
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
3
|
+
// This function handles dropdown toggle behavior
|
|
4
|
+
const dropdowns = document.querySelectorAll('.dropdown-toggle');
|
|
5
|
+
|
|
6
|
+
dropdowns.forEach(function(dropdown) {
|
|
7
|
+
dropdown.addEventListener('click', function(e) {
|
|
8
|
+
e.preventDefault();
|
|
9
|
+
const parent = this.parentElement;
|
|
10
|
+
parent.classList.toggle('open');
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Close dropdowns when clicking outside
|
|
15
|
+
document.addEventListener('click', function(e) {
|
|
16
|
+
if (!e.target.closest('.dropdown')) {
|
|
17
|
+
document.querySelectorAll('.dropdown.open').forEach(function(dropdown) {
|
|
18
|
+
dropdown.classList.remove('open');
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Navbar dropdown functionality
|
|
2
|
+
// Converted from Stimulus controller to vanilla JavaScript for broader compatibility
|
|
3
|
+
|
|
4
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
5
|
+
initializeNavbarDropdowns();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// Also initialize on Turbo page loads if Turbo is present
|
|
9
|
+
if (typeof Turbo !== 'undefined') {
|
|
10
|
+
document.addEventListener('turbo:load', function() {
|
|
11
|
+
initializeNavbarDropdowns();
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function initializeNavbarDropdowns() {
|
|
16
|
+
const dropdowns = document.querySelectorAll('[data-navbar-dropdown]');
|
|
17
|
+
|
|
18
|
+
dropdowns.forEach(function(dropdown) {
|
|
19
|
+
const toggle = dropdown.querySelector('[data-action="toggle"]');
|
|
20
|
+
const menu = dropdown.querySelector('[data-menu]');
|
|
21
|
+
|
|
22
|
+
if (!toggle || !menu) return;
|
|
23
|
+
|
|
24
|
+
// Remove any existing listeners to prevent duplicates
|
|
25
|
+
const newToggle = toggle.cloneNode(true);
|
|
26
|
+
toggle.parentNode.replaceChild(newToggle, toggle);
|
|
27
|
+
|
|
28
|
+
// Add click handler for toggle button
|
|
29
|
+
newToggle.addEventListener('click', function(event) {
|
|
30
|
+
event.preventDefault();
|
|
31
|
+
event.stopPropagation();
|
|
32
|
+
|
|
33
|
+
const isOpen = dropdown.classList.contains('open');
|
|
34
|
+
|
|
35
|
+
// Close all other dropdowns first
|
|
36
|
+
closeAllNavDropdowns();
|
|
37
|
+
|
|
38
|
+
// Toggle this dropdown
|
|
39
|
+
if (!isOpen) {
|
|
40
|
+
dropdown.classList.add('open');
|
|
41
|
+
newToggle.setAttribute('aria-expanded', 'true');
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Close dropdowns when clicking outside
|
|
47
|
+
document.addEventListener('click', function(event) {
|
|
48
|
+
if (!event.target.closest('[data-navbar-dropdown]')) {
|
|
49
|
+
closeAllNavDropdowns();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Close dropdowns on Escape key
|
|
54
|
+
document.addEventListener('keydown', function(event) {
|
|
55
|
+
if (event.key === 'Escape') {
|
|
56
|
+
closeAllNavDropdowns();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function closeAllNavDropdowns() {
|
|
62
|
+
const openDropdowns = document.querySelectorAll('[data-navbar-dropdown].open');
|
|
63
|
+
|
|
64
|
+
openDropdowns.forEach(function(dropdown) {
|
|
65
|
+
dropdown.classList.remove('open');
|
|
66
|
+
const toggle = dropdown.querySelector('[data-action="toggle"]');
|
|
67
|
+
if (toggle) {
|
|
68
|
+
toggle.setAttribute('aria-expanded', 'false');
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// Theme switcher functionality for neon_sakura
|
|
2
|
+
// Handles theme switching via localStorage with optional backend persistence
|
|
3
|
+
|
|
4
|
+
(function() {
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
// Only initialize once
|
|
8
|
+
if (window.neonSakuraThemeInitialized) return;
|
|
9
|
+
window.neonSakuraThemeInitialized = true;
|
|
10
|
+
|
|
11
|
+
const STORAGE_KEY_NAME = 'neon_sakura_theme_name';
|
|
12
|
+
const STORAGE_KEY_MODE = 'neon_sakura_theme_mode';
|
|
13
|
+
|
|
14
|
+
// Get available themes from configuration (embedded in page)
|
|
15
|
+
function getAvailableThemes() {
|
|
16
|
+
const themesElement = document.querySelector('[data-available-themes]');
|
|
17
|
+
if (themesElement) {
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(themesElement.dataset.availableThemes);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.error('Failed to parse available themes:', e);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Fallback to single default theme
|
|
25
|
+
return [{ name: 'purple', mode: 'dark', label: 'Purple Dark' }];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Get current theme from localStorage or default
|
|
29
|
+
function getCurrentTheme() {
|
|
30
|
+
const themes = getAvailableThemes();
|
|
31
|
+
const storedName = localStorage.getItem(STORAGE_KEY_NAME);
|
|
32
|
+
const storedMode = localStorage.getItem(STORAGE_KEY_MODE);
|
|
33
|
+
|
|
34
|
+
// If we have stored theme, validate it exists in available themes
|
|
35
|
+
if (storedName && storedMode) {
|
|
36
|
+
const found = themes.find(t => t.name === storedName && t.mode === storedMode);
|
|
37
|
+
if (found) {
|
|
38
|
+
return found;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Return first available theme as default
|
|
43
|
+
return themes[0];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Apply theme to HTML element
|
|
47
|
+
function applyTheme(theme) {
|
|
48
|
+
const html = document.documentElement;
|
|
49
|
+
html.setAttribute('data-theme-name', theme.name);
|
|
50
|
+
html.setAttribute('data-theme-mode', theme.mode);
|
|
51
|
+
|
|
52
|
+
// Store in localStorage
|
|
53
|
+
localStorage.setItem(STORAGE_KEY_NAME, theme.name);
|
|
54
|
+
localStorage.setItem(STORAGE_KEY_MODE, theme.mode);
|
|
55
|
+
|
|
56
|
+
// Set cookie for server-side access
|
|
57
|
+
document.cookie = `theme_name=${theme.name}; path=/; max-age=31536000`; // 1 year
|
|
58
|
+
document.cookie = `theme_mode=${theme.mode}; path=/; max-age=31536000`;
|
|
59
|
+
|
|
60
|
+
// Optional: Send to backend if API endpoint is configured
|
|
61
|
+
const apiEndpoint = document.querySelector('[data-theme-api-endpoint]');
|
|
62
|
+
if (apiEndpoint && apiEndpoint.dataset.themeApiEndpoint) {
|
|
63
|
+
sendThemeToBackend(theme, apiEndpoint.dataset.themeApiEndpoint);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Update UI elements
|
|
67
|
+
updateThemeUI(theme);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Send theme to backend via AJAX
|
|
71
|
+
function sendThemeToBackend(theme, endpoint) {
|
|
72
|
+
fetch(endpoint, {
|
|
73
|
+
method: 'PATCH',
|
|
74
|
+
headers: {
|
|
75
|
+
'Content-Type': 'application/json',
|
|
76
|
+
'X-CSRF-Token': document.querySelector('[name="csrf-token"]')?.content || ''
|
|
77
|
+
},
|
|
78
|
+
body: JSON.stringify({
|
|
79
|
+
theme_name: theme.name,
|
|
80
|
+
theme_mode: theme.mode
|
|
81
|
+
})
|
|
82
|
+
}).catch(function(error) {
|
|
83
|
+
console.error('Failed to save theme to backend:', error);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Update theme switcher UI (icons, labels, checkmarks)
|
|
88
|
+
function updateThemeUI(currentTheme) {
|
|
89
|
+
const themes = getAvailableThemes();
|
|
90
|
+
|
|
91
|
+
// Update toggle button icon (for 2-theme mode)
|
|
92
|
+
const themeIcon = document.getElementById('theme-icon');
|
|
93
|
+
if (themeIcon) {
|
|
94
|
+
// Show moon for light mode (switch to dark), sun for dark mode (switch to light)
|
|
95
|
+
themeIcon.innerHTML = currentTheme.mode === 'light'
|
|
96
|
+
? '<%= render "shared/icons/moon", css_class: "w-4 h-4" %>' // Switch to dark
|
|
97
|
+
: '<%= render "shared/icons/sun", css_class: "w-4 h-4" %>'; // Switch to light
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Update checkmarks in dropdown (for 3+ theme mode)
|
|
101
|
+
const themeItems = document.querySelectorAll('[data-theme-item]');
|
|
102
|
+
themeItems.forEach(function(item) {
|
|
103
|
+
const themeName = item.dataset.themeName;
|
|
104
|
+
const themeMode = item.dataset.themeMode;
|
|
105
|
+
const isActive = themeName === currentTheme.name && themeMode === currentTheme.mode;
|
|
106
|
+
|
|
107
|
+
const checkmark = item.querySelector('[data-theme-checkmark]');
|
|
108
|
+
if (checkmark) {
|
|
109
|
+
checkmark.style.display = isActive ? 'inline' : 'none';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (isActive) {
|
|
113
|
+
item.setAttribute('aria-current', 'true');
|
|
114
|
+
} else {
|
|
115
|
+
item.removeAttribute('aria-current');
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Handle theme switching
|
|
121
|
+
function switchTheme(theme) {
|
|
122
|
+
applyTheme(theme);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Handle toggle button click (2-theme mode)
|
|
126
|
+
function handleToggleClick() {
|
|
127
|
+
const themes = getAvailableThemes();
|
|
128
|
+
if (themes.length !== 2) return;
|
|
129
|
+
|
|
130
|
+
const current = getCurrentTheme();
|
|
131
|
+
// Switch to the other theme
|
|
132
|
+
const nextTheme = themes.find(t => t.name !== current.name || t.mode !== current.mode) || themes[0];
|
|
133
|
+
switchTheme(nextTheme);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Initialize theme switcher
|
|
137
|
+
function initializeThemeSwitcher() {
|
|
138
|
+
const currentTheme = getCurrentTheme();
|
|
139
|
+
|
|
140
|
+
// Apply theme on page load
|
|
141
|
+
applyTheme(currentTheme);
|
|
142
|
+
|
|
143
|
+
// Set up toggle button (2-theme mode)
|
|
144
|
+
const toggleButton = document.getElementById('theme-toggle');
|
|
145
|
+
if (toggleButton) {
|
|
146
|
+
toggleButton.addEventListener('click', handleToggleClick);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Set up dropdown items (3+ theme mode)
|
|
150
|
+
const themeItems = document.querySelectorAll('[data-theme-item]');
|
|
151
|
+
themeItems.forEach(function(item) {
|
|
152
|
+
item.addEventListener('click', function(event) {
|
|
153
|
+
event.preventDefault();
|
|
154
|
+
const themeName = item.dataset.themeName;
|
|
155
|
+
const themeMode = item.dataset.themeMode;
|
|
156
|
+
const theme = getAvailableThemes().find(t => t.name === themeName && t.mode === themeMode);
|
|
157
|
+
if (theme) {
|
|
158
|
+
switchTheme(theme);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Initialize on DOMContentLoaded
|
|
165
|
+
if (document.readyState === 'loading') {
|
|
166
|
+
document.addEventListener('DOMContentLoaded', initializeThemeSwitcher);
|
|
167
|
+
} else {
|
|
168
|
+
initializeThemeSwitcher();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Re-initialize on Turbo page loads
|
|
172
|
+
if (typeof Turbo !== 'undefined') {
|
|
173
|
+
document.addEventListener('turbo:load', initializeThemeSwitcher);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Re-initialize on Turbolinks page loads (legacy)
|
|
177
|
+
document.addEventListener('turbolinks:load', initializeThemeSwitcher);
|
|
178
|
+
|
|
179
|
+
// Export functions for external use
|
|
180
|
+
window.NeonSakura = window.NeonSakura || {};
|
|
181
|
+
window.NeonSakura.Theme = {
|
|
182
|
+
initialize: initializeThemeSwitcher,
|
|
183
|
+
getCurrentTheme: getCurrentTheme,
|
|
184
|
+
switchTheme: switchTheme,
|
|
185
|
+
applyTheme: applyTheme
|
|
186
|
+
};
|
|
187
|
+
})();
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<div class="min-h-screen bg-gray-900 text-white flex items-center justify-center px-4">
|
|
2
|
+
<div class="max-w-2xl w-full">
|
|
3
|
+
<!-- Error Content -->
|
|
4
|
+
<div class="text-center">
|
|
5
|
+
<!-- Error Icon -->
|
|
6
|
+
<div class="mb-8 flex justify-center">
|
|
7
|
+
<% case @error_code %>
|
|
8
|
+
<% when 400, 422 %>
|
|
9
|
+
<%= render "shared/icons/alert_triangle", css_class: "w-24 h-24 text-yellow-400" %>
|
|
10
|
+
<% when 401, 403 %>
|
|
11
|
+
<%= render "shared/icons/shield_check", css_class: "w-24 h-24 text-red-400" %>
|
|
12
|
+
<% when 404 %>
|
|
13
|
+
<%= render "shared/icons/file_question", css_class: "w-24 h-24 text-cyan-400" %>
|
|
14
|
+
<% when 500, 503 %>
|
|
15
|
+
<%= render "shared/icons/server_error", css_class: "w-24 h-24 text-red-400" %>
|
|
16
|
+
<% else %>
|
|
17
|
+
<%= render "shared/icons/alert_circle", css_class: "w-24 h-24 text-gray-400" %>
|
|
18
|
+
<% end %>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<!-- Error Code -->
|
|
22
|
+
<div class="mb-4">
|
|
23
|
+
<h1 class="text-6xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
|
24
|
+
<%= @error_code %>
|
|
25
|
+
</h1>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<!-- Error Title -->
|
|
29
|
+
<h2 class="text-3xl font-bold text-white mb-4">
|
|
30
|
+
<%= @error_title %>
|
|
31
|
+
</h2>
|
|
32
|
+
|
|
33
|
+
<!-- Error Message -->
|
|
34
|
+
<p class="text-lg text-gray-300 mb-8 max-w-lg mx-auto">
|
|
35
|
+
<%= @error_message %>
|
|
36
|
+
</p>
|
|
37
|
+
|
|
38
|
+
<!-- Error Details (if provided) -->
|
|
39
|
+
<% if @error_details.present? %>
|
|
40
|
+
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700 mb-8 text-left max-w-xl mx-auto">
|
|
41
|
+
<h3 class="text-lg font-semibold text-white mb-3 flex items-center">
|
|
42
|
+
<%= render "shared/icons/alert_circle", css_class: "w-5 h-5 text-yellow-400 mr-2" %>
|
|
43
|
+
Additional Details
|
|
44
|
+
</h3>
|
|
45
|
+
<div class="text-sm text-gray-400">
|
|
46
|
+
<% if @error_details.is_a?(Array) %>
|
|
47
|
+
<ul class="list-disc list-inside space-y-1">
|
|
48
|
+
<% @error_details.each do |detail| %>
|
|
49
|
+
<li><%= detail %></li>
|
|
50
|
+
<% end %>
|
|
51
|
+
</ul>
|
|
52
|
+
<% elsif @error_details.is_a?(Hash) %>
|
|
53
|
+
<dl class="space-y-2">
|
|
54
|
+
<% @error_details.each do |key, value| %>
|
|
55
|
+
<div>
|
|
56
|
+
<dt class="font-semibold text-gray-300"><%= key.to_s.titleize %>:</dt>
|
|
57
|
+
<dd class="ml-4">
|
|
58
|
+
<% if value.is_a?(Array) %>
|
|
59
|
+
<ul class="list-disc list-inside">
|
|
60
|
+
<% value.each do |v| %>
|
|
61
|
+
<li><%= v %></li>
|
|
62
|
+
<% end %>
|
|
63
|
+
</ul>
|
|
64
|
+
<% else %>
|
|
65
|
+
<%= value %>
|
|
66
|
+
<% end %>
|
|
67
|
+
</dd>
|
|
68
|
+
</div>
|
|
69
|
+
<% end %>
|
|
70
|
+
</dl>
|
|
71
|
+
<% else %>
|
|
72
|
+
<p><%= @error_details %></p>
|
|
73
|
+
<% end %>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
<% end %>
|
|
77
|
+
|
|
78
|
+
<!-- Action Buttons -->
|
|
79
|
+
<div class="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
|
80
|
+
<%= link_to root_path,
|
|
81
|
+
class: "px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white rounded-lg font-medium transition-all inline-flex items-center",
|
|
82
|
+
"aria-label": "Go to homepage" do %>
|
|
83
|
+
<%= render "shared/icons/arrow_up", css_class: "w-5 h-5 mr-2 transform rotate-180" %>
|
|
84
|
+
Go Home
|
|
85
|
+
<% end %>
|
|
86
|
+
|
|
87
|
+
<button
|
|
88
|
+
onclick="window.history.back()"
|
|
89
|
+
class="px-6 py-3 bg-gray-700 hover:bg-gray-600 text-white rounded-lg font-medium transition-colors inline-flex items-center"
|
|
90
|
+
aria-label="Go back to previous page"
|
|
91
|
+
>
|
|
92
|
+
<%= render "shared/icons/arrow_up", css_class: "w-5 h-5 mr-2 transform rotate-180" %>
|
|
93
|
+
Go Back
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<!-- Help Text -->
|
|
98
|
+
<div class="mt-8 text-sm text-gray-500">
|
|
99
|
+
<p>
|
|
100
|
+
If this problem persists, please contact support.
|
|
101
|
+
</p>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title><%= @error_code %> - <%= @error_title %> | Artemis</title>
|
|
7
|
+
|
|
8
|
+
<link rel="icon" href="/icon.png" type="image/png">
|
|
9
|
+
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
|
10
|
+
|
|
11
|
+
<%= stylesheet_link_tag "base", "data-turbo-track": "reload" %>
|
|
12
|
+
<%= stylesheet_link_tag "components", "data-turbo-track": "reload" %>
|
|
13
|
+
<%= stylesheet_link_tag "forms", "data-turbo-track": "reload" %>
|
|
14
|
+
</head>
|
|
15
|
+
|
|
16
|
+
<body>
|
|
17
|
+
<%= yield %>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<% if NeonSakura.config.nav_links.blank? %>
|
|
2
|
+
<%# Only show default Mission Control application selection when custom nav is NOT configured %>
|
|
3
|
+
<% if defined?(MissionControl::Jobs) && MissionControl::Jobs.applications.size > 1 %>
|
|
4
|
+
<div class="tabs">
|
|
5
|
+
<ul>
|
|
6
|
+
<% MissionControl::Jobs.applications.each do |application| %>
|
|
7
|
+
<li class="<%= "is-active" if application == Current.application %>">
|
|
8
|
+
<%= link_to application.name, mission_control_jobs.application_jobs_path(application) %>
|
|
9
|
+
</li>
|
|
10
|
+
<% end %>
|
|
11
|
+
</ul>
|
|
12
|
+
</div>
|
|
13
|
+
<% end %>
|
|
14
|
+
<% end %>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<% if NeonSakura.config.nav_links.present? %>
|
|
2
|
+
<!-- Custom back link and auto-refresh inline section -->
|
|
3
|
+
<div class="mc-back-section">
|
|
4
|
+
<div class="container">
|
|
5
|
+
<a href="/">
|
|
6
|
+
<%= render "shared/icons/arrow_left" %>
|
|
7
|
+
Back to <%= NeonSakura.config.app_name %>
|
|
8
|
+
</a>
|
|
9
|
+
<button id="auto-refresh-btn">
|
|
10
|
+
<%= render "shared/icons/refresh" %>
|
|
11
|
+
Start Auto-refresh
|
|
12
|
+
</button>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
<% else %>
|
|
16
|
+
<!-- Default Mission Control back link -->
|
|
17
|
+
<a href="/">
|
|
18
|
+
<%= render "shared/icons/arrow_left" %>
|
|
19
|
+
Back to <%= NeonSakura.config.app_name %>
|
|
20
|
+
</a>
|
|
21
|
+
<% end %>
|