Skip to content

Commit

Permalink
New Root URL + Group: Fix the auto-filled {Source, Name, URL} to alwa…
Browse files Browse the repository at this point in the history
…ys be reasonable

Also:
* Entity Tree: Intelligently retarget selection when new root URL or group
  created for selected link
* serve: Don't invalidate served XML files by inserting <script> links
  • Loading branch information
davidfstr committed Jan 8, 2024
2 parents 1fe3e7f + 92df1ad commit 730224a
Show file tree
Hide file tree
Showing 15 changed files with 1,107 additions and 156 deletions.
17 changes: 13 additions & 4 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,26 @@ Release Notes ⋮
### main (v2.0.0?)

* First-time-run experience improvements
* New Root URL Dialog:
* Accept URLs in loose format, similar to what regular web browsers accept.
* Better error message when try to create duplicate root URL.
* Disallow create of empty root URL.
* New Root URL and New Group Dialogs:
* Improve suggested name when creating a new root URL or group.
* Make it optional to provide a name.
* Rearrange fields to deemphasize the name field.
* Update the selected node in the Entity Tree intelligently after
creating or forgetting a root URL or a group.
* Allow resizing.
* New Root URL Dialog:
* Accept URLs in loose format, similar to what regular web browsers accept.
* Better error message when try to create duplicate root URL.
* Disallow create of empty root URL.
* New Group Dialog:
* Improve suggested source when creating a new group.
* Main Window
* Prevent resizing the window to be too small.

* Serving improvements
* XML files like Atom feeds and RSS feeds are now served correctly,
without introducing an invalid `<script>` tag.

* Fixes
* Fix disappearance of error nodes when new root URL or group is added.

Expand Down
65 changes: 34 additions & 31 deletions src/crystal/browser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ def _create_actions(self) -> None:
self._forget_action = Action(wx.ID_ANY,
'&Forget',
wx.AcceleratorEntry(wx.ACCEL_CTRL, wx.WXK_BACK),
self._on_remove_entity,
self._on_forget_entity,
enabled=False)
self._download_action = Action(wx.ID_ANY,
'&Download',
Expand Down Expand Up @@ -349,28 +349,24 @@ def _create_button_bar(self, parent: wx.Window):
# === Entity Pane: Properties ===

@property
def _selection_initial_url(self) -> Optional[str]:
selected_entity = self.entity_tree.selected_entity
if isinstance(selected_entity, (Resource, RootResource)):
return selected_entity.resource.url
elif isinstance(selected_entity, ResourceGroup):
return selected_entity.url_pattern
def _suggested_url_or_url_pattern_for_selection(self) -> Optional[str]:
selected_entity_pair = self.entity_tree.selected_entity_pair
selected_or_related_entity = selected_entity_pair[0] or selected_entity_pair[1]

if isinstance(selected_or_related_entity, (Resource, RootResource)):
return selected_or_related_entity.resource.url
elif isinstance(selected_or_related_entity, ResourceGroup):
return selected_or_related_entity.url_pattern
else:
return self.project.default_url_prefix

@property
def _selection_initial_source(self) -> Optional[ResourceGroupSource]:
selected_entity = self.entity_tree.selected_entity
if isinstance(selected_entity, (Resource, RootResource)):
parent_of_selected_entity = self.entity_tree.parent_of_selected_entity
if isinstance(parent_of_selected_entity, (ResourceGroup, RootResource)):
return parent_of_selected_entity
else:
return None
elif isinstance(selected_entity, ResourceGroup):
return selected_entity.source
else:
return None
def _suggested_source_for_selection(self) -> ResourceGroupSource:
return self.entity_tree.source_of_selection

@property
def _suggested_name_for_selection(self) -> Optional[str]:
return self.entity_tree.name_of_selection

# === Operations ===

Expand Down Expand Up @@ -436,7 +432,8 @@ def root_url_exists(url: str) -> bool:
AddRootUrlDialog(
self._frame, self._on_add_url_dialog_ok,
url_exists_func=root_url_exists,
initial_url=self._selection_initial_url or '',
initial_url=self._suggested_url_or_url_pattern_for_selection or '',
initial_name=self._suggested_name_for_selection or '',
)

@fg_affinity
Expand All @@ -453,20 +450,23 @@ def _on_add_group(self, event: wx.CommandEvent) -> None:
AddGroupDialog(
self._frame, self._on_add_group_dialog_ok,
self.project,
initial_url=self._selection_initial_url or '',
initial_source=self._selection_initial_source)
initial_url_pattern=self._suggested_url_or_url_pattern_for_selection or '',
initial_source=self._suggested_source_for_selection,
initial_name=self._suggested_name_for_selection or '')
except CancelLoadUrls:
pass

def _on_add_group_dialog_ok(self, name: str, url_pattern: str, source):
# TODO: Validate user input:
# * Is name or url_pattern empty?
# * Is name or url_pattern already taken?
rg = ResourceGroup(self.project, name, url_pattern)
rg.source = source
rg = ResourceGroup(self.project, name, url_pattern, source)

def _on_remove_entity(self, event):
self.entity_tree.selected_entity.delete()
def _on_forget_entity(self, event):
selected_entity_pair = self.entity_tree.selected_entity_pair
selected_or_related_entity = selected_entity_pair[0] or selected_entity_pair[1]

selected_or_related_entity.delete()
# TODO: This update() should happen in response to a delete
# event fired by the entity itself.
self.entity_tree.update()
Expand Down Expand Up @@ -594,20 +594,23 @@ def start_server(self) -> 'ProjectServer':

return self._project_server

def _on_selected_entity_changed(self, event):
selected_entity = self.entity_tree.selected_entity # cache
def _on_selected_entity_changed(self, event: wx.TreeEvent) -> None:
selected_entity_pair = self.entity_tree.selected_entity_pair # cache
selected_entity = selected_entity_pair[0]
selected_or_related_entity = selected_entity_pair[0] or selected_entity_pair[1]

readonly = self._readonly # cache
self._forget_action.enabled = (
(not readonly) and
type(selected_entity) in (ResourceGroup, RootResource))
isinstance(selected_or_related_entity, (ResourceGroup, RootResource)))
self._download_action.enabled = (
(not readonly) and
selected_entity is not None)
self._update_membership_action.enabled = (
(not readonly) and
type(selected_entity) is ResourceGroup)
isinstance(selected_entity, ResourceGroup))
self._view_action.enabled = (
type(selected_entity) in (Resource, RootResource))
isinstance(selected_entity, (Resource, RootResource)))

# === Task Pane: Init ===

Expand Down
23 changes: 13 additions & 10 deletions src/crystal/browser/addgroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@


class AddGroupDialog:
_INITIAL_URL_WIDTH = AddRootUrlDialog._INITIAL_URL_WIDTH
_INITIAL_URL_PATTERN_WIDTH = AddRootUrlDialog._INITIAL_URL_WIDTH
_MAX_VISIBLE_PREVIEW_URLS = 100

# === Init ===
Expand All @@ -26,16 +26,18 @@ def __init__(self,
parent: wx.Window,
on_finish: Callable[[str, str, ResourceGroupSource], None],
project: Project,
initial_url: str='',
initial_source: Optional[ResourceGroupSource]=None,
initial_url_pattern: str='',
initial_source: ResourceGroupSource=None,
initial_name: str='',
) -> None:
"""
Arguments:
* parent -- parent wx.Window that this dialog is attached to.
* on_finish -- called when OK pressed on dialog. Is a callable(name, url_pattern, source).
* project -- the project.
* initial_url -- overrides the initial URL displayed.
* initial_url_pattern -- overrides the initial URL pattern displayed.
* initial_source -- overrides the initial source displayed.
* initial_name -- overrides the initial name displayed.
Raises:
* CancelLoadUrls
Expand Down Expand Up @@ -92,7 +94,7 @@ def __init__(self,

content_sizer = wx.BoxSizer(wx.VERTICAL)
content_sizer.Add(
self._create_fields(dialog, initial_url, initial_source),
self._create_fields(dialog, initial_url_pattern, initial_source, initial_name),
flag=wx.EXPAND)
content_sizer.Add(preview_box, proportion=1, flag=wx.EXPAND|preview_box_flags, border=preview_box_border)

Expand All @@ -114,17 +116,18 @@ def __init__(self,

def _create_fields(self,
parent: wx.Window,
initial_url: str,
initial_source: Optional[ResourceGroupSource]
initial_url_pattern: str,
initial_source: ResourceGroupSource,
initial_name: str,
) -> wx.Sizer:
fields_sizer = wx.FlexGridSizer(cols=2,
vgap=_FORM_ROW_SPACING, hgap=_FORM_LABEL_INPUT_SPACING)
fields_sizer.AddGrowableCol(1)

pattern_field_sizer = wx.BoxSizer(wx.VERTICAL)
self.pattern_field = wx.TextCtrl(
parent, value=initial_url,
size=(self._INITIAL_URL_WIDTH, wx.DefaultCoord),
parent, value=initial_url_pattern,
size=(self._INITIAL_URL_PATTERN_WIDTH, wx.DefaultCoord),
name='cr-add-group-dialog__pattern-field')
bind(self.pattern_field, wx.EVT_TEXT, self._on_pattern_field_changed)
self.pattern_field.Hint = 'https://example.com/post/*'
Expand Down Expand Up @@ -157,7 +160,7 @@ def _create_fields(self,

fields_sizer.Add(wx.StaticText(parent, label='Name:', style=wx.ALIGN_RIGHT), flag=wx.EXPAND)
self.name_field = wx.TextCtrl(
parent,
parent, value=initial_name,
name='cr-add-group-dialog__name-field')
self.name_field.Hint = 'Post'
self.name_field.SetSelection(-1, -1) # select all upon focus
Expand Down
7 changes: 4 additions & 3 deletions src/crystal/browser/addrooturl.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def __init__(self,
on_finish: Callable[[str, str], None],
url_exists_func: Callable[[str], bool],
initial_url: str='',
initial_name: str='',
) -> None:
"""
Arguments:
Expand All @@ -52,7 +53,7 @@ def __init__(self,
bind(dialog, wx.EVT_CLOSE, self._on_close)
bind(dialog, wx.EVT_WINDOW_DESTROY, self._on_destroyed)

dialog_sizer.Add(self._create_fields(dialog, initial_url), flag=wx.EXPAND|wx.ALL,
dialog_sizer.Add(self._create_fields(dialog, initial_url, initial_name), flag=wx.EXPAND|wx.ALL,
border=_WINDOW_INNER_PADDING)
dialog_sizer.Add(dialog.CreateButtonSizer(wx.OK|wx.CANCEL), flag=wx.EXPAND|wx.BOTTOM,
border=_WINDOW_INNER_PADDING)
Expand All @@ -75,7 +76,7 @@ def __init__(self,
if os.environ.get('CRYSTAL_RUNNING_TESTS', 'False') == 'True':
AddRootUrlDialog._last_opened = self

def _create_fields(self, parent: wx.Window, initial_url: str) -> wx.Sizer:
def _create_fields(self, parent: wx.Window, initial_url: str, initial_name: str) -> wx.Sizer:
fields_sizer = wx.FlexGridSizer(rows=2, cols=2,
vgap=_FORM_ROW_SPACING, hgap=_FORM_LABEL_INPUT_SPACING)
fields_sizer.AddGrowableCol(1)
Expand Down Expand Up @@ -115,7 +116,7 @@ def _create_fields(self, parent: wx.Window, initial_url: str) -> wx.Sizer:
name_field_and_space = wx.BoxSizer(wx.HORIZONTAL)
if True:
self.name_field = wx.TextCtrl(
parent,
parent, value=initial_name,
name='cr-add-url-dialog__name-field')
self.name_field.Hint = 'Home'
self.name_field.SetSelection(-1, -1) # select all upon focus
Expand Down
Loading

0 comments on commit 730224a

Please sign in to comment.