DemoHostComponent.vala 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. using Spry;
  2. /**
  3. * DemoHostComponent - A component that hosts demo implementations with source/demo toggle
  4. *
  5. * This component provides a frame for demo content with the ability to toggle
  6. * between viewing the demo and viewing the source code.
  7. *
  8. * Usage:
  9. * var host = factory.create<DemoHostComponent>();
  10. * host.source_file = "examples/CounterComponent.vala";
  11. * host.set_outlet_child("demo-outlet", demo_component);
  12. */
  13. public class DemoHostComponent : Component {
  14. /// Path to the source file to display (relative to project root)
  15. public string source_file { get; set; default = ""; }
  16. /// Toggle state - true when showing source code
  17. public bool showing_source { get; set; default = false; }
  18. /// The loaded source code content (cached after first load)
  19. private string? _source_content = null;
  20. public override string markup { get {
  21. return """
  22. <div class="demo-host" sid="host">
  23. <div class="demo-header">
  24. <span class="demo-title" sid="title">Demo</span>
  25. <div class="demo-toggle-group">
  26. <button class="demo-toggle-btn" spry-action=":ShowDemo" spry-target="host"
  27. class-active-expr="!this.showing_source">Demo</button>
  28. <button class="demo-toggle-btn" spry-action=":ShowSource" spry-target="host"
  29. class-active-expr="this.showing_source">Source</button>
  30. </div>
  31. </div>
  32. <div class="demo-content" sid="content">
  33. <div spry-if="!this.showing_source">
  34. <spry-outlet sid="demo-outlet"/>
  35. </div>
  36. <div spry-if="this.showing_source">
  37. <pre class="demo-source"><code sid="source-code"></code></pre>
  38. </div>
  39. </div>
  40. </div>
  41. """;
  42. }}
  43. public override async void prepare() throws Error {
  44. // Update the source code display if we're showing source
  45. if (showing_source && _source_content != null) {
  46. this["source-code"].text_content = _source_content;
  47. }
  48. }
  49. public async override void handle_action(string action) throws Error {
  50. switch (action) {
  51. case "ShowDemo":
  52. showing_source = false;
  53. break;
  54. case "ShowSource":
  55. if (_source_content == null) {
  56. _source_content = yield load_source_file();
  57. }
  58. showing_source = true;
  59. break;
  60. }
  61. }
  62. /**
  63. * Load the source file from disk and escape HTML entities
  64. */
  65. private async string load_source_file() {
  66. try {
  67. // Try to read from the project root (relative path)
  68. var file = File.new_for_path(source_file);
  69. if (!file.query_exists()) {
  70. return @"Error: Source file not found: $source_file";
  71. }
  72. uint8[] contents;
  73. string etag_out;
  74. yield file.load_contents_async(null, out contents, out etag_out);
  75. var source = (string)contents;
  76. return escape_html(source);
  77. } catch (Error e) {
  78. return @"Error loading source file: $(e.message)";
  79. }
  80. }
  81. /**
  82. * Escape HTML entities for safe display in <pre><code> blocks
  83. */
  84. private string escape_html(string input) {
  85. var result = input
  86. .replace("&", "&amp;")
  87. .replace("<", "&lt;")
  88. .replace(">", "&gt;")
  89. .replace("\"", "&quot;")
  90. .replace("'", "&#39;");
  91. return result;
  92. }
  93. }