New mobile layout, new regular screen, added the ability to edit directory names from settings
This commit is contained in:
@@ -557,6 +557,48 @@ def set_path_hidden(path: str, hidden: bool) -> List[str]:
|
||||
return result
|
||||
|
||||
|
||||
def _rebase_prefix(value: str, old_abs: str, new_abs: str) -> str:
|
||||
"""If `value` equals or is nested under `old_abs`, rewrite that prefix to `new_abs`."""
|
||||
if value == old_abs:
|
||||
return new_abs
|
||||
if value.startswith(old_abs + os.sep):
|
||||
return new_abs + value[len(old_abs):]
|
||||
return value
|
||||
|
||||
|
||||
def rebase_library_path(old_abs: str, new_abs: str) -> None:
|
||||
"""
|
||||
After a directory in the library is renamed/moved on disk, rewrite any
|
||||
stored absolute paths that pointed inside it (hidden_paths.json,
|
||||
recent_views.json's course_path) so curation and Recently Viewed don't
|
||||
silently go stale. Lesson-level progress needs no rebasing - it lives
|
||||
inside the directory itself, keyed by paths relative to it, so it moves
|
||||
with the rename automatically.
|
||||
"""
|
||||
hidden = get_hidden_paths()
|
||||
rebased_hidden = [_rebase_prefix(p, old_abs, new_abs) for p in hidden]
|
||||
if rebased_hidden != hidden:
|
||||
os.makedirs(DATA_DIR, exist_ok=True)
|
||||
with open(HIDDEN_PATHS_FILE, 'w') as f:
|
||||
json.dump(sorted(set(rebased_hidden)), f, indent=2)
|
||||
|
||||
views = get_recent_views()
|
||||
changed = False
|
||||
for entry in views:
|
||||
course_path = entry.get('course_path', '')
|
||||
rebased = _rebase_prefix(course_path, old_abs, new_abs)
|
||||
if rebased != course_path:
|
||||
entry['course_path'] = rebased
|
||||
changed = True
|
||||
if changed:
|
||||
try:
|
||||
os.makedirs(DATA_DIR, exist_ok=True)
|
||||
with open(RECENT_VIEWS_FILE, 'w') as f:
|
||||
json.dump(views, f, indent=2)
|
||||
except OSError as e:
|
||||
print(f"Could not rebase recent views after rename: {e}")
|
||||
|
||||
|
||||
def _contains_visible_course(directory: Path, hidden_set: set) -> bool:
|
||||
"""
|
||||
Whether `directory` leads to at least one course that isn't curated
|
||||
@@ -1037,6 +1079,54 @@ def set_hidden_path_api():
|
||||
return jsonify({'success': True, 'hidden_paths': updated})
|
||||
|
||||
|
||||
@app.route('/api/rename-path', methods=['POST'])
|
||||
def rename_path_api():
|
||||
"""Rename a course/folder directory in the library, in place on disk."""
|
||||
global current_course
|
||||
|
||||
data = request.json or {}
|
||||
path = data.get('path', '')
|
||||
new_name = (data.get('new_name') or '').strip()
|
||||
|
||||
if not path:
|
||||
return jsonify({'error': 'path is required'}), 400
|
||||
if not new_name:
|
||||
return jsonify({'error': 'New name cannot be empty'}), 400
|
||||
if '/' in new_name or '\\' in new_name or '\x00' in new_name or new_name in ('.', '..'):
|
||||
return jsonify({'error': 'New name cannot contain path separators'}), 400
|
||||
if new_name.startswith('.'):
|
||||
return jsonify({'error': 'New name cannot start with a dot'}), 400
|
||||
|
||||
library_root = os.path.abspath(get_library_root())
|
||||
old_abs = os.path.abspath(path)
|
||||
if not (old_abs == library_root or old_abs.startswith(library_root + os.sep)):
|
||||
return jsonify({'error': 'Path outside library root'}), 403
|
||||
if old_abs == library_root:
|
||||
return jsonify({'error': 'Cannot rename the library root itself'}), 400
|
||||
if not os.path.isdir(old_abs):
|
||||
return jsonify({'error': 'Directory not found'}), 404
|
||||
|
||||
new_abs = os.path.join(os.path.dirname(old_abs), new_name)
|
||||
if os.path.exists(new_abs):
|
||||
return jsonify({'error': f'"{new_name}" already exists here'}), 409
|
||||
|
||||
try:
|
||||
os.rename(old_abs, new_abs)
|
||||
except OSError as e:
|
||||
return jsonify({'error': f'Rename failed: {e}'}), 500
|
||||
|
||||
rebase_library_path(old_abs, new_abs)
|
||||
|
||||
active_course_reset = False
|
||||
if current_course is not None:
|
||||
course_abs = os.path.abspath(current_course.path)
|
||||
if course_abs == old_abs or course_abs.startswith(old_abs + os.sep):
|
||||
current_course = None
|
||||
active_course_reset = True
|
||||
|
||||
return jsonify({'success': True, 'new_path': new_abs, 'active_course_reset': active_course_reset})
|
||||
|
||||
|
||||
@app.route('/settings')
|
||||
def settings_page():
|
||||
"""Render the display-settings page."""
|
||||
|
||||
Reference in New Issue
Block a user