/ crates / willow-rummager / src / willow_panel.rs
willow_panel.rs
  1  use crate::db::kvp::KEY_VALUE_STORE;
  2  use crate::gpui;
  3  use crate::util::ResultExt as _;
  4  use anyhow::{Context as _, bail};
  5  use gpui::{prelude::FluentBuilder, *};
  6  use serde::{Deserialize, Serialize};
  7  
  8  // Import Entry from willow_ui for consistent data model
  9  use crate::willow_ui::Entry;
 10  
 11  // use willow_panel_settings::WillowPanelSettings;
 12  use crate::workspace::{
 13      Panel, Workspace,
 14      dock::{DockPosition, PanelEvent},
 15      ui::{
 16          ActiveTheme, Clickable, Color, Icon, IconButton, IconName, IconSize, Label, LabelCommon,
 17          LabelSize, ListItem, ListItemSpacing, h_flex, v_flex,
 18      },
 19  };
 20  
 21  actions!(
 22      workspace,
 23      [
 24          ToggleFocus,
 25          OpenWillow,
 26          CreateDocument,
 27          CreateSubspace,
 28          ViewDocument,
 29          DeleteDocument,
 30          RefreshDocuments,
 31          CloseDialog,
 32          SaveDocument,
 33      ]
 34  );
 35  
 36  pub fn init(cx: &mut App) {
 37      cx.observe_new(
 38          |workspace: &mut Workspace, window, cx: &mut Context<Workspace>| {
 39              let Some(window) = window else { return };
 40  
 41              workspace.register_action(|workspace, _: &ToggleFocus, window, cx| {
 42                  workspace.toggle_panel_focus::<WillowPanel>(window, cx);
 43              });
 44  
 45              cx.spawn_in(window, async move |workspace_handle, cx| {
 46                  let willow_panel = WillowPanel::load(workspace_handle.clone(), cx.clone()).await;
 47                  let Ok(willow_panel) = willow_panel else {
 48                      bail!("missing willow panel");
 49                  };
 50  
 51                  workspace_handle.update_in(cx, move |workspace, window, cx| {
 52                      workspace.add_panel(willow_panel, window, cx);
 53                  })?;
 54  
 55                  Ok(())
 56              })
 57              .detach();
 58          },
 59      )
 60      .detach();
 61  }
 62  
 63  const WILLOW_PANEL_KEY: &str = "WillowPanel";
 64  
 65  #[derive(Serialize, Deserialize)]
 66  struct SerializedWillowPanel {
 67      width: Option<Pixels>,
 68  }
 69  
 70  // Remove old structures and use Entry from willow_ui instead
 71  #[derive(Debug, Clone)]
 72  enum DialogState {
 73      None,
 74      CreateDocument { path: String, _content: String },
 75      CreateSubspace {},
 76      ViewDocument { entry: Entry },
 77  }
 78  
 79  pub struct WillowPanel {
 80      width: Option<Pixels>,
 81      pending_serialization: Task<Option<()>>,
 82      focus_handle: FocusHandle,
 83      entries: Vec<Entry>,
 84      dialog_state: DialogState,
 85      // TODO: Add Willow store when dependencies are working
 86      // store: Arc<WillowStore>,
 87  }
 88  
 89  impl WillowPanel {
 90      pub async fn load(
 91          workspace: WeakEntity<Workspace>,
 92          mut cx: AsyncWindowContext,
 93      ) -> anyhow::Result<Entity<Self>> {
 94          let serialized_panel = cx
 95              .background_executor()
 96              .spawn(async move { KEY_VALUE_STORE.read_kvp(WILLOW_PANEL_KEY) })
 97              .await
 98              .context("loading willow panel")
 99              .log_err()
100              .flatten()
101              .map(|panel| serde_json::from_str::<SerializedWillowPanel>(&panel))
102              .transpose()
103              .log_err()
104              .flatten();
105  
106          workspace.update_in(&mut cx, |workspace, window, cx| {
107              let panel = Self::new(workspace, window, cx);
108              if let Some(serialized_panel) = serialized_panel {
109                  panel.update(cx, |panel, cx| {
110                      panel.width = serialized_panel.width.map(|px| px.round());
111                      cx.notify();
112                  });
113              }
114              panel
115          })
116      }
117  
118      pub fn new(
119          workspace: &mut Workspace,
120          _window: &mut Window,
121          cx: &mut Context<Workspace>,
122      ) -> Entity<Self> {
123          let _user_store = workspace.app_state().user_store.clone();
124  
125          cx.new(|cx| {
126              let panel = Self {
127                  width: None,
128                  pending_serialization: Task::ready(None),
129                  focus_handle: cx.focus_handle(),
130                  entries: vec![
131                      // Sample data matching willow_ui entries
132                      Entry {
133                          namespace_id: "family".to_string(),
134                          subspace_id: "alice".to_string(),
135                          path: "/family/alice/Documents".to_string(),
136                          timestamp: 1704067200, // 2 days ago
137                      },
138                      Entry {
139                          namespace_id: "family".to_string(),
140                          subspace_id: "alice".to_string(),
141                          path: "/family/alice/Photos".to_string(),
142                          timestamp: 1703462400, // 1 week ago
143                      },
144                      Entry {
145                          namespace_id: "family".to_string(),
146                          subspace_id: "bob".to_string(),
147                          path: "/family/bob/Music".to_string(),
148                          timestamp: 1703980800, // 3 days ago
149                      },
150                      Entry {
151                          namespace_id: "work".to_string(),
152                          subspace_id: "projects".to_string(),
153                          path: "/work/projects/willow-fs".to_string(),
154                          timestamp: 1704153600, // 1 hour ago
155                      },
156                      Entry {
157                          namespace_id: "work".to_string(),
158                          subspace_id: "projects".to_string(),
159                          path: "/work/projects/presentation.pdf".to_string(),
160                          timestamp: 1704067200, // Yesterday
161                      },
162                      Entry {
163                          namespace_id: "photos".to_string(),
164                          subspace_id: "vacation_2024".to_string(),
165                          path: "/photos/vacation_2024/beach.jpg".to_string(),
166                          timestamp: 1702857600, // 2 weeks ago
167                      },
168                      Entry {
169                          namespace_id: "photos".to_string(),
170                          subspace_id: "vacation_2024".to_string(),
171                          path: "/photos/vacation_2024/mountains.jpg".to_string(),
172                          timestamp: 1702857600, // 2 weeks ago
173                      },
174                  ],
175                  dialog_state: DialogState::None,
176              };
177  
178              panel
179          })
180      }
181  
182      pub fn create_document(
183          &mut self,
184          _action: &CreateDocument,
185          _window: &mut Window,
186          cx: &mut Context<Self>,
187      ) {
188          self.dialog_state = DialogState::CreateDocument {
189              path: String::new(),
190              _content: String::new(),
191          };
192          cx.notify();
193      }
194  
195      pub fn create_subspace(
196          &mut self,
197          _action: &CreateSubspace,
198          _window: &mut Window,
199          cx: &mut Context<Self>,
200      ) {
201          self.dialog_state = DialogState::CreateSubspace {};
202          cx.notify();
203      }
204  
205      pub fn close_dialog(
206          &mut self,
207          _action: &CloseDialog,
208          _window: &mut Window,
209          cx: &mut Context<Self>,
210      ) {
211          self.dialog_state = DialogState::None;
212          cx.notify();
213      }
214  
215      pub fn save_document(
216          &mut self,
217          _action: &SaveDocument,
218          _window: &mut Window,
219          cx: &mut Context<Self>,
220      ) {
221          // For demo purposes, create a mock entry
222          if let DialogState::CreateDocument { path, _content: _ } = &self.dialog_state {
223              if !path.is_empty() {
224                  let new_entry = Entry {
225                      namespace_id: "user".to_string(),
226                      subspace_id: "documents".to_string(),
227                      path: path.clone(),
228                      timestamp: 1704157200, // Current timestamp (example)
229                  };
230  
231                  self.entries.push(new_entry);
232                  self.dialog_state = DialogState::None;
233                  cx.notify();
234              }
235          }
236      }
237  
238      pub fn delete_document(&mut self, entry: &Entry, _window: &mut Window, cx: &mut Context<Self>) {
239          self.entries.retain(|e| {
240              !(e.path == entry.path
241                  && e.namespace_id == entry.namespace_id
242                  && e.subspace_id == entry.subspace_id)
243          });
244          cx.notify();
245      }
246  }
247  
248  impl Focusable for WillowPanel {
249      fn focus_handle(&self, _cx: &App) -> FocusHandle {
250          self.focus_handle.clone()
251      }
252  }
253  
254  impl EventEmitter<PanelEvent> for WillowPanel {}
255  
256  impl Panel for WillowPanel {
257      fn persistent_name() -> &'static str {
258          "Willow"
259      }
260  
261      fn position(&self, _window: &Window, _cx: &App) -> DockPosition {
262          DockPosition::Left
263      }
264  
265      fn position_is_valid(&self, position: DockPosition) -> bool {
266          matches!(position, DockPosition::Left | DockPosition::Right)
267      }
268  
269      fn set_position(
270          &mut self,
271          _position: DockPosition,
272          _window: &mut Window,
273          _cx: &mut Context<Self>,
274      ) {
275          // Position changes are handled by the dock
276      }
277  
278      fn size(&self, _window: &Window, _cx: &App) -> Pixels {
279          self.width.unwrap_or(px(320.))
280          // .unwrap_or_else(|| {
281          // WillowPanelSettings::get_global(cx)
282          //     .default_width
283          //     .unwrap_or(px(320.))
284          // })
285      }
286  
287      fn set_size(&mut self, size: Option<Pixels>, _window: &mut Window, cx: &mut Context<Self>) {
288          self.width = size;
289          self.serialize(cx);
290      }
291  
292      fn icon(&self, _window: &Window, _cx: &App) -> Option<IconName> {
293          Some(IconName::DatabaseZap)
294      }
295  
296      fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
297          Some("Willow Panel - Distributed data store explorer")
298      }
299  
300      fn toggle_action(&self) -> Box<dyn Action> {
301          Box::new(ToggleFocus)
302      }
303  
304      fn activation_priority(&self) -> u32 {
305          3
306      }
307  
308      fn panel_key() -> &'static str {
309          "willow"
310      }
311  }
312  
313  impl WillowPanel {
314      fn serialize(&mut self, cx: &mut Context<Self>) {
315          let width = self.width;
316          self.pending_serialization = cx.background_executor().spawn(async move {
317              KEY_VALUE_STORE
318                  .write_kvp(
319                      WILLOW_PANEL_KEY.into(),
320                      serde_json::to_string(&SerializedWillowPanel { width })
321                          .unwrap()
322                          .into(),
323                  )
324                  .await
325                  .log_err();
326              Some(())
327          });
328      }
329  
330      fn render_dialog(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> {
331          match &self.dialog_state {
332              DialogState::None => None,
333              DialogState::CreateDocument { .. } => Some(
334                  div()
335                      .absolute()
336                      .top_0()
337                      .left_0()
338                      .size_full()
339                      .bg(gpui::black().alpha(0.5))
340                      .flex()
341                      .items_center()
342                      .justify_center()
343                      .child(
344                          div()
345                              .bg(cx.theme().colors().panel_background)
346                              .border_1()
347                              .border_color(cx.theme().colors().border)
348                              .rounded_lg()
349                              .p_4()
350                              .w(px(400.))
351                              .child(
352                                  v_flex()
353                                      .gap_3()
354                                      .child(
355                                          Label::new("Create Document")
356                                              .size(LabelSize::Default)
357                                              .color(Color::Default),
358                                      )
359                                      .child(
360                                          Label::new("Path: documents/new-file.txt")
361                                              .size(LabelSize::Small)
362                                              .color(Color::Muted),
363                                      )
364                                      .child(
365                                          Label::new("Content: Hello, Willow!")
366                                              .size(LabelSize::Small)
367                                              .color(Color::Muted),
368                                      )
369                                      .child(
370                                          h_flex()
371                                              .gap_2()
372                                              .justify_end()
373                                              .child(
374                                                  IconButton::new("cancel-create", IconName::Close)
375                                                      .on_click(cx.listener(
376                                                          |this, _event, _window, cx| {
377                                                              this.dialog_state = DialogState::None;
378                                                              cx.notify();
379                                                          },
380                                                      )),
381                                              )
382                                              .child(
383                                                  IconButton::new("confirm-create", IconName::Check)
384                                                      .on_click(cx.listener(
385                                                          |this, _event, window, cx| {
386                                                              this.save_document(
387                                                                  &SaveDocument,
388                                                                  window,
389                                                                  cx,
390                                                              );
391                                                          },
392                                                      )),
393                                              ),
394                                      ),
395                              ),
396                      ),
397              ),
398              DialogState::CreateSubspace { .. } => Some(
399                  div()
400                      .absolute()
401                      .top_0()
402                      .left_0()
403                      .size_full()
404                      .bg(gpui::black().alpha(0.5))
405                      .flex()
406                      .items_center()
407                      .justify_center()
408                      .child(
409                          div()
410                              .bg(cx.theme().colors().panel_background)
411                              .border_1()
412                              .border_color(cx.theme().colors().border)
413                              .rounded_lg()
414                              .p_4()
415                              .w(px(400.))
416                              .child(
417                                  v_flex()
418                                      .gap_3()
419                                      .child(
420                                          Label::new("Create Subspace")
421                                              .size(LabelSize::Default)
422                                              .color(Color::Default),
423                                      )
424                                      .child(
425                                          Label::new("Name: New Subspace")
426                                              .size(LabelSize::Small)
427                                              .color(Color::Muted),
428                                      )
429                                      .child(
430                                          h_flex()
431                                              .gap_2()
432                                              .justify_end()
433                                              .child(
434                                                  IconButton::new("cancel-subspace", IconName::Close)
435                                                      .on_click(cx.listener(
436                                                          |this, _event, _window, cx| {
437                                                              this.dialog_state = DialogState::None;
438                                                              cx.notify();
439                                                          },
440                                                      )),
441                                              )
442                                              .child(
443                                                  IconButton::new(
444                                                      "confirm-subspace",
445                                                      IconName::Check,
446                                                  )
447                                                  .on_click(cx.listener(
448                                                      |this, _event, _window, cx| {
449                                                          // Create a sample entry for the new subspace
450                                                          let new_entry = Entry {
451                                                              namespace_id: "user".to_string(),
452                                                              subspace_id: "new_subspace".to_string(),
453                                                              path: "/user/new_subspace/welcome.txt"
454                                                                  .to_string(),
455                                                              timestamp: 1704157200, // Current timestamp
456                                                          };
457                                                          this.entries.push(new_entry);
458                                                          this.dialog_state = DialogState::None;
459                                                          cx.notify();
460                                                      },
461                                                  )),
462                                              ),
463                                      ),
464                              ),
465                      ),
466              ),
467              DialogState::ViewDocument { entry, .. } => Some(
468                  div()
469                      .absolute()
470                      .top_0()
471                      .left_0()
472                      .size_full()
473                      .bg(gpui::black().alpha(0.5))
474                      .flex()
475                      .items_center()
476                      .justify_center()
477                      .child(
478                          div()
479                              .bg(cx.theme().colors().panel_background)
480                              .border_1()
481                              .border_color(cx.theme().colors().border)
482                              .rounded_lg()
483                              .p_4()
484                              .w(px(600.))
485                              .h(px(500.))
486                              .child(
487                                  v_flex()
488                                      .gap_3()
489                                      .child(
490                                          h_flex()
491                                              .justify_between()
492                                              .items_center()
493                                              .child(
494                                                  Label::new(&entry.path)
495                                                      .size(LabelSize::Default)
496                                                      .color(Color::Default),
497                                              )
498                                              .child(
499                                                  IconButton::new("close-document", IconName::Close)
500                                                      .on_click(cx.listener(
501                                                          |this, _event, _window, cx| {
502                                                              this.dialog_state = DialogState::None;
503                                                              cx.notify();
504                                                          },
505                                                      )),
506                                              ),
507                                      )
508                                      .child(
509                                          div()
510                                              .flex_1()
511                                              .overflow_hidden()
512                                              .p_3()
513                                              .bg(cx.theme().colors().editor_background)
514                                              .rounded_md()
515                                              .border_1()
516                                              .border_color(cx.theme().colors().border_variant)
517                                              .child(
518                                                  v_flex()
519                                                      .gap_2()
520                                                      .child(
521                                                          h_flex()
522                                                              .gap_2()
523                                                              .child(
524                                                                  Label::new("Namespace:")
525                                                                      .size(LabelSize::Small)
526                                                                      .color(Color::Muted),
527                                                              )
528                                                              .child(
529                                                                  Label::new(&entry.namespace_id)
530                                                                      .size(LabelSize::Small)
531                                                                      .color(Color::Default),
532                                                              ),
533                                                      )
534                                                      .child(
535                                                          h_flex()
536                                                              .gap_2()
537                                                              .child(
538                                                                  Label::new("Subspace:")
539                                                                      .size(LabelSize::Small)
540                                                                      .color(Color::Muted),
541                                                              )
542                                                              .child(
543                                                                  Label::new(&entry.subspace_id)
544                                                                      .size(LabelSize::Small)
545                                                                      .color(Color::Default),
546                                                              ),
547                                                      )
548                                                      .child(
549                                                          h_flex()
550                                                              .gap_2()
551                                                              .child(
552                                                                  Label::new("Last Modified:")
553                                                                      .size(LabelSize::Small)
554                                                                      .color(Color::Muted),
555                                                              )
556                                                              .child(
557                                                                  Label::new(&self.format_timestamp(
558                                                                      entry.timestamp,
559                                                                  ))
560                                                                  .size(LabelSize::Small)
561                                                                  .color(Color::Default),
562                                                              ),
563                                                      ),
564                                              ),
565                                      ),
566                              ),
567                      ),
568              ),
569          }
570      }
571  
572      fn format_timestamp(&self, timestamp: i64) -> String {
573          // Simple timestamp formatting - in a real app you'd use a proper date library
574          match timestamp {
575              1704153600 => "1 hour ago".to_string(),
576              1704067200 => "Yesterday".to_string(),
577              1703980800 => "3 days ago".to_string(),
578              1703462400 => "1 week ago".to_string(),
579              1702857600 => "2 weeks ago".to_string(),
580              _ => format!("Timestamp: {}", timestamp),
581          }
582      }
583  
584      fn get_file_icon(&self, path: &str) -> IconName {
585          let path_parts: Vec<&str> = path.split('/').collect();
586          let name = path_parts.last().unwrap_or(&"").to_string();
587          let is_directory = !name.contains('.');
588  
589          if is_directory {
590              IconName::Folder
591          } else {
592              match name.split('.').last().unwrap_or("") {
593                  "jpg" | "jpeg" | "png" | "gif" | "bmp" => IconName::Image,
594                  "pdf" => IconName::File,
595                  "mp3" | "wav" | "flac" => IconName::File,
596                  "mp4" | "avi" | "mkv" => IconName::File,
597                  _ => IconName::File,
598              }
599          }
600      }
601  
602      fn render_timeline(&self, cx: &mut Context<Self>) -> impl IntoElement {
603          let mut sorted_entries = self.entries.clone();
604          sorted_entries.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); // Most recent first
605  
606          v_flex()
607              .gap_2()
608              // Timeline header
609              .child(
610                  h_flex()
611                      .justify_between()
612                      .items_center()
613                      .child(
614                          Label::new("Recent Activity")
615                              .size(LabelSize::Small)
616                              .color(Color::Muted),
617                      )
618                      .child(
619                          Label::new(format!("{}", sorted_entries.len()))
620                              .size(LabelSize::XSmall)
621                              .color(Color::Muted),
622                      ),
623              )
624              // Timeline entries
625              .children(sorted_entries.iter().enumerate().map(|(i, entry)| {
626                  let path_parts: Vec<&str> = entry.path.split('/').collect();
627                  let filename = path_parts.last().unwrap_or(&"").to_string();
628                  let is_directory = !filename.contains('.');
629  
630                  ListItem::new(("timeline_entry", i))
631                      .spacing(ListItemSpacing::Dense)
632                      .start_slot(
633                          Icon::new(self.get_file_icon(&entry.path))
634                              .size(IconSize::Small)
635                              .color(if is_directory {
636                                  Color::Accent
637                              } else {
638                                  Color::Default
639                              }),
640                      )
641                      .child(
642                          v_flex()
643                              .gap_1()
644                              .child(
645                                  h_flex()
646                                      .gap_2()
647                                      .items_center()
648                                      .child(
649                                          Label::new(&filename)
650                                              .size(LabelSize::Small)
651                                              .color(Color::Default),
652                                      )
653                                      .child(
654                                          h_flex()
655                                              .gap_1()
656                                              .child(
657                                                  div()
658                                                      .px_1p5()
659                                                      .py_0p5()
660                                                      .bg(cx.theme().colors().element_background)
661                                                      .rounded_md()
662                                                      .child(
663                                                          Label::new(&entry.namespace_id)
664                                                              .size(LabelSize::XSmall)
665                                                              .color(Color::Accent),
666                                                      ),
667                                              )
668                                              .child(
669                                                  div()
670                                                      .px_1p5()
671                                                      .py_0p5()
672                                                      .bg(cx.theme().colors().element_background)
673                                                      .rounded_md()
674                                                      .child(
675                                                          Label::new(&entry.subspace_id)
676                                                              .size(LabelSize::XSmall)
677                                                              .color(Color::Muted),
678                                                      ),
679                                              ),
680                                      ),
681                              )
682                              .child(
683                                  h_flex()
684                                      .gap_2()
685                                      .items_center()
686                                      .child(
687                                          Label::new(&entry.path)
688                                              .size(LabelSize::XSmall)
689                                              .color(Color::Muted),
690                                      )
691                                      .child(
692                                          Label::new("•").size(LabelSize::XSmall).color(Color::Muted),
693                                      )
694                                      .child(
695                                          Label::new(&self.format_timestamp(entry.timestamp))
696                                              .size(LabelSize::XSmall)
697                                              .color(Color::Muted),
698                                      ),
699                              ),
700                      )
701                      .end_slot(
702                          h_flex()
703                              .gap_1()
704                              .child(IconButton::new(("view", i), IconName::Eye).on_click({
705                                  let entry_clone = entry.clone();
706                                  cx.listener(move |this, _event, _window, cx| {
707                                      this.dialog_state = DialogState::ViewDocument {
708                                          entry: entry_clone.clone(),
709                                      };
710                                      cx.notify();
711                                  })
712                              }))
713                              .child(IconButton::new(("delete", i), IconName::Trash).on_click({
714                                  let entry_clone = entry.clone();
715                                  cx.listener(move |this, _event, window, cx| {
716                                      this.delete_document(&entry_clone, window, cx);
717                                  })
718                              })),
719                      )
720              }))
721              .when(sorted_entries.is_empty(), |this| {
722                  this.child(
723                      Label::new("No entries yet. Create some content to get started!")
724                          .size(LabelSize::XSmall)
725                          .color(Color::Muted),
726                  )
727              })
728      }
729  }
730  
731  impl Render for WillowPanel {
732      fn render(&mut self, _window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
733          let main_content = v_flex()
734              .size_full()
735              .bg(cx.theme().colors().panel_background)
736              .on_action(cx.listener(Self::create_document))
737              .on_action(cx.listener(Self::create_subspace))
738              .on_action(cx.listener(Self::close_dialog))
739              .on_action(cx.listener(Self::save_document))
740              // Sidebar header
741              .child(
742                  h_flex()
743                      .w_full()
744                      .justify_between()
745                      .p_2()
746                      .border_b_1()
747                      .border_color(cx.theme().colors().border_variant)
748                      .child(
749                          Label::new("Willow Data Store")
750                              .size(LabelSize::Default)
751                              .color(Color::Default),
752                      )
753                      .child(
754                          h_flex()
755                              .gap_2()
756                              .child(IconButton::new("create-document", IconName::File).on_click(
757                                  cx.listener(|this, _event, window, cx| {
758                                      this.create_document(&CreateDocument, window, cx);
759                                  }),
760                              ))
761                              .child(
762                                  IconButton::new("create-subspace", IconName::Person).on_click(
763                                      cx.listener(|this, _event, window, cx| {
764                                          this.create_subspace(&CreateSubspace, window, cx);
765                                      }),
766                                  ),
767                              ),
768                      ),
769              )
770              // Sidebar body
771              .child(
772                  div().flex_1().overflow_hidden().p_3().child(
773                      v_flex()
774                          .gap_3()
775                          // Subspaces section
776                          .child(self.render_timeline(cx)),
777                  ),
778              );
779  
780          if let Some(dialog) = self.render_dialog(cx) {
781              div().relative().child(main_content).child(dialog)
782          } else {
783              main_content
784          }
785      }
786  }