diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..a1a1d4d --- /dev/null +++ b/.envrc @@ -0,0 +1,9 @@ +# -*- mode: sh -*- +PATH_add scripts + +# Set all Locale to en_US.UTF-8 +export LANG="en_US.UTF-8" +export LANGUAGE="en_US.UTF-8" + +# Avoid "direnv: PS1 cannot be exported" error +unset PS1 diff --git a/.gitignore b/.gitignore index 9a5535a..3a6d9fd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .DS_Store project.xcworkspace/ xcuserdata/ +/.DerivedData diff --git a/Base.lproj/PrefsWin.xib b/Base.lproj/PrefsWin.xib index d8af3f9..bfa4b0d 100644 --- a/Base.lproj/PrefsWin.xib +++ b/Base.lproj/PrefsWin.xib @@ -18,20 +18,20 @@ - + - + - + - + @@ -354,7 +354,7 @@ - + @@ -625,7 +625,7 @@ - + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..bbbec84 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,74 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Phoenix Slides (originally codenamed "creevey") is a fast macOS image browser and slideshow application written in Objective-C. The application has been in development since 2005 and uses Cocoa/AppKit frameworks. + +## Build Commands + +```bash +# Build application +scripts/build # debug +scripts/build d # debug +scripts/build r # release + +# Run the +scripts/run # debug +scripts/run d # debug +scripts/run r # release + +# Open xcode +scripts/ide +``` + +## Architecture + +### Core Components + +**Main Window & Browser** +- `CreeveyMainWindowController`: Central controller managing the main window, containing both the directory browser and thumbnail grid +- `DYCreeveyBrowser`: Custom NSBrowser subclass for directory navigation with typing support and drag/drop +- `DirBrowserDelegate`: Handles directory browser logic and path management + +**Image Display & Caching** +- `DYImageView`: Custom view handling image display with zoom, rotation, and flip capabilities +- `DYImageCache`: Manages thumbnail caching for performance +- `DYWrappingMatrix`: Custom matrix view for thumbnail grid display + +**Slideshow** +- `SlideshowWindow`: Full-screen or windowed slideshow presentation +- Supports random, loop, and auto-advance modes + +**File Management** +- `DYFileWatcher`: Monitors file system changes using VDKQueue +- `DYRandomizableArray`: Array with randomization support for slideshow ordering + +### External Dependencies + +The project includes embedded libraries: +- `libjpeg`: For JPEG manipulation +- `exiftags`: For EXIF metadata extraction +- `DYjpegtran`: Wrapper for lossless JPEG transformations + +### Key Features Implementation + +- **Fast thumbnailing**: Uses embedded EXIF/JPEG previews when available, falls back to macOS Image I/O +- **EXIF sorting**: Custom comparator implementation in `CreeveyMainWindowController` +- **Animated GIF/WebP**: Handled through CGImageSource in `DYImageView` + +## Interface Files + +- `Base.lproj/CreeveyWindow.xib`: Main window layout +- `Base.lproj/MainMenu.xib`: Application menu +- `Base.lproj/PrefsWin.xib`: Preferences window +- `Base.lproj/DYJpegtranPanel.xib`: JPEG transformation panel +- `Base.lproj/ThumbnailContextMenu.xib`: Right-click menu for thumbnails + +## Development Notes + +- The project requires the latest version of Xcode to build from the master branch +- Code uses modern Objective-C with ARC (Automatic Reference Counting) +- The application supports macOS 10.14+ features when available +- Localization support exists but translations are managed through Google Translate diff --git a/CreeveyController.h b/CreeveyController.h index 6216776..d040e37 100644 --- a/CreeveyController.h +++ b/CreeveyController.h @@ -59,6 +59,10 @@ NSDirectoryEnumerator *CreeveyEnumerator(NSString *path, BOOL recurseSubfolders) - (IBAction)applySlideshowPrefs:(id)sender; - (IBAction)slideshowDefaultsChanged:(id)sender; - (IBAction)chooseDefaultSlideshowMode:(id)sender; +- (IBAction)folderBrowserFontSizeChanged:(id)sender; +- (IBAction)thumbnailLabelFontSizeChanged:(id)sender; +- (IBAction)resetFolderBrowserFontSize:(id)sender; +- (IBAction)resetThumbnailLabelFontSize:(id)sender; // info window - (IBAction)openGetInfoPanel:(id)sender; diff --git a/CreeveyController.m b/CreeveyController.m index a7a2608..eb019e4 100644 --- a/CreeveyController.m +++ b/CreeveyController.m @@ -16,6 +16,7 @@ #import "DYJpegtranPanel.h" #import "DYVersChecker.h" #import "DYExiftags.h" +#import "UKPrefsPanel.h" // The thumbs cache should always store images using the resolved filename as the key. // This prevents duplication somewhat, but it means when you look things up @@ -158,6 +159,8 @@ +(void)initialize @"startupSlideshowSuppressNewWindows":@NO, @"slideshowDefaultMode":@0, @"MainWindowSplitViewTopHeight":@151.0f, + @"folderBrowserFontSize":@0.0f, // 0 means use default (NSFont.systemFontSize) + @"thumbnailLabelFontSize":@0.0f, // 0 means use automatic sizing }]; // migrate old RBSplitView pref @@ -1078,6 +1081,86 @@ - (IBAction)chooseDefaultSlideshowMode:(id)sender { [self updateAlternateSlideshowMenuItem]; } +- (IBAction)folderBrowserFontSizeChanged:(id)sender { + CGFloat fontSize = [sender floatValue]; + [NSUserDefaults.standardUserDefaults setFloat:fontSize forKey:@"folderBrowserFontSize"]; + // Update the label in preferences + NSTextField *label = [prefsWin.contentView viewWithTag:1001]; + if (label) { + label.stringValue = [NSString stringWithFormat:@"%.0f pt", fontSize]; + } + // Notify all windows to update their folder browser font + [creeveyWindows makeObjectsPerformSelector:@selector(updateFolderBrowserFont)]; +} + +- (IBAction)thumbnailLabelFontSizeChanged:(id)sender { + CGFloat fontSize = [sender floatValue]; + [NSUserDefaults.standardUserDefaults setFloat:fontSize forKey:@"thumbnailLabelFontSize"]; + // Update the label in preferences + NSTextField *label = [prefsWin.contentView viewWithTag:1002]; + if (label) { + if (fontSize <= 0) { + label.stringValue = NSLocalizedString(@"Auto", @""); + } else { + label.stringValue = [NSString stringWithFormat:@"%.0f pt", fontSize]; + } + } + // Notify all windows to update their thumbnail label font + [creeveyWindows makeObjectsPerformSelector:@selector(updateThumbnailLabelFont)]; +} + +- (IBAction)resetFolderBrowserFontSize:(id)sender { + [NSUserDefaults.standardUserDefaults setFloat:0.0f forKey:@"folderBrowserFontSize"]; + // Update the slider and label + for (NSView *view in prefsWin.contentView.subviews) { + if ([view isKindOfClass:[NSTabView class]]) { + NSTabView *tabView = (NSTabView *)view; + for (NSTabViewItem *item in tabView.tabViewItems) { + if ([item.identifier isEqualToString:@"fonts"]) { + for (NSView *subview in item.view.subviews) { + if ([subview isKindOfClass:[NSSlider class]]) { + NSSlider *slider = (NSSlider *)subview; + if (slider.action == @selector(folderBrowserFontSizeChanged:)) { + slider.floatValue = NSFont.systemFontSize; + [self folderBrowserFontSizeChanged:slider]; + break; + } + } + } + } + } + } + } + // Notify all windows to update their folder browser font + [creeveyWindows makeObjectsPerformSelector:@selector(updateFolderBrowserFont)]; +} + +- (IBAction)resetThumbnailLabelFontSize:(id)sender { + [NSUserDefaults.standardUserDefaults setFloat:0.0f forKey:@"thumbnailLabelFontSize"]; + // Update the slider and label + for (NSView *view in prefsWin.contentView.subviews) { + if ([view isKindOfClass:[NSTabView class]]) { + NSTabView *tabView = (NSTabView *)view; + for (NSTabViewItem *item in tabView.tabViewItems) { + if ([item.identifier isEqualToString:@"fonts"]) { + for (NSView *subview in item.view.subviews) { + if ([subview isKindOfClass:[NSSlider class]]) { + NSSlider *slider = (NSSlider *)subview; + if (slider.action == @selector(thumbnailLabelFontSizeChanged:)) { + slider.floatValue = 0.0; + [self thumbnailLabelFontSizeChanged:slider]; + break; + } + } + } + } + } + } + } + // Notify all windows to update their thumbnail label font + [creeveyWindows makeObjectsPerformSelector:@selector(updateThumbnailLabelFont)]; +} + - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)c diff --git a/CreeveyMainWindowController.h b/CreeveyMainWindowController.h index 452ad4a..691c826 100644 --- a/CreeveyMainWindowController.h +++ b/CreeveyMainWindowController.h @@ -45,6 +45,8 @@ - (void)fileWasDeleted:(NSString *)s; - (void)filesWereUndeleted:(NSArray *)a; - (void)updateExifInfo; +- (void)updateFolderBrowserFont; +- (void)updateThumbnailLabelFont; @end diff --git a/CreeveyMainWindowController.m b/CreeveyMainWindowController.m index 8bbb57a..7b24d25 100644 --- a/CreeveyMainWindowController.m +++ b/CreeveyMainWindowController.m @@ -922,6 +922,32 @@ - (void)updateExifInfo { [self updateExifInfo:nil]; } +- (void)updateFolderBrowserFont { + // Update the folder browser font size + CGFloat fontSize = [NSUserDefaults.standardUserDefaults floatForKey:@"folderBrowserFontSize"]; + if (fontSize <= 0) fontSize = NSFont.systemFontSize; + + // Save the current path + NSString *currentPath = dirBrowser.path; + + // Update the cell prototype font + NSFont *font = [NSFont systemFontOfSize:fontSize]; + [dirBrowser.cellPrototype setFont:font]; + + // Reload the browser to apply the new font + [dirBrowser loadColumnZero]; + + // Restore the path if needed + if (currentPath) { + [dirBrowser setPath:currentPath]; + } +} + +- (void)updateThumbnailLabelFont { + // Force the image matrix to redraw with new font size + [imgMatrix setNeedsDisplay:YES]; +} + #pragma mark splitview delegate - (void)splitViewDidResizeSubviews:(NSNotification *)notification diff --git a/DYCreeveyBrowser.m b/DYCreeveyBrowser.m index 6f3e4f6..caa4b64 100644 --- a/DYCreeveyBrowser.m +++ b/DYCreeveyBrowser.m @@ -85,7 +85,11 @@ - (instancetype)initWithFrame:(NSRect)frameRect { self.titled = NO; self.hasHorizontalScroller = YES; [self setCellClass:[DYBrowserCell class]]; - [self.cellPrototype setFont:[NSFont systemFontOfSize:NSFont.smallSystemFontSize-1]]; + // Use font size from preferences, defaulting to system font size if not set + CGFloat fontSize = [NSUserDefaults.standardUserDefaults floatForKey:@"folderBrowserFontSize"]; + if (fontSize <= 0) fontSize = NSFont.systemFontSize; + NSFont *font = [NSFont systemFontOfSize:fontSize]; + [self.cellPrototype setFont:font]; self.allowsEmptySelection = NO; self.columnResizingType = NSBrowserUserColumnResizing; self.prefersAllColumnUserResizing = NO; diff --git a/DYWrappingMatrix.m b/DYWrappingMatrix.m index f6e7650..fcee35f 100644 --- a/DYWrappingMatrix.m +++ b/DYWrappingMatrix.m @@ -569,7 +569,13 @@ - (void)drawRect:(NSRect)rect { NSRect textCellRect = NSMakeRect(0, 0, area_w, textHeight + _vPadding/2); NSRect cellRect; NSWindow *myWindow = self.window; - myTextCell.font = [NSFont systemFontOfSize:cellWidth >= 160 ? 12 : 4+cellWidth/20]; // ranges from 6 to 12: 6 + 6*(cellWidth-40)/(160-40) + // Use font size from preferences, or automatic sizing if not set + CGFloat fontSize = [NSUserDefaults.standardUserDefaults floatForKey:@"thumbnailLabelFontSize"]; + if (fontSize <= 0) { + // Automatic sizing: ranges from 6 to 12 based on cell width + fontSize = cellWidth >= 160 ? 12 : 4+cellWidth/20; + } + myTextCell.font = [NSFont systemFontOfSize:fontSize]; myTextCell.textColor = bgColor.bestTextColor; for (i=0; i 0 ) { baseWindowName = [NSString stringWithFormat:@"%@ : ", wndTitle]; } - + // Make sure our autosave-name is based on the one of our prefs window: self.autosaveName = tabView.window.frameAutosaveName; // defined below -DY - + // Select the preferences page the user last had selected when this window was opened: NSString *key = [NSString stringWithFormat:@"%@.prefspanel.recenttab", autosaveName]; NSInteger index = [NSUserDefaults.standardUserDefaults integerForKey:key]; if (index >= tabView.numberOfTabViewItems) index = 0; // prevent crash if number of items has changed [tabView selectTabViewItemAtIndex:index]; - + // Actually hook up our toolbar and the tabs: [self mapTabsToToolbar]; @@ -278,4 +285,131 @@ -(NSArray*) toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar NSToolbarCustomizeToolbarItemIdentifier] ]; } +- (void)addFontsTab { + // Check if Font Settings tab already exists + for (NSTabViewItem *item in tabView.tabViewItems) { + if ([item.identifier isEqualToString:@"fonts"]) { + return; // Already exists + } + } + + // Create Font Settings tab + NSTabViewItem *fontTab = [[NSTabViewItem alloc] initWithIdentifier:@"fonts"]; + fontTab.label = NSLocalizedString(@"Fonts", @"Font settings tab"); + + // Create the content view for the tab + NSView *fontView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 487, 448)]; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + + // Folder Browser Font Size + NSTextField *folderLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 380, 200, 20)]; + folderLabel.stringValue = NSLocalizedString(@"Folder Browser Font Size:", @""); + folderLabel.bezeled = NO; + folderLabel.drawsBackground = NO; + folderLabel.editable = NO; + folderLabel.selectable = NO; + [fontView addSubview:folderLabel]; + + NSSlider *folderSlider = [[NSSlider alloc] initWithFrame:NSMakeRect(20, 350, 300, 20)]; + folderSlider.minValue = 10.0; + folderSlider.maxValue = 24.0; + CGFloat folderFontSize = [defaults floatForKey:@"folderBrowserFontSize"]; + if (folderFontSize <= 0) folderFontSize = NSFont.systemFontSize; + folderSlider.floatValue = folderFontSize; + folderSlider.target = [NSApp delegate]; + folderSlider.action = @selector(folderBrowserFontSizeChanged:); + folderSlider.continuous = YES; + [fontView addSubview:folderSlider]; + + NSTextField *folderSizeLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(330, 350, 50, 20)]; + folderSizeLabel.stringValue = [NSString stringWithFormat:@"%.0f pt", folderFontSize]; + folderSizeLabel.bezeled = NO; + folderSizeLabel.drawsBackground = NO; + folderSizeLabel.editable = NO; + folderSizeLabel.selectable = NO; + folderSizeLabel.tag = 1001; // Tag for updating + [fontView addSubview:folderSizeLabel]; + + NSButton *folderResetBtn = [[NSButton alloc] initWithFrame:NSMakeRect(390, 345, 80, 30)]; + folderResetBtn.title = NSLocalizedString(@"Reset", @""); + folderResetBtn.bezelStyle = NSBezelStyleRounded; + folderResetBtn.target = [NSApp delegate]; + folderResetBtn.action = @selector(resetFolderBrowserFontSize:); + [fontView addSubview:folderResetBtn]; + + // Thumbnail Label Font Size + NSTextField *thumbLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 280, 200, 20)]; + thumbLabel.stringValue = NSLocalizedString(@"Thumbnail Label Font Size:", @""); + thumbLabel.bezeled = NO; + thumbLabel.drawsBackground = NO; + thumbLabel.editable = NO; + thumbLabel.selectable = NO; + [fontView addSubview:thumbLabel]; + + NSSlider *thumbSlider = [[NSSlider alloc] initWithFrame:NSMakeRect(20, 250, 300, 20)]; + thumbSlider.minValue = 0.0; // 0 means automatic + thumbSlider.maxValue = 18.0; + CGFloat thumbFontSize = [defaults floatForKey:@"thumbnailLabelFontSize"]; + thumbSlider.floatValue = thumbFontSize; + thumbSlider.target = [NSApp delegate]; + thumbSlider.action = @selector(thumbnailLabelFontSizeChanged:); + thumbSlider.continuous = YES; + [fontView addSubview:thumbSlider]; + + NSTextField *thumbSizeLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(330, 250, 50, 20)]; + if (thumbFontSize <= 0) { + thumbSizeLabel.stringValue = NSLocalizedString(@"Auto", @""); + } else { + thumbSizeLabel.stringValue = [NSString stringWithFormat:@"%.0f pt", thumbFontSize]; + } + thumbSizeLabel.bezeled = NO; + thumbSizeLabel.drawsBackground = NO; + thumbSizeLabel.editable = NO; + thumbSizeLabel.selectable = NO; + thumbSizeLabel.tag = 1002; // Tag for updating + [fontView addSubview:thumbSizeLabel]; + + NSButton *thumbResetBtn = [[NSButton alloc] initWithFrame:NSMakeRect(390, 245, 80, 30)]; + thumbResetBtn.title = NSLocalizedString(@"Reset", @""); + thumbResetBtn.bezelStyle = NSBezelStyleRounded; + thumbResetBtn.target = [NSApp delegate]; + thumbResetBtn.action = @selector(resetThumbnailLabelFontSize:); + [fontView addSubview:thumbResetBtn]; + + // Info text + NSTextField *infoLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 180, 450, 40)]; + infoLabel.stringValue = NSLocalizedString(@"Note: Changes apply immediately to all windows. Thumbnail label size of 'Auto' adjusts based on thumbnail width.", @""); + infoLabel.bezeled = NO; + infoLabel.drawsBackground = NO; + infoLabel.editable = NO; + infoLabel.selectable = NO; + infoLabel.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; + infoLabel.textColor = [NSColor secondaryLabelColor]; + [fontView addSubview:infoLabel]; + + fontTab.view = fontView; + [tabView addTabViewItem:fontTab]; + + // Create a fonts icon if it doesn't exist + if (![NSImage imageNamed:@"fonts"]) { + NSImage *fontsIcon = nil; + if (@available(macOS 11.0, *)) { + // Use SF Symbol on macOS 11+ + fontsIcon = [NSImage imageWithSystemSymbolName:@"textformat.size" accessibilityDescription:@"Fonts"]; + } + if (!fontsIcon) { + // Fallback: create a simple text-based icon + fontsIcon = [[NSImage alloc] initWithSize:NSMakeSize(32, 32)]; + [fontsIcon lockFocus]; + NSFont *font = [NSFont boldSystemFontOfSize:20]; + NSDictionary *attributes = @{NSFontAttributeName: font, + NSForegroundColorAttributeName: [NSColor labelColor]}; + [@"Aa" drawAtPoint:NSMakePoint(3, 5) withAttributes:attributes]; + [fontsIcon unlockFocus]; + } + [fontsIcon setName:@"fonts"]; + } +} + @end diff --git a/scripts/br b/scripts/br new file mode 100755 index 0000000..4b97658 --- /dev/null +++ b/scripts/br @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Build and run +set -Eeuo pipefail +trap 'echo "${BASH_SOURCE[0]}: line $LINENO: $BASH_COMMAND: exitcode $?"' ERR +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Pass configuration argument to both build and run scripts +"$SCRIPT_DIR/build" "$@" && exec "$SCRIPT_DIR/run" "$@" diff --git a/scripts/build b/scripts/build new file mode 100755 index 0000000..f9a419e --- /dev/null +++ b/scripts/build @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Build the project +set -Eeuo pipefail +trap 'echo "${BASH_SOURCE[0]}: line $LINENO: $BASH_COMMAND: exitcode $?"' ERR +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR/.." +START_TIME=$(date +%s.%N) + +# Parse configuration argument (default to Debug) +CONFIG="Debug" +if [ $# -gt 0 ]; then + case "${1,,}" in + r|rel|release) + CONFIG="Release" + ;; + d|deb|debug) + CONFIG="Debug" + ;; + *) + echo "Unknown configuration: $1" + echo "Usage: $0 [d|debug|r|release]" + exit 1 + ;; + esac +fi + +echo "Building $CONFIG configuration..." +if [ "$CONFIG" = "Debug" ]; then + # For Debug builds, only build for the current architecture + xcodebuild -quiet -project creevey.xcodeproj -configuration "$CONFIG" ONLY_ACTIVE_ARCH=NO +else + # For Release builds, build for all architectures + xcodebuild -quiet -project creevey.xcodeproj -configuration "$CONFIG" +fi + +END_TIME=$(date +%s.%N) +ELAPSED_TIME=$(echo "$END_TIME - $START_TIME" | bc --mathlib | xargs printf "%.2f\n") +echo "✅ Build completed in $ELAPSED_TIME seconds!" diff --git a/scripts/ide b/scripts/ide new file mode 100755 index 0000000..7a09aeb --- /dev/null +++ b/scripts/ide @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Run the IDE +set -Eeuo pipefail +trap 'echo "${BASH_SOURCE[0]}: line $LINENO: $BASH_COMMAND: exitcode $?"' ERR +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR/.." + +open -a Xcode "creevey.xcodeproj" diff --git a/scripts/lint b/scripts/lint new file mode 100755 index 0000000..6ffcb8c --- /dev/null +++ b/scripts/lint @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +set -Eeuo pipefail +trap 'echo "${BASH_SOURCE[0]}: line $LINENO: $BASH_COMMAND: exitcode $?"' ERR +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")" +cd "$SCRIPT_DIR/.." + +YELLOW='\033[1;33m' +GRAY='\033[90m' +CYAN='\033[36m' +NC='\033[0m' # No Color + +# Set VERBOSE=non-number to VERBOSE=1, otherwise use the number +[[ "${VERBOSE:-0}" =~ ^[0-9]+$ ]] && VERBOSE="${VERBOSE:-0}" || VERBOSE=1 + +trace () { + [[ "$VERBOSE" -lt 2 ]] || echo >&2 -e "🔬 ${GRAY}$*${NC}" +} +debug () { + [[ "$VERBOSE" -lt 1 ]] || echo >&2 -e "🔍 ${CYAN}$*${NC}" +} + +if ! command -v shellcheck &>/dev/null ; then + echo -e >&2 "${YELLOW}$SCRIPT_NAME: shellcheck not installed; skipping checks${NC}" + exit 0 +fi + +# Function to find shell script files +# ^[^.]+$ - files with no dots (no extension) +# \.sh$ - files ending with .sh +find_shell_files() { + local search_path="${1:-.}" + fd --hidden -t f '(^[^.]+$|\.sh$)' --exclude '.git' -0 "$search_path" +} + +FILES=() +if [[ $# -gt 0 ]]; then + # Use files specified on the command line + for file in "$@"; do + if [[ -d "$file" ]]; then + # Find all matching files in the directory + while IFS= read -r -d '' found_file; do + FILES+=("$found_file") + done < <(find_shell_files "$file") + else + FILES+=("$file") + fi + done +else + # Find all the script files in the entire project + while IFS= read -r -d '' file; do + FILES+=("$file") + done < <(find_shell_files) +fi + +# Scan all the files +EXITCODE=0 +for file in "${FILES[@]}"; do + # Ignore files that don't have shell-script shebang: "#!...sh" + if ! awk 'NR==1 && /^#!/ && /sh$/{exit 0} {exit 1}' "$file" ; then + trace "Skipping $file (not shell script)" + continue + fi + + debug "Checking $file" + shellcheck "$file" || EXITCODE=1 +done + +exit "$EXITCODE" diff --git a/scripts/publish b/scripts/publish new file mode 100755 index 0000000..e22cb19 --- /dev/null +++ b/scripts/publish @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Copy the application to /Applications +set -Eeuo pipefail +trap 'echo "${BASH_SOURCE[0]}: line $LINENO: $BASH_COMMAND: exitcode $?"' ERR +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR/.." + +# Parse configuration argument (default to Release) +CONFIG="Release" +if [ $# -gt 0 ]; then + case "${1,,}" in + r|rel|release) + CONFIG="Release" + ;; + d|deb|debug) + CONFIG="Debug" + ;; + *) + echo "Unknown configuration: $1" + echo "Usage: $0 [d|debug|r|release]" + exit 1 + ;; + esac +fi + +APP_NAME="Phoenix Slides.app" +SOURCE_PATH="./build/$CONFIG/$APP_NAME" +DEST_PATH="/Applications/$APP_NAME" + +# Check if the app exists in the build directory +if [[ ! -d "$SOURCE_PATH" ]]; then + echo "Error: $SOURCE_PATH does not exist" + echo "Please build the application first using: scripts/build ${1:-}" + exit 1 +fi + +# Kill any running instances of Phoenix Slides +pkill -f "Phoenix Slides" 2>/dev/null || true + +# Give it a moment to shut down cleanly +sleep 0.1 + +# Remove existing app if present +if [[ -d "$DEST_PATH" ]]; then + echo "Removing existing application at $DEST_PATH" + rm -rf "$DEST_PATH" +fi + +# Copy the app to /Applications +echo "Copying $CONFIG build to /Applications..." +cp -R "$SOURCE_PATH" "$DEST_PATH" + +echo "Successfully published Phoenix Slides ($CONFIG) to /Applications" diff --git a/scripts/run b/scripts/run new file mode 100755 index 0000000..3e74802 --- /dev/null +++ b/scripts/run @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Run the project without building it +set -Eeuo pipefail +trap 'echo "${BASH_SOURCE[0]}: line $LINENO: $BASH_COMMAND: exitcode $?"' ERR +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR/.." + +# Parse configuration argument (default to Debug) +CONFIG="Debug" +if [ $# -gt 0 ]; then + case "${1,,}" in + r|rel|release) + CONFIG="Release" + ;; + d|deb|debug) + CONFIG="Debug" + ;; + *) + echo "Unknown configuration: $1" + echo "Usage: $0 [d|debug|r|release]" + exit 1 + ;; + esac +fi + +# Kill any running instances of Phoenix Slides +pkill -f "Phoenix Slides" 2>/dev/null || true + +# Give it a moment to shut down cleanly +sleep 0.1 + +open "./build/$CONFIG/Phoenix Slides.app"