parapet-rails 0.1.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +48 -0
- data/Rakefile +7 -0
- data/app/assets/config/parapet/grammar-registry.json +52 -0
- data/app/assets/images/parapet/icons/activities/component-inspector.svg +4 -0
- data/app/assets/images/parapet/icons/activities/coverage-heatmap.svg +3 -0
- data/app/assets/images/parapet/icons/activities/discovery-sidebar.svg +3 -0
- data/app/assets/images/parapet/icons/activities/environment-toggles.svg +4 -0
- data/app/assets/images/parapet/icons/activities/failure-inspector.svg +5 -0
- data/app/assets/images/parapet/icons/activities/flaky-history.svg +3 -0
- data/app/assets/images/parapet/icons/activities/flight-recorder.svg +5 -0
- data/app/assets/images/parapet/icons/activities/health-graph.svg +3 -0
- data/app/assets/images/parapet/icons/activities/injection-engine.svg +3 -0
- data/app/assets/images/parapet/icons/activities/live-sql-stream.svg +5 -0
- data/app/assets/images/parapet/icons/activities/live-web-preview.svg +4 -0
- data/app/assets/images/parapet/icons/activities/monaco-editor.svg +5 -0
- data/app/assets/images/parapet/icons/activities/progress-area.svg +3 -0
- data/app/assets/images/parapet/icons/activities/registry-metadata.svg +5 -0
- data/app/assets/images/parapet/icons/activities/seed-inspector.svg +4 -0
- data/app/assets/images/parapet/icons/activities/slow-test-bottlenecks.svg +4 -0
- data/app/assets/images/parapet/icons/activities/smart-search.svg +4 -0
- data/app/assets/images/parapet/icons/activities/snippet-library.svg +4 -0
- data/app/assets/images/parapet/icons/activities/terminal.svg +4 -0
- data/app/assets/images/parapet/icons/activities/unified-schema-view.svg +4 -0
- data/app/assets/images/parapet/icons/activities/visual-comparison.svg +3 -0
- data/app/assets/images/parapet/icons/activities/visual-summary-bar.svg +5 -0
- data/app/assets/images/parapet/icons/nav/ideas.svg +4 -0
- data/app/assets/images/parapet/icons/nav/languages.svg +8 -0
- data/app/assets/images/parapet/icons/nav/profile.svg +4 -0
- data/app/assets/images/parapet/icons/nav/pulse.svg +7 -0
- data/app/assets/images/parapet/icons/nav/settings.svg +4 -0
- data/app/assets/images/parapet/icons/nav/state.svg +5 -0
- data/app/assets/images/parapet/icons/nav/styles.svg +7 -0
- data/app/assets/images/parapet/icons/nav/tests.svg +4 -0
- data/app/assets/images/parapet/icons/nav/traffic.svg +12 -0
- data/app/assets/javascripts/parapet/activity-card.js +439 -0
- data/app/assets/javascripts/parapet/activity-cards.js +291 -0
- data/app/assets/javascripts/parapet/activity-registry.js +181 -0
- data/app/assets/javascripts/parapet/activity-toggles.js +76 -0
- data/app/assets/javascripts/parapet/application.js +37 -0
- data/app/assets/javascripts/parapet/card-failure-inspector.js +134 -0
- data/app/assets/javascripts/parapet/card-file-editor.js +1722 -0
- data/app/assets/javascripts/parapet/card-progress-area.js +297 -0
- data/app/assets/javascripts/parapet/card-smart-search.js +114 -0
- data/app/assets/javascripts/parapet/card-snippet-library.js +251 -0
- data/app/assets/javascripts/parapet/explorer-resizer.js +92 -0
- data/app/assets/javascripts/parapet/parapet_qss.js +342 -0
- data/app/assets/javascripts/parapet/proficiency-filter.js +53 -0
- data/app/assets/javascripts/parapet/sidebar-resizer.js +197 -0
- data/app/assets/javascripts/parapet/styles-workbench.js +819 -0
- data/app/assets/javascripts/parapet/tree-logic.js +320 -0
- data/app/assets/stylesheets/parapet/_qss_variables.scss +122 -0
- data/app/assets/stylesheets/parapet/parapet_qss.scss +69 -0
- data/app/assets/stylesheets/parapet/qss_application.css +260940 -0
- data/app/assets/stylesheets/parapet/qss_application.css.map +1 -0
- data/app/assets/stylesheets/parapet/qss_application.scss +5 -0
- data/app/assets/stylesheets/parapet/qss_workspace.scss +209 -0
- data/app/assets/stylesheets/parapet/sample.qss +22 -0
- data/app/controllers/parapet/application_controller.rb +54 -0
- data/app/controllers/parapet/base_controller.rb +18 -0
- data/app/controllers/parapet/build_suite/dashboard_controller.rb +10 -0
- data/app/controllers/parapet/css_suite/compiler_controller.rb +95 -0
- data/app/controllers/parapet/css_suite/dashboard_controller.rb +10 -0
- data/app/controllers/parapet/css_suite/preview_controller.rb +46 -0
- data/app/controllers/parapet/dashboard_controller.rb +8 -0
- data/app/controllers/parapet/drafts_controller.rb +30 -0
- data/app/controllers/parapet/explorers_controller.rb +53 -0
- data/app/controllers/parapet/files_controller.rb +208 -0
- data/app/controllers/parapet/footers_controller.rb +24 -0
- data/app/controllers/parapet/intentions_suite/dashboard_controller.rb +10 -0
- data/app/controllers/parapet/landing_controller.rb +14 -0
- data/app/controllers/parapet/monitor_suite/dashboard_controller.rb +10 -0
- data/app/controllers/parapet/pm_suite/publish_controller.rb +16 -0
- data/app/controllers/parapet/polyglot_suite/dashboard_controller.rb +10 -0
- data/app/controllers/parapet/runner_controller.rb +34 -0
- data/app/controllers/parapet/sessions_controller.rb +35 -0
- data/app/controllers/parapet/signal_suite/dashboard_controller.rb +10 -0
- data/app/controllers/parapet/test_suite/dashboard_controller.rb +10 -0
- data/app/controllers/parapet/widgets_controller.rb +33 -0
- data/app/helpers/parapet/application_helper.rb +23 -0
- data/app/helpers/parapet/icon_helper.rb +57 -0
- data/app/jobs/parapet/application_job.rb +4 -0
- data/app/mailers/parapet/application_mailer.rb +6 -0
- data/app/models/parapet/activity.rb +14 -0
- data/app/models/parapet/application_record.rb +5 -0
- data/app/models/parapet/current.rb +6 -0
- data/app/models/parapet/draft_recovery.rb +9 -0
- data/app/models/parapet/internal_record.rb +34 -0
- data/app/models/parapet/run_history.rb +22 -0
- data/app/models/parapet/runtime_setting.rb +8 -0
- data/app/models/parapet/session.rb +7 -0
- data/app/models/parapet/suite_assignment.rb +30 -0
- data/app/models/parapet/user.rb +12 -0
- data/app/services/parapet/activity_phrase_renderer.rb +35 -0
- data/app/services/parapet/activity_writer.rb +26 -0
- data/app/services/parapet/authorization.rb +198 -0
- data/app/services/parapet/consistency_auditor.rb +58 -0
- data/app/services/parapet/directory_scanner.rb +159 -0
- data/app/services/parapet/draft_recovery_store.rb +18 -0
- data/app/services/parapet/explorer_manager.rb +402 -0
- data/app/services/parapet/file_save_service.rb +21 -0
- data/app/services/parapet/git_side_ref_committer.rb +50 -0
- data/app/services/parapet/noun_discovery.rb +26 -0
- data/app/services/parapet/publish_gate.rb +83 -0
- data/app/services/parapet/test_runner.rb +178 -0
- data/app/views/layouts/parapet/application.html.erb +83 -0
- data/app/views/layouts/parapet/landing.html.erb +19 -0
- data/app/views/parapet/activities/_ai_error_explainer.html.erb +33 -0
- data/app/views/parapet/activities/_ai_explainer_widget.html.erb +20 -0
- data/app/views/parapet/activities/_calendar_widget.html.erb +31 -0
- data/app/views/parapet/activities/_code_snippets_widget.html.erb +22 -0
- data/app/views/parapet/activities/_coverage_explorer.html.erb +1 -0
- data/app/views/parapet/activities/_dropdown_test.html.erb +20 -0
- data/app/views/parapet/activities/_explorer_node.html.erb +102 -0
- data/app/views/parapet/activities/_explorer_tree.html.erb +16 -0
- data/app/views/parapet/activities/_file_editor.html.erb +183 -0
- data/app/views/parapet/activities/_file_explorer.html.erb +23 -0
- data/app/views/parapet/activities/_fix_suggestions_widget.html.erb +13 -0
- data/app/views/parapet/activities/_generic.html.erb +6 -0
- data/app/views/parapet/activities/_live_preview.html.erb +13 -0
- data/app/views/parapet/activities/_perspective_filters.html.erb +36 -0
- data/app/views/parapet/activities/_progress_area.html.erb +37 -0
- data/app/views/parapet/activities/_project_explorer.html.erb +1 -0
- data/app/views/parapet/activities/_registry_explorer.html.erb +1 -0
- data/app/views/parapet/activities/_run_history.html.erb +2 -0
- data/app/views/parapet/activities/_smart_search.html.erb +11 -0
- data/app/views/parapet/activities/file_explorer/_perspective_scripts.html.erb +58 -0
- data/app/views/parapet/activities/file_explorer/_perspectives.html.erb +0 -0
- data/app/views/parapet/activities/file_explorer/_viewport_scripts.html.erb +33 -0
- data/app/views/parapet/css_suite/_workbench.html.erb +51 -0
- data/app/views/parapet/dashboards/index.html.erb +113 -0
- data/app/views/parapet/debug_panel.html.erb +79 -0
- data/app/views/parapet/debug_panel_tailwind.html.erb +98 -0
- data/app/views/parapet/erb_patch_test.html.erb +5 -0
- data/app/views/parapet/explorers/show.html.erb +1 -0
- data/app/views/parapet/git_viewer.html.erb +62 -0
- data/app/views/parapet/git_viewer_after.html.erb +14 -0
- data/app/views/parapet/git_viewer_before.html.erb +14 -0
- data/app/views/parapet/git_viewer_governed.html.erb +88 -0
- data/app/views/parapet/git_viewer_governed_after.html.erb +36 -0
- data/app/views/parapet/git_viewer_governed_before.html.erb +36 -0
- data/app/views/parapet/git_viewer_tailwind.html.erb +88 -0
- data/app/views/parapet/git_viewer_thin_wrapper.html.erb +85 -0
- data/app/views/parapet/hierarchical_layout_test.html.erb +85 -0
- data/app/views/parapet/hybrid_stress_test.html.erb +25 -0
- data/app/views/parapet/index.html.erb +35 -0
- data/app/views/parapet/mutation_stress_test.html.erb +82 -0
- data/app/views/parapet/recursive_anchoring_test.html.erb +66 -0
- data/app/views/parapet/sessions/new.html.erb +28 -0
- data/app/views/parapet/shared/_cad_inspector.html.erb +92 -0
- data/app/views/parapet/shared/_discovery_sidebar.html.erb +48 -0
- data/app/views/parapet/shared/_explorer_bar.html.erb +71 -0
- data/app/views/parapet/shared/_footer.html.erb +10 -0
- data/app/views/parapet/shared/_footer_ideas.html.erb +36 -0
- data/app/views/parapet/shared/_footer_languages.html.erb +33 -0
- data/app/views/parapet/shared/_footer_pulse.html.erb +33 -0
- data/app/views/parapet/shared/_footer_state.html.erb +33 -0
- data/app/views/parapet/shared/_footer_styles.html.erb +37 -0
- data/app/views/parapet/shared/_footer_tests.html.erb +39 -0
- data/app/views/parapet/shared/_footer_traffic.html.erb +45 -0
- data/app/views/parapet/shared/_global_nav.html.erb +55 -0
- data/app/views/parapet/shared/_head.html.erb +4 -0
- data/app/views/parapet/shared/_header.html.erb +20 -0
- data/app/views/parapet/shared/_object_inspector.html.erb +76 -0
- data/app/views/parapet/shared/_ribbon_header.html.erb +3 -0
- data/app/views/parapet/shared/_root_selector_modal.html.erb +15 -0
- data/app/views/parapet/shared/_tab_bar.html.erb +62 -0
- data/app/views/parapet/shared/activities_ribbon/_agent_group.html.erb +15 -0
- data/app/views/parapet/shared/activities_ribbon/_analysis_group.html.erb +15 -0
- data/app/views/parapet/shared/activities_ribbon/_architect_group.html.erb +21 -0
- data/app/views/parapet/shared/activities_ribbon/_explorer_group.html.erb +12 -0
- data/app/views/parapet/shared/activities_ribbon/_inspector_group.html.erb +15 -0
- data/app/views/parapet/shared/activities_ribbon/_proficiency_filter.html.erb +24 -0
- data/app/views/parapet/shared/activities_ribbon/_runner_group.html.erb +21 -0
- data/app/views/parapet/shared/activities_ribbon/_spatial_inspector_group.html.erb +18 -0
- data/app/views/parapet/shared/activities_ribbon/_studio_group.html.erb +18 -0
- data/app/views/parapet/shared/activities_ribbon/_stylist_group.html.erb +18 -0
- data/app/views/parapet/shared/explorers/_base.html.erb +107 -0
- data/app/views/parapet/shared/explorers/_css.html.erb +1 -0
- data/app/views/parapet/shared/explorers/_intentions.html.erb +1 -0
- data/app/views/parapet/shared/explorers/_project.html.erb +1 -0
- data/app/views/parapet/shared/explorers/_tests.html.erb +1 -0
- data/app/views/parapet/shared/head/_meta.html.erb +3 -0
- data/app/views/parapet/shared/head/_styles_activities.html.erb +152 -0
- data/app/views/parapet/shared/head/_styles_shared.html.erb +52 -0
- data/app/views/parapet/shared/head/_styles_workspace.html.erb +67 -0
- data/app/views/parapet/shared/ribbon_header/_diagnostic_controls.html.erb +17 -0
- data/app/views/parapet/shared/ribbon_header/_execution_controls.html.erb +28 -0
- data/app/views/parapet/shared/ribbon_header/_focus_search.html.erb +5 -0
- data/app/views/parapet/shared/root_selector/_header.html.erb +10 -0
- data/app/views/parapet/shared/root_selector/_path_input.html.erb +13 -0
- data/app/views/parapet/shared/root_selector/_quick_select.html.erb +20 -0
- data/app/views/parapet/shared/root_selector/_scripts.html.erb +61 -0
- data/app/views/parapet/shared/root_selector/_warning.html.erb +11 -0
- data/app/views/parapet/shared/widgets/_base.html.erb +6 -0
- data/app/views/parapet/shared/widgets/_css.html.erb +79 -0
- data/app/views/parapet/widgets/_explainer.html.erb +17 -0
- data/app/views/parapet/widgets/_hotkeys.html.erb +20 -0
- data/app/views/parapet/widgets/_search.html.erb +68 -0
- data/app/views/parapet/widgets/_spatial_inspector.html.erb +54 -0
- data/app/views/parapet/widgets/_widget-explainer.html.erb +17 -0
- data/app/views/parapet/widgets/_widget-hotkeys.html.erb +20 -0
- data/app/views/parapet/widgets/_widget-search.html.erb +68 -0
- data/app/views/parapet/widgets/_widget-snippets.html.erb +18 -0
- data/app/views/parapet/widgets/_widget-suggestions.html.erb +15 -0
- data/app/views/parapet/widgets/show.html.erb +3 -0
- data/app/views/shared/_footer.html.erb +0 -0
- data/config/parapet/qss_config.yml +117 -0
- data/config/parapet/qss_registry.yml +14 -0
- data/config/routes.rb +40 -0
- data/db/migrate/20260916035300_create_run_histories.rb +16 -0
- data/db/migrate/20261002000000_expand_run_histories_for_activity_tracking.rb +7 -0
- data/db/parapet_migrate/20260929184200_create_parapet_users.rb +12 -0
- data/db/parapet_migrate/20260929184300_create_parapet_sessions.rb +9 -0
- data/db/parapet_migrate/20260930000000_create_parapet_runtime_settings.rb +12 -0
- data/db/parapet_migrate/20260930000100_create_parapet_suite_assignments.rb +14 -0
- data/db/parapet_migrate/20261002000000_create_parapet_activities.rb +16 -0
- data/db/parapet_migrate/20261002000100_create_parapet_draft_recoveries.rb +13 -0
- data/db/parapet_schema.rb +64 -0
- data/db/schema.rb +21 -0
- data/db/schema.released.rb +0 -0
- data/lib/parapet/engine.rb +11 -0
- data/lib/parapet/environment_guard.rb +14 -0
- data/lib/parapet/version.rb +3 -0
- data/lib/parapet-rails.rb +1 -0
- data/lib/parapet.rb +59 -0
- data/lib/tasks/parapet_qss.rake +26 -0
- data/lib/tasks/parapet_tasks.rake +25 -0
- data/lib/tasks/randomize_explorer_health.rb +101 -0
- metadata +423 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
(function(global) {
|
|
2
|
+
|
|
3
|
+
class ActivityCard {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
this.id = options.id || `card-${Math.random().toString(36).substr(2, 9)}`;
|
|
6
|
+
|
|
7
|
+
// Subclasses declare identity via getters (cardTitle, cardIcon, cardKeyArea, cardProficiency).
|
|
8
|
+
// options can override at spawn time.
|
|
9
|
+
let icon = this.cardIcon || options.icon || 'layout';
|
|
10
|
+
if (!icon.includes('<svg') && !icon.includes('<i')) {
|
|
11
|
+
icon = `<i class="lucide lucide-${icon} qss-w[16] qss-h[16] qss-opacity-100"></i>`;
|
|
12
|
+
}
|
|
13
|
+
this.title = this.cardTitle || options.title || 'Untitled Activity';
|
|
14
|
+
this.icon = icon;
|
|
15
|
+
this.keyArea = this.cardKeyArea || options.keyArea || 'general';
|
|
16
|
+
this.proficiency = this.cardProficiency !== undefined ? this.cardProficiency : (options.proficiency || 0);
|
|
17
|
+
|
|
18
|
+
this.element = null;
|
|
19
|
+
this.isDragging = false;
|
|
20
|
+
this.isResizing = false;
|
|
21
|
+
|
|
22
|
+
// Merge: base defaults → subclass defaults() → spawn-time options
|
|
23
|
+
const initialProps = { ...this.defaults(), ...options };
|
|
24
|
+
const savedState = this.loadState();
|
|
25
|
+
|
|
26
|
+
// Merge saved state over defaults to ensure new properties are present
|
|
27
|
+
this.state = savedState ? { ...initialProps, ...savedState } : initialProps;
|
|
28
|
+
|
|
29
|
+
// Initialize preferred dimensions for expansion logic
|
|
30
|
+
this.state.preferredWidth = this.state.preferredWidth || this.state.width;
|
|
31
|
+
this.state.preferredHeight = this.state.preferredHeight || this.state.height;
|
|
32
|
+
|
|
33
|
+
if (!ActivityCards.Z_LEVELS.includes(this.state.zIndex)) {
|
|
34
|
+
this.state.zIndex = this.getBaseZ();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Subclass provides content via buildContent()
|
|
38
|
+
this.contentHtml = this.buildContent();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Subclass overrides ────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
// Static identity — override as getters in subclass
|
|
44
|
+
get cardTitle() { return null; }
|
|
45
|
+
get cardIcon() { return null; }
|
|
46
|
+
get cardKeyArea() { return null; }
|
|
47
|
+
get cardProficiency() { return undefined; }
|
|
48
|
+
|
|
49
|
+
// Default state shape — subclass spreads and overrides
|
|
50
|
+
defaults() {
|
|
51
|
+
return {
|
|
52
|
+
width: 400, height: 300, x: 20, y: 20, zIndex: 10,
|
|
53
|
+
pinned: false, active: true, orientation: 'neutral', size: 'medium',
|
|
54
|
+
minWidth: 150, minHeight: 100, userPositioned: false
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Card body HTML — subclass returns its own markup
|
|
59
|
+
buildContent() { return this._getFallbackContent(); }
|
|
60
|
+
|
|
61
|
+
// Lifecycle hooks — subclass overrides as needed
|
|
62
|
+
onInit() { }
|
|
63
|
+
onResize() { }
|
|
64
|
+
onDestroy() { }
|
|
65
|
+
|
|
66
|
+
// Card header actions — subclass returns HTML for additional buttons
|
|
67
|
+
renderHeaderActions() { return ''; }
|
|
68
|
+
|
|
69
|
+
// ── Internal ──────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
_getFallbackContent() {
|
|
72
|
+
const template = document.getElementById(`qss-tpl-${this.id}`);
|
|
73
|
+
if (template) return template.innerHTML;
|
|
74
|
+
return `
|
|
75
|
+
<div class="qss-flex qss-flex-col qss-gap[8] qss-p[16] qss-text-size[11] qss-font-mono qss-text-slate-500">
|
|
76
|
+
<div class="qss-flex qss-justify-between qss-border-b qss-border-slate-800/50 qss-pb[4]"><span>ID:</span><span class="qss-text-slate-300">${this.id}</span></div>
|
|
77
|
+
<div class="qss-flex qss-justify-between qss-border-b qss-border-slate-800/50 qss-pb[4]"><span>Proficiency:</span><span class="qss-text-slate-300">${this.proficiency}</span></div>
|
|
78
|
+
<div class="qss-flex qss-justify-between qss-border-b qss-border-slate-800/50 qss-pb[4]"><span>Key Area:</span><span class="qss-text-slate-300">${this.keyArea}</span></div>
|
|
79
|
+
<div class="qss-flex qss-justify-between qss-border-b qss-border-slate-800/50 qss-pb[4]"><span>Z-Index:</span><span class="qss-text-slate-300">${this.state.zIndex}</span></div>
|
|
80
|
+
</div>
|
|
81
|
+
`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
render(container) {
|
|
85
|
+
this.container = container;
|
|
86
|
+
const card = document.createElement('div');
|
|
87
|
+
card.id = this.id;
|
|
88
|
+
card.className = `qss-activity-card qss-proficiency-border-${this.proficiency} ${this.state.pinned ? 'qss-pinned' : ''}`;
|
|
89
|
+
card.setAttribute('data-key-area', this.keyArea);
|
|
90
|
+
card.setAttribute('data-proficiency', this.proficiency);
|
|
91
|
+
card.setAttribute('data-orientation', this.state.orientation);
|
|
92
|
+
card.setAttribute('data-size', this.state.size);
|
|
93
|
+
|
|
94
|
+
// The coordinates (x,y) and dimensions (width,height) refer to the static Bounding Box.
|
|
95
|
+
card.style.width = `${this.state.width}px`;
|
|
96
|
+
card.style.height = `${this.state.height}px`;
|
|
97
|
+
card.style.zIndex = this.state.zIndex;
|
|
98
|
+
card.classList.add('qss-absolute');
|
|
99
|
+
card.style.left = `${this.state.x}px`;
|
|
100
|
+
card.style.top = `${this.state.y}px`;
|
|
101
|
+
|
|
102
|
+
card.innerHTML = `
|
|
103
|
+
<div class="qss-card-inner-container">
|
|
104
|
+
<div class="qss-card-header qss-px[6]">
|
|
105
|
+
<div class="qss-flex qss-items-center qss-gap[6] qss-overflow-hidden">
|
|
106
|
+
<div class="qss-flex qss-items-center qss-justify-center qss-w[16] qss-h[16] qss-text-slate-300 qss-activity-card-icon">
|
|
107
|
+
${this.icon}
|
|
108
|
+
</div>
|
|
109
|
+
<span class="qss-card-title qss-truncate">${this.title}</span>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="qss-flex qss-items-center qss-gap[8] qss-flex-shrink-0">
|
|
112
|
+
<div class="qss-header-actions qss-flex qss-items-center qss-gap[8]">
|
|
113
|
+
${this.renderHeaderActions()}
|
|
114
|
+
</div>
|
|
115
|
+
<div class="qss-flex qss-items-center qss-bg-slate-900/50 qss-rounded qss-px[4] qss-py[2] qss-gap[6] qss-border qss-border-slate-800/50">
|
|
116
|
+
<div class="qss-z-down-btn qss-cursor-pointer qss-text-slate-500 hover:qss-text-white qss-flex qss-items-center qss-justify-center qss-w[10] qss-h[10] qss-transition-colors">
|
|
117
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" style="width:100%;height:100%;display:block;"><polyline points="6 9 12 15 18 9"></polyline></svg>
|
|
118
|
+
</div>
|
|
119
|
+
<span class="qss-text-size[9] qss-font-bold qss-text-slate-300 qss-min-w-[12px] qss-text-center qss-z-index-display qss-select-none">${ActivityCards.zLabel(this.state.zIndex)}</span>
|
|
120
|
+
<div class="qss-z-up-btn qss-cursor-pointer qss-text-slate-500 hover:qss-text-white qss-flex qss-items-center qss-justify-center qss-w[10] qss-h[10] qss-transition-colors">
|
|
121
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" style="width:100%;height:100%;display:block;"><polyline points="18 15 12 9 6 15"></polyline></svg>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
<div class="qss-pin-btn qss-cursor-pointer qss-text-slate-500 hover:qss-text-indigo-400 qss-flex qss-items-center qss-justify-center qss-w[12] qss-h[12] qss-transition-colors ${this.state.pinned ? 'qss-pin-active' : ''}">
|
|
125
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="width:100%;height:100%;display:block;">
|
|
126
|
+
<line x1="12" y1="17" x2="12" y2="22"></line>
|
|
127
|
+
<path d="M5 17h14v-1.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V6a3 3 0 0 0-3-3 3 3 0 0 0-3 3v4.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24Z"></path>
|
|
128
|
+
</svg>
|
|
129
|
+
</div>
|
|
130
|
+
<div class="qss-w[1] qss-h[12] qss-bg-slate-800/50"></div>
|
|
131
|
+
<div class="qss-close-btn qss-cursor-pointer qss-text-slate-500 hover:qss-text-red-400 qss-items-center qss-justify-center qss-w[12] qss-h[12] qss-transition-colors ${this.id === 'file-editor' && ActivityCards.getProficiencyLevel() < 2 ? 'qss-hidden' : 'qss-flex'}">
|
|
132
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" style="width:100%;height:100%;display:block;">
|
|
133
|
+
<line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line>
|
|
134
|
+
</svg>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
<div class="qss-card-content qss-custom-scrollbar">${this.contentHtml}</div>
|
|
139
|
+
<div class="qss-resize-handle qss-absolute qss-bottom-1 qss-right-1 qss-w[16] qss-h[16] qss-cursor-se-resize qss-flex qss-items-end qss-justify-end qss-p[2] qss-opacity-30 hover:qss-opacity-100">
|
|
140
|
+
<svg width="8" height="8" viewBox="0 0 8 8" fill="none"><path d="M7 1V7H1" stroke="currentColor" stroke-width="1" stroke-linecap="round"/></svg>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
this.element = card;
|
|
146
|
+
const targetLayer = this.getTargetLayer(container);
|
|
147
|
+
targetLayer.appendChild(card);
|
|
148
|
+
this.constrainToBoundaries();
|
|
149
|
+
this.alignToGrid();
|
|
150
|
+
this.updateElementStyles();
|
|
151
|
+
this.initInteractions();
|
|
152
|
+
this.onInit();
|
|
153
|
+
return card;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
getTargetLayer(container) {
|
|
157
|
+
if (!this.state.pinned) return container.querySelector('#pp-floating-layer') || container;
|
|
158
|
+
return container.querySelector(`[data-layer="${this.proficiency}"]`) || container;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
initInteractions() {
|
|
162
|
+
const header = this.element.querySelector('.qss-card-header');
|
|
163
|
+
const resizeHandle = this.element.querySelector('.qss-resize-handle');
|
|
164
|
+
const closeBtn = this.element.querySelector('.qss-close-btn');
|
|
165
|
+
const pinBtn = this.element.querySelector('.qss-pin-btn');
|
|
166
|
+
const zUpBtn = this.element.querySelector('.qss-z-up-btn');
|
|
167
|
+
const zDownBtn = this.element.querySelector('.qss-z-down-btn');
|
|
168
|
+
const zDisplay = this.element.querySelector('.qss-z-index-display');
|
|
169
|
+
|
|
170
|
+
zUpBtn.addEventListener('click', (e) => {
|
|
171
|
+
e.stopPropagation();
|
|
172
|
+
const levels = ActivityCards.Z_LEVELS;
|
|
173
|
+
const idx = levels.indexOf(this.state.zIndex);
|
|
174
|
+
const nextIdx = Math.min(idx >= 0 ? idx + 1 : 0, levels.length - 1);
|
|
175
|
+
this.state.zIndex = levels[nextIdx];
|
|
176
|
+
this.element.classList.toggle('qss-promoted', this.state.zIndex === ActivityCards.PROMOTE_Z);
|
|
177
|
+
this.updateZIndexUI(zDisplay);
|
|
178
|
+
});
|
|
179
|
+
zDownBtn.addEventListener('click', (e) => {
|
|
180
|
+
e.stopPropagation();
|
|
181
|
+
const levels = ActivityCards.Z_LEVELS;
|
|
182
|
+
const idx = levels.indexOf(this.state.zIndex);
|
|
183
|
+
if (idx <= 0) return;
|
|
184
|
+
this.state.zIndex = levels[idx - 1];
|
|
185
|
+
this.element.classList.remove('qss-promoted');
|
|
186
|
+
if (ActivityCards.state.promotedCardId === this.id) ActivityCards.state.promotedCardId = null;
|
|
187
|
+
this.updateZIndexUI(zDisplay);
|
|
188
|
+
});
|
|
189
|
+
pinBtn.addEventListener('click', (e) => { e.stopPropagation(); this.togglePin(); });
|
|
190
|
+
header.addEventListener('mousedown', (e) => this.startDrag(e));
|
|
191
|
+
resizeHandle.addEventListener('mousedown', (e) => this.startResize(e));
|
|
192
|
+
closeBtn.addEventListener('click', () => this.destroy());
|
|
193
|
+
|
|
194
|
+
document.addEventListener('mousemove', (e) => {
|
|
195
|
+
if (this.isDragging) { e.preventDefault(); this.drag(e); }
|
|
196
|
+
if (this.isResizing) { e.preventDefault(); this.resize(e); }
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
document.addEventListener('mouseup', (e) => {
|
|
200
|
+
if (this.isDragging || this.isResizing) {
|
|
201
|
+
const wasDragging = this.isDragging;
|
|
202
|
+
const wasResizing = this.isResizing;
|
|
203
|
+
this.isDragging = false;
|
|
204
|
+
this.isResizing = false;
|
|
205
|
+
this.element.classList.remove('qss-dragging');
|
|
206
|
+
document.body.style.userSelect = '';
|
|
207
|
+
this.alignToGrid();
|
|
208
|
+
if (e.ctrlKey) this.applyMagneticSnapping();
|
|
209
|
+
if (wasDragging || wasResizing) {
|
|
210
|
+
this.state.userPositioned = true;
|
|
211
|
+
this.handleCollisions();
|
|
212
|
+
const restoreZ = this._preDragZIndex !== undefined ? this._preDragZIndex : this.getBaseZ();
|
|
213
|
+
this.state.zIndex = restoreZ;
|
|
214
|
+
this.element.style.zIndex = restoreZ;
|
|
215
|
+
const zDisplay = this.element.querySelector('.qss-z-index-display');
|
|
216
|
+
if (zDisplay) zDisplay.textContent = ActivityCards.zLabel(restoreZ);
|
|
217
|
+
this._preDragZIndex = undefined;
|
|
218
|
+
}
|
|
219
|
+
this.saveState();
|
|
220
|
+
this.updateElementStyles();
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
alignToGrid() {
|
|
226
|
+
const G = ActivityCards.settings.gridSize;
|
|
227
|
+
this.state.x = Math.round(this.state.x / G) * G;
|
|
228
|
+
this.state.y = Math.round(this.state.y / G) * G;
|
|
229
|
+
this.state.width = Math.round(this.state.width / G) * G;
|
|
230
|
+
this.state.height = Math.round(this.state.height / G) * G;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
applyMagneticSnapping() {
|
|
234
|
+
const G = ActivityCards.settings.gridSize;
|
|
235
|
+
const THRESHOLD = G * 2;
|
|
236
|
+
const MARGIN = G;
|
|
237
|
+
const peers = Object.values(ActivityCards.active).filter(c => c !== this && c.state.zIndex === this.state.zIndex);
|
|
238
|
+
peers.forEach(t => {
|
|
239
|
+
const tr = { left: t.state.x, right: t.state.x + t.state.width, top: t.state.y, bottom: t.state.y + t.state.height };
|
|
240
|
+
const sr = { left: this.state.x, right: this.state.x + this.state.width, top: this.state.y, bottom: this.state.y + this.state.height };
|
|
241
|
+
if (Math.abs(sr.left - (tr.right + MARGIN)) < THRESHOLD) this.state.x = tr.right + MARGIN;
|
|
242
|
+
else if (Math.abs(sr.right - (tr.left - MARGIN)) < THRESHOLD) this.state.x = tr.left - MARGIN - this.state.width;
|
|
243
|
+
if (Math.abs(sr.top - (tr.bottom + MARGIN)) < THRESHOLD) this.state.y = tr.bottom + MARGIN;
|
|
244
|
+
else if (Math.abs(sr.bottom - (tr.top - MARGIN)) < THRESHOLD) this.state.y = tr.top - MARGIN - this.state.height;
|
|
245
|
+
});
|
|
246
|
+
if (this.container) {
|
|
247
|
+
const cRect = this.container.getBoundingClientRect();
|
|
248
|
+
if (Math.abs(this.state.x) < THRESHOLD) this.state.x = 0;
|
|
249
|
+
if (Math.abs(this.state.y) < THRESHOLD) this.state.y = 0;
|
|
250
|
+
if (Math.abs(this.state.x + this.state.width - cRect.width) < THRESHOLD) this.state.x = cRect.width - this.state.width;
|
|
251
|
+
if (Math.abs(this.state.y + this.state.height - cRect.height) < THRESHOLD) this.state.y = cRect.height - this.state.height;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
handleCollisions() {
|
|
256
|
+
const peers = Object.values(ActivityCards.active).filter(c => c !== this && c.state.zIndex === this.state.zIndex);
|
|
257
|
+
let found = true, safety = 0;
|
|
258
|
+
while (found && safety < 10) {
|
|
259
|
+
found = false;
|
|
260
|
+
for (const t of peers) {
|
|
261
|
+
if (this.isOverlapping(this.state, t.state)) { found = true; this.nudgeCard(t); break; }
|
|
262
|
+
}
|
|
263
|
+
safety++;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
isOverlapping(r1, r2) { return !(r1.x + r1.width <= r2.x || r2.x + r2.width <= r1.x || r1.y + r1.height <= r2.y || r2.y + r2.height <= r1.y); }
|
|
268
|
+
|
|
269
|
+
nudgeCard(target) {
|
|
270
|
+
target.state.x += ActivityCards.settings.gridSize;
|
|
271
|
+
target.alignToGrid();
|
|
272
|
+
target.constrainToBoundaries();
|
|
273
|
+
target.updateElementStyles();
|
|
274
|
+
target.saveState();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
updateElementStyles() {
|
|
278
|
+
if (!this.element) return;
|
|
279
|
+
this.element.style.width = `${this.state.width}px`;
|
|
280
|
+
this.element.style.height = `${this.state.height}px`;
|
|
281
|
+
if (!this.state.pinned) {
|
|
282
|
+
this.element.style.left = `${this.state.x}px`;
|
|
283
|
+
this.element.style.top = `${this.state.y}px`;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
updateZIndexUI(displayElement) {
|
|
288
|
+
this.element.style.zIndex = this.state.zIndex;
|
|
289
|
+
if (displayElement) displayElement.textContent = ActivityCards.zLabel(this.state.zIndex);
|
|
290
|
+
this.saveState();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
getBaseZ() {
|
|
294
|
+
const levels = ActivityCards.Z_LEVELS;
|
|
295
|
+
return levels[Math.min(this.proficiency, levels.length - 2)];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
demote() {
|
|
299
|
+
const base = this.getBaseZ();
|
|
300
|
+
this.state.zIndex = base;
|
|
301
|
+
this.element.style.zIndex = base;
|
|
302
|
+
this.element.classList.remove('qss-promoted');
|
|
303
|
+
const zDisplay = this.element.querySelector('.qss-z-index-display');
|
|
304
|
+
if (zDisplay) zDisplay.textContent = ActivityCards.zLabel(base);
|
|
305
|
+
if (ActivityCards.state.promotedCardId === this.id) ActivityCards.state.promotedCardId = null;
|
|
306
|
+
this.saveState();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
togglePin() {
|
|
310
|
+
this.state.pinned = !this.state.pinned;
|
|
311
|
+
const pinBtn = this.element.querySelector('.qss-pin-btn');
|
|
312
|
+
if (this.state.pinned) { this.element.classList.add('qss-pinned'); pinBtn.classList.add('qss-pin-active'); }
|
|
313
|
+
else { this.element.classList.remove('qss-pinned'); pinBtn.classList.remove('qss-pin-active'); }
|
|
314
|
+
if (this.container) { this.getTargetLayer(this.container).appendChild(this.element); }
|
|
315
|
+
this.saveState();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
startDrag(e) {
|
|
319
|
+
if (this.state.pinned) return;
|
|
320
|
+
if (e.target.closest('.qss-card-header')) {
|
|
321
|
+
this.isDragging = true;
|
|
322
|
+
this.dragStartX = e.clientX - this.element.offsetLeft;
|
|
323
|
+
this.dragStartY = e.clientY - this.element.offsetTop;
|
|
324
|
+
this._preDragZIndex = this.state.zIndex;
|
|
325
|
+
ActivityCards.state.lastFocused = this.id;
|
|
326
|
+
this.element.style.zIndex = ActivityCards.DRAG_Z;
|
|
327
|
+
this.element.classList.add('qss-dragging');
|
|
328
|
+
document.body.style.userSelect = 'none';
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
drag(e) {
|
|
333
|
+
if (!this.isDragging || this.state.pinned) return;
|
|
334
|
+
this.state.x = e.clientX - this.dragStartX;
|
|
335
|
+
this.state.y = e.clientY - this.dragStartY;
|
|
336
|
+
this.constrainToBoundaries();
|
|
337
|
+
this.element.style.left = `${this.state.x}px`;
|
|
338
|
+
this.element.style.top = `${this.state.y}px`;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
constrainToBoundaries(targetW, targetH) {
|
|
342
|
+
if (!this.container && targetW === undefined) return;
|
|
343
|
+
|
|
344
|
+
// Use passed dimensions (from ResizeObserver) or measure container
|
|
345
|
+
const widthBound = targetW !== undefined ? targetW : this.container.getBoundingClientRect().width;
|
|
346
|
+
const heightBound = targetH !== undefined ? targetH : this.container.getBoundingClientRect().height;
|
|
347
|
+
|
|
348
|
+
// Expansion Logic: Try to grow back to preferred size if space was previously restricted
|
|
349
|
+
if (this.state.width < this.state.preferredWidth && this.state.width < widthBound) {
|
|
350
|
+
this.state.width = Math.min(this.state.preferredWidth, widthBound - this.state.x);
|
|
351
|
+
}
|
|
352
|
+
if (this.state.height < this.state.preferredHeight && this.state.height < heightBound) {
|
|
353
|
+
this.state.height = Math.min(this.state.preferredHeight, heightBound - this.state.y);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Shrink Logic: Force shrink if card is now larger than workspace
|
|
357
|
+
if (this.state.width > widthBound) {
|
|
358
|
+
this.state.width = Math.max(this.state.minWidth, widthBound);
|
|
359
|
+
}
|
|
360
|
+
if (this.state.height > heightBound) {
|
|
361
|
+
this.state.height = Math.max(this.state.minHeight, heightBound);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Reposition Logic: Push card back into view if it's hanging off right/bottom
|
|
365
|
+
this.state.x = Math.max(0, Math.min(this.state.x, widthBound - this.state.width));
|
|
366
|
+
this.state.y = Math.max(0, Math.min(this.state.y, heightBound - this.state.height));
|
|
367
|
+
|
|
368
|
+
// Final safety clamp
|
|
369
|
+
if (this.state.x + this.state.width > widthBound) {
|
|
370
|
+
this.state.width = Math.max(this.state.minWidth, widthBound - this.state.x);
|
|
371
|
+
}
|
|
372
|
+
if (this.state.y + this.state.height > heightBound) {
|
|
373
|
+
this.state.height = Math.max(this.state.minHeight, heightBound - this.state.y);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
startResize(e) {
|
|
378
|
+
if (this.state.pinned) return;
|
|
379
|
+
this.isResizing = true;
|
|
380
|
+
this.resizeStartX = e.clientX;
|
|
381
|
+
this.resizeStartY = e.clientY;
|
|
382
|
+
this.resizeStartWidth = this.element.offsetWidth;
|
|
383
|
+
this.resizeStartHeight = this.element.offsetHeight;
|
|
384
|
+
e.stopPropagation();
|
|
385
|
+
e.preventDefault();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
resize(e) {
|
|
389
|
+
if (!this.isResizing) return;
|
|
390
|
+
let newW = this.resizeStartWidth + (e.clientX - this.resizeStartX);
|
|
391
|
+
let newH = this.resizeStartHeight + (e.clientY - this.resizeStartY);
|
|
392
|
+
newW = Math.max(this.state.minWidth, newW);
|
|
393
|
+
newH = Math.max(this.state.minHeight, newH);
|
|
394
|
+
if (this.state.maxWidth) newW = Math.min(this.state.maxWidth, newW);
|
|
395
|
+
if (this.state.maxHeight) newH = Math.min(this.state.maxHeight, newH);
|
|
396
|
+
if (this.container) {
|
|
397
|
+
const cRect = this.container.getBoundingClientRect();
|
|
398
|
+
newW = Math.min(newW, cRect.width - this.state.x);
|
|
399
|
+
newH = Math.min(newH, cRect.height - this.state.y);
|
|
400
|
+
}
|
|
401
|
+
this.state.width = newW;
|
|
402
|
+
this.state.height = newH;
|
|
403
|
+
|
|
404
|
+
// Manual resize updates preferred dimensions
|
|
405
|
+
this.state.preferredWidth = newW;
|
|
406
|
+
this.state.preferredHeight = newH;
|
|
407
|
+
|
|
408
|
+
this.element.style.width = `${this.state.width}px`;
|
|
409
|
+
this.element.style.height = `${this.state.height}px`;
|
|
410
|
+
this.onResize();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
saveState() { ActivityCards.updateCardState(this.id, this.state); }
|
|
414
|
+
loadState() { return ActivityCards.getCardState(this.id); }
|
|
415
|
+
|
|
416
|
+
destroy() {
|
|
417
|
+
// Enforcement: File Editor cannot be closed at Novice/Intermediate levels
|
|
418
|
+
if (this.id === 'file-editor') {
|
|
419
|
+
const level = ActivityCards.getProficiencyLevel();
|
|
420
|
+
if (level !== -1 && level < 2) {
|
|
421
|
+
ActivityCards.showNotice("File Editor is required at this level", "error");
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
this.onDestroy();
|
|
427
|
+
if (this.element) this.element.remove();
|
|
428
|
+
if (ActivityCards.active[this.id]) {
|
|
429
|
+
delete ActivityCards.active[this.id];
|
|
430
|
+
this.state.visible = false;
|
|
431
|
+
ActivityCards.updateCardState(this.id, this.state);
|
|
432
|
+
}
|
|
433
|
+
document.dispatchEvent(new CustomEvent('cardClosed', { detail: { id: this.id } }));
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
global.ActivityCard = ActivityCard;
|
|
438
|
+
|
|
439
|
+
})(typeof window !== 'undefined' ? window : this);
|